How to ignore certain column from being fetched when using findBy.. in JPA - spring-boot

I have a User entity which has all the user's data including the password hash. I also have an API getUserDetails which performs a simple userRepository.findById(id) to get all the user's data. However, in the process it also returns the password hash which I don't want to.
One way to remove the password hash is to remove it from the service layer after all the data has been fetched. Like this -
fun getUserDetails(id: Long): Optional<User> {
val user: Optional<User> = userRepository.findById(id)
user.get().password = ""
return user
}
The output will be like this -
{
"id": 4,
"roles": "ADMIN",
"userName": "user3",
"emailId": "hello#outlook.com",
"password": "",
"phoneNumber": "1234",
"organisationName": "test",
"isActive": true
}
The password value has been removed but the key still exists. I would like to get rid of that too.
Is there any other way which is more baked into JPA which I can use to achieve the said result?

Use #get:JsonIgnore on the field to skip serialization of that in response.
Obviously you don't need to send the password hash in the response. You need the password in service, it just ignored when giving serialize the response.

You can add new layer called DTO, and using famous library modelmapper
Add this in your pom.xml
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.5</version>
</dependency>
Create bean to use modelmapper in your application main for example
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
And then create class UserDto example
public class UserDto {
private Long id;
String username;
// standard getters and setters
private UserDto convertToDto(User user) {
UserDto userDto = modelMapper.map(user, UserDto);
return userDto;
}
}
In your controller or service
return convertToDto(userRepository.findById(id)); // wil return userDto
Hope useful.

Related

Spring Validation: ConstraintViolationException for #Pattern due to password encoding

I'm just implementing a basic CRUD service where a user can be created in the database with their password matching a certain regex and being encoded using BCryptPasswordEncoder.
My tests are failing due to a ConstraintViolationException on the password saying that it does not satisfy the regex requirement:
javax.validation.ConstraintViolationException: Validation failed for classes [com.hoaxify.hoaxify.user.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$"', propertyPath=password, rootBeanClass=class com.hoaxify.hoaxify.user.User, messageTemplate='{javax.validation.constraints.Pattern.message}'}
It wasn't getting caught in my #ExceptionHandler since it was throwing a ConstraintViolationException and not a MethodArgumentNotValidException. I debugged and found that, while it was trying to match to the given regex, the value for the password itself was showing as:
$2a$10$pmRUViwj3Ey4alK0eqT1Dulz4BpGSlSReHyBR28K6bIE4.LZ7nYWG
while the password being passed in was:
P4ssword
So it appears the validation is being run on the encrypted password and not the raw password. I thought the validation should occur on the object received in the createUser method - before any other manipulation occurred.
Any help on why this is happening and how to fix would be greatly appreciated.
Note:
Validation works for all other fields
UserController
#RestController
#RequestMapping("{my/path}")
class UserController {
#Autowired
lateinit var userService: UserService
#PostMapping
fun createUser(#Valid #RequestBody user: User): GenericResponse {
userService.save(user)
return GenericResponse("Saved user")
}
#ExceptionHandler(MethodArgumentNotValidException::class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleValidationException(exception: MethodArgumentNotValidException, request: HttpServletRequest): ApiError {
val error = ApiError(400, "Validation error", request.servletPath)
val bindingResult = exception.bindingResult
bindingResult.fieldErrors.forEach {
error.validationErrors[it.field] = it.defaultMessage ?: "invalid"
}
return error
}
}
User
#Entity
class User(
#Id
#GeneratedValue
val id: Long,
#field:NotBlank
#field:Size(min = 4, max = 50)
var username: String,
#field:NotBlank
#field:Size(min = 4, max = 50)
var displayName: String,
#field:NotBlank
#field:Pattern(regexp = """^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{8,50}$""")
var password: String
)
UserService
#Service
class UserService(
private val userRepository: UserRepository,
private val passwordEncoder: BCryptPasswordEncoder = BCryptPasswordEncoder()
) {
fun save(user: User): User {
user.password = passwordEncoder.encode(user.password)
return userRepository.save(user)
}
}
UserControllerTest
(relevant test)
#Test
fun postUser_whenUserIsValid_receiveOk() {
val user = User(
0,
"test-user",
"test-display",
"P4ssword"
)
val response: ResponseEntity<Any> = testRestTemplate.postForEntity(API_USERS_BASE, user, Any::class.java)
assertThat(response.statusCode).isEqualTo(HttpStatus.OK)
}
The problem is that you use the same entity in the controller as in the service. So in the controller, it works as you expect. But in the service, you update the unencrypted password with the encrypted one and save that to the database. When you save to the database, the validation annotations are also checked, triggering the ConstraintViolationException.
The best option is to create a separate object for the controller. For example, create a CreateUserRequest class which is similar to the User entity, but only contains the fields that the controller needs. You can add your validation annotations there. Then in the service, convert the CreateUserRequest instance to a User entity. On the user class, remove the #Pattern validation since you don't want to validate the encrypted password.

At what positions I can use #Cacheable in spring boot with redis cache

