1

Hi all i ave an application with two different databases. The first db is for our business app, the second belong to a framework we are using.

I have a manager in witch i need to access both dbs to perform some operations. I configured my app with two entityManagerFactory, one for each datasource, and two transaction managers, one for each entityManagerFactory...

Here my configuration

my persistence.xml

<persistence version="1.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_1_0.xsd">
  <persistence-unit name="ENPIM_BD_PU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <non-jta-data-source>jdbc/businessDataSource</non-jta-data-source>
    <class>it.aicof.projects.enpim.persistence.entities.AccountingRecords</class>
    .....
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.show_sql" value="false"/>
    </properties>
  </persistence-unit>
  <persistence-unit name="ENPIM_SERV_PU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>it.aicof.projects.enpim.persistence.entities.Authusers</class>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.show_sql" value="false"/>
    </properties>
  </persistence-unit>
</persistence>

my app-context.xml

<context:annotation-config/>
    <context:component-scan base-package="it.aicof.projects.*"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
    <bean id="dataSourceBusiness" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          destroy-method="close">
            <property name="driverClass" value="${profile.database.driverClassName}" />
            <property name="jdbcUrl" value="jdbc:postgresql://${profile.database.hostname}:${profile.database.port}/${profile.database.business}" />
            <property name="user" value="${profile.database.username}" />
            <property name="password" value="${profile.database.password}" />

        <!-- these are C3P0 properties -->
            <property name="acquireIncrement" value="${c3p0.acquireIncrement}" />
            <property name="initialPoolSize" value="${c3p0.initialPoolSize}" />
            <property name="minPoolSize" value="${c3p0.minPoolSize}" />
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}" />
            <property name="maxIdleTime" value="${c3p0.maxIdleTime}" />
    </bean>

    <bean id="dataSourceServ" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          destroy-method="close">
            <property name="driverClass" value="${profile.database.driverClassName}" />
            <property name="jdbcUrl" value="jdbc:postgresql://${profile.database.hostname}:${profile.database.port}/${profile.database.serv}" />
            <property name="user" value="${profile.database.username}" />
            <property name="password" value="${profile.database.password}" />

        <!-- these are C3P0 properties -->
            <property name="acquireIncrement" value="${c3p0.acquireIncrement}" />
            <property name="initialPoolSize" value="${c3p0.initialPoolSize}" />
            <property name="minPoolSize" value="${c3p0.minPoolSize}" />
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}" />
            <property name="maxIdleTime" value="${c3p0.maxIdleTime}" />
    </bean>       

    <bean id="transactionManagerBusiness" class="org.springframework.orm.jpa.JpaTransactionManager"
           p:entityManagerFactory-ref="entityManagerFactory">
        <qualifier value="business" />   
    </bean>

    <bean id="transactionManagerServ" class="org.springframework.orm.jpa.JpaTransactionManager"
           p:entityManagerFactory-ref="entityManagerFactoryServ">
        <qualifier value="serv" />   
    </bean>

    <bean id="entityManagerFactory"
           class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
           p:dataSource-ref="dataSourceBusiness"
           p:jpaVendorAdapter-ref="jpaAdapter">
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <property name="persistenceUnitName" value="ENPIM_BD_PU"></property>
    </bean>

    <bean id="entityManagerFactoryServ"
           class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
           p:dataSource-ref="dataSourceServ"
           p:jpaVendorAdapter-ref="jpaAdapter">
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <property name="persistenceUnitName" value="ENPIM_SERV_PU"></property>
    </bean>    

    <bean id="jpaAdapter"
             class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
             p:database="POSTGRESQL" p:showSql="false"/>

Here is the code i'm using

