Keep SOLID in mind working with Hibernate

Whether you like it or not, but Hibernate is the most popular ORM solution for JVM based languages. I have used it in all companies I worked for(starting from big E-commerce sites and ending with small startups). Honestly I don't like having Hibernate or any JPA implementations in projects I am working on because in my humble opinion it brings a huge layer of complexity(read on to know a reason). There are some alternatives such as JOOQ and MyBatis(personally my favorite one). Also I heard about Spring Data JDBC but didn't have a chance to use it. So why do I think that Hibernate is a bad thing, well , one part of a reason is a documentation but the biggest problem is developers

Tom Reads book
Tom & Jerry by William Hanna and Joseph Barbera

Documentation

The documentation of Hibernate is really obscure. It doesn't cover a great variety of cases and the only way to find out why you have an unexpected behaviour is to refer to Vlad Mihalcea blog posts(or StackOverFlow).

As an example, in one of the companies I worked for we had a problem when EntityGraph didn't work for nested collections. There is no documentation that covers a reason and searching through the github issues gave us a ticket with exactly the same problem that has been solved in a newest Hibernate release, because project was using Spring Boot, we had to wait for Spring Boot team to release a new major version that bumps Hibernate version in a parent bom.
But these kind of problems are somewhat resolvable, you google , debug Hibernate's source code(trust me, not a pleasant experience) or see release notes to see if your problem has been solved, so documentation is not a biggest pitfall.

Developers

One thing I noticed everytime I start working on a new project. Java developers don't know how to organize an entity layer. As an example, let's say we are building an e-commerce website, of course we have a user,orders,items,comment tables. Order has a foreign key to user and item , and so does a comment table. We can go to Hibernate's quick start page and see how to map these relationships to Entity class.

