Spring JPA, Lazy Initialization and Dto [duplicate] - java

This question already has answers here:
How to fix Hibernate LazyInitializationException: failed to lazily initialize a collection of roles, could not initialize proxy - no Session
(15 answers)
Closed 2 years ago.
I am trying to fetch user's profile data post login (from login success filter) but I am seeing an exception for Lazy loading the data. Please see the following sample code:
AuthenticationSuccessHandler.java
#Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Autowired
private UserService userService;
#Autowired
private Gson gson;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
User user = (User) authentication.getPrincipal();
UserLoginResponseDto userLoginResponseDto = userService.login(user.getUsername());
response.setStatus(HttpStatus.OK.value());
response.setContentType("application/json; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().println(gson.toJson(userLoginResponseDto));
response.getWriter().flush();
clearAuthenticationAttributes(request);
}
}
UserService.java
public class UserService implements UserDetailsService, TokenService {
#Autowired
private UserRepository userRepository;
#Transactional(readOnly = true)
public UserLoginResponseDto login(String email) {
Optional<UserModel> userOptional = userRepository.findByEmailIgnoreCase(email);
UserModel userModel = userOptional.get();
UserLoginResponseDto userLoginResponseDto = userModel.toUserLoginResponseDto();
return userLoginResponseDto;
}
}
UserModel.java
public class UserModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, unique = true, updatable = false)
private UUID id;
[A FEW MORE FIELDS]
#Column(length = 256, nullable = false, unique = true, updatable = false)
private String email;
#OneToMany(cascade = { CascadeType.ALL })
private List<RoleModel> roleModels;
public UserLoginResponseDto toUserLoginResponseDto() {
return new UserLoginResponseDto().setId(id).setEmail(email).setRoles(roleModels);
}
}
UserLoginResponseDto.java
public class UserLoginResponseDto {
private UUID id;
private String email;
private List<RoleModel> roles;
}
When an object of type UserLoginResponseDto is serialized in AuthenticationSuccessHandler, I see the following error message -
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: UserModel.roleModels, could not initialize proxy - no Session
QQ - How do I resolve this correctly without employing any of the following techniques?
[ANTIPATTERN] Open-In-View
[ANTIPATTERN] hibernate.enable_lazy_load_no_trans
FetchType.EAGER

Your problem is that you're passing the actual lazy List into setRoles, which doesn't trigger the full load. This indicates (immediately) that while you've separated your top-level database class from your top-level DTO, it's a "shallow" separation, which doesn't fully materialize the values. You haven't shown whether RoleModel is an entity or an embeddable, and that matters.
So the first step is to copy the items into a non-JPA form. If RoleModel embeddable (essentially a POJO), this could be as simple as setRoles(new ArrayList<>(roles)). Otherwise, you need a nested DTO, and at that point might consider something like MapStruct.
In either case, though, you're likely to run into the N+1 problem. You in fact do want an eager fetch in this case, and that's what JPA entity graph is for. You can tell Spring Data to fetch the list eagerly only when you want it, and this is a perfect example of when to do that.

Related

Spring One-To-Many relationship throws Exception : org.springframework.http.converter.HttpMessageNotWritableException

I try to create a simple relationship between two entities with spring. There is one User entity that holds many profile entities.
User Entity
#Entity
public class User {
#OneToMany(mappedBy = "user")
private List<Profile> profiles;
public List<Profile> getProfiles() {
return profiles;
}
public void setProfiles(List<Profile> profiles) {
this.profiles = profiles;
}
}
Profile Entity
#Entity
public class Profile {
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
When I try to find a profile with this.profileRepository.findById(id).get() inside a #RestController I get this error:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: User.profiles, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: User.profiles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->User["profiles"])]
The server encountered an unexpected condition that prevented it from fulfilling the request.
Can anyone explain to me why this is not working? I followed this tutorial.
as Mandar said, you can resolve it by eager fetch. But if you don't want to fetch all I mean eager fetch, then you have to initialize them for lazy fetch like this to the related class, your Profile class:
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Entity
public class Profile implements Serializable{
//
//
.. your other stuffs
}
Edit: also change your User entity like this:
#Entity
public class User implements Serializable{
#OneToMany(mappedBy = "user")
private List<Profile> profiles = new LinkedHashSet<Profile>();
//...other stufs..
.
.
}
Hope, this will help!
I faced the same issue.
But I was getting the error of " Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialise a collection of role:"
because I did not have declared toString method in the viewDTO
Below are the things which I had done
declared #Transactional in service method.
declared the field in persistence DTO as below
#Convert(converter = StringListConverter.class)
#JsonIgnore
#ElementCollection
protected List<String> someField;
where
public class StringListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> list) {
return String.join(",", list);
}
#Override
public List<String> convertToEntityAttribute(String joined) {
return new ArrayList<>(Arrays.asList(joined.split(",")));
}
}
was creating the viewDTO out of persistence DTO and setting the viewDTO in response entity .

