I'm learning Spring with JPA. I created a local DB with MySQL storing "Users", a web service with Spring and a front with angular.
I fed some data to my DB, and managed to display it with Angular. But the Post request coming from the Angular form does not seems to work. The form works well and provide an Object.
User model:
#Entity
#Table(name = "utilisateur")
public class Utilisateur {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String firstname;
private String lastname;
private int age;
DAOuser :
#Repository
public interface DAOutilisateur extends JpaRepository<Utilisateur, Integer> {
}
UserController :
#RestController
#CrossOrigin(origins = "http://localhost:4200")
public class UtilisateurController {
DAOutilisateur utilisateurDAO;
#Autowired
public UtilisateurController(final DAOutilisateur utilisateurDAO) {
this.utilisateurDAO = utilisateurDAO;
}
#GetMapping({"/listeUtilisateur"})
public List<Utilisateur> listUtilisateur(){
System.out.println("Liste des utilisateurs");
return utilisateurDAO.findAll();
}
#PostMapping("/listeUtilisateur")
void addUser(#RequestBody Utilisateur user) {
System.out.println("ECHO");
utilisateurDAO.save(user);
}
}
TypeScript Fonction used in Angular to access the Post URL, User is an Object created via an Form:
public saveUserSpring(user: UserSpring) {
return this.http.post<UserSpring>(this.userUrl, user);
}
Thank you for your Help !
Bertrand
Solved
As Rohit Kavathekar mentioned above, I didn't subscribed to the .post method in my angular project, which return an Observable and required to be subscribed.
Thank you.
Related
Im currently trying to perform a GET method on my "Courses" microservice controller. But when i try to fetch i get a 405 error code.
The general idea is that i perform a GET on the "Courses" microservice to get objects by id from the "Purchases" microservice.
Feign class in the purchases where im performing a GET
#FeignClient(value = "COURSES", configuration = {
FeignConfig.class
})
public interface CourseService {
#GetMapping("/courses/all-by-id")
CoursesByIdResponse getAllByCourseIds(#RequestBody CourseIdsRequest courseIds);
}
Course Controller
#RestController()
#CrossOrigin(origins = "*", maxAge = 3600)
public class CourseController {
private final CoursePaginatedService courseService;
public CourseController(#Qualifier("paginatedService") CoursePaginatedService courseService) {
this.courseService = courseService;
}
#GetMapping("/courses/all-by-id")
public ResponseEntity<CoursesByIdResponse> getAllByCourseIds(#RequestBody CourseIdsRequest courseIds){
return ResponseEntity.ok(courseService.getAllByCourseIds(courseIds.getCourseIds()));
}
}
I have more methods in the Course controller that work propperly (200 ok).
#GetMapping("/courses/course/{id}")
public ResponseEntity<CourseByIdResponse> getCourseById(#PathVariable(value = "id") Long id){
return ResponseEntity.ok(courseService.getCourseById(id));
}
My Request and response classes.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CourseIdsRequest {
private List<Long> courseIds;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
public class CoursesByIdResponse {
List<Course> courses;
}
I have no idea what it could be.
When I try to use the findById method from MongoRepository with a string passed in, I get an error with the mapper which leads to no results being returned, even though such exists.
Here is my project's structure and files:
Entity Post.java
#Document("posts")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Post {
#Id
private String Id;
private Integer userId;
private String content;
private Integer likes;
private Integer dislikes;
}
Repository PostRepository.java
public interface PostRepository extends MongoRepository<Post, String> {
}
Service PostService.java
#Service
public class PostService {
#Autowired
private PostRepository repository;
public List<Post> getPosts() {
return repository.findAll();
}
public Post getPostById(String id) {
return repository.findById(id).orElse(null);
}
public Post savePost(Post post) {
return repository.save(post);
}
public void deletePostById(String id) {
repository.deleteById(id);
}
}
Controller PostController.java
#RestController
#RequestMapping("/posts")
public class PostController {
#Autowired
private PostService service;
#Autowired
private StreamBridge streamBridge;
#GetMapping("")
public List<Post> getPosts() {
return service.getPosts();
}
#GetMapping("/{postId}")
public Post getPostById(#PathVariable String postId) {
return service.getPostById(postId);
}
#PostMapping("/createPost")
public Post createPost(#RequestBody Post post) {
streamBridge.send("postCreated-out-0", post.getUserId());
return service.savePost(post);
}
#DeleteMapping("/{postId}")
public void deletePostById(#PathVariable String postId) {
service.deletePostById(postId);
}
}
When I try running either a GET such as localhost:9192/posts/62a76719145e644e5b640327 or a DELETE localhost:9192/posts/62a76719145e644e5b640327, where 62a76719145e644e5b640327 is a correct id associated with a entry in the document in MongoDB I get this error in the console:
[nio-9192-exec-2] o.s.d.mongodb.core.convert.QueryMapper : Could not map 'Post.id'. Maybe a fragment in 'String' is considered a simple type. Mapper continues with id.
I also tried writing a custom query using the #Query annotation that overwrites the default findById as such:
#Override
#Query("{ 'id' : ?0}")
Optional<Post> findById(String s);
And I still get the same error. I am using spring-boot-starter-data-mongodb version 2.7.0 and I am running MongoDB locally.
EDIT: Forgot to mention that the same thing happens when using the deleteById method
i think the problem is in the identifier u named it with capital I
replace Id in posts document with id ?
give it a try... it will work
When using the reactive programming model with Spring Data MongoDB it's possible to execute transactions like this:
Mono<DeleteResult> result = template.inTransaction()
.execute(action -> action.remove(query(where("id").is("step-1")), Step.class));
But Spring Data MongoDB also has support for "reactive repositories", for example:
public interface PersonRepository extends ReactiveMongoRepository<Person, String>
Flux<Person> findByLocationNear(Point location, Distance distance);
}
and
public interface CarRepository extends ReactiveMongoRepository<Car, String>
Flux<Car> findByYear(int year);
}
My question is, given that you have ReactiveMongoRepository's, can you somehow leverage MongoDB transactions and e.g. insert both a Person and Car in the same transaction (using PersonRepository and CarRepository in the case)? If so, how do you do this?
I had also been trying hard to find solution for the Transactional support in Reactive style of Mongo DB & Spring Boot
But luckily I figured it myself. Though few of the things from google were also helpful but those were non reactive.
Important Note - For Spring boot 2.2.x it works well, but with spring boot 2.3.x it has some other issues, it has internal re-write & changes all together
You need to use ReactiveMongoTransactionManager along with ReactiveMongoDatabaseFactory, most of the details at the end, also sharing the code repo for the same
For getting the mongo db to support the Transactions we need to make sure that the DB should be running in replica mode.
Why we need that? Because you will get some error like this otherwise:-
Sessions are not supported by the MongoDB cluster to which this client is connected
The instructions for the same are below:-
run the docker-compose based mongo db server using docker-compose.yml as shared below:-
version: "3"
services:
mongo:
hostname: mongo
container_name: localmongo_docker
image: mongo
expose:
- 27017
ports:
- 27017:27017
restart: always
entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "rs0" ]
volumes:
- ./mongodata:/data/db # need to create a docker volume named as mongodata first
After the image comes up, execute the command(here localmongo_docker is the name of the container):-
docker exec -it localmongo_docker mongo
Copy and paste the command below and execute that
rs.initiate(
{
_id : 'rs0',
members: [
{ _id : 0, host : "mongo:27017" }
]
}
)
And then exit the execution by entering exit
Important - The code repo can be found here on my github - https://github.com/krnbr/mongo-spring-boot-template
Important notes for the code are as below:-
MongoConfiguration class in the config package is the important part to make the transactions working, link to the configuration class is here
Main part is the Bean
#Bean
ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory dbFactory) {
return new ReactiveMongoTransactionManager(dbFactory);
}
For checking the working of the code's Transactional requirement you may go through the class UserService in service package here
Code shared in case the links do not work for someone:-
The Configuration and inside the Beans
#Configuration
public class MongoConfiguration extends AbstractMongoClientConfiguration {
#Autowired
private MongoProperties mongoProperties;
#Bean
ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory dbFactory) {
return new ReactiveMongoTransactionManager(dbFactory);
}
#Override
protected String getDatabaseName() {
return mongoProperties.getDatabase();
}
#Override
public MongoClient mongoClient() {
return MongoClients.create(mongoProperties.getUri());
}
}
application.properties (related to mongo db)
spring.data.mongodb.database=mongo
spring.data.mongodb.uri=mongodb://localhost:27017/mongo?replicaSet=rs0
Document Classes
Role Class
#Getter
#Setter
#Accessors(chain = true)
#Document(collection = "roles")
#TypeAlias("role")
public class Role implements Persistable<String> {
#Id
private String id;
#Field("role_name")
#Indexed(unique = true)
private String role;
#CreatedDate
private ZonedDateTime created;
#LastModifiedDate
private ZonedDateTime updated;
private Boolean deleted;
private Boolean enabled;
#Override
#JsonIgnore
public boolean isNew() {
if(getCreated() == null)
return true;
else
return false;
}
}
User Class
#Getter
#Setter
#Accessors(chain = true)
#Document(collection = "users")
#JsonInclude(JsonInclude.Include.NON_NULL)
#TypeAlias("user")
public class User implements Persistable<String> {
#Id()
private String id;
#Field("username")
#Indexed(unique = true)
#JsonProperty("username")
private String userName;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#CreatedDate
private ZonedDateTime created;
#LastModifiedDate
private ZonedDateTime updated;
private Boolean deleted;
private Boolean enabled;
#DBRef(lazy = true)
#JsonProperty("roles")
private List<Role> roles = new ArrayList();
#Override
#JsonIgnore
public boolean isNew() {
if(getCreated() == null)
return true;
else
return false;
}
}
UserProfile Class
#Getter
#Setter
#Accessors(chain = true)
#Document(collection = "user_profiles")
#JsonInclude(JsonInclude.Include.NON_NULL)
#TypeAlias("user_profile")
public class UserProfile implements Persistable<String> {
#Id
private String id;
#Indexed(unique = true)
private String mobile;
#Indexed(unique = true)
private String email;
private String address;
private String firstName;
private String lastName;
#DBRef
private User user;
#CreatedDate
private ZonedDateTime created;
#LastModifiedDate
private ZonedDateTime updated;
private Boolean deleted;
private Boolean enabled;
#Override
#JsonIgnore
public boolean isNew() {
if(getCreated() == null)
return true;
else
return false;
}
}
ReactiveMongoRepository Interface(s)
RoleRepository
public interface RoleRepository extends ReactiveMongoRepository<Role, String> {
Mono<Role> findByRole(String role);
Flux<Role> findAllByRoleIn(List<String> roles);
}
UserRepository
public interface UserRepository extends ReactiveMongoRepository<User, String> {
Mono<User> findByUserName(String userName);
}
UserProfileRepository
public interface UserProfileRepository extends ReactiveMongoRepository<UserProfile, String> {
}
The User Service Class Need to create your own RuntimeException Class here, here it is AppRuntimeException Class, I had been using
#Slf4j
#Service
public class UserService {
#Autowired
private RoleRepository roleRepository;
#Autowired
private UserRepository userRepository;
#Autowired
private UserProfileRepository userProfileRepository;
#Transactional
public Mono<UserProfile> saveUserAndItsProfile(final UserRequest userRequest) {
Mono<Role> roleMono = roleRepository.findByRole("USER");
Mono<User> userMono = roleMono.flatMap(r -> {
User user = new User()
.setUserName(userRequest.getUsername())
.setPassword(userRequest.getPassword());
user.setRoles(Arrays.asList(r));
return userRepository.save(user);
}).onErrorResume(ex -> {
log.error(ex.getMessage());
if(ex instanceof DuplicateKeyException) {
String errorMessage = "The user with the username '"+userRequest.getUsername()+"' already exists";
log.error(errorMessage);
return Mono.error(new AppRuntimeException(errorMessage, ErrorCodes.CONFLICT, ex));
}
return Mono.error(new AppRuntimeException(ex.getMessage(), ErrorCodes.INTERNAL_SERVER_ERROR, ex));
});
Mono<UserProfile> userProfileMono = userMono.flatMap(u -> {
UserProfile userProfile = new UserProfile()
.setAddress(userRequest.getAddress())
.setEmail(userRequest.getEmail())
.setMobile(userRequest.getMobile())
.setUser(u);
return userProfileRepository.save(userProfile);
}).onErrorResume(ex -> {
log.error(ex.getMessage());
if(ex instanceof DuplicateKeyException) {
String errorMessage = "The user with the profile mobile'"+userRequest.getMobile()+"' and/or - email '"+userRequest.getEmail()+"' already exists";
log.error(errorMessage);
return Mono.error(new AppRuntimeException(errorMessage, ErrorCodes.CONFLICT, ex));
}
return Mono.error(new AppRuntimeException(ex.getMessage(), ErrorCodes.INTERNAL_SERVER_ERROR, ex));
});
return userProfileMono;
}
}
Controller and the Model Class
UserRequest Model Class
#Getter
#Setter
#Accessors(chain = true)
#Slf4j
#JsonInclude(JsonInclude.Include.NON_NULL)
public class UserRequest {
private String username;
private String password;
private String mobile;
private String email;
private String address;
private String firstName;
private String lastName;
}
UserProfileApisController class
#Slf4j
#RestController
#RequestMapping("/apis/user/profile")
public class UserProfileApisController {
#Autowired
private UserService userService;
#PostMapping
public Mono<UserProfile> saveUserProfile(final #RequestBody UserRequest userRequest) {
return userService.saveUserAndItsProfile(userRequest);
}
}
Just an addition to the accepted answer regarding MongoDB replica set initialization.
If one needs a non-fixed port single replica set for testing they might use the Testcontainers’ MongoDB Module that encapsulates such initialization:
final MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:4.2.8");
We can start it via ‘mongoDBContainer.start()’ and stop it via try-with-resources or ‘mongoDBContainer.stop()’. See more details on this module and Spring Data MongoDB here.
If one needs a non-fixed port multi-node replica set for testing complex production issues, they might use this project, for example:
try (
//create a PSA mongoDbReplicaSet and auto-close it afterwards
final MongoDbReplicaSet mongoDbReplicaSet = MongoDbReplicaSet.builder()
//with 2 working nodes
.replicaSetNumber(2)
//with an arbiter node
.addArbiter(true)
//create a proxy for each node to simulate network partitioning
.addToxiproxy(true)
.build()
) {
//start it
mongoDbReplicaSet.start();
assertNotNull(mongoDbReplicaSet.getReplicaSetUrl());
//do some testing
}
I don't understand what I need to add to make my program become HATEOAS. I have Account.java, Post.java, Controllers and repositories. From some guides they add AccountResource and PostResource where they build links inside these classes. What is the difference between AccountResource and Account? Do I need both? If so, do I create a resource class for each normal class? I tried doing this and it didn't work at all. I have no idea what I'm doing anymore :( . I need some help understanding how to migrate from a normal REST to HATEOAS. What classes do I need to add?
public class Account {
//Account ID
#Id private String userId;
//General info
protected String firstName;
protected String lastName;
protected String username;
protected String email;
protected String password;
protected String birthDate;
protected String activities;
protected String uri;
private Set<Post> posts = new HashSet<>();
List<Account> friends = new ArrayList<Account>();
//Getter, constructor...
#RestController
public class AccountController {
#Autowired
private AccountRepository accountRepository;
//Create account
#RequestMapping(value="/accounts", method = RequestMethod.POST)
public ResponseEntity<?> accountInsert(#RequestBody Account account) {
account = new Account(account.getUri(), account.getUsername(), account.getFirstName(), account.getLastName(), account.getEmail(), account.getPassword(), account.getBirthDate(), account.getActivities(), account.getFriends());
accountRepository.save(account);
HttpHeaders httpHeaders = new HttpHeaders();
Link forOneAccount = new AccountResource(account).getLink("self");
httpHeaders.setLocation(URI.create(forOneAccount.getHref()));
return new ResponseEntity<>(null, httpHeaders, HttpStatus.CREATED);
}
public class AccountResource extends ResourceSupport {
private Account account;
public AccountResource(Account account) {
String username = account.getUsername();
this.account = account;
this.add(new Link(account.getUri(), "account-uri"));
this.add(linkTo(AccountController.class, username).withRel("accounts"));
this.add(linkTo(methodOn(AccountController.class, username).getUniqueAccount(account.getUserId())).withSelfRel());
}
public AccountResource() {
}
public Account getAccount() {
return account;
}
}
Account, Post etc are your domain entities, while the *Resource are their representation exposed by your API to the external world.
Domain entities might be, for example, JPA Entities, containing all the metadata required for their persistency and relations with other entities.
Even if they are not JPA entities, they are the internal representation used by your application business logic.
Resources contains the information for JSON Serialisation/Deserialisation, and no direct references to other resources.
Have a look at this sample project https://github.com/opencredo/spring-hateoas-sample and to the related blog post: Implementing HAL hypermedia REST API using Spring HATEOAS
It implements a simple, but not-so-trivial library API, with a Book - Author - Publisher domain.
I have the following data model, and I want to get a specific object in the sub list objects, I know it's possible to get the entire list and go through each object and compare with what the search id, but I wonder if it is possible use MongoRepository to do this.
#Document
public class Host {
#Id
private String id;
#NotNull
private String name;
#DBRef
private List<Vouchers> listVoucher;
public Host() {
}
//Getters and Setters
}
And..
#Document
public class Vouchers {
#Id
private String id;
#NotNull
private int codeId;
public Vouchers() {
}
//Getters and Setters
}
The Repository Class:
public interface HostRepository extends MongoRepository<Host, String> {
List<Host> findAll();
Host findById(String id);
Host findByName(String name);
//How to build the correct query ??????????
List<Vouchers> findVouchersAll();
Vouchers findByVouchersById(String hostId, String voucherId);
}
The Controller Class:
#RestController
#RequestMapping(value = "api/v1/host")
public class VoucherController {
#Inject
HostRepository hostRepository;
#RequestMapping(value = "/{hostId}/voucher",method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public List<Vouchers> list() {
return hostRepository.findVouchersAll();
}
#RequestMapping(value = "/{hostId}/voucher/{voucherId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Vouchers getOneVoucher(#PathVariable String hostId, #PathVariable String voucherId) {
Vouchers voucher = hostRepository.findByVouchersById(hostId, voucherId);
if (voucher != null) {
return voucher;
} else {
throw new VoucherNotFoundException(String.format("There is no voucher with id=%s", voucherId));
}
}
}
Thanks in Advance!
I think there is a way to do this although I have not tried this myself but maybe I can shed some light in how I would do it.
Firstly, I would rather use the more flexible way of querying mongodb by using MongoTemplate. MongoTemplate is already included in the Spring Boot Mongodb data library and it looks like you are already using the library so it is not an additional library that you will have to use. In Spring there is a way to #Autowired your MongoTemplate up so it is quick and easy to get the object for completing this task.
With mongoTemplate, you would do something like this:
Query query = new Query();
query.addCriteria(Criteria.where("listVouchers.id").is("1234"));
List<Host> host = mongoTemplate.find(query, Host.class);
Please see docs here: https://docs.mongodb.org/manual/tutorial/query-documents/