my dao

    @PersistenceContext(unitName = "ENPIM_BD_PU")
    protected EntityManager em; 

    @PersistenceContext(unitName = "ENPIM_SERV_PU")
    protected EntityManager emServ; 

    ....

 public void edit(AddressBook addressBook) throws NonexistentEntityException, Exception {
        try {

            addressBook = em.merge(addressBook);
        } catch (Exception ex) {
            Logger.getLogger(this.getClass().getName()).error("edit", ex);
            String msg = ex.getLocalizedMessage();
            if (msg == null || msg.length() == 0) {
                Integer id = addressBook.getId();
                if (findAddressBook(id) == null) {
                    throw new NonexistentEntityException("The addressBook with id " + id + " no longer exists.");
                }
            }
            throw ex;
        } 
    }   

    public void createBeneficiaryLoginAccount(String username, String password) throws Exception{
         try {
            Query q = emServ.createNativeQuery("INSERT INTO authusers(username, passwd, registrationdate, lastaccess, lastpasswordchange, active)" +
                        " VALUES (?, ?, ?, ?, ?,  ?)");
            q.setParameter(1, username);
            q.setParameter(2, password);
            q.setParameter(3, new Date());
            q.setParameter(4, new Date());
            q.setParameter(5, new Date());
            q.setParameter(6, 1);
            q.executeUpdate();
        }catch (Exception ex) {
            Logger.getLogger(this.getClass().getName()).error("createBeneficiaryAccount", ex);
            throw ex;
        }
    }

my manager

@Override
public void createBeneficiaryAccount(AddressBook addressBook, String password) {
    String username = addressBook.getUsername();
    try {
        AddressBook ab = this.addressBookJpaDAO.findAddressBook(addressBook.getId());
        ab.setUsername(username);
        //creo l'utente sul db serv e gli assegno il ruolo di beneficiario
        this.createBeneficiaryLoginAccount(username, password);

        this.createBeneficiaryUsername(ab, password);
    } catch (Throwable t) {
        Logger.getLogger(this.getClass().getName()).error("", t);
    }
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class, value = "serv")
private void createBeneficiaryLoginAccount(String username, String password) throws Throwable {
    try {
        //creo l'utente sul db serv e gli assegno il ruolo di beneficiario
        this.addressBookJpaDAO.createBeneficiaryLoginAccount(username, password);
    } catch (Throwable t) {
        Logger.getLogger(this.getClass().getName()).error("createLoginAccount", t);
        throw t;
    }
}   

@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class, value = "business")
private void createBeneficiaryUsername(AddressBook addressBook, String password) throws Throwable {
    try {
        this.addressBookJpaDAO.edit(addressBook);
    } catch (Throwable t) {
        Logger.getLogger(this.getClass().getName()).error("createBeneficiaryUsername", t);
        throw t;
    }
}

when the method that uses the second entityManager is called i get the exception

2014-11-26 11:10:35,844 ERROR controllers.AddressBookJpaController - createBeneficiaryAccount
javax.persistence.TransactionRequiredException: Executing an update/delete query
    at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:71)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
.....

and debugging the application i found that when we try to use the second entityManager we have this error

Exception occurred in target VM: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead 
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:198)
    at com.sun.proxy.$Proxy145.getTransaction(Unknown Source)
    at it.aicof.projects.enpim.persistence.controllers.AddressBookJpaController.createBeneficiaryLoginAccount(AddressBookJpaController.java:854)

i take a look on the web for some example but all have the same configuration as mine. So.. why it's not working?

what's wrong?

thanks in advance

andrea

Andrea
  • 190
  • 1
  • 20
  • It's a Spring Aop gotcha!, read this: http://stackoverflow.com/questions/4396284/does-spring-transactional-attribute-work-on-a-private-method – André Nov 26 '14 at 10:24

1 Answers1

0

You're annotating @Transactional at a private method. That doesn't work with the Spring Aop Proxy Beans.

The quickest way is to set both methods to public

@Transactional("serv")
public void createBeneficiaryLoginAccount(String username, String password) throws Throwable {
    try {
        //creo l'utente sul db serv e gli assegno il ruolo di beneficiario
        this.addressBookJpaDAO.createBeneficiaryLoginAccount(username, password);
    } catch (Throwable t) {
        Logger.getLogger(this.getClass().getName()).error("createLoginAccount", t);
        throw t;
    }
}   

@Transactional("business")
public void createBeneficiaryUsername(AddressBook addressBook, String password) throws Throwable {
    try {
        this.addressBookJpaDAO.edit(addressBook);
    } catch (Throwable t) {
        Logger.getLogger(this.getClass().getName()).error("createBeneficiaryUsername", t);
        throw t;
    }
}

Or you might want to use AspectJ mode for AOP, but that is another layer of headaches IMHO.

See: Does Spring @Transactional attribute work on a private method?

Community
  • 1
  • 1
André
  • 2,184
  • 1
  • 22
  • 30
  • thanks... i saw also i have to change the mode attribute to aspectj in the tx:annotation-driven declaration. – Andrea Nov 26 '14 at 10:57