Mixed transactions
In transactions of the ORM framework's transaction manager, using JdbcTemplate to execute SQL will not be included in transaction management.
The following is a source code analysis to see why JdbcTemplate must be used within the transaction of DataSourceTransactionManager.
1. Start transactions
DataSourceTransactionManager
protected void doBegin(Object transaction,TransactionDefinition definition) { DataSourceTransactionObjecttxObject = (DataSourceTransactionObject) transaction; Connection con = null; try { if(() == null || ().isSynchronizedWithTransaction()){ ConnectionnewCon = (); if(()) { ("AcquiredConnection [" + newCon + "] for JDBC transaction"); } (newConnectionHolder(newCon), true); } ().setSynchronizedWithTransaction(true); con =().getConnection(); IntegerpreviousIsolationLevel = (con,definition); (previousIsolationLevel); // Switch to manualcommit if necessary. This is very expensive in some JDBC drivers, // so we don't wantto do it unnecessarily (for example if we've explicitly // configured theconnection pool to set it already). if(()) { (true); if(()) { ("SwitchingJDBC Connection [" + con + "] to manual commit"); } (false); } ().setTransactionActive(true); int timeout =determineTimeout(definition); if (timeout !=TransactionDefinition.TIMEOUT_DEFAULT) { ().setTimeoutInSeconds(timeout); } // Bind the sessionholder to the thread. if(()) { (getDataSource(),()); } } catch (Exception ex) { (con,); throw newCannotCreateTransactionException("Could not open JDBC Connection fortransaction", ex); } }
The doBegin() method will use the data source name Key and ConnectionHolder as Value, and bind the database connection that has been opened to a ThreadLocal variable.
2. Bind the connection
public static void bindResource(Objectkey, Object value) throws IllegalStateException { Object actualKey =(key); (value,"Value must not be null"); Map<Object, Object> map = (); // set ThreadLocal Map ifnone found if (map == null) { map = newHashMap<Object, Object>(); (map); } Object oldValue = (actualKey, value); // Transparently suppress aResourceHolder that was marked as void... if (oldValue instanceofResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null; } if (oldValue != null) { throw newIllegalStateException("Already value [" + oldValue + "] for key[" + actualKey+ "] bound to thread [" + ().getName() +"]"); } if (()){ ("Boundvalue [" + value + "] for key [" + actualKey + "] to thread[" + ().getName()+ "]"); } }
The resources variable is the ThreadLocal variable mentioned above, so that the subsequent JdbcTemplate can use DataSource as the Key to find the database connection.
3. Execute SQL
JdbcTemplate
public Objectexecute(PreparedStatementCreator psc, PreparedStatementCallback action) throwsDataAccessException { (psc,"PreparedStatementCreator must not be null"); (action,"Callback object must not be null"); if (()){ String sql =getSql(psc); ("Executingprepared SQL statement" + (sql != null ? " [" + sql +"]" : "")); } Connection con = (getDataSource()); PreparedStatement ps = null; try { Connection conToUse= con; if( != null && ()){ conToUse =(con); } ps =(conToUse); applyStatementSettings(ps); PreparedStatementpsToUse = ps; if( != null) { psToUse =(ps); } Object result =(psToUse); handleWarnings(ps); return result; } catch (SQLException ex) { // ReleaseConnection early, to avoid potential connection pool deadlock // in the case whenthe exception translator hasn't been initialized yet. if (psc instanceofParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } String sql =getSql(psc); psc = null; (ps); ps = null; (con,getDataSource()); con = null; throwgetExceptionTranslator().translate("PreparedStatementCallback", sql,ex); } finally { if (psc instanceofParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } (ps); (con,getDataSource()); } }
4. Get a connection
DataSourceUtils
public static Connection doGetConnection(DataSourcedataSource) throws SQLException { (dataSource,"No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder)(dataSource); if (conHolder != null&& (() ||())) { (); if(!()) { ("Fetchingresumed JDBC Connection from DataSource"); (()); } (); } // Else we either got noholder or an empty thread-bound holder here. ("FetchingJDBC Connection from DataSource"); Connection con =(); if (()){ ("Registeringtransaction synchronization for JDBC Connection"); // Use sameConnection for further JDBC actions within the transaction. // Thread-boundobject will get removed by synchronization at transaction completion. ConnectionHolderholderToUse = conHolder; if (holderToUse ==null) { holderToUse= new ConnectionHolder(con); } else { (con); } (); ( newConnectionSynchronization(holderToUse, dataSource)); (true); if (holderToUse !=conHolder) { (dataSource,holderToUse); } } return con; }
It can be seen that DataSourceUtils also obtains connection through TransactionSynchronizationManager. Therefore, as long as JdbcTemplate and DataSourceTransactionManager have the same DataSource, you will definitely get the same database connection and naturally you can submit and roll back transactions correctly.
Let’s take Hibernate as an example to illustrate the problem mentioned at the beginning, and see why the transaction manager of the ORM framework cannot manage JdbcTemplate.
5 ORM Transaction Manager
HibernateTransactionManager
if(()) { (getSessionFactory(),()); }
Because the ORM framework does not directly inject DataSource into TransactionManager for use, but uses its own SessionFactory and other objects to operate DataSource, just like the Hibernate transaction manager above. So although the underlying data source of SessionFactory and JdbcTemplate may be the same, because different keys are used when binding in TransactionSynchronizationManager (one is the sessionFactory name and the other is the dataSource name), JdbcTemplate cannot get the database connection that the ORM transaction manager starts the transaction when executing.
The distinction between beans
Spring configuration file in a public project may be referenced by multiple projects. Because each project may only require a portion of the beans in the public project, when the Spring container of these projects is started, it is necessary to distinguish which beans to be created.
1. Application examples
Take a configuration in Jetspeed, an open source framework of Apache, as an example:
<bean name="xmlPageManager"class=""init-method="init" destroy-method="destroy"> <meta key="j2:cat" value="xmlPageManager orpageSerializer" /> <constructor-arg index="0"> <ref bean="IdGenerator"/> </constructor-arg> <constructor-arg index="1"> <refbean="xmlDocumentHandlerFactory" /> </constructor-arg> …… </bean> <bean class=""init-method="init" destroy-method="destroy"> <meta key="j2:cat" value="dbPageManager orpageSerializer" /> <!-- OJB configuration file resourcepath --> <constructor-arg index="0"> <value>JETSPEED-INF/ojb/</value> </constructor-arg> <!-- fragment id generator --> <constructor-arg index="1"> <ref bean="IdGenerator"/> </constructor-arg> …… </bean>
Filter
When the JetspeedBeanDefinitionFilter parses each bean definition in the Spring container, it will take out the value corresponding to j2:cat in the above bean configuration, such as dbPageManageror pageSerializer. This part is then matched as a regular expression to the current Key (read from the configuration file). Only the beans on the matching will be created by the Spring container.
JetspeedBeanDefinitionFilter
public boolean match(BeanDefinition bd) { String beanCategoriesExpression = (String)(CATEGORY_META_KEY); boolean matched = true; if (beanCategoriesExpression != null) { matched = ((matcher != null)&& (beanCategoriesExpression)); } return matched; } public void registerDynamicAlias(BeanDefinitionRegistry registry, String beanName,BeanDefinition bd) { String aliases =(String)(ALIAS_META_KEY); if (aliases != null) { StringTokenizer st = newStringTokenizer(aliases, " ,"); while (()) { String alias = (); if (!(beanName)) { (beanName, alias); } } } }
The value of CATEGORY_META_KEY in the match() method is j2:cat. The current key is saved in the matcher class, and it is responsible for matching the current key with the regular expression of each bean.
The role of registerDynamicAlias is: after the bean match successfully, the customized Spring container will call this method to register an alias for the bean. For details, see the source code in 1.3 below.
3. Customize Spring Container
Customize a Spring container, override the registerBeanDefinition() method, and intercept it when Spring registers a bean.
public class FilteringXmlWebApplicationContextextends XmlWebApplicationContext { private JetspeedBeanDefinitionFilterfilter; publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext) { this(filter, configLocations,initProperties, servletContext, null); } publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext,ApplicationContext parent) { super(); if (parent != null) { (parent); } if (initProperties != null) { PropertyPlaceholderConfigurer ppc =new PropertyPlaceholderConfigurer(); (true); (PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK); (initProperties); addBeanFactoryPostProcessor(ppc); } setConfigLocations(configLocations); setServletContext(servletContext); = filter; } protected DefaultListableBeanFactorycreateBeanFactory() { return new FilteringListableBeanFactory(filter,getInternalParentBeanFactory()); } } public classFilteringListableBeanFactory extends DefaultListableBeanFactory { private JetspeedBeanDefinitionFilterfilter; public FilteringListableBeanFactory(JetspeedBeanDefinitionFilterfilter, BeanFactory parentBeanFactory) { super(parentBeanFactory); = filter; if ( == null) { = newJetspeedBeanDefinitionFilter(); } (); } /** * Override of the registerBeanDefinitionmethod to optionally filter out a BeanDefinition and * if requested dynamically register anbean alias */ public void registerBeanDefinition(StringbeanName, BeanDefinition bd) throws BeanDefinitionStoreException { if ((bd)) { (beanName, bd); if (filter != null) { (this, beanName, bd); } } } }
4. Alias the Bean
Use the BeanReferenceFactoryBean factory bean to wrap the two beans configured above (xmlPageManager and dbPageManager). The keys are matched into their own, and the implementation is to switch between the two implementations by configuring the current key. All alias are matched into one, so that the bean that refers to their bean is just quoting the alias directly. For example, the PageLayoutComponent below.
<bean class=""> <meta key="j2:cat"value="xmlPageManager" /> <meta key="j2:alias"value="" /> <propertyname="targetBeanName" value="xmlPageManager" /> </bean> <bean class=""> <meta key="j2:cat"value="dbPageManager" /> <meta key="j2:alias"value="" /> <propertyname="targetBeanName" value="dbPageManager" /> </bean> <bean id="" class=""> <meta key="j2:cat"value="default" /> <constructor-arg index="0"> <refbean="" /> </constructor-arg> <constructor-arg index="1"> <value>jetspeed-layouts::VelocityOneColumn</value> </constructor-arg> </bean>