Retrieve an object which has a POJO as a primary key with Spring JPA - spring

I have the following classes: DepartmentMember and Account, mapped by a OneToOne relationship.
This is the DepartmentMember class:
#Entity(name="departmentmember")
#Table(name="departmentmember")
#Embeddable
public class DepartmentMember {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name="name", nullable=false)
private String nume;
#Column(name="lastName", nullable=false)
private String prenume;
#OneToOne(mappedBy="departmentMember",cascade=CascadeType.ALL,fetch=FetchType.LAZY, optional=false)
#JsonIgnore
private Account account;
public DepartmentMember() {}
public DepartmentMember(String nume, String prenume, String cNP, String email) {
super();
this.nume = nume;
this.prenume = prenume;
}
//getters and setters
}
And this is the Account class :
#Entity(name="users")
#Table(name="users")
public class Account {
#Id
private int id;
#Column(name="username", unique=true, nullable=false)
private String username;
#Column(name="password", nullable = false)
private String password;
#Column(name="authorities", nullable=false)
private String authorities;
#OneToOne(fetch=FetchType.EAGER)
#MapsId
#Embedded
private DepartmentMember departmentMember;
public Account() {}
public Account(String username, String password, String authorities) {
super();
this.username = username;
this.password = password;
this.authorities = authorities;
}
//getters and setters
}
I have defined an interface AccountRepository which extends the CrudRepository interface provided by Spring JPA.
What I want to do is define a query, which takes as a parameter a DepartmentMember id and retrieves the associated account for that member. Now this is how an Account object looks like:
{
"username": "Maria_Popescu",
"password": "4ec38c6e-2463-4562-99ba-9f6c2b4528c4",
"authorities": "ROLE_USER",
"departamentMember": {
"id": 2,
"nume": "Popescu",
"prenume": "Maria",
}
I tried using the findOne(int id) method, but it didn't work, so which is the correct approach to solve this?
Edit:
In the AccountRepository I have defined the following method :
Account findByDepartmentMemberId(int id) and I still get a not found error.

There was actually another problem in my controller. I managed to get it working by adding
Account findByDepartmentMemberId(#Param("id")int id);
in the AccountRepository

Related

HashSet in mapRow for JDBC template

I would like to make a select from one of my user table. In this table there is a field, which is a HashSet. This Set has "role" objects. I need a unique mapRow method for this operation, but I don't know how to to do it with the Set.
You can see my code below:
#Entity
#Getter
#Setter
#ToString
public class User {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
#Column( nullable=false )
private String password;
#Column( unique=true, nullable=false )
private String email;
private Boolean active;
#ManyToMany()
#JoinTable(name="role_user")
private Set<Role> roles;
}
public class UserMapper implements RowMapper<User> {
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
user.setEmail(rs.getString("email"));
user.setActive(rs.getBoolean("active"));
//user.setRoles();
return user;
}
}

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!

Hibernate : Unable to locate appropriate constructor on class

i want to retrieve data with DTO Projection using sprind data jpa, but unfortunately when i call the method an error has occurred :
[2020-05-28 21:02:03] Unable to locate appropriate constructor on class [com.burgerbuilder.backend.DTO.Response.UserResponse]. Expected arguments are: java.util.UUID, java.lang.String , java.lang.String , java.lang.String , java.util.Collection
[select new com.burgerbuilder.backend.DTO.Response.UserResponse(u.id,u.email,u.name,u.lastName,u.authorities) from com.burgerbuilder.backend.Model.User u where u.id=:id]
my repository :
#Repository
public interface UserRepository extends JpaRepository<User, UUID> {
#Query("select new com.burgerbuilder.backend.DTO.Response.UserResponse(u.id,u.email,u.name,u.lastName,u.authorities) from User u where u.id=:id")
Optional<UserResponse> findUserById(#Param("id") String id);}
User class:
#Entity
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Type(type=”uuid-char”)
private UUID id;
#NotNull
private String email;
#NotNull
private String password;
private String name;
private String lastName;
private String phoneNumber;
private String emailVerificationToken;
private boolean isEmailVerified=false;
private boolean isPhoneNumberVerified=false;
#OneToMany(mappedBy = “user”,cascade = CascadeType.ALL)
private List<Authority> authorities=new ArrayList();
DTO Class :
public class UserResponse {
private String userId;
private String email;
private String name;
private String lastName;
private List<Authority> authorities=new ArrayList<>();
public UserResponse(UUID userId, String email, String name, String lastName, List<Authority> authorities) {
this.userId = userId.toString();
this.email = email;
this.name = name;
this.lastName = lastName;
this.authorities=authorities;
}
}
can someone help me please ?

How get User to email?

I need get user to email with jpa
i have this code:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(#Param("email") String email);
User findById(#Param("id") Long id);
}
When i tried this :
User owner = userRepository.findById(user.getId()); //yes
User user=userRepository.findByEmail(email);//no
My class User is:
#Entity
#Table(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = -3009157732242241606L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
public User() {
}
//getters and setters
}
I want to obtain a user through the user's email, if I can obtain it through his id but I also need to obtain it through email
In you Model class "email" is not an unique so that it will return multiple rows to avoid that add
#Entity
#Table(name = "User")
public class User implements Serializable {
.....
#Column(unique=true,...)// .. add your extra
private String email;
.....
}
If you want top 1 result to be returned you could change the method
User findByEmail(#Param("email") String email);
with
User findOneByEmail(String email);

Spring Security + JPA user schema

As per Spring documentation if you need to manage spring security via database you should have some standard schema of tables. for example.
create table users(
username varchar(256) not null primary key,
password varchar(256) not null,
enabled boolean not null
);
create table authorities (
username varchar(256) not null,
authority varchar(256) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
The problem I am facing is following.
1) Not able to understand how could I achieve such schema of tables using JPA?
I have tried something as follows.
#Entity
#Table(name="USERS")
public class UsersPersistence extends Users implements Serializable{
private static final long serialVersionUID = 1009548075747154488L;
public UsersPersistence() {
super();
}
public UsersPersistence(long id, String userName, String password, boolean enabled) {
super(id, userName,password,enabled);
}
#Id
#GeneratedValue
#Column(name="id")
#Override
public long getId() {
return super.getId();
}
#Column(name="username", nullable=false)
#Override
public String getUserName() {
return super.getUserName();
}
#Column(name="password", nullable=false)
#Override
public String getPassword() {
return super.getPassword();
}
#Column(name="enabled", nullable=false)
#Override
public boolean isEnabled() {
return super.isEnabled();
}
}
This table created as per requirement stated in Spring documentation schema.
Problem in understanding is when i am trying to assign a foreign key on username in authorities table.Since JPA assign the foreign key's via the id of parent table (primary key Table) Or may be i do not know how to assign it.
Following is the JPA class which create problem :-
#Entity
#Table(name="AUTHORITIES")
public class AuthoritiesPersistence extends Authorities implements Serializable{
private static final long serialVersionUID = 1L;
public AuthoritiesPersistence() {
super();
}
public AuthoritiesPersistence(long id, UsersPersistence userName, String authority) {
super(id,userName,authority);
}
#Id
#GeneratedValue
#Column(name="id")
#Override
public long getId() {
return super.getId();
}
#Override
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="username", nullable=false)
public UsersPersistence getUserName() {
return (UsersPersistence) super.getUserName();
}
#Column(name="authority", nullable=false)
#Override
public String getAuthority() {
return super.getAuthority();
}
}
This table is created successfully but Spring security authentication is not able to recognize the username because JPA uses the foreign key id than the actual user name.
Any help would be appreciable. I am really stuck in creating a foreign key which will be based on the username rather than the id.
Thanks
You only have to stick to the schema given in Spring Security reference docs, when using a default JdbcDaoImpl as UserDetailsService implementation, which is the case if you have the <jdbc-user-service> tag in your security configuration. Even then it is possible to override the default SQL queries it uses to fetch users and authorities (refer to the namespace appendix).
However if you manage user accounts using hibernate, it would make sense to write your own UserDetailsService implementation, instead of trying to create JPA entities that result in the specific schema required by the JdbcDaoImpl.
The documentation states as well:
If your application does use an ORM tool, you might prefer to write a custom UserDetailsService to reuse the mapping files you've probably already created.
There are a couple of ways you could handle this:
You could write your own AuthenticationProvider using Hibernate for
DB access
You could use the <jdbc-user-service> and overwrite the SQL queries as mentioned by #zagyi. (http://static.springsource.org/spring-security/site/docs/current/reference/appendix-namespace.html#nsa-jdbc-user-service)
Or you can create the your schema to fit the standard <jdbc-user-service>
To use the approach you are interested in you have to know that aside from having the fields username, password and enabled spring security expects the username to be a unique identifier. This means that you can use the username property of your entity as Id for your DB and Hibernate.
If you don't want to do this a way of approaching this is to set a table wihch defines the authorites using an ID/Name and the authority. And then to set up the use a jointable to map them to the users.
Some untested examplecode:
Role:
#Entity
#DynamicUpdate
#Table(name ="authorities")
public class Authority{
private String authority;
#Id
#Column(name="authority")
public String getAuthority() {
return authority;
}
User:
#Entity
#DynamicUpdate
#Table(name = "users", uniqueConstraints={ #UniqueConstraint(columnNames={"username"})})
public class User {
private String username;
private List<Authority> authorities;
#Type(type = "numeric_boolean")
private boolean enabled;
#Id
#Column(name="username")
public String getUsername() {
return username;
}
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "authorities",
joinColumns = #JoinColumn(name = "username"),
inverseJoinColumns = #JoinColumn(name = "rolename")
)
public List<Authority> getauthorities() {
return authorities;
}
#Column(name="ENABLED")
public boolean isEnabled() {
return enabled;
}
When the base is running you can add properties for internal use as u like.
I have figure out the Alternative which is "Configuring the JdbcUserDetailsManager to use custom SQL queries" at least i can create my tables via JPA and Can hope
" users-by-username-query and authorities-by-username-query " would do my work indeed.
To achieve it I have to add following schema .
create table custom_user_authorities (
id bigint identity,
user bigint not null,
authority varchar(256) not null,
);
this schema have id(which will be auto-incremented) which will definitely work for JPA.
I was able to map those tables using the following class definition:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column
private boolean enabled;
#Column
private String firstName;
#ElementCollection
#JoinTable(name = "authorities", joinColumns = {#JoinColumn(name = "email")})
#Column(name = "authority")
private Set<String> roles;
public User() {
}
public Serializable getId() {
return username;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Set<String> getRoles() {
return roles;
}
public void setRoles(Set<String> roles) {
this.roles = roles;
}
}

Resources