事务传播指的是当一个事务方法被另一个事务方法调用时,如何处理事务的策略。
以下是几种常用的事务传播行为:
- 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);
}
}