MyBatis 与 Spring 集成
问题
MyBatis 如何与 Spring/Spring Boot 集成?MyBatis-Plus 有什么优势?
答案
Spring Boot 集成
pom.xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml # XML 文件位置
type-aliases-package: com.example.entity # 实体类包别名
configuration:
map-underscore-to-camel-case: true # 驼峰映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志
启动类
@SpringBootApplication
@MapperScan("com.example.mapper") // 扫描 Mapper 接口
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
关键集成组件
| 组件 | 作用 |
|---|---|
| SqlSessionTemplate | 线程安全的 SqlSession 代理,每次调用创建新的 SqlSession |
| MapperScannerConfigurer | 扫描 Mapper 接口,为每个接口注册 MapperFactoryBean |
| MapperFactoryBean | 通过 SqlSession.getMapper() 创建代理,注册到 Spring 容器 |
MyBatis-Plus
MyBatis-Plus 是 MyBatis 的增强,只做增强不做改变:
Mapper 继承 BaseMapper
// 继承 BaseMapper,自动获得通用 CRUD
public interface UserMapper extends BaseMapper<User> {
// 自定义方法仍然可用
List<User> selectByCondition(@Param("condition") UserCondition condition);
}
实体类注解
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_name")
private String userName;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic // 逻辑删除
private Integer deleted;
}
Service 使用
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
public Page<User> pageQuery(int page, int size, String name) {
// 条件构造器
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.like(StringUtils.isNotBlank(name), User::getUserName, name)
.eq(User::getDeleted, 0)
.orderByDesc(User::getCreateTime);
// 分页查询(内置分页插件)
return page(new Page<>(page, size), wrapper);
}
}
MyBatis vs MyBatis-Plus
| 对比 | MyBatis | MyBatis-Plus |
|---|---|---|
| CRUD | 需要手写 SQL | BaseMapper 内置通用 CRUD |
| 条件构造 | XML 动态 SQL | LambdaQueryWrapper 链式 API |
| 分页 | 需要 PageHelper 插件 | 内置分页插件 |
| 代码生成 | 需第三方工具 | 内置代码生成器 |
| 逻辑删除 | 手动处理 | @TableLogic 自动处理 |
| 自动填充 | 自定义插件 | MetaObjectHandler |
| 乐观锁 | 手动处理 | @Version 自动处理 |
| 多租户 | 自定义拦截器 | 内置多租户插件 |
事务管理
MyBatis 与 Spring 集成后,事务由 Spring 统一管理:
事务使用
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockMapper stockMapper;
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 同一事务中,共享同一个 SqlSession(一级缓存生效)
orderMapper.insert(dto.toOrder());
stockMapper.decrease(dto.getProductId(), dto.getQuantity());
}
}
常见面试问题
Q1: MyBatis 如何保证 SqlSession 的线程安全?
答案:
MyBatis 原始的 SqlSession 是非线程安全的。Spring 通过 SqlSessionTemplate 解决——它是 SqlSession 的代理,内部使用 SqlSessionInterceptor(动态代理),每次方法调用时从 SqlSessionHolder(绑定到 TransactionSynchronizationManager)获取或创建 SqlSession,保证每个线程使用自己的 SqlSession。
Q2: @Mapper 和 @MapperScan 的区别?
答案:
@Mapper:加在每个 Mapper 接口上,逐个标记@MapperScan:加在启动类上,批量扫描指定包下的所有 Mapper 接口
两者二选一即可。@MapperScan 更方便,避免每个 Mapper 都加注解。
Q3: MyBatis-Plus 的 LambdaQueryWrapper 原理?
答案:
LambdaQueryWrapper 通过方法引用(如 User::getUserName)获取属性名,再通过实体类的注解映射到列名,最终拼接 SQL 条件。好处是类型安全——重命名字段后编译时就能发现错误,而字符串方式要运行时才出错。
Q4: MyBatis 和 JPA/Hibernate 的区别?
答案:
| 对比 | MyBatis | JPA/Hibernate |
|---|---|---|
| 定位 | 半自动 ORM(SQL 手写) | 全自动 ORM(自动生成 SQL) |
| SQL 控制 | 完全控制 | 自动生成,复杂 SQL 不方便 |
| 学习成本 | 低 | 高(缓存、延迟加载、HQL) |
| 适用场景 | 复杂查询多、需要优化 SQL | 简单 CRUD、快速开发 |
| 国内流行度 | 主流 | 较少 |
MyBatis 适合需要精细控制 SQL 的场景(如复杂报表、高性能查询),JPA 适合标准 CRUD 多的项目。
相关链接
- MyBatis Spring Boot Starter
- MyBatis-Plus 官方文档
- 执行流程 - SqlSession 工作原理
- 事务管理 - Spring 事务