Spring中事务回滚失败的解决方案

Spring中事务回滚失败解决方案

前言

       事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
百度百科中解释:指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
简单的说,事务就是并发控制的单位,是用户定义的一个操作序列。
而一个逻辑工作单元要成为事务,就必须满足ACID属性(即事务的四大特性)。

Spring 事务:

        Spring事务的本质是对数据库事务的封装支持,没有数据库对事务的支持,Spring本身无法提供事务管理功能。对于用JDBC操作数据库想要用到事务,必须经过获取连接——》开启事务——》执行CRUD操作——》提交/回滚事务——》关闭连接几部分操作。使用Spring管理事务后,可以省掉自己写代码开启、提交/回滚事务的操作。
        spring实现事务管理的方式有两种:

	 -  声明式事务管理(今天主要针对声明式事务讲解)
	 -  编程式事务管理

        Spring事务通过AOP动态代理实现,使用上通常要先在配置文件中开启事务,然后通过xml文件或注解配置要执行注解的类方法,然后在调用对应类实例方法时,Spring会自动生成代理,在调用前设置事务操作、调用方法后进行事务回滚或提交操作。

事务的特征
特性 说明
原子性 一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做
一致性 数据不会因为事务的执行而遭到破坏
持久性 一个事物一旦提交,它对数据库的改变就是永久的
隔离性 一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰
事务的隔离级别
隔离级别 说明
read_uncommitted 读未提交,一个事务可以感知或者操作另外一个未提交的事务,可能会出现脏读、不可重复读、幻读
read_committed 读已提交,一个事务只能感知或者操作另一个已经提交的事务,可能会出现不可重复读、幻读
repeatable_read 可重复读,能够避免脏读,不可重复读,不能避免幻读
serializable 串行化,隔离级别最高,消耗资源最低,代价最高,能够防止脏读, 不可重复读,幻读。

MySql 默认的事务隔离级别是 repeatable_read

Spring七种事务传播行为:
传播行为 说明
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并没有进行回滚,提交成功了,这就违背了事务的原子性了;在这里记录一下我是如何进行解决的,共有三种方案可以选择。

Spring中事务回滚失败的解决方案_第1张图片

代码环节:

项目整体结构:
Spring中事务回滚失败的解决方案_第2张图片

  • sql语句:
// 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;
  • controller:
@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;
	}
}

  • serviceImpl:
@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();
			}
		}
	}
}
  • mapper文件:
	-- 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动态代理后的类.

解决方案一:注入自己:

在这里插入图片描述Spring中事务回滚失败的解决方案_第3张图片

解决方案二:借助ApplicationContext:

在这里插入图片描述
Spring中事务回滚失败的解决方案_第4张图片

解决方案三:借助AopContext

      首先需要进行代理暴露。expose-proxy=“true”Spring中事务回滚失败的解决方案_第5张图片
Spring中事务回滚失败的解决方案_第6张图片

注意点:

  • 1.方法为Public类型
    -在这里插入图片描述
  • 2.若要抛出try-catch,请抛出RuntimeException异常。
    -Spring中事务回滚失败的解决方案_第7张图片

---------end 小编亲测有效,若有其他问题,欢迎留言。

类似文章: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

你可能感兴趣的:(学习旅途,踩坑之路,Spring,事务回滚)