Hello all,
I have a small issue. Unfortunately this one is quite complicated and only someone that has had this before will spot the problem.
Well, I have wired an application with Spring. There is a datasource and an entity manager that are configured together. So far so good. However to play nicely together the transaction manager hierarchy is as follows:
Bitronix
Spring JTA
Spring JPA
Here are the pertinent configurations (sorry about the length, included all just for completeness). The only interesting parts I think are the jpaPropertyMap in the Spring config, and the persist method in the class.
Spring:Code:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<context:property-placeholder
location="classpath:/META-INF/spring-test.properties" />
<!-- Bean post-processor for JPA annotations. -->
<context:annotation-config />
<!-- The transaction manager services. -->
<bean
id="bitronix.tm.TransactionManagerServices"
class="bitronix.tm.TransactionManagerServices"
factory-method="getConfiguration"
destroy-method="shutdown">
<property name="serverId" value="spring-btm" />
<property name="disableJmx" value="false" />
</bean>
<!-- The transaction manager. -->
<bean
id="bitronix.tm.BitronixTransactionManager"
depends-on="bitronix.tm.TransactionManagerServices"
class="bitronix.tm.TransactionManagerServices"
factory-method="getTransactionManager"
destroy-method="shutdown"
p:transactionTimeout="600000">
</bean>
<!-- The Spring transaction manager. -->
<bean
id="org.springframework.transaction.jta.JtaTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"
p:userTransaction-ref="bitronix.tm.BitronixTransactionManager"
p:transactionManager-ref="bitronix.tm.BitronixTransactionManager"
p:allowCustomIsolationLevels="true">
</bean>
<!--
The Spring JPA transaction manager.
name="transactionManager"
-->
<bean
id="org.springframework.orm.jpa.JpaTransactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory" />
<!-- For multiple persistence units we need this manager. -->
<bean
id="persistenceUnitManager"
class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"
p:defaultDataSource-ref="orbisDataSource"
p:persistenceXmlLocations="${persistence-test.xml}">
</bean>
<!--
This factory is for the entity managers. Entity manager factories are heavy and entity managers are light.
OpenJpaVendorAdapter, HibernateJpaVendorAdapter
-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:jpaVendorAdapter-ref="jpaVendorAdapter"
p:jpaPropertyMap-ref="jpaPropertyMap"
p:persistenceUnitManager-ref="persistenceUnitManager">
</bean>
<util:map id="jpaPropertyMap">
<entry key="openjpa.TransactionMode" value="managed" />
<entry key="openjpa.ConnectionFactoryMode" value="managed" />
<entry key="openjpa.ManagedRuntime" value="invocation(TransactionManagerMethod=bitronix.tm.TransactionManagerServices.getTransactionManager)" />
<entry key="openjpa.ConnectionFactoryProperties" value="PrettyPrint=true,PrettyPrintLineLength=150" />
<entry key="openjpa.Log" value="DefaultLevel=TRACE,SQL=TRACE,File=openjpa.log,Runtime=TRACE,Tool=TRACE" />
<entry key="openjpa.QueryCache" value="true(CacheSize=10000,SoftReferenceSize=1000)" />
<entry key="openjpa.LockTimeout" value="600000" />
<entry key="openjpa.Connection2UserName" value="${jdbc.user}" />
<entry key="openjpa.Connection2Password" value="${jdbc.password}" />
<entry key="openjpa.Connection2URL" value="${jdbc.url}" />
<entry key="openjpa.Connection2DriverName" value="${jdbc.driver}" />
<entry key="openjpa.ConnectionUserName" value="${jdbc.user}" />
<entry key="openjpa.ConnectionPassword" value="${jdbc.password}" />
<entry key="openjpa.ConnectionURL" value="${jdbc.url}" />
<entry key="openjpa.ConnectionDriverName" value="${jdbc.driver}" />
<entry key="hibernate.connection.username" value="${jdbc.user}" />
<entry key="hibernate.connection.password" value="${jdbc.password}" />
<entry key="hibernate.dialect" value="${jdbc.dialect}" />
<entry key="hibernate.connection.url" value="${jdbc.url}" />
<entry key="hibernate.connection.driver_class" value="${jdbc.driver}" />
<entry key="hibernate.query.factory_class" value="org.hibernate.hql.ast.ASTQueryTranslatorFactory" />
<entry key="hibernate.hbm2ddl.auto" value="none" />
<entry key="hibernate.show_sql" value="true" />
<entry key="hibernate.format_sql" value="false" />
<entry key="hibernate.cache.use_second_level_cache" value="false" />
<entry key="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup" />
<!--<entry key="hibernate.current_session_context_class" value="resourceLocal" />-->
<!--<entry key="transaction.factory.class" value="org.hibernate.transaction.JTATransactionFactory" />-->
</util:map>
<!-- OpenJpaVendorAdapter, HibernateJpaVendorAdapter -->
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<!-- This is the datasource that will be used by the application it's self.Typically this datasource should be transactional. -->
<bean id="orbisDataSource"
class="bitronix.tm.resource.jdbc.PoolingDataSource"
init-method="init"
destroy-method="close"
p:className="${jdbc.dataSource}"
p:uniqueName="${jdbc.dataSource}"
p:minPoolSize="${jdbc.min.pool.size}"
p:maxPoolSize="${jdbc.max.pool.size}"
p:allowLocalTransactions="false"
p:automaticEnlistingEnabled="false"
p:useTmJoin="true"
p:driverProperties-ref="driverProperties">
</bean>
<util:properties id="driverProperties">
<prop key="URL">${jdbc.url}</prop>
<prop key="user">${jdbc.user}</prop>
<prop key="password">${jdbc.password}</prop>
</util:properties>
<bean
id="com.agfa.spring.persistence.DataBaseJpa"
name="com.agfa.spring.persistence.DataBaseJpa"
class="com.agfa.spring.persistence.DataBaseJpa" />
<!-- The transaction advice is necessary for the transaction handler. -->
<tx:advice id="org.springframework.aop.BeforeAdvice"
transaction-manager="org.springframework.transaction.jta.JtaTransactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="com.agfa.spring.persistence.DataBaseJpa.Pointcut" expression="execution(* com.agfa.spring.persistence.DataBaseJpa.*(..))" />
<aop:advisor advice-ref="org.springframework.aop.BeforeAdvice" pointcut-ref="com.agfa.spring.persistence.DataBaseJpa.Pointcut" />
</aop:config>
</beans>
Persistence:Code:
<?xml version="1.0"?>
<persistence 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" version="1.0">
<persistence-unit name="ServiceBeanUnit" transaction-type="RESOURCE_LOCAL">
<!--
org.hibernate.ejb.HibernatePersistence,
org.apache.openjpa.persistence.PersistenceProviderImpl
-->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.agfa.spring.bean.PdfDocument</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>
</persistence>
And the class for persistence:Code:
package com.agfa.spring.persistence;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import org.apache.log4j.Logger;
import com.agfa.spring.IConstants;
/**
* This bean is for accessing the database with JPA.
*
* @author axhup
* @since 28.04.10
* @version 01.00
*/
@Local(value = { DataBase.class })
@Remote(value = { IDataBase.class })
@Stateless(mappedName = DataBaseJpa.JNDI_NAME)
public class DataBaseJpa implements DataBase, IDataBase {
public static final String JNDI_NAME = "DataBaseJpa/remote";
/** The logger for the bean. */
private Logger logger = Logger.getLogger(DataBaseJpa.class);
/** Entity manager for the bean will be injected. */
@PersistenceContext(type = PersistenceContextType.TRANSACTION, unitName = IConstants.PSERSISTENCE_UNIT)
protected EntityManager entityManager;
/**
* {@inheritDoc}
*/
public <T> T remove(Class<T> klass, Long id) {
T toBeRemoved = find(klass, id);
if (toBeRemoved != null) {
entityManager.remove(toBeRemoved);
}
return toBeRemoved;
}
/**
* {@inheritDoc}
*/
public <T> T persist(T object) {
if (object != null) {
entityManager.persist(object);
}
return object;
}
/**
* {@inheritDoc}
*/
public <T> T merge(T object) {
if (object != null) {
object = entityManager.merge(object);
}
return object;
}
/**
* {@inheritDoc}
*/
public <T> T find(Class<T> klass, Long id) {
return entityManager.find(klass, id);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <T> T find(String queryName, Map<String, Object> parameters) {
Query query = entityManager.createNamedQuery(queryName);
query.setFirstResult(0);
query.setMaxResults(1);
setParameters(query, parameters);
try {
return (T) query.getSingleResult();
} catch (NoResultException e) {
logger.info("No result found " + queryName + ", " + parameters);
}
return null;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <T> List<T> find(String queryName, Map<String, Object> parameters, int firstResult, int maxResults) {
Query query = entityManager.createNamedQuery(queryName);
query.setFirstResult(firstResult);
query.setMaxResults(maxResults);
setParameters(query, parameters);
return query.getResultList();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <T> List<T> find(Class<T> klass, int firstResult, int maxResults) {
String name = klass.getSimpleName();
StringBuilder builder = new StringBuilder("select ");
builder.append(name);
builder.append(" from ");
builder.append(name);
builder.append(" as ");
builder.append(name);
Query query = entityManager.createQuery(builder.toString());
query.setFirstResult(firstResult);
query.setMaxResults(maxResults);
return query.getResultList();
}
/**
* {@inheritDoc}
*/
public int execute(String sql, Map<String, Object> parameters) {
Query query = entityManager.createQuery(sql);
setParameters(query, parameters);
return query.executeUpdate();
}
/**
* Sets the parameters in the query on the database.
*
* @param query
* the query to set the parameters for
* @param parameters
* the parameters for the query
*/
private void setParameters(Query query, Map<String, Object> parameters) {
if (parameters == null) {
return;
}
Iterator<String> keys = parameters.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
Object parameter = parameters.get(key);
query.setParameter(key, parameter);
}
}
}
Now on the grounds that this configuration works with OpenJpa I have to assume that there is somthing that I am missing from the Hibernate configuration. However I don't see any warnings from Bitronix saying that there are no resources in the transaction. I also specified that Hibernate print the SQL, but I see no insert statement, I do see a select though, and an access to the sequence table, which is duly incremented. This is in a unit test environment, as an aside.
I can't think of anything that could be helpfull. I thank you for reading this far, and for the attention.
Thanks in advance for any thoughts.
Regards,
Michael