pombreda / google-guice

Automatically exported from code.google.com/p/google-guice
Apache License 2.0
0 stars 1 forks source link

JpaLocalTxnInterceptor is inconsistent. It uses both UnitOfWork and JpaPersistService #753

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago

Original comment by sberlin on 20 Dec 2013 at 2:16