Spring boot JPA many to many with extra column insert and update issue - spring

Here is my initial question.
Spring Data JPA Many to Many with extra column User and Roles
Now I have the right tables created, but can't make it work for the update.
Here is the code:
User.java
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<UserRole> roles;
// getters and setters
}
Role.java
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// getters and setters
}
UserRole.java
#Entity
#Table(name = "users_to_role")
public class UserRole implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
private Date createdAt;
public UserRole(){}
public UserRole(User user, Role role, Date date) {
this.user = user;
this.role = role;
this.createdAt = date;
}
// getters and setters
}
Controller
#RestController
public class APIController {
#Autowired
RoleRepository roleRepository;
#Autowired
UserRepository userRepository;
#ResponseBody
#RequestMapping(value = "create", method = RequestMethod.GET)
public String create(){
//Insert test - WORKING BUT NOT SURE IF ITS RIGHT WAY
List<UserRole> userRoles = new ArrayList<>();
Role role = roleRepository.getOne((long) 1);
//Create user
User user = new User();
user.setUsername("test");
//Create userRole
userRoles.add(new UserRole(user, role, new Date()));
user.setRoles(userRoles);
userRepository.save(user);
return "created";
}
#ResponseBody
#RequestMapping(value = "edit", method = RequestMethod.GET)
public String edit(){
//Edit test - NOT working
List<UserRole> userRoles = new ArrayList<>();
Role role = roleRepository.getOne((long) 2);
//get user from db
User user = userRepository.getOne((long) 1);
//Create userRole
userRoles.add(new UserRole(user, role, new Date()));
// WAS FIRST ATTEMPT using user.setRoles(userRoles); but got error and use
//https://stackoverflow.com/questions/9430640/a-collection-with-cascade-all-delete-orphan-was-no-longer-referenced-by-the-ow
//user.setRoles(userRoles);
user.getRoles().clear();
user.getRoles().addAll(userRoles);
userRepository.save(user);
return "done";
}
}
I am getting this error:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'user_id' cannot be null

Related

JPA OneToOne and shared primary key need manual assignment

I'm using Springboot and JPA to create two tables sharing the same primary key.
For the first table I write:
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
private UserLogin login;
}
For the second table I write:
public class UserLogin implements Serializable
{
#Id
private Long user_id;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user)
{
this.user = user;
this.user_id = user.getId();
}
}
Other stuff are omitted for conciseness. The code works because I manually set the id of UserLogin by writing the statement
this.user_id = user.getId();
otherwise I get the error:
Hibernate error: ids for this class must be manually assigned before calling save():
I guess that the ids can be manually managed but I cannot get the right configuration.
UPDATE:
I found the solution thanks (see the accepted answer). Now I just would get rid of the findById() when setting the user login.
//these methods are defined within a dedicated #Service
#Transactional
public void createLoginInfo(UserAccount user)
{
UserLogin userlogin=new UserLogin();
this.addLoginToUser(userlogin,user);
loginService.save(userlogin);
}
#Transactional
public void addLoginToUser(UserLogin login, UserAccount account)
{
//whit this commented line works
//UserAccount acc= this.findById(account.getId());
login.setUser(account);
account.setLogin(login);
}
//In a transactional test method I first create the user then I call
userService.save(theuser);
userService.createLoginInfo(theuser);
You have a bidirectional relationship, but have mapped it with a few competing options that don't work well together. First, in UserAccount, it isn't clear why you have an ID that is generated, yet try to also map it with the relationship (specifically using a PrimaryKeyJoinColumn). If you want it generated, it can't also be a foreign key value in a reference - and you've already got this relationship setup as the 'other' side via the 'mappedBy' setting. It should just be:
public class UserAccount implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE,
CascadeType.REFRESH}, fetch=FetchType.LAZY)
private UserLogin login;
}
User login then should just be:
public class UserLogin implements Serializable {
#Id
private Long user_id;
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user) {
this.user = user;
}
}
Note because you have the mapsId annotation on the user relationship, JPA will set the user_id property for you once the ID is assigned - there is no need to manually set it yourself. You can, but if you do you must insure it was assigned previously - which requires a save/flush on the UserAccount. If you don't actually use the Long user_id property, you don't really even need to map it; you can just mark the user property as the ID:
public class UserLogin implements Serializable {
#Id
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},
fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private UserAccount user;
public void setUser(UserAccount user) {
this.user = user;
}
}
The Long ID from UserAccount then can be used to lookup UesrAccounts and UserLogin instances.
Try this :
public class UserLogin implements Serializable
{
#Id
private Long user_id;
#OneToOne(fetch=FetchType.LAZY)
#MapsId
#JoinColumn(name = "user_id")
private UserAccount user;
public UserAccount getUser() {
return user;
}
public void setUser(UserAccount user) {
this.user = user;
}
}
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
To persist UserLogin :
EntityManager em;
UserAccount user = em.find(UserAccount.class, 1L)
UserLogin login = new UserLogin();
login.setUser(user);
em.persist(login);

