1

I am working on creating RESTful versions of some existing services for a project. The current application uses Hibernate 3 with a jboss 6 server so any new services I add have to work within that environment. I decided to start with a simple GET request. I managed to get everything set up correctly and my service method is being run. The problem I'm running into is that I get 'lazy load' exceptions when Jackson tries to serialize the response to send it back to the client.

The two most common solutions I have see for this issue will not work here. Changing the fetch type to EAGER would potentially break my existing services. Adding annotations to ignore the fields causing the exception would also not work since those fields are needed by the client.

Normally, my solution to something like this would be to 'front-load' the collections need by the client during the service call. Unfortunately, that can only be done inside a transaction. By the time Jackson is ready to serialize my response, the service method has already returned and the transaction has been closed.

Does anyone know of a way to work around this issue? Is there some 'hook' that jackson provides that would let me do the serialization inside a transaction?

Update here is example of what my code looks like:

service provider-

@Path("service")
public interface ClientServiceProvider
{
    @GET
    @Path("web/brand/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public abstract Response getBrandWeb(@PathParam("id") Long brandId);
}

service bean-

@Stateless(name = "ClientServiceProvider")
@Local(ClientServiceProvider.class)
public class ClientServiceProviderBean implements ClientServiceProvider
{
    @Override
    public Response getBrandWeb(Long brandId)
    {
        try
        {
            Functions.beginTx(tx);

            Brand b = entityManager.find(Brand.class, brandId);
            if (!b.getSubCollections().isEmpty())
            {
                b.getSubCollections().size();
            }
            Response r = null;
            if (b != null)
            {
                r = Response.ok(b).build();
            }
            else
            {
                r = Response.serverError().build();
            }
            Functions.commitTx(tx);
            return r;
        }
        catch (Exception e)
        {
            return quietRollback(tx, e, Response.class);
        }
    }
}
pbuchheit
  • 1,371
  • 1
  • 20
  • 47

2 Answers2

1

Firstly, using DTO is the best solution. I don't like DTO, but in this case it can be very helpful. Because of, you will need to implement not only a GET endpoint, but POST/PUT endpoints too.

Also, if you will use Swagger, with entities you will have all Hibernate support properties (like bidirectional associations) in the Swagger documentation.

You can use Dozer or your own mapper to decrease number of DTO mapping errors. I had implemented my own simply mapper in such situation (without shallow copy and with additional custom methods for collections and other complex cases).

Also, using a DTO mapper, you can auto fetch lazy properties that need to be serialized — a mapper will try to copy those properties and will trigger a lazy fetching.

So, what you can to do, if you don't want to use DTO:

  1. Fetch all associations that you need when you have an open session (inside a transaction).
  2. Use jackson-datatype-hibernate module to tweak Jackson to not serialize not fetched associations, as described here: Avoid Jackson serialization on non fetched lazy objects
Community
  • 1
  • 1
v.ladynev
  • 19,275
  • 8
  • 46
  • 67
  • 1. I already do this. The problem is, the session closes before serialization takes place. As a result, I end up with lazy load exceptions during serialization. 2. The jboss version of resteasy already includes that module. Simply ignoring those fields is not an option however since they are needed by the client. – pbuchheit May 02 '17 at 15:17
  • @pbuchheit if those fields are required, you should load them while a session is opened. – v.ladynev May 02 '17 at 17:09
  • Take a look at the code sample I posted. In the service bean I call ' b.getSubCollections().size();' to load the collection. After the service call returns, Jackson will walk the fields of the 'Brand' class again as part of the serialization process. At that point however, the session has already been closed, and the exception will be thrown. – pbuchheit May 02 '17 at 17:57
0

The most flexible solution is to use DTOs created in the transactional context.

With DTOs you have the ability to tailor the objects to be serialized to the exact needs of the client consuming the resulting JSON. Also, the domain model (Hibernate entities) is decoupled from the JSON (de)serialization logic, allowing the two to evolve independently.

Community
  • 1
  • 1
Dragan Bozanovic
  • 23,102
  • 5
  • 43
  • 110
  • I had considered using DTOs as well. For one or two classes that might not be bad, but to scale that up across the entire application would be a huge task. Even if I could devote the time to creating and testing all those DTOs, the notion of effectively doubling what is already a fairly large code base and trying to keep all those classes in sync makes that seem like a bad idea. – pbuchheit May 01 '17 at 18:07