Skip to content

Commit

Permalink
Support serialisation of lazy loaded POJOs
Browse files Browse the repository at this point in the history
* Configure Hibernate5Module to serialize lazy loaded POJOs & add test.
* Added ability to switch off lazy-loading.
  • Loading branch information
ant3 authored and nickbabcock committed Apr 20, 2016
1 parent a24eed6 commit 2c3e8d5
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package io.dropwizard.hibernate;

import org.hibernate.SessionFactory;

import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module.Feature;
import com.google.common.collect.ImmutableList;

import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.db.PooledDataSourceFactory;
import io.dropwizard.db.DatabaseConfiguration;
import io.dropwizard.db.PooledDataSourceFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.util.Duration;
import org.hibernate.SessionFactory;

public abstract class HibernateBundle<T extends Configuration> implements ConfiguredBundle<T>, DatabaseConfiguration<T> {
public static final String DEFAULT_NAME = "hibernate";

private SessionFactory sessionFactory;
private boolean lazyLoadingEnabled = true;

private final ImmutableList<Class<?>> entities;
private final SessionFactoryFactory sessionFactoryFactory;
Expand All @@ -39,7 +43,11 @@ public final void initialize(Bootstrap<?> bootstrap) {
* Override to configure the {@link Hibernate5Module}.
*/
protected Hibernate5Module createHibernate5Module() {
return new Hibernate5Module();
Hibernate5Module module = new Hibernate5Module();
if(lazyLoadingEnabled) {
module.enable(Feature.FORCE_LAZY_LOADING);
}
return module;
}

/**
Expand Down Expand Up @@ -73,6 +81,14 @@ private UnitOfWorkApplicationListener registerUnitOfWorkListerIfAbsent(Environme
environment.jersey().register(listener);
return listener;
}

public boolean isLazyLoadingEnabled() {
return lazyLoadingEnabled;
}

public void setLazyLoadingEnabled(boolean lazyLoadingEnabled) {
this.lazyLoadingEnabled = lazyLoadingEnabled;
}

public SessionFactory getSessionFactory() {
return sessionFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void onEvent(RequestEvent event) {
UnitOfWork unitOfWork = methodMap.get(event.getUriInfo()
.getMatchedResourceMethod().getInvocable().getDefinitionMethod());
unitOfWorkAspect.beforeStart(unitOfWork);
} else if (event.getType() == RequestEvent.Type.RESP_FILTERS_START) {
} else if (event.getType() == RequestEvent.Type.FINISHED) {
try {
unitOfWorkAspect.afterEnd();
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.dropwizard.hibernate;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonProperty;

@Entity
@Table(name = "dogs")
public class Dog {
@Id
private String name;

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="owner")
private Person owner;

@JsonProperty
public String getName() {
return name;
}

@JsonProperty
public void setName(String name) {
this.name = name;
}

@JsonProperty
public Person getOwner() {
return owner;
}

@JsonProperty
public void setOwner(Person owner) {
this.owner = owner;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package io.dropwizard.hibernate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Test;

import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;

import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;
import io.dropwizard.jackson.Jackson;
import io.dropwizard.jersey.DropwizardResourceConfig;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import io.dropwizard.lifecycle.setup.LifecycleEnvironment;
import io.dropwizard.logging.BootstrapLogging;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

public class LazyLoadingTest extends JerseyTest {

private Bootstrap<?> bootstrap;
private HibernateBundle<Configuration> bundle;

static {
BootstrapLogging.bootstrap();
}

public static class DogDAO extends AbstractDAO<Dog> {
public DogDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}

public Optional<Dog> findByName(String name) {
return Optional.fromNullable(get(name));
}
}

@Path("/dogs/{name}")
@Produces(MediaType.APPLICATION_JSON)
public static class DogResource {
private final DogDAO dao;

public DogResource(DogDAO dao) {
this.dao = dao;
}

@GET
@UnitOfWork(readOnly = true)
public Optional<Dog> find(@PathParam("name") String name) {
return dao.findByName(name);
}
}

private SessionFactory sessionFactory;

@Override
@After
public void tearDown() throws Exception {
super.tearDown();

if (sessionFactory != null) {
sessionFactory.close();
}
}

@Override
protected Application configure() {
forceSet(TestProperties.CONTAINER_PORT, "0");

final MetricRegistry metricRegistry = new MetricRegistry();
final SessionFactoryFactory factory = new SessionFactoryFactory();
final DataSourceFactory dbConfig = new DataSourceFactory();
final Environment environment = mock(Environment.class);
final LifecycleEnvironment lifecycleEnvironment = mock(LifecycleEnvironment.class);
when(environment.lifecycle()).thenReturn(lifecycleEnvironment);
when(environment.metrics()).thenReturn(metricRegistry);
bundle = new HibernateBundle<Configuration>(null, factory) {
@Override
public DataSourceFactory getDataSourceFactory(Configuration configuration) {
return dbConfig;
}
};

dbConfig.setUrl("jdbc:hsqldb:mem:DbTest-" + System.nanoTime()+"?hsqldb.translate_dti_types=false");
dbConfig.setUser("sa");
dbConfig.setDriverClass("org.hsqldb.jdbcDriver");
dbConfig.setValidationQuery("SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS");

this.sessionFactory = factory.build(bundle,
environment,
dbConfig,
ImmutableList.<Class<?>>of(Person.class, Dog.class));

final Session session = sessionFactory.openSession();
try {
session.createSQLQuery("DROP TABLE people IF EXISTS").executeUpdate();
session.createSQLQuery(
"CREATE TABLE people (name varchar(100) primary key, email varchar(16), birthday timestamp with time zone)")
.executeUpdate();
session.createSQLQuery(
"INSERT INTO people VALUES ('Coda', 'coda@example.com', '1979-01-02 00:22:00+0:00')")
.executeUpdate();
session.createSQLQuery("DROP TABLE dogs IF EXISTS").executeUpdate();
session.createSQLQuery(
"CREATE TABLE dogs (name varchar(100) primary key, owner varchar(100), CONSTRAINT fk_owner FOREIGN KEY (owner) REFERENCES people(name))")
.executeUpdate();
session.createSQLQuery(
"INSERT INTO dogs VALUES ('Raf', 'Coda')")
.executeUpdate();
} finally {
session.close();
}

bootstrap = mock(Bootstrap.class);
final ObjectMapper objMapper = Jackson.newObjectMapper();
when(bootstrap.getObjectMapper()).thenReturn(objMapper);
// Bundle is initialised at start of actual test methods to allow lazy loading to be enabled or disabled.

final DropwizardResourceConfig config = DropwizardResourceConfig.forTesting(metricRegistry);
config.register(new UnitOfWorkApplicationListener("hr-db", sessionFactory));
config.register(new DogResource(new DogDAO(sessionFactory)));
config.register(new JacksonMessageBodyProvider(objMapper));
config.register(new DataExceptionMapper());

return config;
}

@Override
protected void configureClient(ClientConfig config) {
config.register(new JacksonMessageBodyProvider(Jackson.newObjectMapper()));
}

@Test
public void serialisesLazyObjectWhenEnabled() throws Exception {
bundle.initialize(bootstrap);

final Dog raf = target("/dogs/Raf").request(MediaType.APPLICATION_JSON).get(Dog.class);

assertThat(raf.getName())
.isEqualTo("Raf");

assertThat(raf.getOwner())
.isNotNull();

assertThat(raf.getOwner().getName())
.isEqualTo("Coda");
}

@Test
public void sendsNullWhenDisabled() throws Exception {
bundle.setLazyLoadingEnabled(false);
bundle.initialize(bootstrap);

final Dog raf = target("/dogs/Raf").request(MediaType.APPLICATION_JSON).get(Dog.class);

assertThat(raf.getName())
.isEqualTo("Raf");

assertThat(raf.getOwner())
.isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class UnitOfWorkApplicationListenerTest {

private final RequestEvent requestStartEvent = mock(RequestEvent.class);
private final RequestEvent requestMethodStartEvent = mock(RequestEvent.class);
private final RequestEvent responseFiltersStartEvent = mock(RequestEvent.class);
private final RequestEvent responseFinishedEvent = mock(RequestEvent.class);
private final RequestEvent requestMethodExceptionEvent = mock(RequestEvent.class);
private final Session session = mock(Session.class);
private final Session analyticsSession = mock(Session.class);
Expand All @@ -68,10 +68,10 @@ public void setUp() throws Exception {

when(appEvent.getType()).thenReturn(ApplicationEvent.Type.INITIALIZATION_APP_FINISHED);
when(requestMethodStartEvent.getType()).thenReturn(RequestEvent.Type.RESOURCE_METHOD_START);
when(responseFiltersStartEvent.getType()).thenReturn(RequestEvent.Type.RESP_FILTERS_START);
when(responseFinishedEvent.getType()).thenReturn(RequestEvent.Type.FINISHED);
when(requestMethodExceptionEvent.getType()).thenReturn(RequestEvent.Type.ON_EXCEPTION);
when(requestMethodStartEvent.getUriInfo()).thenReturn(uriInfo);
when(responseFiltersStartEvent.getUriInfo()).thenReturn(uriInfo);
when(responseFinishedEvent.getUriInfo()).thenReturn(uriInfo);
when(requestMethodExceptionEvent.getUriInfo()).thenReturn(uriInfo);

prepareAppEvent("methodWithDefaultAnnotation");
Expand Down Expand Up @@ -281,7 +281,7 @@ private void execute() {
listener.onEvent(appEvent);
RequestEventListener requestListener = listener.onRequest(requestStartEvent);
requestListener.onEvent(requestMethodStartEvent);
requestListener.onEvent(responseFiltersStartEvent);
requestListener.onEvent(responseFinishedEvent);
}

private void executeWithException() {
Expand Down

0 comments on commit 2c3e8d5

Please sign in to comment.