事务传播指的是当一个事务方法被另一个事务方法调用时,如何处理事务的策略。

以下是几种常用的事务传播行为:

  • REQUIRED(required):默认传播行为,如果当前存在事务则加入该事务,如果不存在事务则新建一个事务。
  • SUPPORTS(supports):支持当前事务,如果当前存在事务则加入该事务,如果不存在事务则以非事务方式执行。
  • MANDATORY(mandatory):强制要求当前存在事务,如果不存在事务则抛出异常。
  • REQUIRES_NEW(requires_new):如果当前存在事务,则挂起该事务并创建一个新的事务执行;如果不存在事务,则新建一个事务执行。保证同一时刻只有一个事务在运行。
  • NOT_SUPPORTED(not_supported):以非事务方式执行操作,如果当前存在事务则挂起该事务。
  • NEVER(never):以非事务方式执行操作,如果当前存在事务则抛出异常。
  • NESTED(nested):如果当前存在事务,则开启一个嵌套事务;如果不存在事务,则新建一个事务。嵌套事务可以在回滚时选择回滚到保存点(Savepoint),而不是整个事务回滚。如果外层事务回滚,则嵌套事务也会回滚。

例子

假设有两个方法:A为送积分过程,B为登录过程。

A方法和B方法都使用了@Transactional注解来管理事务。在B方法中调用A方法,此时就会涉及到事务传播。

如果A方法的事务传播行为为REQUIRED,则B方法会加入A方法的事务,即它们会在同一个事务中运行;

如果A方法的事务传播行为为REQUIRES_NEW,则B方法会新开一个事务运行,与A方法的事务相互独立。

事务例子

实体类

定义user实体类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private String password;
    private String roles;
}

定义积分实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Award {
    private Integer id;
    private Integer userId; //用户id
    private Integer point; //积分
}

定义日志实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AwardLog {
    private Integer id;
    private Integer awardId;
    private Integer point;
    private String description;
    private LocalDateTime grantDate; //给的时间
}

Mapper层

创建一个mapper,在里面创建用户登录的方法:

@Mapper
public interface UserMapper {
    /**
     * 通过用户名查询用户
     * @param username
     * @return 返回user对象
     */
    User findUserByUsername(String username);
}

编写sql语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.highedu.csmall.demo.mapper.UserMapper">
    <select id="findUserByUsername" resultType="cn.highedu.csmall.demo.entity.User">
        select *
        from user
        where username=#{username}
    </select>
</mapper>

创建AwardMapper,用于积分的增删改查:

@Mapper
public interface AwardMapper {
    /**
     * 添加积分
     * @param award
     * @return
     */
    int saveAward(Award award);
    /**
     * 修改积分
     * @param award
     * @return
     */
    int updateAward(Award award);

    /**
     * 通过id查询积分
     * @param id
     * @return
     */
    Award findAwardById(Integer id);

    /**
     * 通过用户id查询积分
     * @param userId
     * @return
     */
    Award findAwardByUserId(Integer userId);
}

编写与积分mapper对应的sql语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.highedu.csmall.demo.mapper.AwardMapper">
    <insert id="saveAward" useGeneratedKeys="true" keyProperty="id">
        insert into award(user_id, point)
        values (#{userId}, #{point})
    </insert>

    <update id="updateAward">
        update award
        set user_id = #{userId},
            point   = #{point}
        where id = #{id}
    </update>

    <select id="findAwardById" resultType="cn.highedu.csmall.demo.entity.Award">
        select *
        from award
        where id = #{id}
    </select>

    <select id="findAwardByUserId" resultType="cn.highedu.csmall.demo.entity.Award">
        select *
        from award
        where user_id = #{userId}
    </select>
</mapper>

创建awardLogMapper:

@Mapper
public interface AwardLogMapper {
    int saveAwardLog(AwardLog awardLog);
}

编写sql语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.highedu.csmall.demo.mapper.AwardLogMapper">
    <insert id="saveAwardLog" useGeneratedKeys="true" keyProperty="id">
        insert into award_log(award_id, point, description, grant_date)
        values (#{awardId}, #{point}, #{description}, #{grantDate})
    </insert>
</mapper>

Service层

创建userService接口:

public interface UserService {
    User getByUsername(String username);

    /**
     * 用户登录的方法
     * @param username
     * @param password
     * @return
     */
    User login(String username, String password);
}

创建AwardService接口:

public interface AwardService {
    /**
     * 登录送积分
     */
    void grantAward(User user, String description, Integer point);

    /**
     * 保存积分日志
     */
    void addAwardLog(Integer awardId, String description, Integer point);
}

实现userService接口:

@Service
@Slf4j
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AwardService awardService;
    @Override
    public User getByUsername(String username) {
        return null;
    }

    @Transactional
    @Override
    public User login(String username, String password) {
        if (StringUtils.isEmpty(username)||StringUtils.isEmpty(password)){
            throw new RuntimeException("用户名或密码不能为空");
        }
        User user = userMapper.findUserByUsername(username);
        if (user ==null){
            log.warn("用户名[{}]不存在",username);
            throw new RuntimeException("该用户不存在");
        }
        if (!user.getPassword().equals(password)){
            log.warn("密码错误");
            throw new RuntimeException("密码错误");
        }
        //登录送积分
        awardService.grantAward(user,"用户登录",50);
        return user;
    }
}

实现AwardService接口:

@Service
@Slf4j
@Transactional
public class AwardServiceImpl implements AwardService {

    @Autowired
    private AwardMapper awardMapper;

    @Autowired
    private AwardLogMapper awardLogMapper;

    @Override
    //默认传播行为 如果当前没有事务,开启新事物,如果有事务,参与当前的事务
    @Transactional(propagation = Propagation.REQUIRED)
    public void grantAward(User user, String description, Integer point) {
        Award award = awardMapper.findAwardByUserId(user.getId());
        //如果积分对象为空 说明是第一次登录
        if (award == null) {
            //创建积分对象
            award = new Award(null, user.getId(), 0);
            //保存积分对象
            int rows = awardMapper.saveAward(award);
            if (rows < 1) {
                throw new RuntimeException("保存积分对象失败");
            }
        }
        log.info("积分对象:{}", award);
        //赠送积分
        award.setPoint(award.getPoint() + point);
        //修改数据
        int rows = awardMapper.updateAward(award);
        if (rows < 1) {
            throw new RuntimeException("修改积分失败");
        }
        //保存赠送积分日志
        this.addAwardLog(award.getId(), description, point);
    }

    @Override
    public void addAwardLog(Integer awardId, String description, Integer point) {
        //创建积分日志对象
        AwardLog awardLog = new AwardLog(null, awardId, point, description, LocalDateTime.now());
        //保存积分日志
        int rows = awardLogMapper.saveAwardLog(awardLog);
        if (rows < 1) {
            throw new RuntimeException("保存积分日志失败");
        }
    }

测试类

@SpringBootTest
@Slf4j
class UserServiceImplTest {

    @Autowired
    private UserService userService;

    @Test
    void getByUsername() {
    }

    @Test
    void login() {
        User user = userService.login("tom","1234");
        log.info("登录成功:{}",user);
    }
}

测试结果

最后修改:2023 年 10 月 09 日
如果觉得我的文章对你有用,请随意赞赏