JPA lazy initialization error with #OneToMany #EmbeddedId

In Sprinboot/JPA I defined an entity with one-to-may association as follows:
#Entity
#Table(name = "useraccount", catalog = "useraccount")
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//other stuff...
#OneToMany(mappedBy ="tokenId.user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, orphanRemoval =true, fetch=FetchType.LAZY)
private Set<SecureToken> tokens = new HashSet<>();
public Set<SecureToken> getTokens()
{
return this.tokens;
}
//other getter and setter
}
The SecureToken entity:
#Entity
#Table(name = "secureToken", catalog = "useraccount")
public class SecureToken implements Serializable
{
#EmbeddedId
public SecureTokenId tokenId= new SecureTokenId();
#Column(unique = true)
private String token;
private Timestamp isConsumed;
#CreationTimestamp
#Column(updatable = false)
private Timestamp timestamp;
#Column(updatable = false)
#Basic(optional = false)
private Timestamp expireAt;
#MapsId("user_id")
#JoinColumn(name = "user_id", referencedColumnName ="id")
#ManyToOne
private UserAccount user;
public SecureToken(UserAccount user, String token, String tokenType, Timestamp timestamp, Timestamp expire)
{
super();
this.token=token;
this.tokenId.setTokenType(tokenType);
this.tokenId.setUser(user);
this.timestamp=timestamp;
this.expireAt=expire;
this.isExpired=false;
}
}
The SecureTokenId:
#Embeddable
public class SecureTokenId implements Serializable
{
#Column(name="tokenType")
private String tokenType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private UserAccount user;
public SecureTokenId()
{
super();
}
public SecureTokenId(String tokenType)
{
//this.user_id=user_id;
this.tokenType=tokenType;
}
#Override
public boolean equals(Object o)
{
if (o == null || getClass() != o.getClass())
return false;
SecureTokenId that = (SecureTokenId) o;
return Objects.equals(this.tokenType, that.tokenType) &&
Objects.equals(this.user.getId(), that.user.getId());
}
#Override
public int hashCode() {
return Objects.hash(tokenType, this.user.getId());
}
public void setTokenType(String tokenType)
{
this.tokenType=tokenType;
}
public String getTokenType()
{
return this.tokenType;
}
public void setUser(UserAccount user)
{
this.user=user;
}
public UserAccount getUser()
{
return this.user;
}
public Long getTokenId()
{
return this.user.getId();
}
}
But calling the method getToken() of entity UserAccount gets the famous "LazyInitializationException". I generally use Hibernate.initialize, but with this configuration I cannot get rid of the problem.
This how I create a token within a #Service annoted SecureTokenService class.
#Override
#Transactional
public SecureToken generateToken(UserAccount user, String tokenType)
{
byte[] random = new byte[64];
new SecureRandom().nextBytes(random);
Timestamp timestamp = java.sql.Timestamp.valueOf(LocalDateTime.now());
LocalDateTime expire= LocalDateTime.now().plusHours(12);
SecureToken token = new SecureToken(new SecureTokenId(user, tokenType),Base64.encodeBase64URLSafeString(random),
timestamp, Timestamp.valueOf(expire));
return token;
}
Then in the UserService class (#Service annotated) I try to create a token:
SecureToken token = secureTokenService.generateToken(user, type);
secureTokenService.save(token);
user.addSecureToken(token); //Error
this.save(user)
When I try to associate the token with the user the error is thrown. Without that statement, the application seems working but even with "spring.jpa.open-in-view = false" in application.properties calling user.getTokens() rises the lazy initialization error.
In parent child relationship, you didn't declare any parent reference from child side.
In the parent side (UserAccount), you declared as follows
#OneToMany(mappedBy ="user"....
Which means your child side (SecureToken) there is no such property named user.
To get rid of this situation,
First you need to declare user inside of SecureToken / SecureTokenId. From your definition, you declared user_id inside SecureTokenId, instead declare user inside SecureTokenId.
...
public class SecureTokenId ... {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private UserAccount user;
...
}
Then in the UserAccount declare the #OneToMany as follows
#OneToMany(mappedBy ="tokenId.user"...
private Set<SecureToken> tokens;

Spring boot UserDetailsService Multi-User with extra fields

I have a spring boot project that has 3 types of users (Admin, Expert, Customer) and the application is for Experts that register on site for giving services like fixing computers to Customers that are asking help in site.
I have an inheritance of different kind of User types as following.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.INTEGER)
public abstract class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private Set<String> roles = new HashSet<>();
// getter & setter...
}
#Entity
#DiscriminatorValue("1")
public class Admin extends User {
}
#Entity
#DiscriminatorValue("2")
public class Expert extends User {
private Byte[] expertPhoto;
private String password;
// some other fields & getter & setter...
}
#Entity
#DiscriminatorValue("3")
public class Customer extends User {
private Long credit;
private Set<CustomerOrder> orders = new HashSet<>();
// some other fields & getter & setter...
}
I want to use spring boot security and implement UserDetailsService, my problem is that how to design when I have different User types (Expert, Customer, etc.)?
I want users to be able to have different roles (admin, expert, customer) with one username.
How should I design my system to solve these requirements?
Your role modal seems a bit off. It is better to have a single type of User and fill it with list of a new Role entity. The new User entity will look like the following:
#Table(name = "user")
#Entity
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username", unique = true, nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")},
)
private Set<Role> roles;
// getters and setters & other fields user can have
}
And the Role entity will look like this:
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "role_name", unique = true, nullable = false)
private String roleName;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
}
Then, you need to implement org.springframework.security.core.userdetails.User interface to use as a concrete implementation of spring security class Useron your UserDetailsService. Notice that this class is also called User and is different than the User class on your system.
public class MyUserDetail extends User {
private String otherFieldsLikePhoto; // you can add different fields like this to keep extra information
public MyUserDetail(String username, String password, Collection<? extends GrantedAuthority> authorities, String otherFieldsLikePhoto) {
super(username, password, authorities);
this.otherFieldsLikePhoto = otherFieldsLikePhoto;
}
}
Then, you can create your UserDetailsService by implementing org.springframework.security.core.userdetails.UserDetailsService of spring security.
What you will achieve UserDetailsService is to load the user in the MyUserDetail format we just created. It will be something like this:
public class MyUserDetailsService implements UserDetailsService {
private final UserReadService userReadService; // put your service to get user from db
public MyUserDetailsService(UserReadService UserReadService) {
this.userReadService = UserReadService;
}
#Override
public UserDetails loadUserByUsername(String username) {
User user = userReadService.getByUsername(username); // get user from db
String otherFieldsLikePhoto = getUserPhotoOrAnythingElse(user); // get your extra fields however you want
return new MyUserDetail(
user.getUsername(),
user.getPassword(),
getAuthoritySetOfUser(user), // notice how we embed roles to UserDetail
otherFieldsLikePhoto
);
}
// this function is not necessary but useful to calculate authority set calculation on helper
private Set<SimpleGrantedAuthority> getAuthoritySetOfUser(User user) {
Set<Role> userRoles = user.getRoles(); // get roles of user like ADMIN, EXPERT etc.
Set<SimpleGrantedAuthority> authorities = roles.stream()
.map(rolex -> new SimpleGrantedAuthority(rolex.getRoleName()))
.collect(Collectors.toSet());
return authorities;
}
}

