I want to make a secured restful service using Spring, JPA and Hibernate.
Each endpoint must be secured and I use spring security for that purpose using a specific UserDetailsService as describe in the spring security documentation : http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/reference/htmlsingle/#userdetailsservice-implementations
Here the point : as each request will be authentified, it means that for each request, my UserDetailsService will load a user form my database and need to get its password and its roles.
The default JdbcDaoImpl use 2 requests to find a user and its roles. I don't use it because :
- I want to have my users' Id in a UserDetails object as my business controller use it.
- I want that my users are loaded with only one request
My user business object look like :
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "aa_user")
public class User
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@NotNull
@Column(unique = true)
protected String login;
protected String password;
@ManyToMany(targetEntity = Role.class)
protected List<Role> roles;
//getter/setter
}
And my repository :
public interface UserRepository extends JpaRepository<User, Long>
{
@Query("SELECT u FROM User u JOIN FETCH u.roles where u.login = :login")
User findByLogin(@Param("login") String login);
}
My UserDetailsService :
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User user = null;
try
{
user = userRepository.findByLogin(username);
} catch (Throwable e) {
e.printStackTrace();
throw new UsernameNotFoundException("Can't find username: " + username);
}
if (user == null) {
throw new UsernameNotFoundException("Can't find username: " + username);
}
UserDetailsImpl userDetails = new UserDetailsImpl(user);
return userDetails;
}
I have a lot of User subclasses (like Seller, ...) which have associations to other objects fetch eagerly for business purpose and with this implementation, the userRepository.findByLogin(username)
make something like 3 or more heavy joins to give me the right full fetch user object (which is "normal" of course), but I only want one light query with only the User's field initialized.
According to this question :
Avoiding outer joins across tables when using joined inheritance with Spring Data JPA what I want to do seems complicated, but I found that I could use @Polymorphism
Hibernate annotation with PolymorphismType.EXPLICIT
:
https://stackoverflow.com/a/18358863/1661338
@Polymorphism
is not JPA compliant and brake parts of my business logic or need that I refactor a lot of query.
To avoid that, I add a second class mapped on the same Table :
@Entity
@Table(name = "aa_user")
public class LightUser implements SimpleUser
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@NotNull
@Column(unique = true)
protected String login;
protected String password;
@ManyToMany(targetEntity = Role.class)
protected List<Role> roles;
//getter
}
And my repository :
public interface LightUserRepository extends JpaRepository<User, Long>
{
@Query("SELECT u FROM LightUser u JOIN FETCH u.roles where u.login = :login")
LightUser findByLogin(@Param("login") String login);
}
Both User
and LightUser
implements the same SimpleUser
interface with all getter needed for my UserDetailsImpl
.
Now, lightUserRepository.findByLogin(username)
make the smartest query possible and get only what I want.
I still have questions :
- Is it JPA compliant ?
hbm2ddl.SchemaExport
work but try to put 2 times the same foreign key between tableaa_user
and role table. How to avoid that ?- It could be less painful to write if I can make a query with the same behavior as
PolymorphismType.EXPLICIT
. Does anyone know if it's possible ? - Can LightUser be "readOnly" object to avoid mistakes ?