深入解析Java的Spring框架中的混合事務與bean的區(qū)分
混合事務
在ORM框架的事務管理器的事務內,使用JdbcTemplate執(zhí)行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<Object, Object> map = resources.get();
// set ThreadLocal Map ifnone found
if (map == null) {
map = newHashMap<Object, Object>();
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[" +
actualKey+ "] bound to thread [" + Thread.currentThread().getName() +"]");
}
if (logger.isTraceEnabled()){
logger.trace("Boundvalue [" + value + "] for key [" + actualKey + "] to thread[" +
Thread.currentThread().getName()+ "]");
}
}
resources變量就是上面提到的ThreadLocal變量,這樣后續(xù)JdbcTemplate就可以用DataSource作為Key,查找到這個數據庫連接。
3.執(zhí)行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執(zhí)行時是拿不到ORM事務管理器開啟事務的那個數據庫連接的。
bean的區(qū)分
一個公共工程中的Spring配置文件,可能會被多個工程引用。因為每個工程可能只需要公共工程中的一部分Bean,所以這些工程的Spring容器啟動時,需要區(qū)分開哪些Bean要創(chuàng)建出來。
1.應用實例
以Apache開源框架Jetspeed中的一段配置為例:page-manager.xml
<bean name="xmlPageManager"class="org.apache.jetspeed.page.psml.CastorXmlPageManager"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 id="dbPageManager"class="org.apache.jetspeed.page.impl.DatabasePageManager"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/page-manager-repository.xml</value> </constructor-arg> <!-- fragment id generator --> <constructor-arg index="1"> <ref bean="IdGenerator"/> </constructor-arg> …… </bean>
2.Bean過濾器
JetspeedBeanDefinitionFilter在Spring容器解析每個Bean定義時,會取出上面Bean配置中j2:cat對應的值,例如dbPageManageror pageSerializer。然后將這部分作為正則表達式與當前的Key(從配置文件中讀出)進行匹配。只有匹配上的Bean,才會被Spring容器創(chuàng)建出來。
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
<bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean"> <meta key="j2:cat"value="xmlPageManager" /> <meta key="j2:alias"value="org.apache.jetspeed.page.PageManager" /> <propertyname="targetBeanName" value="xmlPageManager" /> </bean> <bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean"> <meta key="j2:cat"value="dbPageManager" /> <meta key="j2:alias"value="org.apache.jetspeed.page.PageManager" /> <propertyname="targetBeanName" value="dbPageManager" /> </bean> <bean id="org.apache.jetspeed.layout.PageLayoutComponent" class="org.apache.jetspeed.layout.impl.PageLayoutComponentImpl"> <meta key="j2:cat"value="default" /> <constructor-arg index="0"> <refbean="org.apache.jetspeed.page.PageManager" /> </constructor-arg> <constructor-arg index="1"> <value>jetspeed-layouts::VelocityOneColumn</value> </constructor-arg> </bean>
相關文章
SpringBoot整合Flyway的方法(數據庫版本遷移工具)
這篇文章主要介紹了SpringBoot整合Flyway的方法(數據庫版本遷移工具),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
shuffle的關鍵階段sort(Map端和Reduce端)源碼分析
今天小編就為大家分享一篇關于shuffle的關鍵階段sort(Map端和Reduce端)源碼分析,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01

