Saturday, April 24, 2010

EntityCopy a solution to LazyInitializationException

Although there are different solutions for LazyInitializationException exception of Hibernate, as DTO, Dozer or Gilead, but for me these solutions have some disadvantages such as adding new classes, you can not set the depth level of the copy in real time or require special configuration.

In several projects I needed a utility easy to use and that can be used by other programming languages like PHP, AS3, ..etc.

Scenario:

  • A EJB3 container-managed transaction and persistence and the entities configured with JPA annotations (JBoss5.1 and Hibernate).
  • Different client applications in Java and PHP, connected to the container by WebServices.

EntityCopy lets you copy an entity with JPA annotations by reflection and depth level set in real time. EntityCopy has the following levels of copy of an entity:

  • NONE: Copy only the properties that are not associations.
  • DEFAULT: Copy only the associations with FetchType.EAGER.
  • FULL: Copy all the properties of the entity.

For example if we have the following entities:

@Entity
public class Address {
    @Id
    String id;
    String street;
    ...
}

@Entity
public class Person {
    @Id
    String id;
    String firstName;
    String lastName;
    @ManyToOne   // By default FetchType.EAGER 
    Person parent;
    @OneToMany   // By default FetchType.LAZY
    Set<Address> addresses = new HashTable<Address>();
    ..
}

To make a copy of Person with associaiones configured with FetchType.EAGER (only the "parent" association):

Person entity = personDao.findByPk(pk);
...
Person copy = EntityCopy.copy(Person entity, DeepCopy.DEFAULT);

If the second parameter is null, DeepCopyType.DEFAULT always be taken.

You can change the level of copy of a property defining it directly, for example, if you want a copy of Person but not the addresses list:

DeepCopy deepCopy = DeepCopy.createFull().add("addresses", DeepCopy.createNone());
Person copy = EntityCopy.copy(entity, deepCopy);

When DeepCopyType.NONE is assigned to a property, this property will not be copied.

EntityCopy avoids recursion between entities, to prevent infinite loops.

The following is a example of how to use EntityCopy with a web service.

TestServiceSEI.java

@WebService
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT)
public interface TestServiceSEI {
    @WebMethod
    public List<Person> findAllPersons(@javax.jws.WebParam(name="deepCopy")  DeepCopy deepCopy);
}

TestServiceBean.java

@Stateless
@WebService(endpointInterface="TestServiceSEI")
@Remote(TestServiceSEI.class)
public class TestServiceBean  implements TestServiceSEI {
    @javax.persistence.PersistenceContext(unitName = "TestPU")
    protected javax.persistence.EntityManager entityManager;

    @javax.annotation.Resource
    protected javax.xml.ws.WebServiceContext wsCtx;

    public List<Person> findAllPersons(DeepCopy deepCopy) {
            Query query = this.getEntityManager().createQuery("select obj from Person obj");
            List<Person> entities = query.getResultList();
            if (wsCtx != null) {
                List<Person> results = new ArrayList<Person>();
                for (Person entity : entities) {
                    results.add(EntityCopy.copy(entity, deepCopy));
                }
                return results;
            }
            return entities;
    }
}

From a PHP client application:

test.php

<?php
require_once "Cafeto/DeepCopy.php";

class TestService {

    protected $soapClient;

    public function __construct($wsdl) {
        $this->soapClient = new SoapClient($wsdl);
    }

    public function findAllPersons($deepCopy) {
        $ret = $this->soapClient->findAllPersons(array('deepCopy' => $deepCopy));
        if (isset($ret->return))
            if (is_array($ret->return))
                return $ret->return;
            else
                return array($ret->return);
        else
            return array();
    }
}

$service = new TestService("http://127.0.0.1:8080/test-service/TestService?wsdl");

$deepCopy = Cafeto_DeepCopy::createFull()->add("addresses", Cafeto_DeepCopy::createNone());

$persons = $service->findAllPersons($deepCopy);

You can download the jar file cafeto-entitycopy-0.5.0.jar and the PHP library cafeto-deepcopy.zip.

The source code repository is here.

You can also clone the project with Git by running:
$ git clone git://github.com/cafeto/cafeto-entitycopy

No comments:

Post a Comment