事务:程序为了保证业务处理的完整性,执行的一个或多个SQL语句
管理:对事务中的SQL语句进行提交或回滚,叫做事务管理
什么时候使用?主要作用于Service的方法上
事务回顾:
--oracle:commit/rollback DML操作
--JDBC:自动commit
例如:转账:A账户转1000到B账户
A账户减少1000(update)
B账户增加1000(update)
设置:conn.setAutoCommit(false),最后commit/rollback
-------------------------------------------------------------------------------
使用案例:
public void someServiceMethod(){
try{
//开启事务
service.shareNote()
//事务提交
}catch{
//异常回滚
}
}
可以通过spring进行对事务的管理,不用对每一service进行修改,通过AOP进行管理(项目中每一个Dao都是一个事务)
-------------------------------------------------------------------------------
步骤:
1.配置Spring-transaction.xml
2.使用在Service层@Transactional标记
-------------------------------------------------------------------------------
可读可写:
service层中的SQL语句为select
@Transactional(readOnly=true) 设置为只读,在性能上有一定帮助
回滚特性:
默认RuntimeException回滚,其他异常不回滚,若要回滚其它异常则@Transactional(rollbackFor=IOException)
public void f1(){
//DB操作(insert)
//IOException
}
传播特性:
默认类型:REQUIRED
REQUIRED--支持当前事务,如果没有当前事务,就新建一个事务,这时最常见的选择
SUPPORTS--支持当前事务,如果没有当前事务,就以非事务方式执行
MANDATORY--支持当前事务,如果没有当前事务,就抛出异常
例如:
@Transactional
public void f1(){
//业务代码A
f2();
//若使用默认类型:REQUIRED,当f2()方法报错,则f2()方法回滚,f1()方法回滚
//
//
//业务代码B
}
public void f2(){
//业务代码C
}
隔离特性:
默认属性:READ_COMMITED
针对事务并发进行处理
脏读/幻读
=======================================================
利用AOP处理事务称为声明事务处理(AOP可以添加其他功能,也可以用来处理事务)
使用事务可简化try...catch操作
例如:批量删除笔记业务
配置文件:只需要两个标签即可
<?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:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
<!-- Spring事务管理 通过AOP实现的 -->
<!-- 定义事务管理Bean 必须配置事务管理器属性, 其值是一个Bean的ID -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dbcp"/><!-- 来源于spring mybatis配置文件中的连接池 -->
</bean>
<!-- 开启 @Transactional标记 将标记作用在具体方法上-->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- Spring 的事务管理,是 AOP 的应用,将事务作为切面织入到了 Service 层的业务方法中 -->
</beans>
Dao接口:
//彻底删除笔记(单条删除,并不是修改笔记状态)
public int deleteNote(String id);
-------------------------------------------------------------------------------
Mapper文件:
<!-- //彻底删除笔记(单条删除,并不是修改笔记状态) -->
<delete id="deleteNote" parameterType="string">
delete from cn_note
where
cn_note_id = #{id}
</delete>
-------------------------------------------------------------------------------
业务层接口:
//spring事务彻底删除笔记(单条删除,并不是修改笔记状态)String...表示动态参数,就是String[] 数组
public void deleteNotes(String... ids);
-------------------------------------------------------------------------------
业务层实现类:
//spring事务彻底删除笔记(单条删除,并不是修改笔记状态)String...表示动态参数,就是String[] 数组
//要么全部删除,要么都不删除,回滚
@Transactional
public void deleteNotes(String... ids){
for(String id : ids){
int n = dao.deleteNote(id);
if(n != 1){
//删除笔记失败,抛出异常,触发事务的回滚
throw new RuntimeException("删错了");
}
}
}
-------------------------------------------------------------------------------
测试业务层代码:
@Test//测试使用事务批量删除笔记(本质是delete笔记)
public void testDeleteNotes(){
//调用动态参数的时候,可以不创建数组,直接写参数
//String ids = {"id1","id2"};可以不用创建数组
//抛出异常后回滚
service.deleteNotes(
"fd39f2e4d0df435281aaa4c29aecbc85",
"12121212");
}
由于第二个笔记的ID是错误的,所以会回滚,其次在前代码使用了AOP,输出业务层方法的耗时,对抛出的异常进行写日志,这些AOP切面编程不会对事务产生影响,但是要注意在这些AOP中,若对异常进行了处理,一定要再次抛出,否则@Transactional不会回滚,因为要捕获到异常后才回滚
例如分享笔记的service层,代码大致如下:
//分享笔记
@Override
@Transactional//开启事务
public NoteResult<Object> shareNote(String noteId) {
NoteResult<Object> result = new NoteResult<Object>();
//根据noteId查询出Note
Note note = noteDao.findByNoteId(noteId);
Share share = new Share();
share.setCn_share_id(NoteUtil.createId());
share.setCn_share_title(note.getCn_note_title());
share.setCn_share_body(note.getCn_note_body());
share.setCn_note_id(noteId);
dao.save(share);
//模拟异常 AOP的使用 报异常后会根据事务回滚
String str = null;
str.length();
//模拟异常
result.setStatus(0);
result.setMsg("分享笔记成功!");
return result;
}
此处会抛出空指针异常,属于RuntimeException,根据AOP事务控制是应该回滚的,save()方法执行的insert语句回滚,但是在AOP中切入了性能审计的切面代码如下:
//作用:性能审计service层
@Around("within(cn.tedu.cloud_note.service..*)")
public Object audit(ProceedingJoinPoint point){//方法的返回值和参数固定不变
Object obj = null;//obj用于接收point对象调用后的结果,
try {
long t1 = System.currentTimeMillis();
obj = point.proceed();//相当于调用了service层
long t2 = System.currentTimeMillis();
String str = point.getSignature().toString();
System.out.println(str+"耗时: "+(t2 - t1)+" ms");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
代码中捕获了该异常,但是是没处理该异常,包括AOP事务中抛出的RuntimeException,这是应该将其抛出,才会使事务回滚
//性能审计service层
@Around("within(cn.tedu.cloud_note.service..*)")
public Object audit(ProceedingJoinPoint point) throws Throwable{//方法的返回值和参数固定不变
Object obj = null;
try {
long timeStart = System.currentTimeMillis();
obj = point.proceed();
long timeEnd = System.currentTimeMillis();
String str = point.getSignature().toString();
System.out.println(str+"耗时:"+(timeEnd - timeStart));
} catch (Throwable e) {
e.printStackTrace();
throw e;
}
return obj;
}
其他事务相关知识:
(1)使用 Spring 的事务注解管理事务是通过@Transactional 的注解完成的,也可将事务织入到相应方法中。而使用注解方式,只需在配置文件中加入一个 tx 标签,以告诉 spring 使用注解来完成事务的织入。该标签只需指定一个属性,事务管理器,代码如下:
<!-- 事务 -->
<!-- 注册事务管理器 -->
<bean id="myTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- 开启注解驱动 -->
<tx:annotation-driven transaction-manager="myTxManager"/>
(2)只有非只读事务才能回滚的,只读事务是不会回滚的
(3)如果在Service层用了try catch,在catch里面再抛出一个 RuntimeException异常,这样出了异常才会回滚
(4)如果你不喜欢(3)的方式,你还可以直接在catch后面写一句回滚代码
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
来实现回滚,这样的话,就可以在抛异常后也能return 返回值;比较适合需要拿到Service层的返回值的场景。具体的用法可以参见考下面的伪代码
/** TransactionAspectSupport手动回滚事务:*/
@Transactional(rollbackFor = { Exception.class })
public boolean test() {
try {
doDbSomeThing();
} catch (Exception e) {
e.printStackTrace();
//就是这一句了, 加上之后抛了异常就能回滚(有这句代码就不需要再手动抛出运行时异常了)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
return true;
}
(5)MySQL的MyISAM数据库引擎,不支持ACID(不能回滚), InnoDB 支持ACID,本案例采用的mysql是5.0版本,默认InnoDB 引擎,支持事务回滚 (查看引擎见博客)
总结:Spring 的事务管理,是 AOP 的应用,将事务作为切面织入到了 Service 层的业务方法中