Spring boot JPA - Lazy loading is not working for One to One mapping

Please note that I have looked at similar questions and I have explained why they haven't worked for me
I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)
User Entity
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Address address;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Note> notes;
}
Address Entity
#Entity
#Table(name = "addresses")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String street;
#Column
private String city;
#JsonIgnore
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
Note Entity
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.
I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:
Post 1
I also read that spring.jpa.open-in-view defualt value might be the culprit:
Post 2
Post 3
So i tried the following options:
I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me
Could not write JSON: failed to lazily initialize a collection of role error
I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.9</version>
</dependency>
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.
I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.
I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.
As mentioned by one of the users that it might a duplicate of another question on SO:
Suggested Possible duplicate
I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding
mapper.registerModule(new Hibernate5Module());
brings back the address associated to the User in the response.
It's working as in the JPA spec:-
Refer the below URL
https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html
LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.
Eager is mandatory (as the javadoc says the data must be eagerly fetched).
You may take a look at Jackson Serialization Views.
I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.
By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.
The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use #jsonIgnore so at other places you could return an Eager obj.
You can use the #jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :
Create view class:
public class ViewFetchType {
static class lazy{ }
static class Eager extends lazy{ }
}
Annotate your Entity
#Entity
public class User {
#Id
#JsonView(ViewFetchType.Lazy.class)
private String id;
#JsonView(ViewFetchType.Eager.class)
#OneToOne( fetch = FetchType.LAZY)
private Address address ;
}
Specify the FetchType class in your controller:
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping("get-user-details")
#JsonView(ViewFetchType.Eager.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
{
#RequestMapping("get-all-users")
#JsonView(ViewFetchType.Lazy.class)
public #ResponseBody List<User> getUsers() {
return userRepository.findAll();
}
}
Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200

Spring #Transactional cause NoClassDefFoundError

A guy ask me for help him resolve a strange bug in security service of his spring boot app, after hours of trail I manage to fix the bug but I really have no idea what happen. Please look at these classes:
Class User : user infomation in database
#Getter
#Setter
#Entity
#Table(name = "user", uniqueConstraints = {#UniqueConstraint(columnNames = {"user_name"})})
public class User extends DateAudit implements Serializable {
// Id, username, password and constructor... not really important
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> role = new HashSet<>();
}
Class UserDto : implement Spring's interface for manage user credential
#Getter
#Setter
#AllArgsConstructor
public class UserDto implements org.springframework.security.core.userdetails.UserDetails {
private long id;
private String Username;
#JsonIgnore
private String password;
private Collection<? extends GrantedAuthority> authorities;
public static UserDto create(User user) {
List<GrantedAuthority> authorities = user.getRole().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList());
return new UserDto(user.getId(), user.getUserName(), user.getPassword(), authorities);
}
// implement interface's methods, only getters, not important
}
Class CustomUserService : Auth service
#Service
public class CustomUserService implements org.springframework.security.core.userdetails.UserDetailsService {
private final UserRepository userRepository;
#Autowired
public CustomUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
#Transactional
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findUserByUserName(userName).orElseThrow(() -> {
return null;
});
return UserDto.create(user);
}
}
Full source code can be found here (it is only a simple spring boot app with some classes) : https://github.com/raizo000/admin_project (his repo uses embedded tomcat, I've tried to change it to jetty but it is not the cause)
When I run code the first time with these 3 classes. The line
return UserDto.create(user);
Give me a NoClassDefFoundError :
WARN 7252 --- [qtp223566397-22] org.eclipse.jetty.server.HttpChannel : /login
java.lang.NoClassDefFoundError: com/example/admin/dto/UserDto
at com.example.admin.services.CustomUserService.loadUserByUsername(CustomUserService.java:28) ~[classes/:na]
at com.example.admin.services.CustomUserService$$FastClassBySpringCGLIB$$b9234a59.invoke(<generated>) ~[classes/:na]
....
I have checked the jar file, there is a UserDto.class file in the right directory.
Remove the #Transactional help fix the error but cause another lazily initialize error and I end up change fetch = FetchType.LAZY in User class to fetch = FetchType.EAGER as a quick fix.
Why adding Transactional can cause a NoClassDefFoundError? Do remove Transactional is the right solution or there is a better fix?
When a class is not found, the first thing that you should make is clean and build.
Because, the last build is saving some dependency.
So the #Transactional #interface was not found in some corrupt package.
A rebuild(clean build) solves it.

