Java学习第13天 - 数据库事务管理与MyBatis Plus

学习时间: 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. 下一步学习方向

  • 微服务架构设计
  • 分布式事务管理
  • 数据库性能优化
  • 缓存策略设计
  • 消息队列集成

学习建议

  1. 事务测试:编写各种事务场景的测试用例
  2. 异常处理:学会设计合理的异常处理策略
  3. 性能优化:理解事务对性能的影响
  4. 数据一致性:掌握事务保证数据一致性的原理
  5. 最佳实践:学会合理使用事务注解
相关推荐
hqxstudying1 小时前
mybatis过渡到mybatis-plus过程中需要注意的地方
java·tomcat·mybatis
lichkingyang1 小时前
最近遇到的几个JVM问题
java·jvm·算法
ZeroKoop1 小时前
多线程文件下载 - 数组切分,截取文件名称
java
Monly211 小时前
IDEA:控制台中文乱码
java·ide·intellij-idea
叫我阿柒啊2 小时前
从全栈开发到微服务架构:一次真实的Java面试实录
java·redis·ci/cd·微服务·vue3·springboot·jwt
superlls2 小时前
(计算机网络)JWT三部分及 Signature 作用
java·开发语言·计算机网络
多工坊3 小时前
【DataGrip】连接达梦数据库后,能查询数据但是看不到表的几种情况分析,达梦数据库驱动包下载DmJdbcDriver18.jar
java·数据库·jar
秋难降3 小时前
优雅的代码是什么样的?🫣
java·python·代码规范
现在就干3 小时前
Spring事务基础:你在入门时踩过的所有坑
java·后端