深入解析Java的Spring框架中的混合事务与bean的区分

网友投稿 180 2023-07-22


深入解析Java的Spring框架中的混合事务与bean的区分

混合事务

在ORM框架的事务管理器的事务内,使用JdbcTemplate执行SQL是不会纳入事务管理的。

下面进行源码分析,看为什么必须要在DataSourceTransactionManager的事务内使用JdbcTemplate。

1.开启事务

DataSourceTransactionManager

protected void doBegin(Object transaction,TransactionDefinition definition) {

DataSourceTransactionObjecttxObject = (DataSourceTransactionObject) transaction;

Connection con = null;

try {

if(txObject.getConnectionHolder() == null ||

txObject.getConnectionHolder().isSynchronizedWithTransaction()){

ConnectionnewCon = this.dataSource.getConnection();

if(logger.isDebugEnabled()) {

logger.debug("AcquiredConnection [" + newCon + "] for JDBC transaction");

}

txObject.setConnectionHolder(newConnectionHolder(newCon), true);

}

txObject.getConnectionHolder().setSynchronizedWithTransaction(true);

con =txObject.getConnectionHolder().getConnection();

IntegerpreviousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,definition);

txObject.setPreviousIsolationLevel(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(con.getAutoCommit()) {

txObject.setMustRestoreAutoCommit(true);

if(logger.isDebugEnabled()) {

logger.debug("SwitchingJDBC Connection [" + con + "] to manual commit");

}

con.setAutoCommit(false);

}

txObject.getConnectionHolder().setTransactionActive(true);

int timeout =determineTimeout(definition);

if (timeout !=TransactionDefinition.TIMEOUT_DEFAULT) {

txObject.getConnectionHolder().setTimeoutInSeconds(timeout);

}

// Bind the sessionholder to the thread.

if(txObject.isNewConnectionHolder()) {

TransactionSynchronizationManager.bindResource(getDataSource(),txObject.getConnectionHolder());

}

}

catch (Exception ex) {

DataSourceUtils.releaseConnection(con,this.dataSource);

throw newCannotCreateTransactionException("Could not open JDBC Connection fortransaction", ex);

}

}

doBegin()方法会以数据源名为Key,ConnectionHolder(保存着连接)为Value,将已经开启事务的数据库连接绑定到一个ThreadLocal变量上。

2.绑定连接

public static void bindResource(Objectkey, Object value) throws IllegalStateException {

Object actualKey =TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);

Assert.notNull(value,"Value must not be null");

Map map = resources.get();

// set ThreadLocal Map ifnone found

if (map == null) {

map = newHashMap();

resources.set(map);

}

Object oldValue = map.put(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[EIezDCR" +

actualKey+ "] bound to thread [" + Thread.currentThread().getName() +"]");

}

if (logger.isTraceEnabled()){

logger.trace("Boundvalue [" + value + "] for key [" + actualKey + "] to thread[" +

Thread.currentThread().getName()+ "]");

}

}

resources变量就是上面提到的ThreadLocal变量,这样后续JdbcTemplate就可以用DataSource作为Key,查找到这个数据库连接。

3.执行SQL

JdbcTemplate

public Objectexecute(PreparedStatementCreator psc, PreparedStatementCallback action)

throwsDataAccessException {

Assert.notNull(psc,"PreparedStatementCreator must not be null");

Assert.notNull(action,"Callback object must not be null");

if (logger.isDebugEnabled()){

String sql =getSql(psc);

logger.debug("Executingprepared SQL statement" + (sql != null ? " [" + sql +"]" : ""));

}

Connection con = DataSourceUtils.getConnection(getDataSource());

PreparedStatement ps = null;

try {

Connection conToUse= con;

if(this.nativeJdbcExtractor != null &&

this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()){

conToUse =this.nativeJdbcExtractor.getNativeConnection(con);

}

ps =psc.createPreparedStatement(conToUse);

applyStatementSettings(ps);

PreparedStatementpsToUse = ps;

if(this.nativeJdbcExtractor != null) {

psToUse =this.nativeJdbcExtractor.getNativePreparedStatement(ps);

}

Object result =action.doInPreparedStatement(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;

JdbcUtils.closeStatement(ps);

ps = null;

DataSourceUtils.releaseConnection(con,getDataSource());

con = null;

throwgetExceptionTranslator().translate("PreparedStatementCallback", sql,ex);

}

finally {

if (psc instanceofParameterDisposer) {

((ParameterDisposer)psc).cleanupParameters();

}

JdbcUtils.closeStatement(ps);

DataSourceUtils.releaseConnection(con,getDataSource());

}

}

4.获得连接

DataSourceUtils

public static Connection doGetConnection(DataSourcedataSource) throws SQLException {

Assert.notNull(dataSource,"No DataSource specified");

ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);

if (conHolder != null&& (conHolder.hasConnection() ||conHolder.isSynchronizedWithTransaction())) {

conHolder.requested();

if(!conHolder.hasConnection()) {

logger.debug("Fetchingresumed JDBC Connection from DataSource");

conHolder.setConnection(dataSource.getConnection());

}

returnconHolder.getConnection();

}

// Else we either got noholder or an empty thread-bound holder here.

logger.debug("FetchingJDBC Connection from DataSource");

Connection con =dataSource.getConnection();

if (TransactionSynchronizationManager.isSynchronizationActive()){

logger.debug("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 {

holderToUse.setConnection(con);

}

holderToUse.requested();

TransactionSynchronizationManager.registerSynchronization(

newConnectionSynchronization(holderToUse, dataSource));

holderToUse.setSynchronizedWithTransaction(true);

if (holderToUse !=conHolder) {

TransactionSynchronizationManager.bindResource(dataSource,holderToUse);

}

}

return con;

}