At what positions I can use #Cacheable in spring boot with redis cache,
Can I use it with any method?
public UserDTO findByUserID(Long userID) {
User user = findUser(userID);
if (user != null) {
Password password = findPassword(userID);
return userMapper.mapToDTO(user, password);
}
return null;
}
private Password findPassword(Long userID) {
Password password = passwordRepository.findPasswordBasedOnUserID(userID);
return password;
}
#Cacheable("users")
private User findUser(Long userID) {
User user = userRepository.findByUserID(userID);
return user;
}
I have used it with method findUser because findByUserID returns the DTO which is obviously not an entity, so to get rid of it I created two methods that returns domain, but problem is that it is not saving or use redis cache, can anybody suggest me the problem or any solution?
No, you can't have it on private method of same service, because Spring does not handle calls to private methods of same class. You should move findUser or findByUserId to other service.

How to do not send #IdClass object in Spring JSON queries

I'm setting a server to get a CRUD api from a postgresql Database using JPA. Everytime I want to expose an object from the DB it duplicate the idObject.
When I get an object from the database using springframework and send it after that, it duplicate the idObject like this:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1",
"siteIdObject": {
"siteId": 3,
"contractId": "1"
}
}
SiteId and contractId are repeating...
but I want something like that:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1"
}
I want to avoid using DTO because I think there is a better way but I don't find it. Since I'm using springFramework for just one or two month I'm maybe forgeting something...
there is the code:
Site code:
#Entity
#IdClass(SiteId.class)
#Table(name = "site", schema="public")
public class Site {
#Id
#Column(name="siteid")
private Integer siteId;
#Id
#Column(name="clientid")
private Integer contractId;
private String name;
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy = "site")
public Set<Device> devices;
//setter, getter, hash, equals, tostring, constructor empty one and full one
SiteId code:
public class SiteId implements Serializable {
private Integer siteId;
private Integer contractId;
// setter, getter, constructor empty and full, hash and equals
Thanks to help :)
Bessaix Daniel
If you are using Spring you might also be using Jackson so if you annotate your SiteIdclass with #JsonIgnoreType it shouldn't be serialized at all when the Site object is serialized.
I am however unsure if this will break your application logic now that the id object is not serialized anymore.

How to remove some fields of an Object in Spring Boot response control?

this is one of my REST controller,
#RestController
#RequestMapping("/users/Ache")
public class Users {
#GetMapping
public User getUser() {
User user = new User();
return user;
}
}
As response, Spring boot will translate my Object to JSON,
this is response:
{
"username": "Ache",
"password": "eee",
"token": "W0wpuLAUQCwIH1r2ab85gWdJOiy2cp",
"email": null,
"birthday": null,
"createDatetime": "2019-03-15T01:39:11.000+0000",
"updateDatetime": null,
"phoneNumber": null
}
I want to remove password and token fields, How can I do?
I know two hard ways:
create a new hash map
and add some necessary fields, but it too complex
set those two fields to null
but it still leaves two null valued fields, it is too ugly.
Any better solution?
Spring leverages Jackson library for JSON marshalling by default. The easiest solution that comes to mind is making use of Jackson's #JsonIgnore but that would ignore the property on both serialization and de-serialization. So the right approach would be annotating the field with #JsonProperty(access = Access.WRITE_ONLY).
For instance, inside a hypothetical User class:
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
#JsonProperty(access = Access.WRITE_ONLY)
private String token;
An alternative would be using #JsonIgnore only on the getter:
#JsonIgnore
public String getPassword() {
return this.password;
}
You can also create another class, for instance UserResponse with all the fields except password and token, and make it your return type. Of course it involves creating an object and populating it, but you leave your User class clean without Jackson annotations, and de-couples your model from your representation.
Keep the getter and setter but add the WRITE_ONLY JsonProperty. This way password validations will work when you use the entity as the request body.
#NotBlank
#JsonProperty(access = Access.WRITE_ONLY)
private String password;

Sending POST request with Postman with #DBref

I want to send a POST request with Postman that creates a Purchase object and save it in the database.
My class Purchase:
#Document(collection = "purchases")
public class Purchase {
#Id
private String id;
#DBRef
private User buyer;
#DBRef
private List<File> FilesToPurchase;
private Long timestamp;
public Purchase() { }
public Purchase(User buyer, List<File> filesToPurchase) {
this.buyer = buyer;
FilesToPurchase = filesToPurchase;
}
// Getters and setters not posted here.
I want to insert in the database a new purchase done by an already existing User "buyer" who wants to purchase a list of already exsting Files "FilesToPurchase".
I have in my controller a POST function that receives a Purchase object using the annotation #RequestBody, but so far I've got NullPointerExceptions because of the empty Purchase object received.
I don't know how to handle #DBRef annotation. In Postman I try sending a JSON like this:
{
"buyer": {
"$ref":"users",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a2')"
},
"FilesToPurchase": [
{ "$ref":"files",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a5')"
}
]
}
Rename field "FilesToPurchase" and setter to "filesToPurchase" to match java conventions and try this
{ "buyer": { "id" : "5bb5d6634e5a7b2bea75d4a2" }, "filesToPurchase": [ { "id" : "5bb5d6634e5a7b2bea75d4a5" } ] }
By marking controller parameter with #RequestBody you ask Spring to deserialize input json to java object(Jackson ObjectMapper is used by default). It will not automaticly populate the whole #Dbref field, you should do it yourself by querying mongo if you want, however the only field you need in referred object to save object that reffers it is 'id'.

Resources