Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to merge input from a web service to a JPA entity

I'm trying to figure out the best way to use JPA in the context of a restful web service. The input comes in as JSON and I can use Jackson/JAX-RS to convert that to a POJO. This gets passed to a service where I need to somehow merge into a JPA entity.

These are the options I've found so far with pros and cons.

1. JPA merge()
The first thing I tried was probably the simplest. The GET action returns the JPA entity which is easily serialized into JSON. On the update the object is passed back is JSON which can be used to populate a detached entity. This can be saved to the DB using the JPA merge() method.

Pros
Simple architecture with less code duplication (i.e. no DTO's)

Cons
As far as I can tell this only works if you pass the whole model around. If you try to hide certain fields, like the maybe the password on a User entity, then the merge thinks you're trying to set these fields to null in the DB. Not good!

2. DTO's using JPA find() and dozer
Next I thought I'd look at using data transfer objects. Apparently an anti-pattern but worth a look. The service now creates a DTO instance based on the entity and it is this DTO that is serialized to JSON. The update then gets the entity from the DB using a find() method and the values need to be copied across from the DTO to the entity. I tried automating this mapping using the dozer framework.

Pros
You don't have to return the entire model. If you have certain fields you don't want to be updated you can leave them off the DTO and they can't be copied to the entity by mistake. Using dozer means you don't have to manually copy attributes from dto to entity and vice versa.

Cons
It feels like repeating yourself when writing the DTO's. Somehow you have to map between entities and DTO's. I tried to automate this with dozer but it was a bit disappointing. It was nulling out things it shouldn't have been and to get full control you have to write xml.

3. DTO's using manual merge
A third way would be to abandon dozer and just copy the properties across from the DTO to the entity in the service. Everybody seems to say anti-pattern but it's pretty much how every non-trivial application that I've seen in the past has worked.

Summary
It seems to be a decision between keeping things simple for the developer but not having control over the input/output or making a more robust web service but having to use an anti-pattern in the process...

Have I missed anything? Perhaps there's an elusive alternative?

like image 352
Ben Thurley Avatar asked Jul 09 '14 23:07

Ben Thurley


People also ask

What is the purpose of @entity in JPA?

Entities in JPA are nothing but POJOs representing data that can be persisted to the database. An entity represents a table stored in a database. Every instance of an entity represents a row in the table.

Can a JPA entity have multiple Onetomany associations?

You can have multiple one-to-many associations, as long as only one is EAGER.

What is the use of mapper in Spring boot?

A MapStruct mapper is an interface or an abstract class annotated with @Mapper . This special annotation is used by the MapStruct code generator to automatically generate a working implementation of this Java file at build-time.


2 Answers

Using JPA merge looks the simplest, cleanest and with very less effort but as correctly discovered creates problems with detached entity attributes set to null. Another problem which turned out to be big in one of my experiences was that if you rely on JPA merge operation you must be using Cascade feature as well. For simple and less nested relation this works reasonably well, but for deeply nested domain objects and lots of relations, this becomes a big impact on performance. The reason being that the ORM tool (Hibernate in my experience) upfront caches the SQL to load the merge entity ( 'merge path' in Hibernate parlance) and if the nesting is too deep with Cascade mappings the joins in the SQL becomes too big. Marking realtions Lazy does not help here as the merge path is determined by the Cascades in relations. This problem becomes apparent slowly as your model evolves. Plus the prospect of angry DBA waving a huge join query on our face prompted us to do something different :-) There is an interesting issue related to Hibernate regarding Merging of Lazy relations still unresolved (actually rejected but the discussion is very enjoyable to read) in Hibernate JIRA.

We then moved towards the DTO approach where we refrained from using merge and relied on doing it manually. Yes it was tedious and required the knowledge of what state is actally coming from the detached entity, but to us it was worth. This way we do not touch the Lazy relations and attributes not meant to change. and set only what is required. The automatic state detection of Hibernate does the rest on transaction commit.

like image 170
Shailendra Avatar answered Oct 26 '22 05:10

Shailendra


This is approach I am using:

  • suppress serialization of certain fields with XmlTransient annotation
  • when updating the record from the client, get the entity from the database and use ModelMapper with custom property mapping to copy the updated values without changing the fields that are not in the JSON representation.

For example:

public class User {
    @Id
    private long id;

    private String email;

    @XmlTransient
    private String password;
    ...
}

public class UserService {
    ...
    public User updateUser(User dto) {
        User entity = em.find(User.class, dto.getId());
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.addMappings(new UserMap());
        modelMapper.map(userDto, user);
        return user;
    }
}

public class UserMap extends PropertyMap<User, User> {
    protected void configure() {
        skip().setPassword(null);
    }
}

BeanUtils is an alternative to ModelMapper.

It would be nice if these libraries could recognize the XmlTransient annotation so the programmer can avoid creating the custom property map.

like image 43
kensei62 Avatar answered Oct 26 '22 04:10

kensei62