Cascading of persist, does not create identifiaction

Having this entities:
User.java:
#Entity
#NoArgsConstructor
#Getter
#Setter
public class User {
#Id #GeneratedValue
private Long id;
private String username;
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#MapKey(name = "friend_id")
private Map<User, Friendship> friends = new HashMap<>();
}
Friendship.java:
#Entity
#Data
#IdClass(Friendship.class)
public class Friendship implements Serializable {
#Id
private Long owner_id;
#Id
private Long friend_id;
private String level;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("owner_id")
private User owner;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("friend_id")
private User friend;
}
and DemoApplication.java:
#Bean
public CommandLineRunner loadData(UserRepository userRepo){
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
User owner = new User();
owner.setUsername("owner");
User f1 = new User();
f1.setUsername("f1");
User f2 = new User();
f2.setUsername("f2");
Friendship fs1 = new Friendship();
fs1.setOwner(owner);
fs1.setFriend(f1);
Friendship fs2 = new Friendship();
fs2.setOwner(owner);
fs2.setFriend(f2);
owner.getFriends().put(f1, fs1);
owner.getFriends().put(f2, fs2);
userRepo.saveAndFlush(owner);
}
};
}
I get error:
A different object with the same identifier value was already associated with the session : [com.example.demo.model.Friendship#Friendship(owner_id=null, friend_id=null, level=null, owner=com.example.demo.model.User#2b036135, friend=com.example.demo.model.User#a9e28af9)]
Which means both Users f1 and f2, are having null in Long id. The indeed have, when the object is created, but I thought the mapping had specified CascadeType.ALL and #GeneratedValue so the if should be created.
But I had try to set the ids myself:
...
f1.setUsername("f1");
f1.setId(1L);
User f2 = new User();
f2.setUsername("f2");
f2.setId(2L);
...
But now I got
detached entity passed to persist: com.example.demo.model.User
So I guess I should let the creation of primary keys on JPA. But as you saw from above, it does not that even with Cascading. So what now?
Try adding this under your #Id annotation
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)

