So I have been playing around with Spring building a full stack application and I am at the point where I am validating data. First off, I want to validate that the incoming post request to my Spring Controller is able to create a User object and add it to the database. I created a custom validator using the combinator pattern and Java 8 features to do this.
Question 1 is there a way to intercept one of the JSON fields in post method? It is coming back as a String but I need a localdate to satisfy the user object creation/validation.
Question 2 when is it preferred to use validation annotations in the POJO object vs validating when the request comes through the controller? Should you be using both? Are there preferred patterns for validation? Obviosuly the client side will be validated as well before the info reaches the server.
//Using my custom validation here
#PostMapping
public ResponseEntity addUser(#RequestBody User user) {
UserValidation.ValidationResult result = userValidationService.validate(user);
if (result.equals(UserValidation.ValidationResult.SUCCESS)){
logger.info("Added a new user.");
userService.addUser(user);
return ResponseEntity.ok(HttpStatus.OK);
}
logger.info("Could not add new user.");
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
//Using annotation validation here on the POJO
#Data
#Table
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Length(max = 6)
private String firstName;
#NotNull
private String lastName;
private String username;
#Email
private String email;
private LocalDate birthDate;
private String password;
public User() {
}
public User(String firstName, String lastName, String username, String email, String password,
LocalDate birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.username = username;
this.email = email;
this.password = password;
this.birthDate = birthDate;
}
}
You can create a DTO object for Entity and do all validation there.
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
#NotNull
#Length(max = 6)
private String firstName;
#NotNull
private String lastName;
private String username;
#Email
private String email;
private LocalDate birthDate;
private String password;
}
Then put this object in Controller:
#PostMapping
public ResponseEntity addUser(#Valid #RequestBody UserDto userDto) { // #Valid will check if validation is ok in object UserDto
UserValidation.ValidationResult result = userValidationService.validate(userDto);
if (result.equals(UserValidation.ValidationResult.SUCCESS)){
logger.info("Added a new user.");
userService.addUser(toEntity(userDto)); //maps UserDto to User Entity
return ResponseEntity.ok(HttpStatus.OK);
}
logger.info("Could not add new user.");
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
// maps UserDto to User so you can save it to database in Service layer
User toEntity(UserDto userDto) {
User user = new User();
user.setFirstName(userDto.getFirstName);
user.setLastName(userDto.getUserlastName);
user.setUsername(userDto.getUsername);
user.setEmail(userDto.getEmail);
user.setPassword(userDto.getPassword);
user.setBirthDate(userDto.birthDate)
return user;
}
Related
I have two classes User.java and Address.java and there is a one-to-one bi-directional mapping between them.
But when I try to get the address using the User class I get an "java.lang.StackOverflowError: null" exception.
The same thing happens when I try to get the User from the Address class.
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String phone;
private String password;
private String imageUrl;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address")
private Address address;
Address.java
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "address")
private User user;
private String country;
private String state;
private String city;
private String street;
private String pincode;
MainController.java
#Controller
public class MainController {
#Autowired
private UserDao userDao;
#Autowired
private AddressDao addressDao;
#RequestMapping("/test")
#ResponseBody
public String test() {
User user = new User();
user.setName("name");
user.setEmail("email");
user.setPhone("phone");
user.setPassword("password");
user.setImageUrl("imageUrl");
Address address = new Address();
address.setCountry("country");
address.setState("state");
address.setCity("city");
address.setStreet("street");
address.setPincode("123456");
user.setAddress(address);
userDao.save(user);
return "working";
}
#RequestMapping("/fetch")
#ResponseBody
public String fetch() {
User user = userDao.getById((long) 1);
System.out.println(user.getAddress());
return "working";
}
}
I am using the test() function to put data in the database and it is working fine.
database image
But when I call the fetch() function I am getting the following error
java.lang.StackOverflowError: null
at org.hibernate.proxy.pojo.BasicLazyInitializer.invoke(BasicLazyInitializer.java:58) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:43) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
at
Updated MainController.java
package com.demo.controller;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.dao.AddressDao;
import com.demo.dao.UserDao;
import com.demo.entity.Address;
import com.demo.entity.User;
#Controller
public class MainController {
#Autowired
private UserDao userDao;
#Autowired
private AddressDao addressDao;
#RequestMapping("/test")
#ResponseBody
public String test() {
User user = new User();
user.setName("name");
user.setEmail("email");
user.setPhone("phone");
user.setPassword("password");
user.setImageUrl("imageUrl");
userDao.save(user);
Address address = new Address();
address.setCountry("country");
address.setState("state");
address.setCity("city");
address.setStreet("street");
address.setPincode("123456");
addressDao.save(address);
user.setAddress(address);
userDao.save(user);
return "working";
}
#RequestMapping("/fetch")
#ResponseBody
public String fetch() {
Optional<User> op = userDao.findById((long) 1);
User user = op.get();
// working
System.out.println(user.getName() + " " + user.getEmail() + " " + user.getPhone());
// java.lang.StackOverflowError:null
System.out.println(user.getAddress());
return "working";
}
}
TLDR: you aren't actually saving anything anywhere, but it's easy to fix. Here's my code and my explanation:
MainController.java:
#RestController
public class MainController {
private final UserRepository userRepository;
private final AddressRepository addressRepository;
public MainController(UserRepository userRepository, AddressRepository addressRepository){
this.userRepository = userRepository;
this.addressRepository = addressRepository;
}
#GetMapping("/test")
public String test() {
User user = new User();
user.setName("name");
user.setEmail("email");
user.setPhone("phone");
user.setPassword("password");
user.setImageUrl("imageUrl");
user = userRepository.save(user);
System.out.println("saved user");
Address address = new Address();
address.setCountry("country");
address.setState("state");
address.setCity("city");
address.setStreet("street");
address.setPincode("123456");
address = addressRepository.save(address);
System.out.println("saved address");
user.setAddress(address);
userRepository.save(user);
System.out.println("set user's address");
return "working";
}
#GetMapping("/fetch")
public String fetch() {
Optional<User> optionalUser = userRepository.findById((long) 1);
if(optionalUser.isPresent()){
User user = optionalUser.get();
System.out.println(user.getAddress());
boolean addressExists = addressRepository.existsById((long) 1);
System.out.println(addressExists);
System.out.println(user.getAddress().getCountry());
return "working";
}
System.out.println("Error: user with id 1 not found!");
return "failing";
}
}
User.java:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String phone;
private String password;
private String imageUrl;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
//getters and setters omitted for brevity
}
Address.java:
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne(mappedBy = "address")
private User user;
private String country;
private String state;
private String city;
private String street;
private String pincode;
//getters and setters omitted for brevity
}
AddressRepository.java:
public interface AddressRepository extends CrudRepository<Address, Long> {
}
UserRepository.java:
public interface UserRepository extends CrudRepository<User, Long> {
}
UserDAO.java:
public class UserDAO {
private final String name;
private final String email;
private final String phone;
private final String imageUrl;
public UserDAO(User user) {
name = user.getName();
email = user.getEmail();
phone = user.getPhone();
imageUrl = user.getImageUrl();
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPhone() {
return phone;
}
public String getImageUrl() {
return imageUrl;
}
}
A DAO has no connection to the database, it's intent is what the acronym stands for, simply to transfer data, and that's it. When you make a repository, you can stick your objects there by saving them in the repository. Notice that by extending the CrudRepository with correct generics, you don't even need to implement the methods yourself. The save method actually saves the POJO, and returns the saved version, which is why I did user = userRepository.save(user), which may seem counterintuitive at first, but it simply helps ensure that everything is as you expect. If you then want to send the UserDAO object as a response, you can create it using the user object that is returned from the database, maybe something like:
UserDAO dao = new UserDAO(userRepository.save(user));
Please take notice of what is happening inside the test method in MainController. First, we create the POJO User object and set its fields. Then we have to save it to the repository, it is only persisted after you call save method of the repository. Please note that the user object is saved again once it is updated with the address.
This is a very crude way to do things, it is best to create a service layer and do this there with the #Transactional annotation, which would mean that everything is rolled back in case something goes wrong inside a method annotated as #Transactional.
Also, using CascadeType.ALL may be not what you want, please refer to this answer.
Inside fetch method, I ensure that the user indeed exists, which is not guaranteed. To avoid 500 errors, it's important to have a fallback mechanism for when something doesn't work.
As a final side note, you shouldn't be storing raw passwords like that, you should at least use hashing with salt and pepper, or use one of the many available libraries for implementing such functionality (although it can be quite fun getting down and dirty with the code itself). You should also consider how much information you are revealing when something does go wrong, as you don't want to give away too much information which could be used to deanonimise a specific user, or even learn more about your code and the system architecture.
I have REST api with User model - DTO and Create / update form. My userService checks if user is administrator, then allow to getAllUsers in List. When I want to get all users, I get Bad request 400, but it should return Forbidden. It used to work but when I added some changes to my code I got bad request. I don't know what I'm missing...
My User.java
///Lombok annotations
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(AccessLevel.NONE)
#Column(unique = true)
private Long id;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Include
#Column(nullable = false, unique = true)
private UUID uuid = UUID.randomUUID();
#Column(unique = true, nullable = false, length = 254)
private String login;
#Column(nullable = false, length = 254)
private String firstName;
#Column(nullable = false, length = 254)
private String lastName;
#Enumerated(EnumType.STRING)
private RoleType roleType;
#Column(nullable = false, length = 254)
private String password;
#Email
#Column(nullable = false, length = 254)
private String email;
#Positive
private Double cost;
public User(String login, String firstName, String lastName, RoleType roleType, String password,
String email, Double cost) {
this.login = login;
this.firstName = firstName;
this.lastName = lastName;
this.roleType = roleType;
this.password = password;
this.email = email;
this.cost = cost;
}
UserController
#GetMapping("users")
public ResponseEntity<List<UserDto>> getAllUsers(#RequestParam UUID uuid) {
return userService.getListResponseEntity(uuid);
}
UserService
public ResponseEntity<List<UserDto>> getListResponseEntity(UUID adminUuid) {
if (authService.adminAuth(adminUuid)) {
List<User> users = userRepo.findAll();
List<UserDto> userDto = users
.stream()
.map(user -> userMapper.mapToUserDto(user))
.collect(Collectors.toList());
return new ResponseEntity<>(userDto, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
UserDto
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
private String login;
private String firstName;
private String lastName;
private RoleType roleType;
private String email;
private Double cost;
I think you missed uuid parameter in request header.
It will be like this. http://localhost:8080/users?uuid="enter_your_uuid_here"
There could be changes that your JSON request data is not matching with you DTO data fields.
Validate following points in your JSON request
there could be issue of wrong field Name
may be not sending proper data as per the datatype.
how to do validation on only changed field which come as json on update api.
project use:
java+maven+hibernate+spring mvc
#Entity
#Component("user")
#Table(name="user")
#DynamicUpdate
#Inheritance(strategy = InheritanceType.JOINED)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Range(min = 0, max = 1000000)
#Column(name="user_id")
private int UserId;
#Size(min = 6, max = 20)
#Column(name="password")
//#org.hibernate.annotations.Type(type="StringType")
private String password;
#Size(min=2, max=20)
#Column(name="first_name")
private String firstName;
#Size(min=2, max=20)
#Column(name="last_name")
private String lastName;
#Size(min=2,max=20)
#Column(name="surname")
private String surName;
#Email
#Column(name="email_address")
private String EmailAddress;
#Size(min = 10, max = 10)
#Phone(message = "please enter valid phone number")
#Column(name="mobile_number")
private String mobileNumber;
}
and my update api as bellow:
#SuppressWarnings({ "unchecked", "rawtypes" })
#PostMapping(value = "/candidate")
public ResponseEntity addCandidate(#Valid #RequestBody Candidate candidate, BindingResult result) {
Map<String, Object> errors = new HashMap<String, Object>();
if (result.hasErrors()) {
here save error in errors list.
}
errors.put("error", errorList);
return new ResponseEntity(errors, HttpStatus.NOT_ACCEPTABLE);
}
candidateService.addCandidate(candidate);
return new ResponseEntity(HttpStatus.CREATED);
}
due to the above logic, it will check all attributes which do not update actually.
what are other methods to apply validation only on changed attributes?
more explanation:
I have three class
user
candidate
employee
here user class contains all common information of candidate and employee and in a forther password also contains for login.
now when first time signs up, I want to do validation only on email + password only, but here due to #valid annotation, it does validation on all the attribute.
How can I develop a method generic to retrieve data by attribute. Let's say we have a User Class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String password;
private String firstName;
private String lastName;
}
UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findOneByUsername(String username);
Optional<User> findUsersByAttributes(String attribute);
}
I want to develop a method to retrieve data by attribute :
findUsersByAttributes(String attribute){
}
Entity
You already provided your entity, but i added the missing annotations so it can be picked up by any jpa implementation. Just a quick reminder here, whenever you store a password you should consider hashing passwords and not store them as plaintext in your database. You can find more information about this here.
#Entity
#Table(name = "user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
// remainer ommitted for brevity
}
Repository
The most simple option is to create methods using Spring Data JPA.
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findOneByUsername(String username);
List<User> findAllByFirstName(String firstName);
List<User> findAllByLastName(String lastName);
}
However, consider the case where you might want to query users for more attributes at the same time. For example, if you want to find a user by firstName, lastName, gender, phoneNumber, ... it would be impossible to write methods for all sorts of combinations.
findAllByFirstNameAndLastNameAndGenderAndPhoneNumber // pretty confusing
If you only need a few properties, you can use CrudRepository as stated above, if you need more properties you might want to have a look at QueryDSL or Specifications, which deal exactly with that problem. More information about this here.
Service
Next up, you should create a service to decouple your data layer.
#Service
public class UserService {
private UserRepository repository;
#Autowired
public UserService(UserRepository repository) {
this.repository = repository;
}
Optional<User> findOneByUsername(String username) {
return repository.findOneByUsername(username);
}
List<User> findAllByFirstName(String firstName) {
return repository.findAllByFirstName(firstName);
}
List<User> findAllByLastName(String lastName) {
return repository.findAllByLastName(lastName);
}
}
Controller
Finally, create a controller to access you service layer.
#RestController
#RequestMapping("/users")
public UserController {
private UserService service;
#Autowired
public UserController(UserService service) {
this.service = service;
}
#GetMapping()
#ResponseBody
public List<User> findAll(#RequestParam(value = "username", required = false) String username,
#RequestParam(value = "firstName", required = false) String firstName,
#RequestParam(value = "lastName", required = false) String lastName) {
if (username != null) {
return List.of(service.findOneByUsername(username));
}
if (firstName != null) {
return service.findAllByFirstName(username);
}
if (lastName!= null) {
return service.findAllByLastName(username);
}
return service.findAll();
}
}
One last note here, think about whether you want to make you password field also available in requests, if not i would recommend using a UserDTO and ModelMapper.
I am quite new to Hibernate / JPA persistence and am trying to find how to obtain the ID of an inserted record in the database via the new object I create to do so.
I am using Hibernate 4.0.4 and am utilising hibernates session API as opposed to the EntityManager.
User class
#Entity
#Table(name = "users")
public class User implements Serializable {
private String email;
private String firstName;
private int id;
private String lastName;
private String password;
#Column(name = "email", nullable = false, unique = true)
public String getEmail() {
return email;
}
#Column(name = "firstName", nullable = false)
public String getFirstName() {
return firstName;
}
#Id
#Column(name = "id")
public int getId() {
return id;
}
#Column(name = "lastName")
public String getLastName() {
return lastName;
}
#Column(name = "password", nullable = false)
public String getPassword() {
return password;
}
public void setEmail(String email) {
this.email = email;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setHeroes(List<Hero> heroes) {
this.heroes = heroes;
}
public void setId(int id) {
this.id = id;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setPassword(String password) {
this.password = password;
}
}
Execution code - attempt 1
User user = new User();
user.setEmail((String) arg.get("email"));
user.setFirstName((String) arg.get("firstName"));
user.setLastName((String) arg.get("lastName"));
user.setPassword(Password.getSaltedHash((String) arg.get("password")));
Transaction tx = sessionFactory.getCurrentSession().beginTransaction(); //committed later on...
int id = (Integer) sessionFactory.getCurrentSession().save(user); //This always returns 0 even though inserted rows in database have IDs as expected
Execution code - attempt 2
//Same as before for defining user...
Transaction tx = sessionFactory.getCurrentSession().beginTransaction(); //commited later on...
user = (User) sessionFactory.getCurrentSession().merge(user);
int id = user.getId(); //This also always returns 0 even though the rows are being inserted into the database as expected
So my question is:
How can I successfully persist the user object to the database and obtain the ID of the record through that user object?
There are different kind of id columns. I suppose you want to get generated values, check out the different options of the #GeneratedValue annotation in combination with #Id.
As far as I know GenerationType.AUTO is able to auto-update your id field after storing it in the database. But you probably have to call a "flush()" before using the id-getter.
My best practice is to try to not depend on the technical Id of an object. If you need it for relations between objects, than you should definitely prefer to use #OneToMany, etc.