#1 系列目录spring
#2 三种事务模型sql
三种事务模型以下:数据库
先来看几个例子:express
案例1:编程
Connection conn=jdbcDao.getConnection(); PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)"); ps.setString(1,user.getName()); ps.setInt(2,user.getAge()); ps.execute();
案例2:微信
Connection conn=jdbcDao.getConnection(); conn.setAutoCommit(false); try { PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)"); ps.setString(1,user.getName()); ps.setInt(2,user.getAge()); ps.execute(); conn.commit(); } catch (Exception e) { e.printStackTrace(); conn.rollback(); }finally{ conn.close(); }
案例3:session
InitialContext ctx = new InitialContext(); UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction"); try { txn.begin(); //业务代码 txn.commit(); } catch (Exception up) { txn.rollback(); throw up; }
案例4:框架
@Transactional public void save(User user){ jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge()); }
个人见解:分布式
我认为他们各自的特色在于:谁在管理着事务的提交和回滚等操做?ide
这里有三个角色:数据库、开发人员、spring(等第三方)
上述的特色也不是所有合理,以下文提到的Spring的TransactionTemplate,虽然属于编程式事务,可是它的确是把事务的提交和回滚交给了Spring来管理。总之不用过度纠结于划分事务模型
#3 编程式事务
编程式事务:即经过手动编程方式来实现事务操做,大部分状况,都是相似于上述案例二、3状况,开发人员来管理事务的提交和回滚,但也多是Spring本身来管理事务,如Spring的TransactionTemplate。
##3.1 Spring的TransactionTemplate
在前一篇文章中了解到,使用jdbc操做事务,编程很是麻烦,总是须要写一套模板式的try catch代码,因此咱们能够将try catch代码封装成一个模板,这就引出了Spring的TransactionTemplate:
案例以下:
TransactionTemplate template=new TransactionTemplate(); template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); template.setTransactionManager(transactionManager); template.execute(new TransactionCallback<User>() { @Override public User doInTransaction(TransactionStatus status) { //可使用DataSourceUtils获取Connection来执行sql //jdbcTemplate.update(sql2); //可使用SessionFactory的getCurrentSession获取Session来执行 //hibernateTemplate.save(user1) return null; } });
TransactionTemplate继承了DefaultTransactionDefinition,有了默认的事务定义,也能够自定义设置隔离级别、传播属性等
TransactionTemplate须要一个PlatformTransactionManager事务管理器,来执行事务的操做
TransactionTemplate在TransactionCallback中执行业务代码,try catch的事务模板代码,则被封装起来,包裹在业务代码的周围,详细见TransactionTemplate的execute方法,以下:
详细过程以下:
第一步:根据事务定义获取事务
因为TransactionTemplate继承了DefaultTransactionDefinition,因此使用PlatformTransactionManager事务管理器来根据TransactionTemplate来获取事务,这部分就是上一篇文章的内容了,见分布式事务系列(1.1)Spring事务管理器PlatformTransactionManager源码分析
第二步:执行业务代码
在TransactionCallback中的doInTransaction中执行相应的业务代码。
若是使用的是DataSourceTransactionManager,你就可使用JdbcTemplate来执行业务逻辑;或者直接使用Connection,可是必须使用DataSourceUtils来获取Connection
若是使用的是HibernateTransactionManager,就可使用HibernateTemplate来执行业务逻辑,或者则可使用SessionFactory的getCurrentSession方法来获取当前线程绑定的Session,不可以使用SessionFactory的openSession方法。也是不可乱用的,下面详细解释
第三步:若是业务代码出现异常,则回滚事务,没有异常则提交事务
回滚与提交都是经过PlatformTransactionManager事务管理器来进行的
##3.2 事务代码和业务代码能够实现分离的原理
咱们能够看到,使用TransactionTemplate,其实就作到了事务代码和业务代码的分离,分离以后的代价就是,必须保证他们使用的是同一类型事务。以后的声明式事务实现分离也是一样的原理,这里就提早说明一下。
1 若是使用DataSourceTransactionManager:
jdbcTemplate在执行sql时,会使用DataSourceUtils从dataSource中获取一个Connection。获取过程以下:
也是先获取和当前线程绑定的ConnectionHolder(因为事务在执行业务逻辑前已经开启,已经有了和当前线程绑定的ConnectionHolder),因此会获取到和事务中使用的ConnectionHolder,这样就保证了他们使用的是同一个Connection了,天然就能够正常提交和回滚了
若是想使用Connection,则须要使用DataSourceUtils从dataSorce中获取Connection,不能直接从dataSource中获取Connection。
2 若是使用HibernateTransactionManager:
HibernateTemplate在执行save(user)的过程当中,会获取一个Session,方式以下:
session = getSessionFactory().getCurrentSession();
即采用SessionFactory的自带的getCurrentSession方法,获取当前Session。具体什么策略呢?
Hibernate定义了这样的一个接口:CurrentSessionContext,内容以下:
public interface CurrentSessionContext extends Serializable { public Session currentSession() throws HibernateException; }
上述SessionFactory获取当前Session就是依靠CurrentSessionContext的实现。它的实现以下:
在spring环境下,默认采用的是SpringSessionContext,它获取当前Session的方式以下:
也是先获取和当前线程绑定的SessionHolder(因为事务在执行业务逻辑前已经开启,已经有了和当前线程绑定的SessionHolder),因此会获取到和事务中使用的SessionHolder,这样就保证了他们使用的是同一个Session了,天然就能够正常提交和回滚了。
若是不想经过使用HibernateTemplate,想直接经过Session来操做,同理则须要使用SessionFactory的getCurrentSession方法来获取Session,而不能使用SessionFactory的openSession方法。
以上分析都是基于Hibernate4,Hibenrate3的原理自行去看,都差很少。
#4 Spring的声明式事务
Spring能够有三种形式来配置事务拦截,不一样配置形式仅仅是外在形式不一样,里面的拦截原理都是同样的,因此先经过一个小例子了解利用AOP实现事务拦截的原理
##4.1 利用AOP实现声明式事务的原理
###4.1.1 简单的AOP事务例子
从上面的Spring的TransactionTemplate咱们能够实现了,事务代码和业务代码的分离,可是分离的还不完全,仍是须要以下的代码:
TransactionTemplate template=new TransactionTemplate(); template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); template.setTransactionManager(transactionManager);
一大堆的设置,同时业务代码必须嵌套在TransactionCallback中,如何才能作到以下方式的完全分离呢?
@Transactional public void save(User user){ jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge()); }
这就须要使用Spring的AOP机制,SpringAOP的原理就再也不详细说明了,能够参考这里的源码分析Spring AOP源码分析,这里给出一个简单的AOP事务拦截原理的小例子:
@Repository public class AopUserDao implements InitializingBean{ @Autowired private UserDao userDao; private UserDao proxyUserDao; @Resource(name="transactionManager") private PlatformTransactionManager transactionManager; @Override public void afterPropertiesSet() throws Exception { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(userDao); TransactionInterceptor transactionInterceptor=new TransactionInterceptor(); transactionInterceptor.setTransactionManager(transactionManager); Properties properties=new Properties(); properties.setProperty("*","PROPAGATION_REQUIRED"); transactionInterceptor.setTransactionAttributes(properties); proxyFactory.addAdvice(transactionInterceptor); proxyUserDao=(UserDao) proxyFactory.getProxy(); } public void save(User user){ proxyUserDao.save(user); } }
代码分析以下:
首先须要一个原始的UserDao,咱们须要对它进行AOP代理,产生代理对象proxyUserDao,以后保存的功能就是使用proxyUserDao来执行
对UserDao具体的代理过程以下:
使用代理工厂,设置要代理的对象,即target
proxyFactory.setTarget(userDao);
对代理对象加入拦截器
分红2种状况,一种默认拦截原UserDao的全部方法,一种是指定Pointcut,即拦截原UserDao的某些方法。
这里使用proxyFactory.addAdvice(transactionInterceptor);就表示默认拦截原UserDao的全部方法。
若是使用proxyFactory.addAdvisor(advisor),这里的Advisor能够简单当作是Pointcut和Advice的组合,Pointcut则是用于指定是否拦截某些方法。
上述addAdvice就是使用了默认的Pointcut,表示对全部方法都拦截,源码以下:
addAdvisor(pos, new DefaultPointcutAdvisor(advice));
DefaultPointcutAdvisor内容以下:
public DefaultPointcutAdvisor(Advice advice) { this(Pointcut.TRUE, advice); }
Pointcut.TRUE便表示拦截全部方法。
以后再详细讲解这个事务拦截器。
设置好代理工厂要代理的对象和拦截器后,即可以建立代理对象。
proxyUserDao=(UserDao) proxyFactory.getProxy()
以后,咱们在使用建立出的proxyUserDao时,就会首先进入拦截器,执行相关拦截器代码,所以咱们能够在这里实现事务的处理
###4.1.2 事务拦截器的原理分析
事务拦截器须要2个参数:
事务配置的提供者
用于指定哪些方法具备什么样的事务配置
能够经过属性配置方式,或者经过其余一些配置方式,以下三种方式都是为了获取事务配置提供者:
方式1:
<property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property>
方式2:
<tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> </tx:attributes>
方式3:
@Transactional(propagation=Propagation.REQUIRED)
事务管理器PlatformTransactionManager
有了事务的配置,咱们就能够经过事务管理器来获取事务了。
在执行代理proxyUserDao的save(user)方法时,会先进入事务拦截器中,具体的拦截代码以下:
一个事务拦截器的内容大体就是这样。
##4.2 Spring的三种事务配置形式
###4.2.1 使用TransactionProxyFactoryBean
配置案例以下:
<bean id="proxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 为事务代理工厂Bean注入事务管理器 --> <property name="transactionManager" ref="transactionManager" /> <!-- 要在哪一个Bean上面建立事务代理对象 --> <property name="target" ref="productDao" /> <!-- 指定事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
案例分析:
上面有三大配置:
事务管理器transactionManager
事务配置的提供者transactionAttributes(用于指定哪些方法具备什么样的事务配置)
有了以上2个元素,咱们就能够建立出一个事务拦截器TransactionInterceptor
要代理的对象target
TransactionProxyFactoryBean这个工厂bean建立代理对象的原理就是:经过ProxyFactory来对target建立出代理对象了,同时加入上述事务拦截器,就能够实现事务拦截功能了
###4.2.2 使用aop:config和tx:advice
使用TransactionProxyFactoryBean的方式只能针对一个target进行代理,若是想再代理一个target,就须要再配置一个TransactionProxyFactoryBean,比较麻烦,因此使用apo:config的配置形式,就能够针对符合Pointcut的全部target均可以进行代理。
配置案例以下:
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="pointcut" expression="XXXX" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" /> </aop:config>
这里有2种配置:
tx:advice:
有事务管理器transactionManager和事务配置提供者attributes,就能够产生一个事务拦截器TransactionInterceptor
aop:config:
这里会对符合pointcut的bean建立出代理对象,同时加入上述建立的事务拦截器
###4.2.3 使用@Transactional
使用aop:config能够在xml中进行代理的配置,有时候想在代码中直接进行配置,这时候就须要使用注解@Transactional。
案例以下:
xml中启动@Transactional注解扫描
<tx:annotation-driven transaction-manager="transactionManager" />
在代码中就能够经过配置@Transactional来实现事务拦截了
@Transactional(propagation=Propagation.REQUIRED) public void save(User user){ xxxx }
在xml配置中启动注解扫描,会把那些加入了@Transactional标记的容器bean建立出代理对象,同时加入事务拦截器。在执行事务拦截的时候,会从@Transactional注解中取出对应的事务配置和事务管理器配置,进而能够执行事务的建立等操做。
#5 结束语
至此Spring的事务体系大概就讲完了,接下来就要说明如何实现分布式事务了,在涉及jotm、atomikos这些第三方框架以前,先要了解下,分布式事务的一些概念
欢迎关注微信公众号:乒乓狂魔