📜 ⬆️ ⬇️

Hibernate: lazy loading, inheritance and instanceof

Consider, as an example, the following situation. We have a class User with fields describing the user. There is the Phone class, which is the parent for the CellPhone and SatellitePhone classes. In the class User there is a field containing a list of user phones. In order to reduce the load on the database, we have made this list "lazy." It will be loaded only on demand.

It looks like this
public class User { ... @OneToMany(fetch = FetchType.LAZY) private List<Phone> phones = new ArrayList<Phone>(); public List<Phone> getPhones() { return phones; } } public class Phone { ... } public class CellPhone extends Phone { ... } public class SatellitePhone extends Phone { ... } 


In this configuration, when requesting a list of phone numbers for a specific user, we can get both a list of initialized phone objects (for example, if they are already in the cache) and a list of proxy objects.
In most situations, it doesn’t matter to us what exactly we are working with (real object or its proxy). When requesting any field of any object - the proxy object is automatically initialized, and we get the expected data. But if we need to know the type of object, then everything goes awry.

Let's see why this happens. The main problem is that Hibernate is not a psychic and cannot know in advance (without completing database queries) what type of objects are in the list. Accordingly, it creates a list containing proxy objects inherited from Phone.


When our team first encountered this problem, we studied this issue a bit and realized that we would have to do a “crutch”. The error occurred in the service method where it was necessary to know exactly which of the child classes we are dealing with. We have implemented another one right before this check: if the object is a proxy object, then it is initialized. Then they safely forgot this unpleasant story.
')
Over time, the project grew, business logic became more complicated. And now the moment came when there were already too many such crutches (we realized that this would not work on the third or fourth crutch). Moreover, this problem began to arise not only when a lazy list of other objects was requested from a single object, but also when a list of objects was directly queried from the database. I really didn’t want to refuse lazy loading. Our base is already heavily loaded. We decided to no longer mix the architectural layers of the application and create something more universal.

Scheme of our application

In this scheme, the DAO layer deals with queries to the database. It consists of 1 abstract class JpaDao which defines all the basic methods for working with the database. And the sets of classes are his heirs, each of which ultimately uses the methods of the base class. So, how did we overcome the problem with a direct request for a list of objects of different types with a common parent? We created methods in the JpaDao class to initialize a single proxy object and initialize a list of proxy objects. With each request for a list of objects from the database, this list is initialized (We deliberately went to this step, because if we request a list of objects in our application, then almost always it is needed completely initialized).

JpaDao implementation example
 public abstract class JpaDao<ENTITY extends BaseEntity> { ... private ENTITY unproxy(ENTITY entity) { if (entity != null) { if (entity instanceof HibernateProxy) { Hibernate.initialize(entity); entity = (ENTITY) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation(); } } return entity; } private List<ENTITY> unproxy(List<ENTITY> entities) { boolean hasProxy = false; for (ENTITY entity : entities) { if (entity instanceof HibernateProxy) { hasProxy = true; break; } } if (hasProxy) { List<ENTITY> unproxiedEntities = new LinkedList<ENTITY>(); for (ENTITY entity : entities) { unproxiedEntities.add(unproxy(entity)); } return unproxiedEntities; } return entities; } ... public List<ENTITY> findAll() { return unproxy(getEntityManager().createQuery("from " + entityClass.getName(), entityClass).getResultList()); } ... } 


With the solution of the first problem, everything turned out not so smoothly. The above method is not suitable as a lazy loading deals directly with Hibernate. And we made a small concession. In all objects containing lazy lists of different types of objects with one parent (for example, User with a list of Phone), we redefined getters for these lists. While the lists are not requested - everything is in order. The object contains only a proxy list and no extra requests are performed. When a list is requested, it is initialized.

An example of a user’s phone list getter
 public class User { ... @OneToMany(fetch = FetchType.LAZY) private List<Phone> phones = new ArrayList<Phone>(); public List<Phone> getPhones() { return ConverterUtil.unproxyList(phones); } } public class ConverterUtil { ... public static <T> T unproxy(T entity) { if (entity == null) { return null; } Hibernate.initialize(entity); if (entity instanceof HibernateProxy) { entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation(); } return entity; } public static <T> List<T> unproxyList(List<T> list) { boolean hasProxy = false; for (T entity : list) { if (entity instanceof HibernateProxy) { hasProxy = true; break; } } if (hasProxy) { LinkedList<T> result = new LinkedList<T>(); for (T entity : list) { if (entity instanceof HibernateProxy) { result.add(ConverterUtil.unproxy(entity)); } else { result.add(entity); } } list.clear(); list.addAll(result); } return list; } } 


In this article, I demonstrated how to use lazy Hibernate loading when using lists containing objects of one type (with one parent) used in my team. I hope this example will help someone in a similar situation. If you know a more optimal / beautiful way to overcome this problem, I will be glad to add it to the article.

Source: https://habr.com/ru/post/250603/


All Articles