1

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 Userand 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 table aa_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 ?
Community
  • 1
  • 1
gbitaudeau
  • 2,207
  • 1
  • 17
  • 13
  • Have you tried using LEFT JOIN instead of JOIN FETCH, to get lazy loading? – GreyBeardedGeek Jul 22 '14 at 18:10
  • This join works perfectly as expected, the join I don't want were in User Subclasses not shown here – gbitaudeau Jul 22 '14 at 19:20
  • I believe the JPA version of `@Polymorphism` is `@Inheritance`. It allows you to use single-table or joined muti-table. Joined is known to have some performance issues. If you do single-table, then the table will have all fields and allow nulls in those fields, so you might loose some data integrity enforcement. Single table requires you specify a `@DiscriminatorColumn` on the parent object and a `@DiscriminatorValue` on the children that identifies which object JPA needs to build. More info here: http://en.wikibooks.org/wiki/Java_Persistence/Inheritance – CodeChimp Jul 23 '14 at 11:02
  • `@Polymorphism` and `@Inheritance` doesn't target the same goal : - `@Inheritance` as you explain change the way JPA store objects in DB and in fact have a performance impact, but doesn't prevent automatic polymorphism when objects are loaded from DB - `@Polymorphism` only apply to Hibernate and can prevent automatic polymorphism when objects are loaded from DB. See [this open question](http://stackoverflow.com/q/17300157/1661338) What I do is only use the [lightweight pattern](https://community.jboss.org/wiki/LightweightClass) – gbitaudeau Jul 23 '14 at 13:03

0 Answers0