Spring MyBatis

Spring整合MyBatis

引入依赖

<dependencies>
    <!-- Spring 6.x 对应使用 MyBatis-Spring 3.0.x -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>3.0.5</version>
    </dependency>

    <!-- Spring JDBC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.2.15</version>
    </dependency>

    <!-- 数据库连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.11</version>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>

    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.14</version>
    </dependency>
</dependencies>

配置类

@Configuration
@ComponentScan("com.example")
@MapperScan("com.example.mapper")  // 扫描Mapper接口
public class AppConfiguration {

    /**
     * 注册数据源
     */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    /**
     * 注册SqlSessionFactory
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        return factoryBean;
    }
}

如果你不想直接在配置类上使用 @MapperScan,也可以把扫描器本身注册为容器中的组件。这样做的本质,是把“扫描哪个包下的 Mapper 接口”这件事交给一个专门的 MapperScannerConfigurer 组件来完成。

@Configuration
@ComponentScan("com.example")
public class AppConfiguration {

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
        configurer.setBasePackage("com.example.mapper");
        return configurer;
    }

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        return factoryBean;
    }
}

两种写法的目标是一致的:都是把 Mapper 接口注册为 Spring 容器中的代理对象。区别只是一个更偏注解声明,一个更偏显式配置。

Mapper接口与使用

// Mapper接口
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Integer id);

    @Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
    int insert(User user);
}

// Service直接使用
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;  // 直接注入使用

    @Override
    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
}

整合原理

Spring 整合 MyBatis 的组件调用链路图
Spring 整合 MyBatis 的组件调用链路图

最底层先准备 DataSource,它负责提供数据库连接;在它之上,SqlSessionFactoryBean 负责把 MyBatis 的会话工厂交给 Spring 管理;再往上,Mapper 接口会被扫描并注册成可注入的代理对象;最后 Service 层直接注入这些 Mapper 代理对象完成数据库访问。也就是说,业务层并不会直接操作 JDBC 或 SqlSession,而是通过 Spring 容器把整条持久层调用链串接起来。

核心概念:Spring 整合 MyBatis 的关键,不是零散记住几个注解,而是理解数据源、会话工厂、Mapper 代理对象和 Service 调用之间是如何被 Spring 串起来的。

Spring核心接口

  • PlatformTransactionManager 平台事务管理器
  • TransactionStatus 事务状态
  • TransactionDefinition 事务定义

事务基础回顾

阶段JDBC代码MySQL命令
开启事务conn.setAutoCommit(false)START TRANSACTION
提交事务conn.commit()COMMIT
回滚事务conn.rollback()ROLLBACK

后续在连接池的内容中,是DataSource来管理connection,也就是说谁管理了DataSource谁就管理了事务。

那么谁管理了DataSource呢,我们接下来继续来看事务的核心接口

PlatformTransactionManager 平台事务管理器

平台事务管理器,Spring要管理事务,必须使用事务管理器 有多种实现,通过实现此接口,Spring可以管理任何实现了这些接口的事务。 开发人员可以使用统一的编程模型来控制管理事务。

常见的事务管理器的实现 DataSourceTransactionManager,jdbc开发时事务管理器,使用JdbcTemplate、MyBatis(SSM) HibernateTransactionManager,Hibernate开发时事务管理器,整合Hibernate(SSH)

public interface PlatformTransactionManager extends TransactionManager {
  // 开启事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
  // 提交事务
    void commit(TransactionStatus var1) throws TransactionException;
  // 回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

TransactionStatus 事务状态

获取事务的状态(回滚点、是否完成、是否新事务、是否回滚)属性,是一个过程值

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
  boolean hasSavepoint();

  void flush();
}

提供了关于事务状态的方法

image-20240829143413595
image-20240829143413595

TransactionDefinition 事务定义

定义事务的名称、隔离级别、传播行为、超时时间长短、只读属性等

public interface TransactionDefinition {
  int PROPAGATION_REQUIRED = 0;
  int PROPAGATION_SUPPORTS = 1;
  int PROPAGATION_MANDATORY = 2;
  int PROPAGATION_REQUIRES_NEW = 3;
  int PROPAGATION_NOT_SUPPORTED = 4;
  int PROPAGATION_NEVER = 5;
  int PROPAGATION_NESTED = 6;
  int ISOLATION_DEFAULT = -1;
  int ISOLATION_READ_UNCOMMITTED = 1;
  int ISOLATION_READ_COMMITTED = 2;
  int ISOLATION_REPEATABLE_READ = 4;
  int ISOLATION_SERIALIZABLE = 8;
  int TIMEOUT_DEFAULT = -1;

  default int getPropagationBehavior() {
    return 0;
  }

  default int getIsolationLevel() {
    return -1;
  }

  default int getTimeout() {
    return -1;
  }

  default boolean isReadOnly() {
    return false;
  }

  @Nullable
  default String getName() {
    return null;
  }

  static TransactionDefinition withDefaults() {
    return StaticTransactionDefinition.INSTANCE;
  }
}

手动管理事务

DataSource管理了事务,而PlatformTransactionManager依赖于DataSource组件,其实是PlatformTransactionManager对应的实例来管理事务。

我们向容器中注册一个DataSourceTransactionManager(它是PlatformTransactionManager的实现类),注册这个组件的时候需要依赖于DataSource组件。

// 平台事务管理器,Spring要管理事务,必须要使用到这个组件
// dataSource就是连接池
// 管理事务,其实就是管理连接
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
  DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
  transactionManager.setDataSource(dataSource);
  return transactionManager;
}

手动管理事务也就意味着使用PlatformTransactionManager提供的方法来管理事务

@Service
public class MarketAccountServiceImpl implements MarketAccountService {

  @Autowired
  AccountMapper accountMapper;

  // 从容器中获取平台事务管理器组件(DataSourceTransactionManager)
  @Autowired
  PlatformTransactionManager transactionManager;

  @Override
  public int transfer(Integer fromId, Integer destId, Integer money) {
    // 先查询出来信息
    MarketAccount fromAccount = accountMapper.selectByPrimaryKey(fromId);
    MarketAccount destAccount = accountMapper.selectByPrimaryKey(destId);

    // fromAccount它的钱 少了
    fromAccount.setMoney(fromAccount.getMoney() - money);
    fromAccount.setUpdateTime(LocalDateTime.now());
    // destAccount它的钱 多了
    destAccount.setMoney(destAccount.getMoney() + money);
    destAccount.setUpdateTime(LocalDateTime.now());

    TransactionStatus transactionStatus = null;
    try {
      // 开启事务
      transactionStatus = transactionManager.getTransaction(TransactionDefinition.withDefaults());
      // 执行更新操作
      accountMapper.updateByPrimaryKeySelective(fromAccount);
      // 制造异常
      int i = 1 / 0;
      accountMapper.updateByPrimaryKeySelective(destAccount);
      // 提交事务
      transactionManager.commit(transactionStatus);
    } catch (Exception e) {
      // 回滚事务
      transactionManager.rollback(transactionStatus);
      throw new RuntimeException(e);
    }
    return 0;
  }

}

那么如果说其他的方法也做类似的事情呢

也在执行业务代码之前开启事务,然后对业务代码增加try-catch,如果执行成功提交事务,失败则回滚事务

TransactionStatus transactionStatus = null;
try {
  transactionStatus = transactionManager.getTransaction(TransactionDefinition.withDefaults());
  // 核心业务
  transactionManager.commit(transactionStatus);
} catch (Exception e) {
  transactionManager.rollback(transactionStatus);
  throw new RuntimeException(e);
}

比如下面的一个修改业务

@Override
public int modify(Integer id, Integer money) {
  TransactionStatus transactionStatus = null;
  try {
    transactionStatus = transactionManager.getTransaction(TransactionDefinition.withDefaults());
    MarketAccount marketAccount = new MarketAccount();
    marketAccount.setId(id);
    marketAccount.setMoney(money);
    accountMapper.updateByPrimaryKeySelective(marketAccount);
    transactionManager.commit(transactionStatus);
  } catch (Exception e) {
    transactionManager.rollback(transactionStatus);
    throw new RuntimeException(e);
  }
  return 0;
}

这里的方法都使用相同的业务,这时候我们要想要AOP技术

要增加事务的方法 放入到切入点

事务的业务 就是通知

Spring其实也提供了对应的方案,那就是Spring事务\

Spring声明式事务

启用声明式事务

@Configuration
@ComponentScan("com.example")
@MapperScan("com.example.mapper")
@EnableTransactionManagement  // 开启声明式事务
public class AppConfiguration {

    // ... DataSource配置

    // ... SqlSessionFactory配置

    /**
     * 注册事务管理器(必须)
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);
        return manager;
    }
}

@Transactional 使用

@Service
public class TransferServiceImpl implements TransferService {

    @Autowired
    private AccountMapper accountMapper;

    /**
     * @Transactional让方法在事务中执行
     * - 方法执行前:开启事务
     * - 方法正常结束:提交事务
     * - 方法抛出异常:回滚事务
     */
    @Transactional
    @Override
    public void transfer(String from, String to, Double amount) {
        // 扣款
        accountMapper.decreaseBalance(from, amount);

        // 模拟异常
        // int i = 1 / 0;  // 这行会导致事务回滚

        // 收款
        accountMapper.increaseBalance(to, amount);
    }
}

