Spring Boot with application managed persistence context

Viewed 1457

I am trying to migrate an application from EJB3 + JTA + JPA (EclipseLink). Currently, this application makes use of application managed persistent context due to an unknown number of databases on design time.

The application managed persistent context allows us to control how to create EntityManager (e.g. supply different datasources JNDI to create proper EntityManager for specific DB on runtime).

E.g.

    Map properties = new HashMap();

    properties.put(PersistenceUnitProperties.TRANSACTION_TYPE, "JTA");

    //the datasource JNDI is by configuration and without prior knowledge about the number of databases
    //currently, DB JNDI are stored in a externalized file
    //the datasource is setup by operation team
    properties.put(PersistenceUnitProperties.JTA_DATASOURCE, "datasource-jndi");

    properties.put(PersistenceUnitProperties.CACHE_SHARED_DEFAULT, "false");
    properties.put(PersistenceUnitProperties.SESSION_NAME, "xxx");

    //create the proper EntityManager for connect to database decided on runtime                

    EntityManager em = Persistence.createEntityManagerFactory("PU1", properties).createEntityManager();     

    //query or update DB
    em.persist(entity);
    em.createQuery(...).executeUpdate();

When deployed in a EJB container (e.g. WebLogic), with proper TransactionAttribute (e.g. TransactionAttributeType.REQUIRED), the container will take care of the transaction start/end/rollback.

Now, I am trying to migrate this application to Spring Boot. The problem I encounter is that there is no transaction started even after I annotate the method with @Transactional(propagation = Propagation.REQUIRED).

The Spring application is packed as an executable JAR file and run with embadded Tomcat.

When I try to execute those update APIs, e.g. EntityManager.persist(..), EclipseLink always complains about:

javax.persistence.TransactionRequiredException: 'No transaction is currently active'

Sample code below:


    //for data persistence
    @Service
    class DynamicServiceImpl implements DynamicService {

        //attempt to start a transaction
        @Transactional(propagation = Propagation.REQUIRED)
        public void saveData(DbJndi, EntityA){

            //this return false that no transaction started
            TransactionSynchronizationManager.isActualTransactionActive();

            //create an EntityManager based on the input DbJndi to dynamically
            //determine which DB to save the data
            EntityManager em = createEm(DbJndi);

            //save the data 
            em.persist(EntityA);
        }
    }

    //restful service
    @RestController
    class RestController{

        @Autowired
        DynamicService service;

        @RequestMapping( value = "/saveRecord", method = RequestMethod.POST)
        public @ResponseBody String saveRecord(){
             //save data
             service.saveData(...)
        }
    }

    //startup application
    @SpringBootApplication
    class TestApp {

        public static void main(String[] args) {
            SpringApplication.run(TestApp.class, args);
        }
    }

    persistence.xml
    -------------------------------------------

    <persistence-unit name="PU1" transaction-type="JTA">

        <properties>

            <!-- comment for spring to handle transaction??? -->
            <!--property name="eclipselink.target-server" value="WebLogic_10"/ -->
        </properties>

    </persistence-unit>

    -------------------------------------------

    application.properties (just 3 lines of config)
    -------------------------------------------
    spring.jta.enabled=true 
    spring.jta.log-dir=spring-test # Transaction logs directory.
    spring.jta.transaction-manager-id=spring-test
    -------------------------------------------

My usage pattern does not follow most typical use cases (e.g. with known number of DBs - Spring + JPA + multiple persistence units: Injecting EntityManager).

Can anybody give me advice on how to solve this issue?

Is there anybody who has ever hit this situation that the DBs are not known in design time?

Thank you.

2 Answers

I finally got it work with:

  1. Enable tomcat JNDI and create the datasource JNDI to each DS programmatically
  2. Add transaction stuff

    • com.atomikos:transactions-eclipselink:3.9.3 (my project uses eclipselink instead of hibernate)

    • org.springframework.boot:spring-boot-starter-jta-atomikos

    • org.springframework:spring-tx

You have pretty much answered the question yourself: "When deployed in a EJB container (e.g. WebLogic), with proper TransactionAttribute (e.g. TransactionAttributeType.REQUIRED), the container will take care of the transaction start/end/rollback".

WebLogic is compliant with the Java Enterprise Edition specification which is probably why it worked before, but now you are using Tomcat (in embedded mode) which are NOT. So you simply cannot do what you are trying to do. This statement in your persistence.xml file:

<persistence-unit name="PU1" transaction-type="JTA">

requires an Enterprise Server (WebLogic, Glassfish, JBoss etc.)

With Tomcat you can only do this:

<persistence-unit name="PU1" transaction-type="RESOURCE_LOCAL">

And you need to handle transactions by your self:

myEntityManager.getTransaction.begin();
... //Do your transaction stuff
myEntityManager.getTransaction().commit();
Related