@Entity("user)
class UserEntity{    
    @OneToMany(init=LAZY)
    private List<Orders> orders
    @OneToMany(init=LAZY)
    private List<Comments> comments
    //credentials
    private String username
    private String password
    private Role role

Straight forward right? Yes it is , but the problem here is what are responsibilities of this class
(I in SOLID).To put it another way, in which case Am I allowed to change this class ?

Problem 1 - Smart Entities

Let's say we are using Spring Security to add a security layer for http based endpoints.Spring requires you to provide an implementation of UserDetails interface that has methods to get user's name,password and roles. A User Entity class already has these parameters right? So why not just use it ?class UserEntity implements UserDetails. If you do so then the whole security layer will be mixed up with business logic. You will be able to access user's orders right from ServletListener.Whenever you change User Entity apart from checking a business logic you will have to check that it won't break a security layer too . Does it follow a single responsibility principle ? I doubt it does. So what is a solution ?

Hibernate doesn't mention it explicitly but you don't have to have a single Entity that represents a database table. What if we keep a User Entity above and introduce a new one

@Entity("user)//same user table
class UserCredentialsEntity implements UserDetails{    
    //credentials
    private String username
    private String password
    private Role role

So now whenever Hibernate fires a select query it won't touch any dependencies apart from the fields listed in UserCredentials class

Problem 2 - DTO layer

I think everybody knows what the fetch modes in Hibernate are. In essence it's a way to let Hibernate know when you need to use joins to fetch class dependencies or just fetch attributes of a single table. I bet you saw LazyInitException before which happens when you try to access a nested object or collection out of a transaction which was loaded lazily. Let's have another use case with the same User Entity. Imagine when users open their profiles, they only see their names and some meta information such as birth , city and so on. Spring has a pattern called DTO which implies that you can't expose an Entity layer into a controller layer or put it another way, the controller has to return a DTO, not a business Entity. Imagine you have a layer that transforms an entity into a dto(if you don't want to write this layer manually then check out a MapStruct project that does it for you)

@Service
class EntityToDto {    
    public UserDto userToDTO(UserEntity user){
       ...

And here is the UserDTO

class UserDTO
    private String name
    private String birthday
    private List<CommentsDto> comments
    private List<OrdersDto> orders

So how EntityToDto is supposed to know whether UserEntity was loaded with or without corresponding orders? One solution I see often is to inject an EntityManager right intoEntityToDto and check every single dependency (If you don't care about exposing a JPA implementation , you can use static method from Hibernate Hibernate.isInitialized(entity); but new problem arises, it's hard to test a class that uses static methods).

@Service
class EntityToDto
    @Autowired EntityManager entityManager
    public UserDto userToDto(UserEntity user){
        if(entityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(user.getOrders())){
           userDto.setOrders(this.ordersToDto(user.getOrders));

It's just horrible, now Dto layer has an EntityManager, a manager that can easily fire sql queries right from DtoLayer. What is more , Dto layer is becoming too smart, it can work with a case when the user has or doesn't have loaded orders(Interface segregation principle). What is a solution? The one I came up with is to introduce a third UserEntity class that doesn't have a dependency on orders and credentials.

@Entity("user)//same user table
class UserProfileEntity {    
    private String name
    private String birthday

It doesn't have any information about credentials because they are only known by the security layer and it doesn't have orders. So now UserProfileEntity is only responsible for a user page and we can remove entity manager from Dto .Moreover, now we can modify UserEntity class by declaring all dependencies as eager.We know for sure that users of this class will work with orders, so there is no need to declare orders as lazy.

I see your point, but we would end up having too many classes, is it any better than having a single class per table?

Ok, let me convince you with one more example, the problem is not only in DtoLayer but in the repository layer too. Repository layer encapsulates the logic required to access data sources. Having single user entity means we need multiple methods to fetch the same user with/without orders

interface UserRepo extends CrudRepository
    @EntityGraph(attributePaths = {"orders"})
    List<UserEntity> getUserWithOrders(String userId);    

    List<UserEntity> getUser(String userId);    

In the example above we are using Spring Data to generate two methods, using EntityGraph we are telling Hibernate when to load a lazy collection. I think the repository layer shouldn't be mixed up with this logic, it should only be responsible for fetching data without knowing how to do it. Introducing a new entity class that doesn't contain any refereces to other tables precisely solves this problem.

Problem 3 - Spring Data is bloated

I assume nowadays most people are working with Hibernate through Spring Data abstraction, it's great and allows you to get rid of a boilerplate, however, I advise you not to leak Spring Data Repositories into a Service layer.

interface UserRepository extends CrudRepository<UserEntity,Long>{
        UserEntity getOneById(Long id);

Here is one example of Spring Repository, implementation will be created in runtime by spring and using specific naming convention it understands what kind of query you are expecting(in example above it's a select of a single user by id). But because you extend CrudRepository your interface gets additional methods such as fetching and deleting all users. According to Martin Fowler ,Service layer defines a set of available operations and coordinates the application's response in each operation. Having the repository above in UserService we imply that deleting all users from the database is a part of a service operation but it's not. There is a great article that proposes an idea of having Spring layers encapsulated by Domain Objects . But this approach is hard to implement in existing applications. What I propose is something similar, create a sub package where you store Spring data repositories(ideally make it package friendly so only classes from the same package can have an access ) and then introduce a new repository package that contains your domain specific datasource interaction.

class UserRepository{
        @Autowired UserSpringDataRepository repo;
        UserEntity getOneById(Long id){
            return this.repo.getOneById(id);
        }

In the example above we have a repository class which only has 1 method, then instead of injecting Spring Data Repo ,the service layer will use this Repository. Cons ? A new repository clearly defines a datasource operation that can be used by the service layer.

Problem 4 - Transaction management is tricky

I noticed that many people don't really understand how transaction management works.Having @Transactional on top of your method will make a deal right? Let's make a small test, do you see a problem in the code below ? We are trying to update a user with data from html form.

class UserService{
        @Autowired UserRepo repo;
        @Transactional 
        void updateUser(Long userId, UserProfile page) {
            var entity =  this.repo.getOneById(id);
            entity.setName(page.getName);
            if(page.birthday.isValid(){
                    entity.setBirthday(page.birthday);
            }else {
                log.error("Invalid birthday");
                return;
             }
            repo.save(entity);
        }

If you don't then read on. All entities managed by Hibernate have a state, if your entity is inside of a database transaction it has a managed state, as soon as transactional method is about to exit, hibernate checks if any fields were modified , if they were, then Hibernate fires an update query , put it another way , you don't have to call save , calling a setter is enough.That is why, even though birthday was invalid and method save wasn't called, the user's name will be updated anyway, if it's a part of a business requirements then that's totally fine, otherwise good luck debugging the cause .Some other things to consider, If you have a transactional method ,same as above, how to reuse it using a different propagation level(let's say execute a method in a new transaction even though method was called from another transactional). I know some workarounds but don't want to describe them , cause it's not the best choice. Let me know if you are familiar with a stable solution

Problem 5 - How Delete Works

Let's take a look at UserEntity for the last time.What if we want to delete a user by id who has a list of orders, let's say 5 orders. How many sql queries will be executed ? The answer is 6, 5 to delete every individual order , and then one to delete a user. Couldn't Hibernate just run 2 queries instead ? DELETE order where user_id=?; DELETE user where id=?; . Unfortunately it can't because one of the orders can be a part of a different transaction and as a result stored in Hibernate context. If deleting users isn't a common operation then leaving it as it is would be the best choice. However, if it's a critical part of an application then we have to rewrite it. One solution is to introduce a native query in the Repository layer.

interface UserRepository{
    @Modifying
    @Query(nativeQuery = true,value = "DELETE from order where user_id = :userId")
        void deleteOrdersByUser(@Param("userId") Long uidpk);

The problem here is that native queries are not tracked by Hibernate listeners(@EntityListener) which means if you have some hooks that have to be executed right after you delete an order(let's say save some statistics and then send an email to managers) then they won't work.One solution I come up with is to use Spring's integrated event bus library

@Component
class EntityListener
   @Transactional(propagation = Propagation.REQUIRES_NEW)
   @PreRemove
      public void preRemove(final Order order) {...
}

@Component
public class ApplicationDeleteListener implements ApplicationListener<OrderDeleteEvent> {
   @Autowired EntityListener listener
   public void onApplicationEvent(final OrderDeleteEvent entityDeleteEvent) {
       this.listener.preRemove(entityDeleteEvent.getOrder());
    }

In this case when order was deleted by Hibernate, the @EntityListener will be called automatically, otherwise you have to publish an event into the EventBus which will delegate a hook logic to EntityListener.

Conclusion

There is no way JVM based backends(most of them) will get rid of the Hibernate in the near future,so the only thing we can do as developers is to introduce a good level of abstraction and don't forget that SOLID principles could be applied to entity layer too.