Monday, February 15, 2010

Persisting Qi4j Transient Composites using JPA

I've been trying to use Qi4j in a "lightweight mode", while still retaining the JPA (Java Persistence API) programming model. I haven't been successful yet.

Qi4j is a framework for domain driven development aka composite-oriented programming in Java. If that's still not enough buzzwords for you, Qi4j allows you to implement Data-Context-Interaction (DCI) architecture the same way Spring Web MVC allows you to implement MVC.

Fast forward to the current results, what I get is this error:
Testcase: createOne(user.UserTest):        Caused an ERROR
Object: org.qi4j.runtime.composite.TransientInstance@8fa0f0 is not a known entity type.
java.lang.IllegalArgumentException: Object: org.qi4j.runtime.composite.TransientInstance@8fa0f0 is not a known entity type.
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerNewObjectForPersist(UnitOfWorkImpl.java:4147)
        at org.eclipse.persistence.internal.jpa.EntityManagerImpl.persist(EntityManagerImpl.java:368)
        at user.UserTest.createOne(UserTest.java:57)
In short, I was trying to persist a Qi4j Transient Composite object using JPA. Transient Composites are the simplest composite objects in Qi4j, I thought it may work.

But it won't, because JPA expects an entity class (the closest to that would be the a UserMixin class), not a TransientInstance class (which is Qi4j implementation detail).

Here's the test code:

// test/user/UserTest.java
package user;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.junit.Test;
import org.qi4j.bootstrap.AssemblyException;
import org.qi4j.bootstrap.ModuleAssembly;
import org.qi4j.test.AbstractQi4jTest;
import static org.junit.Assert.*;

public class UserTest extends AbstractQi4jTest {

    EntityManager em;

    public void assemble(ModuleAssembly module) throws AssemblyException {
        module.addTransients(UserEntity.class);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("PragmaticDciPU");
        assertNotNull(emf);
        em = emf.createEntityManager();
        assertNotNull(em);
    }

    @Override
    public void tearDown() throws Exception {
        if (em != null) {
            em.close();
        }
        super.tearDown();
    }

    @Test
    public void createOne() {
        UserEntity user = transientBuilderFactory.newTransient(UserEntity.class);
        user.setFirstName("Hendy");
        em.getTransaction().begin();
        em.persist(user);
        em.getTransaction().commit();
        UserEntity user2 = em.find(UserEntity.class, user.getId());
        assertEquals(user.getFirstName(), user2.getFirstName());
    }
} 
Here's the UserEntity composite interface:
// main/user/UserEntity.java
package user;

import org.qi4j.api.composite.TransientComposite;

public interface UserEntity
  extends User, TransientComposite { }
Here's the User mixin interface:
// main/user/User.java
package user;

import org.qi4j.api.mixin.Mixins;
import user.internal.UserMixin;

@Mixins(UserMixin.class)
public interface User {

    public Long getId();
    void setId(Long id);
    String getFirstName();
    void setFirstName(String firstName);
    String getLastName();
    void setLastName(String lastName);
}
And finally, the User mixin implementation:

// main/user/internal/UserMixin.java
package user.internal;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import user.User;

@Entity
public class UserMixin implements User, Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}
If you're curious about the persistence unit, here is it:
<!-- WEB-INF/persistence.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="PragmaticDciPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>user.internal.UserMixin</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
      <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost/pragmaticdci"/>
      <property name="javax.persistence.jdbc.user" value="app"/>
      <property name="javax.persistence.jdbc.password" value="app"/>
    </properties>
  </persistence-unit>
</persistence>

1 comment:

  1. Yes, that doesn't work, and is not supposed to do work either. Composites are just that, composites. They are made up of several mixins, each of which is a separate Java class("POJO"). So if you want to persist anything using JPA it would be a mixin, not a composite, since JPA does not understand the notion that an object can have an internal structure.

    But transients are also just that, transient. They are not meant to be persistent. We have EntityComposites for that purpose. They exist in a UnitOfWork, they have identity, they have concurrency rules, etc. Use EntityComposites to implement entities.

    ReplyDelete