gregwhitaker
gregwhitaker

Reputation: 13410

Implementing Spring Transactions with JAX-RS and Hibernate

Background

I am trying to implement a RESTful web service using Apache-CXF that interacts with a database to do some CRUD operations via Hibernate. The web service consumes and produces JSON formatted objects using the Jackson JAX-RS Provider.

I am currently receiving a "failed to lazily initialize a collection... no session or session was closed" exception that is bubbling up from the Jackson provider when it attempts to serialize the response object.

I assumed that if I marked the service method with @Transactional that the session would be available to the Jackson provider when it serialized the response, but that does not appear to be the case.

Question

How do I get the hibernate session to be available while Jackson is walking the object during serialization?

What I've Tried

Spring Configuration

<context:component-scan base-package="com.db.cif.mapper" />
<context:component-scan base-package="com.db.cif.mapper.repository" />
<context:annotation-config />

<tx:jta-transaction-manager>
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</tx:jta-transaction-manager>

<tx:annotation-driven />

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="cifmapper" />
    <property name="jpaDialect">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    </property>
</bean>

<!-- JAX-RS Configuration -->
<jaxrs:server id="mappingService" address="/">
    <jaxrs:serviceBeans>
        <ref bean="mappingServiceBean" />
    </jaxrs:serviceBeans>
    <jaxrs:extensionMappings>
        <entry key="json" value="application/json" />
    </jaxrs:extensionMappings>
    <jaxrs:providers>
        <ref bean="jsonProvider" />
    </jaxrs:providers>
</jaxrs:server>

<bean id="mappingServiceBean" class="com.db.cif.mapper.MappingService" />

<bean id="jsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" />

Service Bean

@Service("mappingService")
@Transactional
public class MappingService 
{
    private static final Logger logger = Logger.getLogger(MappingService.class);

    @Autowired
    @Qualifier("mappingRepository")
    private MappingRepository mappingRepository;

    @GET
    @Path("/collections/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Collection getCollection(@PathParam("id") String id)
    {
        if(logger.isDebugEnabled())
        {
            logger.debug(String.format("Invoked getCollection, Collection id: %s", id));
        }

        return this.mappingRepository.getCollection(id);
    }

    @POST
    @Path("/collections/")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addCollection(Collection collection)
    {
        if(logger.isDebugEnabled())
        {
            logger.debug(String.format("Invoked addCollection, Collection: %s", collection));
        }

        this.mappingRepository.createCollection(collection);

        return Response.ok(collection).build();
    }
}

Collection Bean

@Entity
@Table(schema = "CIFMAPPER", name = "COLLECTION")
public class Collection implements Serializable
{
    private static final long serialVersionUID = 1579878442412232635L;

    @Id
    @Column(name = "ID")
    private String id;

    @Column(name = "SRC_ENDPT_ID", nullable = false, insertable = false, updatable = false)
    private long sourceEndpointId;

    @Column(name = "DEST_ENDPT_ID", nullable = false, insertable = false, updatable = false)
    private long destinationEndpointId;

    @Column(name = "DESCRIPTION")
    private String description;

    @Column(name = "START_DATE", nullable = false)
    private Date startDate;

    @Column(name = "END_DATE")
    private Date endDate;

    @ManyToOne(optional = false)
    @JoinColumn(name = "SRC_ENDPT_ID")
    private Endpoint source;

    @ManyToOne(optional = false)
    @JoinColumn(name = "DEST_ENDPT_ID")
    private Endpoint destination;

    @OneToMany(mappedBy = "collection", targetEntity = MappingGroup.class, fetch = FetchType.EAGER)
    private List<MappingGroup> mappingGroups;

//Getters and Setters Removed For Brevity
}

Upvotes: 1

Views: 1821

Answers (1)

Bozho
Bozho

Reputation: 597076

While I believe there is a way to make that work (using a Filter or intercepetor that opens and closes the session), I think the right answer is: don't have lazy collections and proxies. Configure your mappings so that you don't have lazy collections.

An alternative approach is to manually initialize them. This is often combined with DTOs - objects with a similar structure to the entities that are used as responses to other components (so that you are not directly exposing entities). So your methods return OrderDTO rather than Order, where the DTO contains as many fields as you need to return to the caller. You manually transfer the values from the entity to the DTO.

Upvotes: 5

Related Questions