Skip to content

MyBatis 进阶应用

分页插件、逆向工程、Spring 集成、批处理、性能优化


分页插件

PageHelper 集成

Maven 依赖

xml
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>6.1.0</version>
</dependency>

配置

xml
<!-- mybatis-config.xml -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
        <property name="reasonable" value="true"/>
        <property name="supportMethodsArguments" value="true"/>
    </plugin>
</plugins>

Spring Boot 配置

yaml
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql

使用

java
// 方式一:静态方法
@Test
public void testPageHelper() {
    PageHelper.startPage(1, 10);  // 第1页,每页10条
    List<User> list = userMapper.selectAll();
    
    PageInfo<User> pageInfo = new PageInfo<>(list);
    
    System.out.println("总记录数:" + pageInfo.getTotal());
    System.out.println("总页数:" + pageInfo.getPages());
    System.out.println("当前页:" + pageInfo.getPageNum());
    System.out.println("当前页数据:" + pageInfo.getList().size());
}

// 方式二:PageRowBounds
@Test
public void testPageRowBounds() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        RowBounds rowBounds = new RowBounds(0, 10);
        List<User> list = session.selectList("selectAll", null, rowBounds);
    }
}

分页参数

参数说明
pageNum当前页码
pageSize每页条数
total总记录数
pages总页数
hasNextPage是否有下一页
hasPreviousPage是否有上一页
navigatePages导航页码数

自定义分页插件

java
@Intercepts({
    @Signature(type = Executor.class, method = "query",
              args = {MappedStatement.class, Object.class, 
                      RowBounds.class, ResultHandler.class,
                      BoundSql.class, CacheKey.class})
})
public class CustomPageInterceptor implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        BoundSql boundSql = (BoundSql) args[4];
        
        // 获取分页参数
        Page<?> page = PageUtil.getPage();
        if (page == null) {
            return invocation.proceed();
        }
        
        // SQL 重写:添加 LIMIT
        MappedStatement ms = (MappedStatement) args[0];
        String originalSql = boundSql.getSql();
        String countSql = "SELECT COUNT(*) " + 
            originalSql.substring(originalSql.toUpperCase().indexOf("FROM"));
        String pageSql = originalSql + " LIMIT " + 
            page.getStartRow() + ", " + page.getPageSize();
        
        // 执行分页查询
        BoundSql newBoundSql = new BoundSql(
            ms.getConfiguration(), pageSql, 
            boundSql.getParameterMappings(), 
            boundSql.getParameterObject());
        
        // ...
        
        return invocation.proceed();
    }
}

逆向工程

MyBatis Generator

Maven 插件

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.4.2</version>
            <configuration>
                <configurationFile>generatorConfig.xml</configurationFile>
                <verbose>true</verbose>
                <overwrite>true</overwrite>
            </configuration>
        </plugin>
    </plugins>
</build>

配置文件 (generatorConfig.xml)

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
    PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
    "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!-- 数据库驱动 -->
    <classPathEntry location="mysql-connector-java.jar"/>
    
    <context id="Mysql">
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/mybatis_db"
            userId="root" password="password"/>
        
        <!-- Java 类型解析 -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        
        <!-- 生成实体类 -->
        <javaModelGenerator targetPackage="com.example.entity" 
                           targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        
        <!-- 生成 Mapper 接口 -->
        <javaClientGenerator type="ANNOTATEDMAPPER"
                            targetPackage="com.example.mapper"
                            targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        
        <!-- 生成表映射 -->
        <table tableName="user" domainObjectName="User">
            <generatedKey column="id" sqlStatement="MYSQL" 
                         identity="true"/>
        </table>
        <table tableName="orders" domainObjectName="Order"/>
    </context>
</generatorConfiguration>

运行

bash
mvn mybatis-generator:generate

生成结果

com.example.entity
├── User.java          # 实体类
└── Order.java         # 实体类

com.example.mapper
├── UserMapper.java    # Mapper 接口
├── UserMapper.xml     # Mapper XML
├── OrderMapper.java
└── OrderMapper.xml

Spring 集成

依赖

xml
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

配置 (application.yml)

yaml
mybatis:
  # Mapper XML 路径
  mapper-locations: classpath:mapper/*.xml
  # 类型别名包
  type-aliases-package: com.example.entity
  # 配置
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    cache-enabled: true
    lazy-loading-enabled: true
    aggressive-lazy-loading: false

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_db?useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: password
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5

Mapper 扫描

java
// 方式一:@MapperScan 注解
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// 方式二:每个 Mapper 加 @Mapper
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);
}

SqlSession 注入

java
// Mapper 通过 SqlSession 获取
try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectById(1L);
}

// Spring 环境自动注入
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public User getUser(Long id) {
        return userMapper.selectById(id);
    }
}

批处理

JDBC 批处理

xml
<!-- UserMapper.xml -->
<insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO user (name, email) VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.name}, #{item.email})
    </foreach>
</insert>

ExecutorType.BATCH

java
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    for (int i = 0; i < 1000; i++) {
        User user = new User();
        user.setName("user" + i);
        user.setEmail("user" + i + "@example.com");
        mapper.insert(user);
    }
    
    session.commit();  // 一次性执行所有批量操作
}

性能对比

方式1000 条数据耗时
单条插入~3000ms
批量 INSERT VALUES~50ms
ExecutorType.BATCH~80ms

性能优化

1. 延迟加载优化

xml
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 只触发关联对象时才加载 -->
    <setting name="lazyLoadTriggerMethods" value="getUser,getOrders"/>
</settings>

2. 预热缓存

java
// 应用启动时预热缓存
@Override
public void afterPropertiesSet() {
    // 查询常用数据,填充缓存
    userMapper.selectAll();
}

3. 防止 N+1 查询

xml
<!-- 嵌套结果优于嵌套查询 -->
<resultMap id="UserWithOrders" type="User">
    <association property="user" javaType="User">
        <!-- 嵌套结果,一次查询 -->
    </association>
</resultMap>

<!-- 而非嵌套查询(N+1) -->
<resultMap id="UserWithOrders2" type="User">
    <association column="user_id" select="selectUserById"/>
    <!-- 先查 user,再查 orders,N+1 问题 -->
</resultMap>

4. 使用抓取策略

java
// 只查询,不触发关联查询
User user = session.selectOne("selectUser", 1L, 
    new FetchPolicy(FetchType.LAZY));  // 自定义策略

5. 批量操作优化

yaml
# 连接池配置
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

常见问题

1. 找不到 Mapper

xml
<!-- 检查 mapper-locations 路径 -->
mybatis:
  mapper-locations: classpath:mapper/*.xml  # 或 classpath*:

2. 列名与属性名不匹配

xml
<!-- 方案一:开启驼峰映射 -->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

<!-- 方案二:使用 resultMap -->
<resultMap id="ResultMap" type="User">
    <result column="user_name" property="name"/>
</resultMap>

3. 事务未提交

java
// 确保 commit
session.insert("insert", user);
session.commit();  // 或 autoCommit=true

4. 性能问题

java
// 添加索引
// 分页查询
// 减少关联查询深度
// 使用懒加载

相关页面

  • **Java\MyBatis\MyBatis-完全指南** - 返回总览
  • **Java\MyBatis\MyBatis-核心配置** - 配置详解
  • **Java\MyBatis\MyBatis-Mapper映射** - SQL 映射
  • **Java\MyBatis\MyBatis-关联查询** - 复杂查询
  • **Java\MyBatis\MyBatis-缓存机制** - 缓存配置

#标签 #mybatis #分页 #逆向工程 #spring #batch #performance

Knowledge4J — Java 知识库