跳到主要内容

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

对比MyBatisMyBatis-Plus
CRUD需要手写 SQLBaseMapper 内置通用 CRUD
条件构造XML 动态 SQLLambdaQueryWrapper 链式 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 的区别?

答案

对比MyBatisJPA/Hibernate
定位半自动 ORM(SQL 手写)全自动 ORM(自动生成 SQL)
SQL 控制完全控制自动生成,复杂 SQL 不方便
学习成本高(缓存、延迟加载、HQL)
适用场景复杂查询多、需要优化 SQL简单 CRUD、快速开发
国内流行度主流较少

MyBatis 适合需要精细控制 SQL 的场景(如复杂报表、高性能查询),JPA 适合标准 CRUD 多的项目。

相关链接