由此可见,DataSourceUtils也是通过TransactionSynchronizationManager获得连接的。所以只要JdbcTemplate与DataSourceTransactionManager有相同的DataSource,就一定能得到相同的数据库连接,自然就能正确地提交、回滚事务。

 

再以Hibernate为例来说明开篇提到的问题,看看为什么ORM框架的事务管理器不能管理JdbcTemplate。

5 ORM事务管理器

HibernateTransactionManager

if(txObject.isNewSessionHolder()) {

TransactionSynchronizationManager.bindResource(getSessionFactory(),txObject.getSessionHolder());

}

因为ORM框架都不是直接将DataSource注入到TransactionManager中使用的,而是像上面Hibernate事务管理器一样,使用自己的SessionFactory等对象来操作DataSource。所以尽管可能SessionFactory和JdbcTemplate底层都是一样的数据源,但因为在TransactionSynchronizationManager中绑定时使用了不同的Key(一个是sessionFactory名,一个是dataSource名),所以JdbcTemplate执行时是拿不到ORM事务管理器开启事务的那个数据库连接的。

bean的区分

一个公共工程中的Spring配置文件,可能会被多个工程引用。因为每个工程可能只需要公共工程中的一部分Bean,所以这些工程的Spring容器启动时,需要区分开哪些Bean要创建出来。

1.应用实例

以Apache开源框架Jetspeed中的一段配置为例:page-manager.xml

……

JETSPEED-INF/ojb/page-manager-repository.xml

……

2.Bean过滤器

JetspeedBeanDefinitionFilter在Spring容器解析每个Bean定义时,会取出上面Bean配置中j2:cat对应的值,例如dbPageManageror pageSerializer。然后将这部分作为正则表达式与当前的Key(从配置文件中读出)进行匹配。只有匹配上的Bean,才会被Spring容器创建出来。

 

JetspeedBeanDefinitionFilter

public boolean match(BeanDefinition bd)

{

String beanCategoriesExpression = (String)bd.getAttribute(CATEGORY_META_KEY);

boolean matched = true;

if (beanCategoriesExpression != null)

{

matched = ((matcher != null)&& matcher.match(beanCategoriesExpression));

}

return matched;

}

public void registerDynamicAlias(BeanDefinitionRegistry registry, String beanName,BeanDefinition bd)

{

String aliases =(String)bd.getAttribute(ALIAS_META_KEY);

if (aliases != null)

{

StringTokenizer st = newStringTokenizer(aliases, " ,");

while (st.hasMoreTokens())

{

String alias = st.nextToken();

if (!alias.equals(beanName))

{

registry.registerAlias(beanName, alias);

}

}

}

}

match()方法中的CATEGORY_META_KEY的值就是j2:cat,matcher类中保存的就是当前的Key,并负责将当前Key与每个Bean的进行正则表达式匹配。

registerDynamicAlias的作用是:在Bean匹配成功后,定制的Spring容器会调用此方法为Bean注册别名。详见下面1.3中的源码。

3.定制Spring容器

定制一个Spring容器,重写registerBeanDefinition()方法,在Spring注册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)

{

this.setParent(parent);

}

if (initProperties != null)

{

PropertyPlaceholderConfigurer ppc =new PropertyPlaceholderConfigurer();

ppc.setIgnoreUnresolvablePlaceholders(true);

ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK);

ppc.setProperties(initProperties);

addBeanFactoryPostProcessor(ppc);

}

setConfigLocations(configLocations);

setServletContext(servletContext);

this.filter = filter;

}

protected DefaultListableBeanFactorycreateBeanFactory()

{

return new FilteringListableBeanFactory(filter,getInternalParentBeanFactory());

}

}

public classFilteringListableBeanFactory extends DefaultListableBeanFactory

{

private JetspeedBeanDefinitionFilterfilter;

public FilteringListableBeanFactory(JetspeedBeanDefinitionFilterfilter, BeanFactory parentBeanFactory)

{

super(parentBeanFactory);

this.filter = filter;

if (this.filter == null)

{

this.filter = newJetspeedBeanDefinitionFilter();

}

this.filter.init();

}

/**

* 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 (filter.match(bd))

{

super.registerBeanDefinition(beanName, bd);

if (filter != null)

{

filter.registerDynamicAlias(this, beanName, bd);

}

}

}

}

4.为Bean起别名

使用BeanReferenceFactoryBean工厂Bean,将上面配置的两个Bean(xmlPageManager和dbPageManager)包装起来。将Key配成各自的,实现通过配置当前Key来切换两种实现。别名都配成一个,这样引用他们的Bean就直接引用这个别名就行了。例如下面的PageLayoutComponent。

 

page-manager.xml

class="org.apache.jetspeed.layout.impl.PageLayoutComponentImpl">

jetspeed-layouts::VelocityOneColumn

class="org.apache.jetspeed.layout.impl.PageLayoutComponentImpl">

jetspeed-layouts::VelocityOneColumn


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java发送javax.mail邮件实例讲解
下一篇:简单讲解Java的Socket网络编程的多播与广播实现
相关文章

 发表评论

暂时没有评论,来抢沙发吧~