Hibernate JPA loop

I created an entity class :
#Entity
#Table(name="users")
#Getter #Setter
public class UserModel implements Serializable {
#Setter(AccessLevel.NONE)
#Getter(AccessLevel.NONE)
private static final long serialVersionUID = -5608230793232883579L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private String userId;
#Column(nullable = false, length = 50)
private String firstName;
#Column(nullable = false, length = 50)
private String lastName;
#Email
#Column(nullable = false, length = 120, unique = true)
private String email;
#Column(nullable = false)
private String encryptedPassword;
private Boolean emailVerificationStatus = false;
private String emailVerificationToken;
#ManyToMany(cascade= { CascadeType.PERSIST }, fetch = FetchType.EAGER )
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<RoleModel> roles;
#JsonManagedReference
#OneToMany(mappedBy = "user")
private List<ProjectModel> projects;
}
For the list of projects, I also have an entity class:
#Entity
#Table(name= "projects")
#Getter #Setter
public class ProjectModel implements Serializable {
#Setter(AccessLevel.NONE)
#Getter(AccessLevel.NONE)
public static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private String projectId;
// ...
#Column
#JsonManagedReference
#OneToMany(mappedBy = "project")
private List<ObjectiveModel> objectives;
// ...
#JsonBackReference
#ManyToOne(
cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH },
fetch = FetchType.LAZY
)
private UserModel user;
}
I also use a DTO layer to communicate with database:
#Getter #Setter
public class UserDto implements Serializable {
#Setter(AccessLevel.NONE)
#Getter(AccessLevel.NONE)
private static final long serialVersionUID = -5352357837541477260L;
// contains more information than models used for rest
private long id;
private String userId;
private String firstName;
private String lastName;
private String email;
private String password;
private String encryptedPassword;
private String emailVerificationToken;
private Boolean emailVerificationStatus = false;
private List<String> roles;
private List<ProjectDto> projects;
}
Each entity has its own Dto equivalent. I can create a user. My issue is trying to log in. My userServiceImpl implements Spring Security UserService. Here is my implementation :
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UserModel userModel = userRepository.findByEmail(email);
if(userModel == null)
throw new UsernameNotFoundException("User with email " + email + " not found");
return new UserPrincipalManager(userModel);
}
My UserPrincipalManager :
public class UserPrincipalManager implements UserDetails {
private static final long serialVersionUID = 7464059818443209139L;
private UserModel userModel;
private ProjectModel projectModel;
#Getter #Setter
private String userId;
#Autowired
public UserPrincipalManager(UserModel userModel) {
this.userModel = userModel;
this.userId = userModel.getUserId();
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
Collection<AuthorityModel> authorityModelEntities = new HashSet<>();
// get user roles
Collection<RoleModel> roleModels = userModel.getRoles();
if (roleModels == null) {
return authorities; // null
}
// get user roles
roleModels.forEach((role) ->{
authorities.add(new SimpleGrantedAuthority(role.getName()));
authorityModelEntities.addAll(role.getAuthorities());
});
// get user authorities
authorityModelEntities.forEach(authorityModel -> {
authorities.add(new SimpleGrantedAuthority(authorityModel.getName()));
});
return authorities;
}
#Override
public String getPassword() {
return this.userModel.getEncryptedPassword();
}
#Override
public String getUsername() {
return this.userModel.getEmail();
}
// we do not store this information in DB
#Override
public boolean isAccountNonExpired() {
return true;
}
// we do not store this information in DB (yet)
#Override
public boolean isAccountNonLocked() {
return true;
}
// we do not store this information in DB (yet)
#Override
public boolean isCredentialsNonExpired() {
return true;
}
// isEnabled depending if account is activated => email verification status value
#Override
public boolean isEnabled() {
return this.userModel.getEmailVerificationStatus();
}
}
While trying to log in a User sql request is looping.
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:59)
at org.modelmapper.internal.converter.MergingCollectionConverter.convert(MergingCollectionConverter.java:31)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:303)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:110)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:242)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:188)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:152)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:106)
In the end the application crashes and returns a 403 error.
2020-10-05 12:07:22.215 DEBUG 4564 --- [nio-8080-exec-8] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.3.3.RELEASE.jar:5.3.3.RELEASE]
The login fonction works if user do not have project associated.
I don't know anything about model mapper, but I would like to provide you an alternative solution because I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(UserModel.class)
public interface UserDto extends Serializable {
#IdMapping
Long getId();
String getUserId();
String getFirstName();
String getLastName();
String getEmail();
String getPassword();
String getEncryptedPassword();
String getEmailVerificationToken();
Boolean getEmailVerificationStatus();
Set<String> getRoles();
Set<ProjectDto> getProjects();
#EntityView(ProjectModel.class)
interface ProjectDto {
#IdMapping
Long getId();
String getProjectId();
// Other mappings...
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserDto a = entityViewManager.find(entityManager, UserDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
The big bonus here, it will only fetch the columns that are actually needed and it validates the DTO model against your JPA model during boot time, so there are no more runtime surprises!

Resources