* Spring 事务管理
* 前置知识事务
* 前期表的准备
* 业务需要
学生转班:小白从1001班转到到1002班
思路:查询小白学生是否在这个班1001班里,更新小白的班级号,更新班级号的人数
要么一起成功,要么一起失败
* JDBC 处理事务
Connection的三个方法与事务有关:
setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置为false,那么相当于开启了事务了;con.setAutoCommit(false) 表示开启事务。
commit():提交结束事务。
rollback():回滚结束事务。
Connection.TRANSACTION_READ_UNCOMMITTED;
Connection.TRANSACTION_READ_COMMITTED;
Connection.TRANSACTION_REPEATABLE_READ;
Connection.TRANSACTION_READ_SERIALIZABLE。
try{
con.setAutoCommit(false);//开启事务
......
con.commit();//try的最后提交事务
} catch() {
con.rollback();//回滚事务
}
BEGIN;
UPDATE student SET cno=1002 WHERE sname='小白';
UPDATE classes SET cnum=cnum-1 WHERE cno=1001;
UPDATE classes SET cnum=cnum+1 WHERE cno=1002;
COMMIT;
SELECT * FROM student;
SELECT * FROM classes;
* 把数据回顾前期准备状态
public void test2() {
Connection connection = null;
PreparedStatement pst = null;
ResultSet rs = null;
// 用jdbc连接数据库获取数据
try {
// 1 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2 通过驱动类获取的数据库的链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/hx01?characterEncoding=utf-8",
"root", "root");
// 开启事务
connection.setAutoCommit(false);
// 查询是否存在此学生
String sql = "SELECT COUNT(*) FROM student WHERE sname=? AND cno=?";
// 4 获取预处理的Statement
pst = connection.prepareStatement(sql);
pst.setString(1, "小白");
pst.setInt(2, 1001);
rs = pst.executeQuery();
if (rs != null ) {
if(rs.next()){
int count = rs.getInt(1);
pst.close();
if(count>0){
// 3 定义SQL语句
sql = "UPDATE student SET cno=? WHERE sname=? ";
pst = connection.prepareStatement(sql);
pst.setInt(1, 1002);
pst.setString(2, "小白");
pst.executeUpdate();
pst.close();
sql = "UPDATE classes SET cnum=cnum-1 WHERE cno=?";
pst = connection.prepareStatement(sql);
pst.setInt(1, 1001);
pst.executeUpdate();
pst.close();
sql = "UPDATE classes SET cnum=cnum+1 WHERE cno=?";
pst = connection.prepareStatement(sql);
pst.setInt(1, 1002);
pst.executeUpdate();
pst.close();
// 提交事务
connection.commit();
}
}
}
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 8 释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pst != null) {
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
* Spring 事务管理
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA或者MyBatis等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate,MyBatis等都提供了对应的事务管理器,
* Spring 事务管理的API
* PlatformTransactionManager接口
* 平台事务管理器.(真正管理事务的类)。该接口有具体的实现类,根据不同的持久层框架,需要选择不同的实现类!
* 接口的实现类
*如果使用的Spring的JDBC模板或者MyBatis框架,需要选择DataSourceTransactionManager实现类
* 如果使用的是Hibernate的框架,需要选择HibernateTransactionManager实现类
* 该接口的常用方法
* void commit(TransactionStatus status)
* TransactionStatus getTransaction(TransactionDefinition definition)
* void rollback(TransactionStatus status)
* TransactionDefinition接口
1. 事务隔离级别的常量
* static int ISOLATION_DEFAULT -- 采用数据库的默认隔离级别
* static int ISOLATION_READ_UNCOMMITTED
* static int ISOLATION_READ_COMMITTED
* static int ISOLATION_REPEATABLE_READ
* static int ISOLATION_SERIALIZABLE
2. 事务的传播行为常量(通常不用设置,使用默认值即可)
* 什么是事务的传播行为:解决的是业务层之间的方法调用!!
* A--->B
* PROPAGATION_REQUIRED(默认值) -- A中有事务,使用A中的事务.如果没有,B就会开启一个新的事务,将A包含进来.(保证A,B在同一个事务中),默认值!!
* PROPAGATION_SUPPORTS -- A中有事务,使用A中的事务.如果A中没有事务.那么B也不使用事务.
* PROPAGATION_MANDATORY -- A中有事务,使用A中的事务.如果A没有事务.抛出异常.
* PROPAGATION_REQUIRES_NEW(记)-- A中有事务,将A中的事务挂起.B创建一个新的事务.(保证A,B没有在一个事务中)
* PROPAGATION_NOT_SUPPORTED -- A中有事务,将A中的事务挂起.
* PROPAGATION_NEVER -- A中有事务,抛出异常.
* PROPAGATION_NESTED(记) -- 嵌套事务.当A执行之后,就会在这个位置设置一个保存点.如果B没有问题.执行通过.如果B出现异常,运行客户根据需求回滚(选择回滚到保存点或者是最初始状态)
* 只读
事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
* 事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
* 回滚规则
些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚,
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
* 参考事务传播行为特点
* TransactionStatus
可以发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。
* Spring事务管理的实现方式
* 事务管理开发
* 基础环境的搭建
* copy Student,Teacher,Classes的实体类
* 配置了JdbcTemplate环境基础(同上)
public interface StudentDao {
public void sayHello();
public Student queryStudentByName(String name,int cno);
public void updateStudent(Student student);
}
@Repository
public class StudentDaoImpl implements StudentDao {
@Autowired
private JdbcTemplate jd;
public JdbcTemplate getJd() {
return jd;
}
public void setJd(JdbcTemplate jd) {
this.jd = jd;
}
@Override
public void sayHello() {
System.out.println("student");
}
@Override
public Student queryStudentByName(String name,int cno) {
final String sql="SELECT sid,cno,sname,ssex,sage,colleage FROM student WHERE sname=? AND cno=? ";
Object[] params={name,cno};
Student student = jd.queryForObject(sql, params, new RowMapper<Student>() {
@Override
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student student=new Student();
student.setSid(rs.getInt(1));
student.setCno(rs.getInt(2));
student.setSname(rs.getString(3));
student.setSsex(rs.getString(4).charAt(0));
student.setSage(rs.getInt(5));
student.setColleage(rs.getString(6));
return student;
}
});
return student;
}
@Override
public void updateStudent(Student student) {
String sql="UPDATE student SET cno=? WHERE sname=?";
Object[] params={student.getCno(),student.getSname()};
jd.update(sql,params);
}
}
public interface ClassessDao {
/**
* isAdd:不是增加学生就是减少学生,true为增加学生,false为减少学生
* @param cno
* @param isAdd
*/
public void updateClasses(int cno,boolean isAdd);
}
@Repository
public class ClassesDaoImpl implements ClassessDao {
@Autowired
private JdbcTemplate jd;
public JdbcTemplate getJd() {
return jd;
}
public void setJd(JdbcTemplate jd) {
this.jd = jd;
}
@Override
public void updateClasses(int cno, boolean isAdd) {
String sqlAdd="UPDATE classes SET cnum=cnum+1 WHERE cno=?";
String sqlMinus="UPDATE classes SET cnum=cnum-1 WHERE cno=?";
String sql=isAdd?sqlAdd:sqlMinus;
jd.update(sql,cno);
}
}
public interface TransferService {
public boolean transfer(String name,int oldCno,int newCno);
}
@Service("transfer")
public class TransferServiceImpl implements TransferService {
@Autowired
private ClassessDao mClassessDao;
@Autowired
private StudentDao mStudentDao;
@Override
public boolean transfer(String name, int oldCno, int newCno) {
Student student = mStudentDao.queryStudentByName(name, oldCno);
if(student!=null){
student.setCno(newCno);
// 更新学生的班级信息
mStudentDao.updateStudent(student);
// 班级人数少1
mClassessDao.updateClasses(oldCno,false);
// 班级人数加1
mClassessDao.updateClasses(newCno,true);
return true;
}
return false;
}
}
@Autowired
private TransferService mTransferService;
@Test
public void test4(){
mTransferService.transfer("小白",1001,1002);
}
* 数据恢复准备前
Spring事务管理的实现方式有两种:
1)Spring的编程式事务管理(不推荐使用,侵入性太高)
2)Spring的声明式事务管理(推荐,底层采用AOP技术)
又分为XML声明式事务 和 注解声明式事务!
思考事务是加在dao层还是加载service层里好?
* 编程式事务开发
* 数据恢复准备前
* 添加事务命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1.配置平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="c3p0"></property>
</bean>
<!-- 2.配置事务管理的模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
@Service("transfer")
public class TransferServiceImpl implements TransferService {
@Autowired
private ClassessDao mClassessDao;
@Autowired
private StudentDao mStudentDao;
@Autowired
private TransactionTemplate mTransactionTemplate;
@Override
public boolean transfer(final String name, final int oldCno, final int newCno) {
mTransactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
Student student =null;
try {
student = mStudentDao.queryStudentByName(name, oldCno);
}catch(EmptyResultDataAccessException e) {
}
if(student!=null){
student.setCno(newCno);
// 更新学生的班级信息
mStudentDao.updateStudent(student);
// 班级人数少1
mClassessDao.updateClasses(oldCno,false);
// 模拟异常
int i=100/0;
// 班级人数加1
mClassessDao.updateClasses(newCno,true);
}
};
});
return true;
}
}
@Test
public void test4(){
boolean isTransfer = mTransferService.transfer("小白", 1001, 1002);
System.out.println(isTransfer);
}
* 声明式事务开发
* AOP
* 恢复之前的环境
* xml
!-- 1.配置平台事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="c3p0"></property>
</bean>
<tx:advice id="txAspect" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAspect" pointcut="execution(* com.hx.hx02.service.TransferServiceImpl.transfer(..))"/>
</aop:config>
name :绑定事务的方法名,可以使用通配符,可以配置多个。
propagation :传播行为
isolation :隔离级别
read-only :是否只读
timeout :超时信息
rollback-for:发生哪些异常回滚.
no-rollback-for:发生哪些异常不回滚.
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="c3p0"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
@Service("transfer")
@Transactional
public class TransferServiceImpl implements TransferService {
@Autowired
private ClassessDao mClassessDao;
@Autowired
private StudentDao mStudentDao;
@Override
public boolean transfer(String name, int oldCno, int newCno) {
Student student =null;
try {
student = mStudentDao.queryStudentByName(name, oldCno);
}catch (EmptyResultDataAccessException e){
}
if(student!=null){
student.setCno(newCno);
// 更新学生的班级信息
mStudentDao.updateStudent(student);
// 班级人数少1
mClassessDao.updateClasses(oldCno,false);
int i=100/0;
// 班级人数加1
mClassessDao.updateClasses(newCno,true);
return true;
}
return false;
}
}