一、事务基本概念
- 事务是一系列操作组成的工作单元,该工作单元的操作是不可分割的,要么所有操作都完成,要么所有操作都不做。它必须满足ACID特性。即原子性、一致性、隔离性、持久性。
- 原子性(atomicity):事务是不可分割的最小工作单元,事务要么全做,要么全不做
- 一致性(consistency):一旦事务完成(不过成功与否),系统必须确保它所建模的业务处于一致的状态,显示的数据不应该被破坏。
- 隔离性(isolation):并发事务不影响,在一个事务内部的操作对其他事务不产生影响,这需要事务隔离级别来指定隔离性。
- 持久性(durable):事务一旦执行成功,塔对数据库数据的改变必须是永久的。
二、Spring事务管理抽象
- Spring事务管理的抽象层主要有三个接口,分别是PlatformTransactionManager、TransactionDefinition、TransactionStatus。
- TransactionDefinition:用于描述事务的隔离级别、超时时间、是否只读、传播行为等控制事务具体行为的具体属性。
- PlatformTransactionManager:根据TransactionDefinition提供的事务属性信息创建事务
- TransactionStatus:描述事务的状态。
三、事务管理器
- Spring并不直接管理事务,而是提供了多种事务管理器,它将事务管理的责任委托给JTA或者其他持久化机制所提供的平台相关的事务实现。
- DataSourceTransactionManager:位于org.springframework.jdbc.datasource,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架,ibatis或MyBatis框架的事务管理。
<!-- 事务管理器,dataSource是数据源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- HibernateTransactionManage:位于org.springframework.orm.hibernate,提供对单个org.hibernate.sessionFactory事务支持,用于集成hibernate框架是的事务管理,该事务管理器只支持hibernate3以上的版本。
<!-- 事务管理器,sessionFactory是hibernate的session工厂 -->
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
- JpaTransactionManager:位于org.springframework.orm.jpa,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理。
<!-- 事务管理器,entityManagerFactory指定需要事务管理的javax.persistence.Entity.ManagerFactory对象 -->
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
- JtaTransactionManager:位于org.springframework.transaction.jta,提供了分布式事务管理的支持,并将事务管理委托给java EE 应用服务器管理器。
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
四、声明式事务(通过AOP实现)
1、定义事务的属性
- 传播行为:定义了客户端与调用方法直接的事务边界
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
PROPAGATION_MANDATORY | 事务当前事务,如果当前没有事务,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,则吧当前事务挂起 |
PROPAGATION_NO_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
- 隔离级别:定义了一个事务受其他事务影响的程度
隔离级别 | 说明 |
ISOLATION_DEFAULT | 使用数据库默认的事务隔离级别(mysql默认是ISOLATION_REPEATABLE_READ;oracle、sql server默认的是)ISOLATION_READ_COMMITTED |
ISOLATION_READ_UNCOMMITTED | A事务可以看到B事务未提交的数据,该隔离级别会产生脏读、不可重复读和幻读 |
ISOLATION_READ_COMMITTED | 保证A事务不能读取B事务未提交的数据,避免了脏读,但可能出现不可重复读和幻读 |
ISOLATION_REPEATABLE_READ | 保证A事务不能读取B事务未提交的数据,也避免了脏读和不可重复读,但可能出现幻读 |
ISOLATION_SERIALIZABLE | 最可靠的隔离级别,避免了脏读、不可重复读、幻读 |
注:(1、脏读:A事务读取了B事务尚未提交的数据;2、不可重复读:A事务读取了B事务提交的修改数据;3、幻读:A事务读取了B事务提交的新增数据)
- 是否只读:设置只读数据库,可以对一些操作进行优化
- 事务超时:限定事务的时间
- 回滚原则:设置事务在遇到哪些异常时回滚
2、基于xml的声明式事务(采用Spring JDBC模板)
- 创建userDao,实现JdbcDaoSupport
public class UserDao extends JdbcDaoSupport {
//保存用户
public void saveUser(String userName) throws Exception{
//创建sql
String sql = "insert into user(userName) values(?)";
//执行sql
this.getJdbcTemplate().update(sql,new Object[]{userName});
}
//获取用户
public User getUser(long id)throws Exception{
final User user = new User();
//创建sql
String sql = "select * from user where id=?";
//执行sql
this.getJdbcTemplate().query(sql, new Object[]{id}, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getLong("id"));
user.setUserName(rs.getString("username"));
}
});
return user;
}
}
注:User类有id和userName两变量,数据库也有id和userName两列
- Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.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">
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 为spring jdbcTemplate注入数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--传播行为:save开头的方法都需要支持事务 -->
<tx:method name="save*" propagation="REQUIRED"/>
<!-- 隔离级别:save开头的方法隔离级别都为SERIALIZABLE -->
<tx:method name="save*" isolation="SERIALIZABLE"/>
<!-- 是否只读:get开头的方法都是只读-->
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务通知器 -->
<aop:config>
<!-- 定义切面 -->
<aop:pointcut id="txPointcut" expression="execution(* com.chensr.test.UserDao.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- 定义userDao,并注入jdbcTemplate目标 -->
<bean id="userDao" class="com.chensr.test.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
注: 事务配置中
- <tx:advice>:事务通知定义,用于指导事务属性,其中transaction-manager属性指定事务管理器。
- <tx:attributes>:指定具体拦截的方法。
- <tx:method>:用name属性指定方法,比如name=”save*”表示拦截以save开头的方法。其中method还有五个属性,propatation表示传播行为、isolation表示隔离级别、read-only表示是否只读、timeout表示事物超时、no-rollbace-for表示回滚原则。
- 创建测试类
public class Test {
@org.junit.Test
public void test() throws Exception{
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-context.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.saveUser("操蛋的特朗普");
User user = userDao.getUser(1l);
System.out.println(user.toString());
}
}
运行结果:数据库中多了一条username为“操蛋的特朗普”的数据,并且控制台打印
User{id=1, userName='操蛋的特朗普'}
- 事务测试,修改save(添加int error =1/0),让程序抛异常
//保存用户
public void saveUser(String userName) throws Exception{
//创建sql
String sql = "insert into user(userName) values(?)";
//执行sql
this.getJdbcTemplate().update(sql,new Object[]{userName});
int error = 1/0;
}
- 运行测试类,会看到控制器报异常,并且数据库并没有添加任何数据(如果没进行事务处理,控制器报异常,但数据库会添加一条数据)。
3、基于注解的声明式事务
- 基于注解的声明式事务不需要在配置文件中配置事务和通知器。只需开启注解<ontent:component-scan/>,开启基于注解的事务管理<tx:annotation-driven transaction-manager="txManager"/>,然后在需要事务管理的bean上加入@Transactional即可。
- 修改配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解 -->
<content:component-scan base-package="com.chensr">
<content:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</content:component-scan>
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 为spring jdbcTemplate注入数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启基于注解的事务 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
- 修改userDao
@Transactional(readOnly = true)
@Repository("userDao")
public class UserDao extends JdbcDaoSupport {
@Autowired
public UserDao(JdbcTemplate jdbcTemplate) {
super.setJdbcTemplate(jdbcTemplate);
}
//保存用户
@Transactional(readOnly = false,isolation = Isolation.SERIALIZABLE)
public void saveUser(String userName) throws Exception{
//创建sql
String sql = "insert into user(userName) values(?)";
//执行sql
this.getJdbcTemplate().update(sql,new Object[]{userName});
}
//获取用户
public User getUser(long id)throws Exception{
final User user = new User();
//创建sql
String sql = "select * from user where id=?";
//执行sql
this.getJdbcTemplate().query(sql, new Object[]{id}, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getLong("id"));
user.setUserName(rs.getString("username"));
}
});
return user;
}
}
注:由于JdbcTemplate是继承于JdbcDaoSupport得来的,所以在注入JdbcTemplate时,需要向父类注入super.setJdbcTemplate(jdbcTemplate)
- 运行测试类,测试结果与上面例子一致
五、编程式事务
- 所谓的编程式事务就是以编码方式实现事务,在配置文件中配置事务管理器,并将配置管理器注入相应的bean中。
- 配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!-- 配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!-- 为spring jdbcTemplate注入数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 为userDao注入jdbc模板和事务管理器 -->
<bean id="userDao" class="com.chensr.test.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
<property name="transactionTemplate" ref="txManager"/>
</bean>
</beans>
- 编写UserDao
public class UserDao extends JdbcDaoSupport {
private TransactionTemplate transactionTemplate;
//保存用户
@Transactional(readOnly = false,isolation = Isolation.SERIALIZABLE)
public void saveUser(final String userName) throws Exception{
//设置事务
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setReadOnly(false);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//创建sql
String sql = "insert into user(userName) values(?)";
//执行sql
getJdbcTemplate().update(sql,new Object[]{userName});
}
});
}
//获取用户
public User getUser(final long id)throws Exception{
//设置事务
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
transactionTemplate.setReadOnly(true);
//返回查询到的数据
return transactionTemplate.execute(new TransactionCallback<User>() {
@Override
public User doInTransaction(TransactionStatus status) {
final User user = new User();
//创建sql
String sql = "select * from user where id=?";
//执行sql
getJdbcTemplate().query(sql, new Object[]{id}, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getLong("id"));
user.setUserName(rs.getString("username"));
}
});
return user;
}
});
}
//注入事务管理器
public void setTransactionTemplate(PlatformTransactionManager transactionTemplate) {
this.transactionTemplate = new TransactionTemplate(transactionTemplate) ;
}
}
注:TransactionCallbackWithoutResult用于没有返回结果的事务管理,TransactionCallback<Object>用于有返回结果的事务管理
- 测试类
public class Test {
@org.junit.Test
public void test() throws Exception{
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-context.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.saveUser("操蛋的特朗普");
User user = userDao.getUser(1l);
System.out.println(user.toString());
}
}
执行结果与上面例子一致
注:相互嵌套的方法不在同一线程执行,不同线程的事务是独立的