Hibernate One-to-One DTO-Entity population

I have 2 entities in my DB with one-to-one one directional mapping:
User and PasswordResetToken. The idea behind this is to create new token each time user requests password reset and store only the latest one.
Below are my entities:
#Entity
#Table(name = "USERS")
#Getter #Setter
public class User implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "usersSeq")
#SequenceGenerator(name = "usersSeq", sequenceName = "SEQ_USERS", allocationSize = 1)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#Column(name = "EMAIL")
private String email;
#Column(name = "ROLE")
private Integer role;
}
///...
#Entity
#Table(name = "PASSWORD_RESET_TOKENS")
#Getter
#Setter
public class PasswordResetToken implements Serializable {
private static final int EXPIRATION = 24;
#Column(name = "TOKEN")
private String token;
#Id
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(nullable = false, name = "user_id")
private User user;
#Column(name = "EXPIRY_DATE")
private Instant expiryDate;
public PasswordResetToken() {
}
public void setExpiryDate(ZonedDateTime expiryDate) {
this.expiryDate = expiryDate.plus(EXPIRATION, ChronoUnit.HOURS).toInstant();
}
}
Also, I have DTOs created for both of them to pass them around my app.
Code snippets:
#Getter #Setter
public class PasswordResetTokenModel {
private String token;
private ZonedDateTime expiryDate;
private UserModel user;
}
UserModel is also used for Spring Security
#Getter
#Setter
public class UserModel extends User {
public UserModel(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private long id;
private String name;
public String getEmail() {
return this.getUsername();
}
}
For population I've created 2 populators:
#Component
public class UserPopulatorImpl implements UserPopulator {
#Autowired
UserDetailsService userDetailsService;
#Override
public UserModel populateToDTO(User user) {
UserModel userModel = new UserModel(user.getEmail(), user.getPassword(), userDetailsService.getAuthorities(user.getRole()));
userModel.setId(user.getId());
return userModel;
}
#Override
public User populateToDAO(UserModel userModel) {
User user = new User();
user.setEmail(userModel.getEmail());
user.setName(userModel.getName());
user.setPassword(userModel.getPassword());
//TODO: change it!
user.setRole(1);
return user;
}
}
//...
#Component
public class PasswordResetTokenPopulatorImpl implements PasswordResetTokenPopulator {
#Autowired
UserPopulator userPopulator;
#Override
public PasswordResetTokenModel populateToDTO(PasswordResetToken passwordResetToken) {
PasswordResetTokenModel passwordResetTokenModel = new PasswordResetTokenModel();
passwordResetTokenModel.setUser(userPopulator.populateToDTO(passwordResetToken.getUser()));
passwordResetTokenModel.setToken(passwordResetToken.getToken());
passwordResetTokenModel.setExpiryDate(ZonedDateTime.ofInstant(passwordResetToken.getExpiryDate(), ZoneId.systemDefault()));
return passwordResetTokenModel;
}
#Override
public PasswordResetToken populateToDAO(PasswordResetTokenModel passwordResetTokenModel) {
PasswordResetToken passwordResetToken = new PasswordResetToken();
passwordResetToken.setExpiryDate(passwordResetTokenModel.getExpiryDate());
passwordResetToken.setUser(userPopulator.populateToDAO(passwordResetTokenModel.getUser()));
passwordResetToken.setToken(passwordResetTokenModel.getToken());
return passwordResetToken;
}
}
I'm saving object using
sessionFactory.getCurrentSession().saveOrUpdate(token);
When I use this code, I'm getting following exception
object references an unsaved transient instance - save the transient instance before flushing: com.demo.megaevents.entities.User
There are currently 2 issues in this code:
Seems like Cascade.ALL in my OneToOne mapping is not working. If
I create separate primary key in Token class everything works almost
as expected but storing every created token in DB (more like
OneToMany relation), however I want to avoid it as I need to store
only one token per user in my DB
I don't like using new in populators, as it forces hibernate to create new object while flushing session. However, I also don't want to do another select to fetch this data from DB because just before mentioned populator I already do this query to fetch it and I think that it's an overhead.
Also, I really want to have DTOs and I don't want to remove DTO layer.
So, my questions:
What is the correct way to handle population between DTO and entities?
Are there any other improvements (probably architectural) to my solution?
Thanks a lot.
I'm not sure why you would let UserModel extend User, but I guess you did that because you didn't want to have to copy all properties from User into UserModel. Too bad, because that's what is going to be needed to have a clean separation between the entity model and data transfer model.
You get that exception because you try to persist a PasswordResetToken that has a reference to a User object with an id, but the User isn't associated with the current session. You don't have to query the user, but at least association it with the session like this:
PasswordResetToken token = // wherever you get that from
Session s = sessionFactory.getCurrentSession();
token.setUser(s.load(User.class, token.getUser().getId());
s.persist(token);
Cascading would cause the User to be created/inserted or updated via a SQL INSERT or UPDATE statement which is apparently not what you want.
You could do the Session.load() call in you populators if you want, but I'd not do that. Actually I would recommend not having populators at all, but instead create the entity objects in your service instead.
Normally you only have a few(mostly 1) ways of actually creating a new entity object, so the full extent of the transformation from DTO to entity will only be relevant in very few cases.
Most of the time you are going to do an update and for that, you should first select the existing entity and apply the fields that are allowed to be changed from the DTO on the entity object.
For providing the presentation layer with DTOs I would recommend using Blaze-Persistence Entity Views to avoid the manual mapping boilerplate and also improve performance of select queries.

How to secure RepositoryRestController

Let's say I have 2 have to entities:
#Entity
public class Post {
#NotEmpty
private String title;
#NotEmpty
#Lob
private String html;
#NotEmpty
#Lob
private String text;
#ManyToOne
private Topic topic;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "content_media", joinColumns = {#JoinColumn(name = "content_id")}, inverseJoinColumns = {#JoinColumn(name = "media_id")})
private Set<Media> medias = new HashSet<>();
#CreatedBy
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn
private User createdBy;
#LastModifiedBy
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn
private User lastModifiedBy;
...
}
#Entity
public class Media {
#NotEmpty
private String localPath;
#NotEmpty
private String fileName;
private long fileLength;
private String fileType;
private int focusPointX;
private int focusPointY;
...
}
And I'm exposing them using:
#RepositoryRestController
public interface MediaRepository extends JpaRepository<Media, Long> {
}
#RepositoryRestController
public interface PostRepository extends JpaRepository<Post, Long> {
}
I want these controllers to be secure. Let me explain myself.
If logged in user does not have ROLE_ADMIN, Medias should only be
accessable through posts and /medias/ should return 403 or 404
Only users that have ROLE_USER should be able to create to posts
Only the user that have created the post or the ones that have the ROLE_ADMIN should be able to update post.
Only the users that have ROLE_ADMIN should be able to delete posts
Is there a way to do these using RepositoryRestController and Spring Security or RepositoryRestController is for public resources only and I should write service layer myself using RestController?
Yes you can directly use Spring Security with Spring Data REST. You need to define the security of your routes using Spring Security Configuration as shown below:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().
antMatchers(HttpMethod.POST, "/posts").hasRole("USER").
antMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN").and().
csrf().disable();
}
}
Repository methods will be secured using Spring Security annotations. e.g.
#RepositoryRestController
public interface PostRepository extends JpaRepository<Post, Long> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN')")
void delete(Long aLong);
}
Code above is just a pointer. You can customize it as per your needs. Here is the link to Spring Data examples repository.
Update
To handle the update of the post by the user who created or by any user who is in ADMIN_ROLE you need to create a controller class and define a method with to handle the update
#RequestMapping(method={RequestMethod.PUT}, value={"posts/{id}"})
public void updatePost(#PathVariable("id") Long id, HttpServletRequest request)
{
//Fetch the authenticated user name
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
}
// Make a database call to verify if the user is owner of the post
Post post = postRepository.getPostByUserName(String username, Long postId);
if (post == null && !request.isUserInRole("ADMIN");) {
//return 403 error code
}
//proceed with the update
}

Categories

Resources