4

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
    -------------------------------------------

    &ltpersistence-unit name="PU1" transaction-type="JTA">

        &ltproperties>

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

    &lt/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.

0xCursor
  • 2,242
  • 4
  • 15
  • 33
Paul.Luk
  • 51
  • 4
  • Creating an EMF using `Persistence.createEntityManagerFactory` results in a JavaSE EMF, where you are responsible for starting transactions (either RESOURCE_LOCAL or JTA UserTransaction). Perhaps Spring would want you to INJECT the EMF, and hence handle such transaction starts via its annotations. – Neil Stockton Jul 11 '17 at 12:57
  • Neil, the problem is that i need to determine which database to connect to, spring INJECT seems not able dynamically to inject proper EMF for me. do you have idea about this? thx. – Paul.Luk Jul 12 '17 at 01:42
  • Before you get JPA working, how are you going to tell Spring which datasource to use in the transaction? Looks like you are better off controlling the transaction yourself. – Chris Jul 12 '17 at 13:45

2 Answers2

1

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
barbsan
  • 3,418
  • 11
  • 21
  • 28
Paul.Luk
  • 51
  • 4
  • I have the same situation here the only difference is that we have only one datasource but the rest are same. embedded tomcat with executable jar in standard jdk container. We have the same problem with transaction and we use hibernate. do you have any info or lead from top of your head to help me out in this? – Mohamad Eghlima Jul 30 '22 at 16:51
0

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();
Plaul
  • 7,191
  • 5
  • 19
  • 22