学习时间: 4-5小时
学习目标: 掌握Spring Boot数据库事务管理,学会使用MyBatis Plus进行高效的数据持久化操作
详细学习清单
✅ 第一部分:数据库事务管理基础(60分钟)
1. 事务概念与ACID特性
事务的ACID特性
css
Atomicity(原子性):事务是不可分割的工作单位
Consistency(一致性):事务执行前后数据保持一致
Isolation(隔离性):并发事务之间相互隔离
Durability(持久性):事务提交后数据永久保存
事务隔离级别
java
// 事务隔离级别(从低到高)
public enum TransactionIsolation {
READ_UNCOMMITTED, // 读未提交 - 最低隔离级别
READ_COMMITTED, // 读已提交 - 默认隔离级别
REPEATABLE_READ, // 可重复读 - MySQL默认
SERIALIZABLE // 串行化 - 最高隔离级别
}
2. Spring事务管理
事务注解使用
java
// TransactionService.java
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Isolation;
import java.math.BigDecimal;
@Service
public class TransactionService {
@Autowired
private UserService userService;
@Autowired
private AccountService accountService;
// 基础事务:转账操作
@Transactional
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
try {
// 1. 检查用户是否存在
User fromUser = userService.getUserById(fromUserId);
User toUser = userService.getUserById(toUserId);
if (fromUser == null || toUser == null) {
throw new RuntimeException("用户不存在");
}
// 2. 检查余额是否足够
Account fromAccount = accountService.getAccountByUserId(fromUserId);
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
// 3. 执行转账
accountService.deductBalance(fromUserId, amount);
accountService.addBalance(toUserId, amount);
// 4. 记录交易日志
TransactionLog log = new TransactionLog();
log.setFromUserId(fromUserId);
log.setToUserId(toUserId);
log.setAmount(amount);
log.setType("TRANSFER");
log.setStatus("SUCCESS");
log.setCreateTime(LocalDateTime.now());
transactionLogService.save(log);
} catch (Exception e) {
// 事务会自动回滚
throw new RuntimeException("转账失败: " + e.getMessage());
}
}
// 嵌套事务:用户注册并赠送余额
@Transactional
public void registerUserWithBonus(User user, BigDecimal bonusAmount) {
// 1. 创建用户
User savedUser = userService.createUser(user);
// 2. 创建账户
Account account = new Account();
account.setUserId(savedUser.getId());
account.setBalance(BigDecimal.ZERO);
accountService.createAccount(account);
// 3. 调用嵌套事务方法赠送余额
addBonusToNewUser(savedUser.getId(), bonusAmount);
}
// 嵌套事务方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addBonusToNewUser(Long userId, BigDecimal amount) {
try {
accountService.addBalance(userId, amount);
// 记录赠送日志
BonusLog bonusLog = new BonusLog();
bonusLog.setUserId(userId);
bonusLog.setAmount(amount);
bonusLog.setType("NEW_USER_BONUS");
bonusLog.setCreateTime(LocalDateTime.now());
bonusLogService.save(bonusLog);
} catch (Exception e) {
throw new RuntimeException("赠送余额失败: " + e.getMessage());
}
}
// 只读事务:查询用户余额
@Transactional(readOnly = true)
public BigDecimal getUserBalance(Long userId) {
Account account = accountService.getAccountByUserId(userId);
return account != null ? account.getBalance() : BigDecimal.ZERO;
}
// 指定隔离级别:处理并发转账
@Transactional(isolation = Isolation.SERIALIZABLE)
public void concurrentTransfer(Long fromUserId, Long toUserId, BigDecimal amount) {
// 使用最高隔离级别防止并发问题
transferMoney(fromUserId, toUserId, amount);
}
// 超时事务:处理长时间操作
@Transactional(timeout = 30) // 30秒超时
public void processLargeBatch(List<Long> userIds) {
for (Long userId : userIds) {
// 处理每个用户
processUserData(userId);
}
}
// 回滚策略:指定异常回滚
@Transactional(rollbackFor = {RuntimeException.class, Exception.class})
public void processWithCustomRollback() {
// 业务逻辑
// 如果抛出RuntimeException或Exception,事务会回滚
}
// 不回滚策略:某些异常不回滚
@Transactional(noRollbackFor = {BusinessException.class})
public void processWithNoRollback() {
// 业务逻辑
// 如果抛出BusinessException,事务不会回滚
}
}
✅ 第二部分:MyBatis Plus基础使用(90分钟)
1. MyBatis Plus配置
添加依赖到pom.xml
xml
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
MyBatis Plus配置类
java
// MybatisPlusConfig.java
package com.example.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Configuration
public class MybatisPlusConfig {
// 分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
// 自动填充处理器
@Component
public static class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
}
2. 实体类设计
基础实体类
java
// BaseEntity.java
package com.example.demo.model;
import com.baomidou.mybatisplus.annotation.*;
import java.time.LocalDateTime;
public class BaseEntity {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
@Version
private Integer version;
// Getter和Setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
public Integer getDeleted() { return deleted; }
public void setDeleted(Integer deleted) { this.deleted = deleted; }
public Integer getVersion() { return version; }
public void setVersion(Integer version) { this.version = version; }
}
用户实体类
java
// User.java - 增强版
package com.example.demo.model;
import com.baomidou.mybatisplus.annotation.*;
import java.time.LocalDateTime;
import java.util.Set;
@TableName("users")
public class User extends BaseEntity {
@TableField("username")
private String username;
@TableField("email")
private String email;
@TableField("password")
private String password;
@TableField("first_name")
private String firstName;
@TableField("last_name")
private String lastName;
@TableField("phone")
private String phone;
@TableField("avatar")
private String avatar;
@TableField("enabled")
private Boolean enabled;
@TableField("last_login_time")
private LocalDateTime lastLoginTime;
@TableField(exist = false)
private Set<Role> roles;
@TableField(exist = false)
private Account account;
// 构造函数
public User() {}
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
this.enabled = true;
}
// Getter和Setter方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getAvatar() { return avatar; }
public void setAvatar(String avatar) { this.avatar = avatar; }
public Boolean getEnabled() { return enabled; }
public void setEnabled(Boolean enabled) { this.enabled = enabled; }
public LocalDateTime getLastLoginTime() { return lastLoginTime; }
public void setLastLoginTime(LocalDateTime lastLoginTime) { this.lastLoginTime = lastLoginTime; }
public Set<Role> getRoles() { return roles; }
public void setRoles(Set<Role> roles) { this.roles = roles; }
public Account getAccount() { return account; }
public void setAccount(Account account) { this.account = account; }
@Override
public String toString() {
return "User{" +
"id=" + getId() +
", username='" + username + '\'' +
", email='" + email + '\'' +
", enabled=" + enabled +
", createTime=" + getCreateTime() +
'}';
}
}
3. Mapper接口
UserMapper.java
java
// UserMapper.java
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 根据用户名查询用户
@Select("SELECT * FROM users WHERE username = #{username} AND deleted = 0")
User selectByUsername(@Param("username") String username);
// 根据邮箱查询用户
@Select("SELECT * FROM users WHERE email = #{email} AND deleted = 0")
User selectByEmail(@Param("email") String email);
// 根据角色查询用户
@Select("SELECT u.* FROM users u " +
"JOIN user_roles ur ON u.id = ur.user_id " +
"JOIN roles r ON ur.role_id = r.id " +
"WHERE r.code = #{roleCode} AND u.deleted = 0")
List<User> selectByRole(@Param("roleCode") String roleCode);
// 分页查询用户
@Select("SELECT * FROM users WHERE deleted = 0 ORDER BY create_time DESC")
IPage<User> selectUserPage(Page<User> page);
// 条件查询用户
@Select("<script>" +
"SELECT * FROM users WHERE deleted = 0 " +
"<if test='username != null and username != \"\"'>" +
"AND username LIKE CONCAT('%', #{username}, '%') " +
"</if>" +
"<if test='email != null and email != \"\"'>" +
"AND email LIKE CONCAT('%', #{email}, '%') " +
"</if>" +
"<if test='enabled != null'>" +
"AND enabled = #{enabled} " +
"</if>" +
"ORDER BY create_time DESC" +
"</script>")
List<User> selectByCondition(@Param("username") String username,
@Param("email") String email,
@Param("enabled") Boolean enabled);
// 更新最后登录时间
@Update("UPDATE users SET last_login_time = #{loginTime} WHERE id = #{userId}")
int updateLastLoginTime(@Param("userId") Long userId, @Param("loginTime") LocalDateTime loginTime);
// 批量更新用户状态
@Update("<script>" +
"UPDATE users SET enabled = #{enabled}, update_time = NOW() " +
"WHERE id IN " +
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
int batchUpdateStatus(@Param("userIds") List<Long> userIds, @Param("enabled") Boolean enabled);
// 统计用户数量
@Select("SELECT COUNT(*) FROM users WHERE deleted = 0")
int countUsers();
// 统计各状态用户数量
@Select("SELECT enabled, COUNT(*) as count FROM users WHERE deleted = 0 GROUP BY enabled")
List<UserStatusCount> countByStatus();
// 用户状态统计类
class UserStatusCount {
private Boolean enabled;
private Integer count;
public Boolean getEnabled() { return enabled; }
public void setEnabled(Boolean enabled) { this.enabled = enabled; }
public Integer getCount() { return count; }
public void setCount(Integer count) { this.count = count; }
}
}
✅ 第三部分:Service层事务管理(90分钟)
1. 用户服务类
UserService.java
java
// UserService.java
package com.example.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import com.example.demo.model.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private RoleService roleService;
@Autowired
private AccountService accountService;
// 创建用户(事务管理)
@Transactional
public User createUser(User user) {
// 1. 验证用户信息
validateUser(user);
// 2. 检查用户名和邮箱是否已存在
if (getUserByUsername(user.getUsername()) != null) {
throw new RuntimeException("用户名已存在: " + user.getUsername());
}
if (getUserByEmail(user.getEmail()) != null) {
throw new RuntimeException("邮箱已被注册: " + user.getEmail());
}
// 3. 加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setEnabled(true);
// 4. 保存用户
save(user);
// 5. 创建用户账户
accountService.createAccountForUser(user.getId());
// 6. 分配默认角色
roleService.assignDefaultRole(user.getId());
return user;
}
// 批量创建用户(事务管理)
@Transactional
public List<User> batchCreateUsers(List<User> users) {
List<User> createdUsers = users.stream()
.map(this::createUser)
.toList();
return createdUsers;
}
// 更新用户信息(事务管理)
@Transactional
public User updateUser(Long id, User updateUser) {
User existingUser = getById(id);
if (existingUser == null) {
throw new RuntimeException("用户不存在, ID: " + id);
}
// 更新允许修改的字段
if (updateUser.getFirstName() != null) {
existingUser.setFirstName(updateUser.getFirstName());
}
if (updateUser.getLastName() != null) {
existingUser.setLastName(updateUser.getLastName());
}
if (updateUser.getPhone() != null) {
existingUser.setPhone(updateUser.getPhone());
}
if (updateUser.getAvatar() != null) {
existingUser.setAvatar(updateUser.getAvatar());
}
// 保存更新
updateById(existingUser);
return existingUser;
}
// 修改密码(事务管理)
@Transactional
public void changePassword(Long userId, String oldPassword, String newPassword) {
User user = getById(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 验证旧密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new RuntimeException("旧密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
updateById(user);
}
// 重置密码(事务管理)
@Transactional
public String resetUserPassword(Long userId) {
User user = getById(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 生成新密码
String newPassword = generateRandomPassword();
user.setPassword(passwordEncoder.encode(newPassword));
updateById(user);
return newPassword;
}
// 更新用户状态(事务管理)
@Transactional
public void updateUserStatus(Long userId, boolean enabled) {
User user = getById(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
user.setEnabled(enabled);
updateById(user);
}
// 批量更新用户状态(事务管理)
@Transactional
public void batchUpdateUserStatus(List<Long> userIds, boolean enabled) {
if (userIds == null || userIds.isEmpty()) {
throw new RuntimeException("用户ID列表不能为空");
}
baseMapper.batchUpdateStatus(userIds, enabled);
}
// 删除用户(逻辑删除,事务管理)
@Transactional
public void deleteUser(Long userId) {
User user = getById(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 逻辑删除用户
removeById(userId);
// 禁用用户账户
accountService.disableAccount(userId);
// 移除用户角色
roleService.removeUserRoles(userId);
}
// 根据用户名查询用户
public User getUserByUsername(String username) {
return baseMapper.selectByUsername(username);
}
// 根据邮箱查询用户
public User getUserByEmail(String email) {
return baseMapper.selectByEmail(email);
}
// 根据角色查询用户
public List<User> getUsersByRole(String roleCode) {
return baseMapper.selectByRole(roleCode);
}
// 分页查询用户
public IPage<User> getUserPage(int pageNum, int pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);
return baseMapper.selectUserPage(page);
}
// 条件查询用户
public List<User> getUsersByCondition(String username, String email, Boolean enabled) {
return baseMapper.selectByCondition(username, email, enabled);
}
// 更新最后登录时间
public void updateLastLoginTime(Long userId) {
baseMapper.updateLastLoginTime(userId, LocalDateTime.now());
}
// 获取用户统计信息
public UserStats getUserStats() {
int totalUsers = baseMapper.countUsers();
List<UserMapper.UserStatusCount> statusCounts = baseMapper.countByStatus();
return new UserStats(totalUsers, statusCounts);
}
// 验证用户信息
private void validateUser(User user) {
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new RuntimeException("用户名不能为空");
}
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
throw new RuntimeException("邮箱不能为空");
}
if (user.getPassword() == null || user.getPassword().trim().isEmpty()) {
throw new RuntimeException("密码不能为空");
}
if (user.getUsername().length() < 3 || user.getUsername().length() > 20) {
throw new RuntimeException("用户名长度必须在3-20个字符之间");
}
if (user.getPassword().length() < 6) {
throw new RuntimeException("密码长度不能少于6个字符");
}
}
// 生成随机密码
private String generateRandomPassword() {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++) {
int index = (int) (Math.random() * chars.length());
sb.append(chars.charAt(index));
}
return sb.toString();
}
// 用户统计信息类
public static class UserStats {
private int totalUsers;
private List<UserMapper.UserStatusCount> statusCounts;
public UserStats(int totalUsers, List<UserMapper.UserStatusCount> statusCounts) {
this.totalUsers = totalUsers;
this.statusCounts = statusCounts;
}
public int getTotalUsers() { return totalUsers; }
public List<UserMapper.UserStatusCount> getStatusCounts() { return statusCounts; }
}
}
2. 账户服务类
AccountService.java
java
// AccountService.java
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.mapper.AccountMapper;
import com.example.demo.model.Account;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
public class AccountService extends ServiceImpl<AccountMapper, Account> {
// 为用户创建账户(事务管理)
@Transactional
public void createAccountForUser(Long userId) {
Account account = new Account();
account.setUserId(userId);
account.setBalance(BigDecimal.ZERO);
account.setStatus("ACTIVE");
save(account);
}
// 增加账户余额(事务管理)
@Transactional
public void addBalance(Long userId, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("金额必须大于0");
}
Account account = getAccountByUserId(userId);
if (account == null) {
throw new RuntimeException("账户不存在");
}
account.setBalance(account.getBalance().add(amount));
updateById(account);
}
// 减少账户余额(事务管理)
@Transactional
public void deductBalance(Long userId, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("金额必须大于0");
}
Account account = getAccountByUserId(userId);
if (account == null) {
throw new RuntimeException("账户不存在");
}
if (account.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足");
}
account.setBalance(account.getBalance().subtract(amount));
updateById(account);
}
// 转账操作(事务管理)
@Transactional
public void transfer(Long fromUserId, Long toUserId, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("转账金额必须大于0");
}
// 减少转出账户余额
deductBalance(fromUserId, amount);
// 增加转入账户余额
addBalance(toUserId, amount);
}
// 禁用账户(事务管理)
@Transactional
public void disableAccount(Long userId) {
Account account = getAccountByUserId(userId);
if (account != null) {
account.setStatus("DISABLED");
updateById(account);
}
}
// 根据用户ID获取账户
public Account getAccountByUserId(Long userId) {
return baseMapper.selectByUserId(userId);
}
// 获取账户余额
public BigDecimal getAccountBalance(Long userId) {
Account account = getAccountByUserId(userId);
return account != null ? account.getBalance() : BigDecimal.ZERO;
}
}
✅ 第四部分:事务异常处理与测试(60分钟)
1. 事务异常处理
自定义业务异常
java
// BusinessException.java
package com.example.demo.exception;
public class BusinessException extends RuntimeException {
private String errorCode;
public BusinessException(String message) {
super(message);
}
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
public String getErrorCode() {
return errorCode;
}
}
// InsufficientBalanceException.java
package com.example.demo.exception;
public class InsufficientBalanceException extends BusinessException {
public InsufficientBalanceException(String message) {
super("INSUFFICIENT_BALANCE", message);
}
public InsufficientBalanceException(BigDecimal required, BigDecimal available) {
super("INSUFFICIENT_BALANCE",
String.format("余额不足,需要: %s, 可用: %s", required, available));
}
}
// UserNotFoundException.java
package com.example.demo.exception;
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(Long userId) {
super("USER_NOT_FOUND", "用户不存在, ID: " + userId);
}
public UserNotFoundException(String username) {
super("USER_NOT_FOUND", "用户不存在, 用户名: " + username);
}
}
2. 事务测试
TransactionServiceTest.java
java
// TransactionServiceTest.java
package com.example.demo.service;
import com.example.demo.model.User;
import com.example.demo.model.Account;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
public class TransactionServiceTest {
@Autowired
private TransactionService transactionService;
@Autowired
private UserService userService;
@Autowired
private AccountService accountService;
@Test
public void testTransferMoney_Success() {
// 1. 创建测试用户
User user1 = createTestUser("user1", "user1@test.com");
User user2 = createTestUser("user2", "user2@test.com");
// 2. 为用户1添加余额
accountService.addBalance(user1.getId(), new BigDecimal("1000"));
// 3. 执行转账
transactionService.transferMoney(user1.getId(), user2.getId(), new BigDecimal("500"));
// 4. 验证结果
BigDecimal balance1 = accountService.getAccountBalance(user1.getId());
BigDecimal balance2 = accountService.getAccountBalance(user2.getId());
assertEquals(new BigDecimal("500"), balance1);
assertEquals(new BigDecimal("500"), balance2);
}
@Test
public void testTransferMoney_InsufficientBalance() {
// 1. 创建测试用户
User user1 = createTestUser("user1", "user1@test.com");
User user2 = createTestUser("user2", "user2@test.com");
// 2. 为用户1添加少量余额
accountService.addBalance(user1.getId(), new BigDecimal("100"));
// 3. 尝试转账超过余额的金额
assertThrows(InsufficientBalanceException.class, () -> {
transactionService.transferMoney(user1.getId(), user2.getId(), new BigDecimal("500"));
});
// 4. 验证余额未变化
BigDecimal balance1 = accountService.getAccountBalance(user1.getId());
BigDecimal balance2 = accountService.getAccountBalance(user2.getId());
assertEquals(new BigDecimal("100"), balance1);
assertEquals(BigDecimal.ZERO, balance2);
}
@Test
public void testTransferMoney_UserNotFound() {
// 尝试转账给不存在的用户
assertThrows(UserNotFoundException.class, () -> {
transactionService.transferMoney(999L, 888L, new BigDecimal("100"));
});
}
@Test
public void testRegisterUserWithBonus() {
// 1. 创建用户
User user = new User("testuser", "test@test.com", "password123");
user.setFirstName("Test");
user.setLastName("User");
// 2. 注册用户并赠送余额
transactionService.registerUserWithBonus(user, new BigDecimal("100"));
// 3. 验证用户创建成功
User savedUser = userService.getUserByUsername("testuser");
assertNotNull(savedUser);
assertTrue(savedUser.getEnabled());
// 4. 验证账户创建成功
Account account = accountService.getAccountByUserId(savedUser.getId());
assertNotNull(account);
assertEquals(new BigDecimal("100"), account.getBalance());
}
private User createTestUser(String username, String email) {
User user = new User(username, email, "password123");
user.setFirstName("Test");
user.setLastName("User");
return userService.createUser(user);
}
}
🎯 今日学习总结
1. 掌握的核心技能
- ✅ Spring事务管理机制
- ✅ 事务传播行为和隔离级别
- ✅ MyBatis Plus基础操作
- ✅ 实体类设计与注解使用
- ✅ 事务异常处理与回滚
2. 事务管理特点
- 声明式事务:使用@Transactional注解
- 传播行为:REQUIRED、REQUIRES_NEW等
- 隔离级别:READ_COMMITTED、SERIALIZABLE等
- 超时设置:防止长时间事务
- 回滚策略:指定异常回滚
3. MyBatis Plus优势
- CRUD操作:内置基础CRUD方法
- 条件构造器:灵活的查询条件构建
- 分页插件:自动分页处理
- 自动填充:创建时间、更新时间等
- 逻辑删除:软删除支持
4. 下一步学习方向
- 微服务架构设计
- 分布式事务管理
- 数据库性能优化
- 缓存策略设计
- 消息队列集成
学习建议
- 事务测试:编写各种事务场景的测试用例
- 异常处理:学会设计合理的异常处理策略
- 性能优化:理解事务对性能的影响
- 数据一致性:掌握事务保证数据一致性的原理
- 最佳实践:学会合理使用事务注解