事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
百度百科中解释:指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
简单的说,事务就是并发控制的单位,是用户定义的一个操作序列。
而一个逻辑工作单元要成为事务,就必须满足ACID属性(即事务的四大特性)。
Spring事务的本质是对数据库事务的封装支持,没有数据库对事务的支持,Spring本身无法提供事务管理功能。对于用JDBC操作数据库想要用到事务,必须经过获取连接——》开启事务——》执行CRUD操作——》提交/回滚事务——》关闭连接几部分操作。使用Spring管理事务后,可以省掉自己写代码开启、提交/回滚事务的操作。
spring实现事务管理的方式有两种:
- 声明式事务管理(今天主要针对声明式事务讲解)
- 编程式事务管理
Spring事务通过AOP动态代理实现,使用上通常要先在配置文件中开启事务,然后通过xml文件或注解配置要执行注解的类方法,然后在调用对应类实例方法时,Spring会自动生成代理,在调用前设置事务操作、调用方法后进行事务回滚或提交操作。
特性 | 说明 |
---|---|
原子性 | 一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做 |
一致性 | 数据不会因为事务的执行而遭到破坏 |
持久性 | 一个事物一旦提交,它对数据库的改变就是永久的 |
隔离性 | 一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰 |
隔离级别 | 说明 |
---|---|
read_uncommitted | 读未提交,一个事务可以感知或者操作另外一个未提交的事务,可能会出现脏读、不可重复读、幻读 |
read_committed | 读已提交,一个事务只能感知或者操作另一个已经提交的事务,可能会出现不可重复读、幻读 |
repeatable_read | 可重复读,能够避免脏读,不可重复读,不能避免幻读 |
serializable | 串行化,隔离级别最高,消耗资源最低,代价最高,能够防止脏读, 不可重复读,幻读。 |
MySql 默认的事务隔离级别是 repeatable_read
传播行为 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
通过controller调用service,serviceA方法加了事务注解,以防抛出异常时进行回滚,然后在serviceA 方法中调用了另一个方法serviceB,均加了事务注解,然后在测试的过程中发现,serviceB抛出异常时,serviceA并没有进行回滚,提交成功了,这就违背了事务的原子性了;在这里记录一下我是如何进行解决的,共有三种方案可以选择。
// role表
DROP TABLE IF EXISTS sys_role;
CREATE TABLE sys_role (
ROLE_ID varchar(64) NOT NULL COMMENT '角色ID',
ROLE_NAME varchar(64) DEFAULT NULL COMMENT '角色名称',
ROLE_TYPE varchar(8) DEFAULT NULL COMMENT '角色类型',
CREATE_PERSON varchar(32) DEFAULT NULL COMMENT '创建人',
CREATE_DATE datetime DEFAULT NULL COMMENT '创建日期',
LAST_MODIFY_PERSON varchar(32) DEFAULT NULL COMMENT '最后修改人',
LAST_MODIFY_DATE datetime DEFAULT NULL COMMENT '最后修改日期',
DELETE_FLAG varchar(8) DEFAULT NULL COMMENT '删除标记',
PRIMARY KEY (ROLE_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';
//user表
DROP TABLE IF EXISTS sys_user;
CREATE TABLE sys_user (
id int(16) NOT NULL,
user_name varchar(64) DEFAULT NULL,
age int(3) DEFAULT NULL,
sex int(2) DEFAULT NULL,
address varchar(128) DEFAULT NULL,
phoneNum varchar(22) DEFAULT NULL,
city varchar(64) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@RestController
@RequestMapping("user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/")
public List getUser(){
return userService.getAll();
}
@RequestMapping(value = "/update",method = RequestMethod.POST)
public Boolean update(@RequestBody SysUser user){
boolean result = false;
if(user != null ){
userService.update(user);
result = true;
}
return result;
}
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Resource
private RoleDao roleDao;
@Override
public List getAll() {
List list = userDao.getAll();
for(SysUser user : list){
if(user.getSex()!=null && user.getSex() !=""){
user.setSex(("1").equals(user.getSex())?"男生":"女生");
}
}
return list;
}
@Override
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void update(SysUser user) {
try {
userDao.update(user);
roleAdd(user);
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
public void roleAdd(SysUser user){
if(null != user){
try {
Role role = new Role();
String uuid = UUID.randomUUID().toString();
role.setRoleId(uuid);
role.setCreatePerson(user.getUserName());
role.setCreateDate(new Date());
role.setRoleName("部门经理");
role.setRoleType("三级类型");
role.setDeleteFlag("0");
roleDao.save(role);
int a = 1/0;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
}
-- roleMapper
role_id,role_name,role_type,create_person,create_date,last_modify_person,last_modify_date,delete_flag
insert into sys_role
( )
values
(
#{roleId,jdbcType=VARCHAR},#{roleName,jdbcType=VARCHAR},#{roleType,jdbcType=VARCHAR},#{createPerson,jdbcType=VARCHAR},
#{createDate,jdbcType=DATE},#{lastModifyPerson,jdbcType=VARCHAR},#{lastModifyDate,jdbcType=DATE},
#{deleteFlag,jdbcType=VARCHAR}
)
-- userMapper:
id,user_name,age,sex,address,phoneNum,city
insert into sys_user
( )
values
(
#{item.id,jdbcType=INTEGER},#{item.userName,jdbcType=VARCHAR},#{item.age,jdbcType=INTEGER},
#{item.sex,jdbcType=VARCHAR},#{item.address,jdbcType=VARCHAR},#{item.phoneNum,jdbcType=VARCHAR},
#{item.city,jdbcType=VARCHAR}
)
update sys_user
user_name = #{userName},
age = #{age},
sex = #{sex},
address = #{address},
phoneNum = #{phoneNum},
city = #{city},
where id = #{id}
从上面代码中可以看出,B方法(roleAdd)是通过当前这个对象(this)调用的,虽然加了事务注解,但是却没有通过动态代理去生成,即不是动态代理的子对象,无法却通过动态代理来增强,从而导致事务失效。
Spring的事务是基于AOP实现的,AOP是基于动态代理实现的.所以@Transactional注解如果想要生效,那么其调用方,需要是被Spring动态代理后的类.
首先需要进行代理暴露。expose-proxy=“true”
类似文章:https://mp.weixin.qq.com/s?__biz=MzIxNTQ4MzE1NA==&mid=2247483692&idx=1&sn=08317368ece6b9b7f90e544e298f9f4e&chksm=9796d751a0e15e474a113a091b94dd86b3c4399d209bdc57df6a4f45df4bacdb1ebad1263b3a&mpshare=1&scene=1&srcid=&sharer_sharetime=1571189585768&sharer_shareid=0d7f5bf2e5612b8441a6cf7316fe5c8d&key=750ed215a501f466c0d2ee86d31540d02dcf5dbed754fb086d8fbd57dd9275c586e3660da0e96c354d27770cb0219ec00463f797abcd3977460369e70c6dd80d59f96b74146ccc6b10f85a432073ff7a&ascene=1&uin=ODE2MjgxNTA1&devicetype=Windows+10&version=62070152&lang=zh_CN&pass_ticket=d%2BH3sl3M6eGi9GXFahjsqb0%2FBbGBTfZsReXqBxYi0UI0wJTRtO8TzhQ280jNEATO
https://juejin.im/post/5d4ac32b6fb9a06b1f141a9f?utm_source=gold_browser_extension