Announcement Announcement Module
Collapse
No announcement yet.
Rollback problem when setting up transactions in custom XML schema extension handler Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Rollback problem when setting up transactions in custom XML schema extension handler

    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:

    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>
    Please note:
    Code:
    <projectx:warehouseTransactionalService id="warehouseTransactionalService2" databaseUrl="${inout.db.warehouse2.url}" />
    and also that dataSource in XML is abstract.

    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:
    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;
    	}
    
    }
    6. So what I did is only registering bean definitions just like I would do it in spring-config.xml.

    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?
Working...
X