My controller:
#PostMapping
public ResponseEntity<UserCreateResponse> createUser(#RequestBody #Valid UserCreateRequest userDto,
BindingResult result)
throws InvalidRequestException {
if (result.hasErrors()) {
throw new InvalidRequestException("Request parameter validation failed");
} else {
return ResponseEntity.ok(userService.createUser(userDto));
}
}
Service:
public UserCreateResponse createUser(UserCreateRequest userDto) {
return convertEntityToDto(userRepository.insert(convertDtoToEntity(userDto)));
}
private User convertDtoToEntity(UserCreateRequest userDto) {
return modelMapper.map(userDto, User.class);
}
private UserCreateResponse convertEntityToDto(User user) {
return modelMapper.map(user, UserCreateResponse.class);
}
And the model is :
#Getter
#Setter
#Document("User")
public class User {
#Id
private String id;
#Indexed(unique = true)
private String userName;
private String name;
private String surname;
private String job;
}
Repository is just a class extending MongoRepository.
When I try to insert 2 User with same userName via postman post request, it is adding 2 exactly same item to db even if I specified #Indexed(unique = true) to userName field. Why does this happen and how can I fix it on Java side without breaking indexing function on the field(I want to index userName field to find faster)
Related
I have to create a very simple Spring "market" app.
No front-end needed
The Market:
The system must operate as a simplified market where users can be buyers or sellers.
Users:
user entity attributes: id:1, username:"User1", account:0
//account just gets incremented with each entry in the database.
The users can buy and sell items.
Items:
item entity attributes: id:3, name:Item1, ownerId:1.
example for interacting with items endpoints:
create: {id:1 name:"Item1", ownerId:1};
getAllItems with ownerId = 1 (use single query)
[
{
"id":3,
"name":”Item1”,
"ownerId":1,
“ownerUsername”:"User1"
}
]
Example:
"User1" owns "Item1". He wants to sell it for $100. He creates an active contract. Other users can review all active contracts and choose to participate. "User2" has enough money in her account and buys "Item1". The contract is now closed. "User1" receives $100 in his account. "User2" is the new owner of "Item1".
Contracts:
contract entity attributes: id, sellerId, buyerId, itemId, price,status. (The seller is the owner of the item and can not be the buyer)
endpoints - CRUD. Example for interacting with contracts endpoints:
create: {itemId : 3, price : 100}. Expected behavior: find the owner of item with id 3 in the DB (ownerId = 1) persist the new active contract in the DB:
{
"sellerId":1,
"itemId":3,
"price":100,
"active":true
}
update price of active contract by id: {"itemId":3, "price":200}
getAllActive contracts (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"itemId":3,
"price":200,
"active":true
}
]
closing active contract by id {"itemId":3, "buyerId":2}.
Expected behavior: update the accounts of users with id 1 and id 2.
getAllClosed contracts by optional parameters: itemId, sellerId, buyerId (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"buyerId":2,
“buyerUsername”:"User2",
"itemId":3,
"price":100,
"active":false
}
]
So far, these are my Entities:
BaseEntity:
#MappedSuperclass
public abstract class BaseEntity {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Users:
#Entity
#Table(name = "users")
public class User extends BaseEntity{
private String username;
private Long account;
private Set<Item> items;
public User() {
}
#Column(name = "username", nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "account", nullable = false)
public Long getAccount() {
return account;
}
public void setAccount(Long account) {
this.account = account;
}
#OneToMany(mappedBy = "id")
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}
Items:
#Entity
#Table(name = "items")
public class Item extends BaseEntity{
private String name;
private String ownerUsername;
private User user;
public Item() {
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//get the id of the item's owner
#ManyToOne
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getOwnerUsername() {
return user.getUsername();
}
public void setOwnerUsername(String ownerUsername) {
this.ownerUsername = ownerUsername;
}
}
So, what should I do from here on?
If you've already created persistence layers (using Spring Data JPA or another mapper), You need to develop service logic and create a presentation layer.
like this (just user domain)
UserService (service layer)
#Service
#RequiredArgsConstructor
public class UserService {
private final UserJpaRepository repository;
#Transactional
public Long createUser(String username) {
User user = new User();
user.setUsername(username);
// other logic ...
repository.save(user);
return user.getId();
}
#Transactional(readonly = true)
public User getUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> IllegalArgumentsException("Not Found Entity."))
}
}
UserAPIController (presentation layer)
#RestController
#RequiredArgsConstructor
public class UserAPIController {
private final UserService userService;
#PostMapping("/users")
public ResponseEntity<Long> createUser(#RequestBody CreateUserDTO dto) {
Long userId = userService.createUser(dto.getUsername());
return new ResponseEntity(userId, HttpStatus.CREATED);
}
#GetMapping("/users/{id}")
public ResponseEntity<User> getUser(#PathVariable Long id) {
User user = userService.getUser(id);
return new ResponseEntity(user, HttpStatus.OK);
}
}
I have UserDto.
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ApiModel(value = "UserDto", description = " DTO User ")
public class UserDto {
private Long userId;
private String firstName;
private String lastName;
private LocalDate dateOfBirth;
private String education;
private String aboutMe;
I need to create update method.That's what I have now.
#PatchMapping("/{user}/edit")
public ResponseEntity<String> update(#RequestBody UserDto userDto, #PathVariable long id) {
Optional<User> optionalUser = userService.getById(id);
if (!optionalUser.isPresent()) {
return ResponseEntity
.badRequest()
.body("Пользователь не найден");
}
User user = optionalUser.get();
userService.update(user);
return new ResponseEntity<>(HttpStatus.OK);
}
How can I use Dto to partial update user data? I assume I need a converter. Thanks!
You must create a constructor in Entity class and transform fields from dto to entity
User model:
#Entity
#Table(name="user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#NotBlank
#Column(name="username")
private String username;
#NotEmpty
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="user_role", joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")})
private Set<Role> roles;
}
Controller:
#RequestMapping(value = {"/users/edit/{id}"}, method = RequestMethod.POST)
public String editUser(ModelMap model, #Valid #ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()) {
return "AddUserView";
}
return "redirect:/users";
}
Test with MockMVC:
#Test
public void performUpdateUserTest() throws Throwable {
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("username", "User"));
}
Well, fine, I can pass a param username as always using param(). But what should I do with ROLES? This field is a separate object. I can't pass it using param(). Then how is it possible to pass it in the test?
The only way out I found is to create an entity and pass it using .flashAttr():
#Test
public void performUpdateUserTest() throws Throwable {
User user = new User("User", new HashSet<Role>(Arrays.asList(new Role("USER"))));
mockMvc.perform(post("/users/edit/{id}", user.getId())
.flashAttr("user", user));
}
But then, what if I need to test that user can't be updated because of binding error in the ROLES field(ROLES can't be null, and suppose, it was set as null)? Thus, I'm not able to create user(and use it with .flashAttr) already with a binding error as the exception will be thrown. And I still have to pass it separately.
Well, after a long time of searching, I found out that I should add a converter to the MockMVC. What converter is you can read HERE, for instance.
I had it already in my project but didn't realize that it didn't work with MockMVC.
So, you can add the converter to MockMVC like that:
#Autowired
private StringToRoleConverter stringToRoleConverter;
#Before
public void init() {
FormattingConversionService cs = new FormattingConversionService();
cs.addConverter(stringToRoleConverter);
mockMvc = MockMvcBuilders.standaloneSetup(userController)
.setConversionService(cs)
.build();
}
Converter itself:
#Component
public class StringToRoleConverter implements Converter<String, Role> {
#Autowired
private RoleService roleService;
#Override
public Role convert(String id) {
Role role = roleService.findById(Integer.valueOf(id));
return role;
}
}
And then I can add param like that:
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("roles", "2"))
though I'm passing a string there, it will be converter to Role with the help of Spring converter.
I Have method in service to save user after registration, but after method invocation I have two same documents in collection.
Controller:
#RequestMapping(value="/registration/male", method= RequestMethod.POST, consumes={ MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody void maleRegistration (#RequestBody MaleDTO maleDTO, HttpServletRequest request) throws EmailExistsException {
User user = registrationService.maleRegistration(maleDTO);
autoLogin(user, request);
}
Method in service:
#Transactional
public User maleRegistration(MaleDTO male) throws EmailExistsException {
if (userRepository.existsByEmail(male.getEmail())) {
throw new EmailExistsException("There is an account with that email address: " + male.getEmail());
}
User user = new User();
user.setName(male.getName());
user.setGender(Gender.MALE);
user.setDateOfBirth(male.getDateOfBirth());
user.setEmail(male.getEmail());
user.setPassword(encoder.encode(male.getPassword()));
user.setRoles(new HashSet<>(Arrays.asList(Role.ROLE_USER)));
userRepository.save(user);
return user;
}
User repository:
public interface UserRepository extends MongoRepository<User, String>{
}
User Class:
#Document(collection = "Users")
public class User {
#Id
private String id;
private String name;
private Gender gender;
private LocalDate dateOfBirth;
private String email;
private String password;
private Set<Role> roles;
//geters and seters
//toString
}
Why it happens?
I would appreciate any help.
I'm a novice java developer and now develop User Management application using Spring-Hibernate. I have two entities User and Email. And User entity has a field Email which is mapped to Email entity as #ManyToOne. Any Email can be used by multiple users.
When I save a new User in DB for every new user I get a new row Email, even if the same record is already in the Email Table. How to properly make save operation to avoid duplication of the same records in the table Email?
User.java
#Entity
#Table(name = "USER")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "email_id")
private Email email;
public User(){
}
public Email getEmail() {
return email;
}
public void setEmail(Email email) {
this.email = email;
}
...
}
Email.java
#Entity
#Table(name = "EMAIL")
public class Email implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID")
private Long id;
#Column(name = "emailaddress")
private String emailaddress;
#OneToMany (mappedBy = "email", targetEntity=User.class)
private Set<User> user= new HashSet<User>();
public Email() {
}
public Email(String emailaddress) {
this.emailaddress = emailaddress;
}
public String getEmailaddress() {
return emailaddress;
}
public void setEmailaddress(String emailaddress) {
this.emailaddress = emailaddress;
}
...
}
Controller.java
#Transactional
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String saveOrder(#ModelAttribute("user") User user, BindingResult result, #RequestParam String action){
emailDAO.create(user.getEmail());
userDAO.create(user);
return "index";
...
}
EmailDAO.java
#Transactional
#Repository
public class EmailDAO{
#Autowired
private SessionFactory sessionFactory;
public Email create(Email email) {
sessionFactory.getCurrentSession().save(email);
return email;
}
}
UserDAO.java
#Transactional
#Repository
public class UserDAO{
#Autowired
private SessionFactory sessionFactory;
public User create(User user) {
sessionFactory.getCurrentSession().save(user);
return user;
}
}
webform.jsp
<form:form action="${formUrl}" method="post" modelAttribute="user">
<form:label path="name" for="appname">username</form:label>
<form:input path="name" id= "appname" cssClass="form-control"/>
<form:label path="email.emailaddress" for="appemail">Email</form:label>
<form:input path="email.emailaddress" id= "appemail"/>
<button type="submit" name="action" value="Add">Save</button>
</form:form>
database diagram
Example of the DB records
That is because you keep on saving the Email as a new Record
#Transactional
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String saveOrder(#ModelAttribute("user") User user, BindingResult result, #RequestParam String action){
emailDAO.create(user.getEmail()); // Inserting Email as New Record
userDAO.create(user);
return "index";
...
}
And you don't have unique=true on Email Entity
#Column(name = "emailaddress", unique = true)
private String emailaddress;
Which you should ideally have so that there will be no duplicate Emails will get inserted even by accidentally.
You need to modify EmailDAO
#Transactional
#Repository
public class EmailDAO{
#Autowired
private SessionFactory sessionFactory;
public Email create(Email email) {
sessionFactory.getCurrentSession().save(email);
return email;
}
public Email getEmail(String inputEmail) {
Email email = null;
Query query = sessionFactory.getCurrentSession().createQuery("FROM Email e WHERE e.emailaddress = :email");
query.setString("email", inputEmail);
List emails = query.list();
if(emails != null && emails.size() > 0) {
email = (Email)emails.get(0);
} else {
email = new Email();
email.setEmailAddress(inputEmail);
}
return email;
}
}
And you getEmail
#Transactional
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String saveOrder(#ModelAttribute("user") User user, BindingResult result, #RequestParam String action){
user.setEmail(emailDAO.getEmail(user.getEmail().getEmailAddress())); // Inserting Email as New Record
userDAO.create(user);
return "index";
...
}