声明式事务原理

┌──────────────────────────────────────────┐
│           @Transactional方法              │
│                                          │
│   调用时实际执行的是代理对象的方法:          │
│                                          │
│   1. 开启事务                             │
│       ↓                                  │
│   2. 执行业务方法                          │
│       ↓                                  │
│   3. 判断结果:                            │
│      ├─ 正常结束 → 提交事务                │
│      └─ 抛出异常 → 回滚事务                │
│                                          │
└──────────────────────────────────────────┘

这张图的关键不是记住三个步骤,而是先理解:当方法被 @Transactional 标记后,外部调用时真正进入的并不是原始业务对象,而是 Spring 生成的代理对象。代理对象会先决定是否开启事务,再去调用真实业务方法;方法返回后,根据执行结果决定提交还是回滚。也就是说,事务控制逻辑始终包裹在业务方法外围,而不是写死在业务代码内部。

Spring通过AOP生成代理对象,对@Transactional方法做环绕增强:

  • 方法执行前:调用PlatformTransactionManager.getTransaction()开启事务
  • 方法执行后:调用commitTransactionAfterReturning()提交事务
  • 抛出异常时:调用completeTransactionAfterThrowing()回滚事务

@Transactional 属性

属性说明默认值
propagation事务传播行为Propagation.REQUIRED
isolation事务隔离级别Isolation.DEFAULT
readOnly是否只读事务false
timeout超时时间(秒)-1(无限制)
rollbackFor哪些异常触发回滚RuntimeExceptionError
@Transactional(
    propagation = Propagation.REQUIRED,
    isolation = Isolation.READ_COMMITTED,
    readOnly = false,
    timeout = 30,
    rollbackFor = Exception.class
)
public void transfer(...) { }

