Think twice before using an object mapping library to get your DTOs
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, 2 monthly Q&A calls, monthly coding challenges, a community of like-minded developers, and regular expert sessions.
I often get asked if I can recommend any object mapping library to map entity objects to DTOs. And you can find several discussions and articles about that on google. But instead of asking which library you should use, you should first ask yourself if you should map an entity to a DTO at all. Because in almost all situations, I recommend avoiding this mapping in the first place. And you then don’t need any form of object mapping library. The reason is that when working with Hibernate, or any other JPA implementation, this form of mapping removes all benefits of a DTO projection. And that is, of course, also the case when using Spring Data JPA.
If that’s all you wanted to know, you can stop reading this article. But I hope you’ll stay and let me explain why object mapping libraries are not a great fit for most of your use cases and what you should do instead.
Before we get into the details, let me quickly explain what an object mapping library is.
What is an object mapping library
An object mapping library maps an object of one class with all its attribute values to an object of another class. Depending on your library and the similarity of the 2 classes, the object mapping library can handle the mapping based on predefined naming conventions or a small configuration file.
I’m not giving any examples of an object mapping library here because I don’t want to create the impression that I’m discrediting any specific library. There are many great object mapping libraries. They can be extremely useful for the right task, simplify your job and even avoid bugs and performance problems.
It’s not the fault of the object mapping library that mapping an entity object to a DTO is usually not a good idea. The problem is caused by the fact that you first need to get an entity object, which you then map to a DTO. The way JPA handles entities creates a noticeable overhead. And you can avoid all the overhead by directly fetching unmanaged DTO projections.
OK, let’s dive a little deeper and discuss how JPA implementations handle entity and DTO objects. To make the following sections a little easier to understand, I expect you to use Hibernate as your JPA implementation. All other JPA implementations work similarly so that the general arguments are the same even if the implementation details slightly differ.
JPA’s handling of entity objects and the overhead it creates
When you fetch an entity object from the database, it becomes a managed object. That means it’s part of the current persistence context and has a managed lifecycle. This gives you several benefits:
- If you change any entity object’s attribute, Hibernate automatically detects the change and decides when to execute the required SQL statement. For performance reasons, it tries to delay the execution of that statement as long as possible.
- Hibernate ensures that within the context of your current session, only 1 entity object represents a specific database record. You will get the same object if you fetch that record multiple times.
- If you call EntityManager‘s find method or traverse a to-one association, Hibernate first tries to get the object from the current persistence context / 1st-level cache. If it gets it from there, it doesn’t need to execute an SQL SELECT statement.
These are useful features that simplify the implementation of write operations and can provide performance benefits. But you don’t get them for free. They come with a few downsides:
- When reading an entity object, Hibernate always initializes all basic attributes.
- Hibernate fetches all eagerly fetched associations when it initializes an entity object.
- Before executing any query, Hibernate has to check the persistence context for changed managed entity objects that might be relevant to the query. If it finds any, it needs to execute the required SQL write operations to update the database before executing the query.
- When processing the result of a query that returns entities, Hibernate checks the persistence context for each record to avoid instantiating a new object for an already managed entity.
All of this takes time and resources. If you’re implementing a write operation, the previously listed advantages of managed entities outweigh these costs by a huge margin.
But that’s not the case if you fetch entity objects from the database and immediately hand them over to an object mapping framework to map them to unmanaged DTOs. In that case, you’re only getting the disadvantages of an entity projection without reaping the benefits.
JPA’s handling of DTO objects and the overhead it avoids
DTOs are unmanaged objects. That means they don’t have any lifecycle, they don’t become part of the persistence context, and Hibernate forgets about them after instantiating them. Due to that, they don’t have any management overhead, and you avoid all the downsides of managed entity objects. They are also not mapped to a specific database table, and you can design them based on the needs of a specific use case.
As always, this also has a few downsides. You can’t use DTO objects to perform any write operations when using Hibernate or any other JPA implementation. Hibernate doesn’t provide lazy loading for any associations between different DTO objects. It also doesn’t ensure that you always get the same object if you read the same information multiple times.
So, by working with unmanaged objects, you not only avoid all the downsides of entity objects, but you’re also losing all of their benefits. That’s why I recommend checking your use case before deciding whether to use entities or DTOs. If you don’t want to write any data to your database, you’re usually not benefiting from the advantages of managed entity objects. In that case, you should better use DTO objects and enjoy their performance benefits.
Using DTO objects is only one way to improve the performance of your persistence layer. If you want to learn more about performance tuning, you should join the Persistence Hub. Besides many other things, that gives you access to my Hibernate Performance Tuning course. It teaches you everything you need to know to improve the performance of your persistence layer.
Avoid the overhead by avoiding object mapping libraries
After discussing how Hibernate handles entities and DTO objects, it’s time to talk about object mapping libraries. As I mentioned earlier, I think object mappers can be very useful. But they are only rarely a good fit to convert entity objects to DTOs. And if you read the 2 previous sections, you hopefully already know why.
When you fetch an entity object from the database and convert it to a DTO object, you still have the managed entity object in your persistence context. But you’re not using it. You’re using the DTO object your object mapping framework created. That means instead of getting the benefits of a DTO object, you get all the overhead of a managed entity object. On top of that, you also get all the disadvantages of the DTO object. So, in the end, you’re just combining the downsides of both approaches.
You can avoid that by fetching DTO objects directly from the database instead of fetching entities and mapping them to DTO objects. You can do that in different ways. The easiest one is using a constructor expression in your JPQL query. Or you could use a Hibernate-specific ResultTransformer (Hibernate 4 & 5) or a TupleTransformer or ListTransformer (Hibernate 6).
The following code snippet shows a basic example of a constructor expression. It starts with the keyword new followed by the fully qualified name of the class you want to instantiate and multiple constructor parameters. Hibernate uses this information to call the described constructor for each record when processing the query result.
TypedQuery<BookWithAuthorNames> q = em .createQuery(""" SELECT new com.thorben.janssen.BookWithAuthorNames(b.id, b.title, b.price, concat(a.firstName, ' ', a.lastName)) FROM Book b JOIN b.author a WHERE b.title LIKE :title """, BookWithAuthorNames.class); q.setParameter("title", "%Hibernate Tips%"); List<BookWithAuthorNames> books = q.getResultList();
As explained earlier, the main benefit of this approach is that Hibernate doesn’t instantiate any entity objects. It directly instantiates a DTO object for each record in the result set.
When using an object mapping library is OK
So far, I have explained in great detail why you shouldn’t map entity objects to DTOs. And that also explains why you shouldn’t use an object mapping library to get your DTOs.
But there are a few situations where you already worked with an entity object and want to get an unmanaged DTO representation of it. A typical example is a use case that performs a write operation and returns the changed data. You obviously use entities to implement the write operation. And If you follow my recommendation to not expose entities in your API, you need to map those entities to DTOs.
In those situations, you have 2 options:
- Execute another database query that selects the changed information and uses a DTO projection.
- Map the entity object to a DTO.
If the returned DTO and the entity object used in your use case are similar, executing an additional query doesn’t make a lot of sense. It only slows down your application without getting any new information. In that case, an object mapping library provides a comfortable way to map your entity object to a DTO. And using it is absolutely fine.
And that gets us back to the initial question: Which object mapping library should you use?
When you read until here, you’re probably not surprised if I tell you I don’t use object mapping libraries very often. But if I do, I usually choose MapStruct. So far, it has worked very well for me.
Managed entity objects provide several benefits but also create an overhead. You should always check if their benefits outweigh the overhead when using them. That’s generally the case when implementing write operations but not when implementing read-only operations.
Due to that, you should avoid fetching entities and immediately converting them to DTOs. If you want to fetch DTOs from the database, you should use features like JPQL’s constructor expression or Hibernate’s TupleTransformer. They directly instantiate an unmanaged DTO object for each record in the result set and avoid the instantiation of entity objects.
The only situation in which using an object mapping framework is an option is when you implement a write operation and want to expose a DTO representation to the caller. You need entity objects to implement the write operation, and you don’t get any benefits from executing an additional query returning a DTO projection. So, in that case, it’s easier to use an object mapping framework to map an entity to a DTO object.