事务的提出是为了保证一组操作的原子性,让这组操作要么全部成功,要么全部不成功,不成功的话,所有操作回滚到事务开始之前的状态。这在很多地方都讲的很清楚了。mysql本身提供了事务,jdbc也提供了事务,hibernate,mybatis这样的ORM框架也提供了事务机制。
值得注意的是,mysql的事务编程依赖于关键字(begin,commit,rollback);jdbc的事务编程依赖于connection连接对象(connection.setAutoCommit(false),connection.commit,connection.rollback);hibernate的事务机制依赖于session对象(transaction=session.beginTransaction(),transaction.commit(),transaction.rollback())。也就是说,不管是数据库本身也好,还是数据库封装框架也好,关于事务的处理都有自己的一套体系,各自为政。
更为严重的是,这些事务的操作和业务逻辑代码纠缠在一起,每段业务代码前后都要加上事务操作。实际使用中,如果不能通过合适的方式将事务管理的代码和业务逻辑代码进行逻辑上的隔离,将直接导致业务代码和事务代码的可重用性降低。
Spring的事务管理机制提供了更高层次的抽象来帮助我们隔离事务和业务逻辑两方面的紧耦合。Spring框架已经实现了大多数关于数据库事务的操作(如jdbc,hibernate等),只需要配置事务bean对象,然后使用Spring的AOP编程模式将事务“切入”到业务逻辑代码。这样,既完美解耦,又整洁代码。
一,Spring事务的3个主要接口
其中,PlatformTransactionManager是用来生成事务的,其他两个是为它服务的。
PlatformTransactionManager接口有很多实现类,这些实现类就是具体的数据库操作啦,常见的有下面2种。
二,经典的转账案例
该案例使用jdbc编程,并使用DataSourceTransactionManager生成事务。
1,dao和service类
package com.jimmy.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class Dao extends JdbcDaoSupport{
public void out(String name, double money) {
this.getJdbcTemplate().update("update account set money=money-? where username=?", money,name);
}
public void in(String name, double money) {
this.getJdbcTemplate().update("update account set money=money+? where username=?", money,name);
}
}
package com.jimmy.service;
import com.jimmy.dao.Dao;
public class Service {
private Dao dao;
public void setDao(Dao dao) {
this.dao = dao;
}
public void zhuanzhang(String outer,String inner,double money) {
dao.out(outer, money);
int i=1/0; // 模拟转账过程中出现异常
dao.in(inner, money);
}
}
2,写配置文件
配置业务对象,依赖注入,配置事务对象,AOP操作都在配置文件中体现。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSourceId" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.DriverClass}">property>
<property name="jdbcUrl" value="${jdbc.URL}">property>
<property name="user" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
<bean id="jdbcTemplateId" class="com.jimmy.dao.Dao">
<property name="dataSource" ref="dataSourceId">property>
bean>
<bean id="serviceId" class="com.jimmy.service.Service">
<property name="dao" ref="jdbcTemplateId">property>
bean>
<aop:config>
<aop:pointcut expression="execution(* com.jimmy.service.Service.*(..))" id="pointcutId"/>
<aop:advisor advice-ref="txAdviceId" pointcut-ref="pointcutId"/>
aop:config>
<bean id="txManagerId" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceId">property>
bean>
<tx:advice id="txAdviceId" transaction-manager="txManagerId">
<tx:attributes>
<tx:method name="zhuanzhang" isolation="DEFAULT" propagation="REQUIRED" timeout="20"/>
tx:attributes>
tx:advice>
beans>
db.properties文件
jdbc.DriverClass=com.mysql.jdbc.Driver
jdbc.URL=jdbc:mysql:///user
jdbc.username=root
jdbc.password=123456
3,测试
package com.jimmy.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jimmy.service.Service;
public class Test1 {
@Test
public void test1(){
String xmlpath = "classpath:applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlpath);
Service service = (Service) applicationContext.getBean("serviceId");
service.zhuanzhang("jimmy", "angela", 100);
}
}
上面代码运行会报错(除0了),但是数据库的数据不会发生改变,因为我们对转账函数加了事务。回过头来看我们的业务逻辑代码,干干净净,清清楚楚。不像以前一样,事务代码放到业务代码前后,整个既紧耦合,又不利于维护。
Spring事务机制把事务从业务代码中分离出来,这对于编码非常有帮助。当然了,Spring事务机制不仅提供了jdbc的事务分离,还提供了hibernate等的事务分离,我们以后再说。