核心概念:声明式事务的价值在于把“开启事务、提交事务、回滚事务”这套固定流程从业务代码中抽离出来,让开发者只关注业务边界。

事务传播行为

什么是传播行为

事务传播行为定义了:当一个事务方法调用另一个事务方法时,事务如何传播

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    @Transactional  // 开启事务T1
    public void methodA() {
        // ... 操作数据库
        serviceB.methodB();  // 调用另一个事务方法
    }
}

@Service
public class ServiceB {
    @Transactional  // 怎么处理?加入T1?新建T2?
    public void methodB() {
        // ... 操作数据库
    }
}

7种传播行为

传播行为含义使用场景
REQUIRED(默认)当前有事务则加入,无则新建大多数场景
REQUIRES_NEW始终新建事务,挂起当前事务需要独立提交/回滚
NESTED当前有事务则创建嵌套事务(保存点)子事务可单独回滚
SUPPORTS有事务则加入,无则以非事务执行不强制要求事务
NOT_SUPPORTED以非事务执行,挂起当前事务不需要事务的方法
MANDATORY强制要求存在事务,无则抛异常必须在事务中执行
NEVER强制要求无事务,有则抛异常不允许在事务中执行

REQUIRED(默认)

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    @Transactional  // 默认REQUIRED,新建事务T1
    public void methodA() {
        serviceB.methodB();  // 加入T1,不新建事务
    }
}

@Service
public class ServiceB {
    @Transactional  // 默认REQUIRED
    public void methodB() {
        // 复用methodA的事务T1
    }
}

特点

  • methodA和methodB共用同一个事务
  • 任一方法抛出异常,整个事务回滚

REQUIRES_NEW

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    @Transactional
    public void methodA() {
        // 在事务T1中
        serviceB.methodB();  // 挂起T1,新建T2
        // 恢复T1
    }
}

@Service
public class ServiceB {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 在独立的事务T2中
    }
}

特点

  • methodB在独立事务T2中执行
  • T1和T2互不影响,各自提交或回滚
  • 适用场景:日志记录,无论主业务成功与否都要记录

NESTED

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    @Transactional
    public void methodA() {
        // 外层事务
        serviceB.methodB();  // 创建嵌套事务(保存点)
        // 继续外层事务
    }
}

@Service
public class ServiceB {
    @Transactional(propagation = Propagation.NESTED)
    public void methodB() {
        // 嵌套事务
    }
}

特点

  • 子事务回滚不影响父事务
  • 父事务回滚,子事务也回滚
  • 基于JDBC保存点(Savepoint)实现

三者对比

REQUIRED:                    REQUIRES_NEW:                NESTED:
┌─────────┐ ┌─────────┐ ┌─────────┐
│methodA │ │methodA │ │methodA │
│(事务T1) │ │(事务T1) │ │(事务T1) │
│ ↓ │ │ ↓ │ │ ↓ │
│methodB │ │挂起T1 │ │保存点1 │
│(共用T1) │ │ ↓ │ │ ↓ │
└─────────┘ │methodB │ │methodB │
│(事务T2) │ │(嵌套事务)│
│ ↓ │ │ ↓ │
│恢复T1 │ │回滚到 │
└─────────┘ │保存点1 │
└─────────┘

核心概念:传播行为本质上是在回答“一个事务方法调用另一个事务方法时,到底共用原事务、挂起原事务,还是在原事务内部建立保存点”。

核心注解速查表

注解作用使用位置
@MapperScan扫描MyBatis Mapper接口配置类
@EnableTransactionManagement开启声明式事务配置类
@Transactional标记事务方法类/方法
@Mapper标记Mapper接口接口

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