I've a problem with setting up transactions programatically. The whole story:
1. I've to manage several connections pools to different database, let's call them shards.
2. So I have to have N services beans with DAO inside, where each DAO is connected to different database.
3. I don't want to have huge XML files so I decided to create my own XML schema handler.
My spring-config.xml looks like this:
Please note:Code:<!-- Datasource definitions --> <bean id="warehouseDataSourceTemplate" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" abstract="true"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="username" value="${inout.warehouse.db.username}" /> <property name="password" value="${inout.warehouse.db.password}" /> <property name="initialSize" value="${inout.warehouse.db.pool.initialSize}" /> <property name="maxActive" value="${inout.warehouse.db.pool.maxActive}" /> <property name="maxIdle" value="${inout.warehouse.db.pool.maxIdle}" /> <property name="minIdle" value="${inout.warehouse.db.pool.minIdle}" /> <property name="maxWait" value="${inout.warehouse.db.pool.maxWait}" /> <property name="validationQuery" value="${inout.warehouse.db.pool.validationQuery}" /> <property name="testOnBorrow" value="${inout.warehouse.db.pool.testOnBorrow}" /> <property name="defaultAutoCommit" value="false" /> <property name="poolPreparedStatements" value="${inout.warehouse.db.pool.poolPreparedStatements}" /> <property name="maxOpenPreparedStatements" value="${inout.warehouse.db.pool.maxOpenPreparedStatements}" /> <property name="defaultTransactionIsolation" value="2" /> <property name="accessToUnderlyingConnectionAllowed" value="true" /> </bean> <bean name="warehouseService" class="c.p.s.i.s.impl.WarehouseServiceImpl"> <property name="warehouseTransactionalServices"> <list> <projectx:warehouseTransactionalService id="warehouseTransactionalService1" databaseUrl="${inout.db.warehouse1.url}" /> <projectx:warehouseTransactionalService id="warehouseTransactionalService2" databaseUrl="${inout.db.warehouse2.url}" /> </list> </property> </bean>
and also that dataSource in XML is abstract.Code:<projectx:warehouseTransactionalService id="warehouseTransactionalService2" databaseUrl="${inout.db.warehouse2.url}" />
4. What I want to achieve is to force my custom tag handler to create the following beans for each database: dataSource, dao, transaction manager, service target bean, transactional service proxy.
5. My tag handler looks like this:
6. So what I did is only registering bean definitions just like I would do it in spring-config.xml.Code:package c.p.s.i.spring; import java.util.Properties; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.interceptor.TransactionProxyFactoryBean; import org.w3c.dom.Element; import c.p.s.i.dao.WarehouseDao; import c.p.s.i.service.impl.WarehouseTransactionalServiceImpl; public class TransactionalWarehouseServiceDefinitionParser extends AbstractBeanDefinitionParser { public static int nextDataSource = 0; protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { ++nextDataSource; // create dataSource GenericBeanDefinition dataSourceDefinition = new GenericBeanDefinition(); dataSourceDefinition.setParentName("warehouseDataSourceTemplate"); MutablePropertyValues propertyValues = dataSourceDefinition.getPropertyValues(); propertyValues.addPropertyValue("url", element.getAttribute("databaseUrl")); // register dataSource String dataSourceName = "warehouseDataSource" + nextDataSource; registerBeanDefinition(new BeanDefinitionHolder(dataSourceDefinition, dataSourceName), parserContext.getRegistry()); // create dao GenericBeanDefinition daoDefinition = new GenericBeanDefinition(); daoDefinition.setBeanClass(WarehouseDao.class); propertyValues = daoDefinition.getPropertyValues(); propertyValues.addPropertyValue("dataSource", parserContext.getRegistry().getBeanDefinition(dataSourceName)); // register dao String daoName = "warehouseDao" + nextDataSource; registerBeanDefinition(new BeanDefinitionHolder(daoDefinition, daoName), parserContext.getRegistry()); // create transaction manager GenericBeanDefinition txManagerDefinition = new GenericBeanDefinition(); txManagerDefinition.setBeanClass(DataSourceTransactionManager.class); propertyValues = txManagerDefinition.getPropertyValues(); propertyValues.addPropertyValue("dataSource", parserContext.getRegistry().getBeanDefinition(dataSourceName)); // register transaction manager String txManagerName = "warehouseTransactionManager" + nextDataSource; registerBeanDefinition(new BeanDefinitionHolder(txManagerDefinition, txManagerName), parserContext.getRegistry()); // create service GenericBeanDefinition warehouseTransactionalServiceTargetDefinition = new GenericBeanDefinition(); warehouseTransactionalServiceTargetDefinition.setBeanClass(WarehouseTransactionalServiceImpl.class); propertyValues = warehouseTransactionalServiceTargetDefinition.getPropertyValues(); propertyValues.addPropertyValue("dao", parserContext.getRegistry().getBeanDefinition(daoName)); // register service String warehouseTransactionalServiceTargetName = "warehouseTransactionalServiceTarget" + nextDataSource; registerBeanDefinition(new BeanDefinitionHolder(warehouseTransactionalServiceTargetDefinition, warehouseTransactionalServiceTargetName), parserContext.getRegistry()); // create transactional aspect GenericBeanDefinition proxyService = new GenericBeanDefinition(); proxyService.setBeanClass(TransactionProxyFactoryBean.class); propertyValues = proxyService.getPropertyValues(); propertyValues.addPropertyValue("target", parserContext.getRegistry().getBeanDefinition(warehouseTransactionalServiceTargetName)); propertyValues.addPropertyValue("transactionManager", parserContext.getRegistry().getBeanDefinition(txManagerName)); Properties propreties = new Properties(); propreties.put("*", "PROPAGATION_REQUIRED"); propertyValues.addPropertyValue("transactionAttributes", propreties); registerBeanDefinition(new BeanDefinitionHolder(proxyService, element.getAttribute(ID_ATTRIBUTE)), parserContext.getRegistry()); return proxyService; } }
7. The problem is that doing this like shown above doesn't work 100% correct. Indeed beans are created, everything looks fine until transaction commit.
Transactions are never committed. They are always rolled back although transaction manager writes in logs that transaction was committed!
Rewriting the code above into 100% declarative, xml form fixes the problem but I want to use my custom tag to keep XML files clean and compact as more databases join the ring.
After diving into deep internals of Spring it turns out the transactions are rolled back in
org.springframework.transaction.support.Transactio nSynchronizationUtils.triggerBeforeCompletion()
and then in:
org.springframework.jdbc.datasource.DataSourceUtil s.ConnectionSynchronization.beforeCompletion()
but I don't understand why it happens. Could someone point me out where something went wrong? How should I register beans definitions to have proper transactions?


Reply With Quote
