Description of the issue:
JpaLocalTxnInterceptor is inconsistent as it uses the "JpaPersistService
emProvider" field to begin a transaction but uses the "UnitOfWork unitOfWork"
to finish it. When the "UnifOfWork" binding of the JpaPersistModule is
overriden, this causes incoherent states and eventually crashes.
Here is a class (I used nested class to keep a single java file) that reproduce
the issue:
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Persistence;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.persist.PersistService;
import com.google.inject.persist.Transactional;
import com.google.inject.persist.UnitOfWork;
import com.google.inject.persist.jpa.JpaPersistModule;
import com.google.inject.util.Modules;
public class TestGuiceUnitOfWork {
@Entity
public static class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Singleton
public static class EntityManagerFactoryProvider implements Provider<EntityManagerFactory>, PersistService {
private EntityManagerFactory emFactory;
@Override
public EntityManagerFactory get() {
return emFactory;
}
@Override
public void start() {
this.emFactory = Persistence.createEntityManagerFactory("my-pu");
}
@Override
public void stop() {
emFactory.close();
emFactory = null;
}
}
@Singleton
public static class EntityManagerProvider implements Provider<EntityManager>, UnitOfWork {
private final ThreadLocal<EntityManager> entityManager = new ThreadLocal<EntityManager>();
@Inject
private Provider<EntityManagerFactory> emf;
@Override
public EntityManager get() {
return entityManager.get();
}
@Override
public void begin() {
System.err.println("Begin");
entityManager.set(emf.get().createEntityManager());
}
@Override
public void end() {
entityManager.remove();
System.err.println("End");
}
}
public static class MyGuicePersistModule extends AbstractModule {
@Override
protected void configure() {
bind(EntityManagerFactory.class).toProvider(EntityManagerFactoryProvider.class);
bind(PersistService.class).to(EntityManagerFactoryProvider.class);
bind(EntityManager.class).toProvider(EntityManagerProvider.class);
bind(UnitOfWork.class).to(EntityManagerProvider.class);
bind(InjectedClass.class);
}
}
public static class InjectedClass {
@Transactional
public void doAction() {
}
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(Modules.override(new JpaPersistModule("a")).with(new MyGuicePersistModule()));
InjectedClass instance = injector.getInstance(InjectedClass.class);
injector.getInstance(PersistService.class).start();
try {
instance.doAction();
} finally {
injector.getInstance(PersistService.class).stop();
}
}
}
an example of the 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="my-pu">
<class>TestGuiceUnitOfWork$MyEntity</class>
<properties>
<property name="hibernate.connection.pool_size" value="16"/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />
<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
And one set of Maven dependencies:
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-persist</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.2.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.2.1.Final</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.172</version>
</dependency>
This leads to the following stacktrace:
Exception in thread "main" java.lang.NullPointerException
at com.google.inject.persist.jpa.JpaPersistService.begin(JpaPersistService.java:70)
at com.google.inject.persist.jpa.JpaLocalTxnInterceptor.invoke(JpaLocalTxnInterceptor.java:49)
at TestGuiceUnitOfWork.main(TestGuiceUnitOfWork.java:121)
Expected behaviour:
JpaLocalTxnInterceptor.invoke should not call "emProvider.begin();" but
"unitOfWork.begin()". This would also require an extra-method on "UnifOfWork":
"isWorking()".
Original issue reported on code.google.com by guillaum...@gmail.com on 5 Jun 2013 at 8:23
Original issue reported on code.google.com by
guillaum...@gmail.com
on 5 Jun 2013 at 8:23