Spring整合MyBatis实战:从配置到性能优化
2026/8/10 9:32:33 网站建设 项目流程

1. 为什么选择Spring整合MyBatis?

在Java企业级应用开发中,持久层框架的选择往往决定了项目的开发效率和后期维护成本。MyBatis作为半自动化的ORM框架,与Spring生态的整合已经成为现代Java开发的标配组合。这种组合方式完美结合了Spring的依赖注入优势和MyBatis的SQL灵活性。

我经历过多个从Hibernate迁移到MyBatis的项目,最大的感受是:对于复杂业务系统,特别是需要精细控制SQL性能的场景,MyBatis提供的原生SQL控制能力是无可替代的。Spring的IoC容器则让MyBatis的配置和使用变得更加优雅。

注意:虽然JPA规范日趋完善,但在需要复杂查询、存储过程调用或数据库特定功能时,MyBatis仍然是更灵活的选择。

1.1 技术选型对比

让我们先看一个简单的对比表格,了解不同持久层方案的特点:

特性纯JDBCHibernateMyBatisSpring Data JPA
SQL控制度完全控制几乎无控制完全控制有限控制
学习曲线陡峭中等平缓平缓
性能优化空间极大有限极大有限
与Spring集成难度极低
适合场景遗留系统CRUD密集型复杂查询快速开发

从实际项目经验来看,MyBatis特别适合以下场景:

  • 需要复用现有复杂SQL的场景
  • 需要调用存储过程的项目
  • 对性能有极致要求的核心业务模块
  • 数据库设计不符合JPA规范的遗留系统

1.2 版本兼容性考量

在开始整合前,版本匹配是首要考虑的问题。根据我的踩坑经验,不同版本的组合可能会带来意想不到的问题:

  • Spring 5.x + MyBatis 3.5+ 是最稳定的组合
  • 如果使用Spring Boot,2.7.x版本对MyBatis的支持最成熟
  • 要特别注意MyBatis-Spring桥接包的版本,它必须与MyBatis核心版本匹配

我曾经在一个项目中使用Spring Boot 2.4 + MyBatis 3.4.6,结果遇到了事务管理不生效的问题。后来发现是桥接包版本不兼容导致的。这个教训告诉我:版本匹配表必须作为项目启动时的第一份技术文档。

2. 环境准备与基础配置

2.1 Maven依赖配置

对于Maven项目,pom.xml中需要配置以下核心依赖:

<!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.7.12</version> </dependency> <!-- MyBatis核心 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- MyBatis-Spring桥接 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!-- Spring JDBC (事务管理需要) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.23</version> </dependency> <!-- 数据库驱动 (以MySQL为例) --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>

在实际项目中,我通常会额外添加这些实用依赖:

  • MyBatis Plus(增强工具包)
  • PageHelper(分页插件)
  • Druid(高性能连接池)

2.2 数据源配置

Spring Boot的application.yml中配置数据源:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource # Druid连接池专用配置 druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false

经验分享:Druid的连接池监控页面非常实用,可以通过以下配置开启:

spring: datasource: druid: stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admin

2.3 MyBatis配置类

创建MyBatis的Spring配置类:

@Configuration @MapperScan("com.example.mapper") // 指定Mapper接口所在包 public class MyBatisConfig { @Autowired private DataSource dataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); // 配置XML文件位置 factoryBean.setMapperLocations( new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/*.xml")); // 添加自定义配置 org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); configuration.setMapUnderscoreToCamelCase(true); // 开启驼峰命名转换 configuration.setDefaultFetchSize(100); // 设置默认获取数量 configuration.setDefaultStatementTimeout(30); // 设置超时时间 factoryBean.setConfiguration(configuration); return factoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } }

我曾经在一个项目中因为没有设置defaultStatementTimeout,导致某些复杂查询长时间运行却不超时,最终拖垮了整个数据库。这个配置项对于生产环境至关重要。

3. Mapper开发全流程

3.1 接口与XML的对应关系

MyBatis的核心设计理念就是将Java接口与XML映射文件绑定。这种设计带来了极大的灵活性,但也容易因配置不当导致各种问题。

正确的对应关系应该遵循这些规则:

  1. 接口全限定名与XML的namespace完全一致
  2. 方法名与XML中的id属性一致
  3. 参数类型与parameterType匹配
  4. 返回类型与resultType/resultMap匹配

一个典型的Mapper接口示例:

public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User selectById(@Param("id") Long id); List<User> selectByCondition(UserQuery query); @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("INSERT INTO users(name,email) VALUES(#{name},#{email})") int insert(User user); @Update("UPDATE users SET name=#{name}, email=#{email} WHERE id=#{id}") int update(User user); @Delete("DELETE FROM users WHERE id=#{id}") int delete(Long id); }

对应的XML映射文件(resources/mapper/UserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.UserMapper"> <resultMap id="userResultMap" type="com.example.entity.User"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="email" column="email"/> <result property="createTime" column="create_time"/> </resultMap> <select id="selectByCondition" resultMap="userResultMap"> SELECT * FROM users <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="email != null"> AND email = #{email} </if> <if test="createTimeStart != null"> AND create_time >= #{createTimeStart} </if> <if test="createTimeEnd != null"> AND create_time <= #{createTimeEnd} </if> </where> ORDER BY id DESC </select> </mapper>

3.2 动态SQL技巧

MyBatis强大的动态SQL能力是其核心竞争力之一。以下是几种常用的动态SQL写法:

条件查询(where + if)

<select id="searchUsers" resultType="User"> SELECT * FROM users <where> <if test="name != null"> name = #{name} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>

选择更新(set + if)

<update id="updateUser" parameterType="User"> UPDATE users <set> <if test="name != null">name=#{name},</if> <if test="email != null">email=#{email},</if> </set> WHERE id=#{id} </update>

批量插入(foreach)

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

多条件选择(choose/when/otherwise)

<select id="selectByStatus" resultType="User"> SELECT * FROM users <where> <choose> <when test="status == 'active'"> AND active = 1 </when> <when test="status == 'inactive'"> AND active = 0 </when> <otherwise> AND active IS NOT NULL </otherwise> </choose> </where> </select>

避坑指南:在foreach中使用集合参数时,务必注意collection属性的取值:

  • 单参数List类型:collection="list"
  • 单参数数组类型:collection="array"
  • 多参数使用@Param注解:collection="参数名"

3.3 结果映射高级技巧

MyBatis的结果映射(resultMap)功能非常强大,可以处理各种复杂的映射场景。

一对一关联查询

<resultMap id="orderWithUserMap" type="Order"> <id property="id" column="order_id"/> <result property="orderNo" column="order_no"/> <!-- 关联用户对象 --> <association property="user" javaType="User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> </association> </resultMap> <select id="selectOrderWithUser" resultMap="orderWithUserMap"> SELECT o.id as order_id, o.order_no, u.id as user_id, u.name as user_name FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE o.id = #{id} </select>

一对多关联查询

<resultMap id="userWithOrdersMap" type="User"> <id property="id" column="id"/> <result property="name" column="name"/> <!-- 订单集合 --> <collection property="orders" ofType="Order"> <id property="id" column="order_id"/> <result property="orderNo" column="order_no"/> </collection> </resultMap> <select id="selectUserWithOrders" resultMap="userWithOrdersMap"> SELECT u.id, u.name, o.id as order_id, o.order_no FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id = #{id} </select>

嵌套结果映射

<resultMap id="detailedOrderMap" type="Order"> <id property="id" column="id"/> <association property="user" resultMap="userMap"/> <collection property="items" resultMap="itemMap"/> </resultMap> <resultMap id="userMap" type="User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> </resultMap> <resultMap id="itemMap" type="OrderItem"> <id property="id" column="item_id"/> <result property="productName" column="product_name"/> </resultMap>

枚举类型处理

public enum UserStatus { ACTIVE(1), INACTIVE(0), LOCKED(-1); private final int code; UserStatus(int code) { this.code = code; } public int getCode() { return code; } public static UserStatus fromCode(int code) { for (UserStatus status : values()) { if (status.code == code) { return status; } } throw new IllegalArgumentException("未知状态码: " + code); } }
<resultMap id="userResultMap" type="User"> <result property="status" column="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> </resultMap>

4. 高级特性与性能优化

4.1 插件开发实战

MyBatis的插件机制允许我们在SQL执行的各个阶段插入自定义逻辑。最常见的插件应用场景包括:

  • SQL性能监控
  • 分页处理
  • 敏感数据加解密
  • 审计字段自动填充

下面是一个完整的SQL执行时间统计插件实现:

@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}) }) public class SqlCostTimeInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(SqlCostTimeInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { long startTime = System.currentTimeMillis(); try { return invocation.proceed(); } finally { long costTime = System.currentTimeMillis() - startTime; StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); String sql = statementHandler.getBoundSql().getSql(); if (costTime > 200) { logger.warn("SQL执行耗时: {}ms - {}", costTime, sql); } else { logger.debug("SQL执行耗时: {}ms - {}", costTime, sql); } } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以读取配置参数 } }

注册插件到MyBatis配置:

@Bean public SqlCostTimeInterceptor sqlCostTimeInterceptor() { return new SqlCostTimeInterceptor(); } @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> { configuration.addInterceptor(sqlCostTimeInterceptor()); }; }

4.2 二级缓存配置

MyBatis的二级缓存可以显著提升查询性能,但使用不当会导致脏读问题。以下是安全使用二级缓存的配置方式:

<!-- 在mybatis-config.xml中 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- 在Mapper.xml中 --> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

实际项目中,我建议:

  1. 只在读多写少的场景使用二级缓存
  2. 设置合理的flushInterval(如5分钟)
  3. 避免在分布式环境中使用本地缓存
  4. 对于关键业务数据,考虑手动控制缓存清除

4.3 批量操作优化

批量操作是性能优化的重点。以下是几种批量处理的正确姿势:

方式一:foreach批量插入

public interface UserMapper { void batchInsert(@Param("list") List<User> users); }
<insert id="batchInsert"> INSERT INTO users (name, email) VALUES <foreach collection="list" item="user" separator=","> (#{user.name}, #{user.email}) </foreach> </insert>

方式二:BATCH执行器

// 获取批量模式的SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); try { UserMapper mapper = sqlSession.getMapper(UserMapper.class); for (User user : userList) { mapper.insert(user); } sqlSession.commit(); } finally { sqlSession.close(); }

方式三:rewriteBatchedStatements在JDBC连接字符串中添加rewriteBatchedStatements=true可以显著提升MySQL批量插入性能:

jdbc:mysql://localhost:3306/db?rewriteBatchedStatements=true

性能测试数据:在插入10000条记录时,普通插入耗时约12秒,批量模式仅需1.5秒,而rewriteBatchedStatements+批量模式仅需0.8秒。

4.4 分页查询实现

分页是Web应用中最常见的需求之一。以下是几种分页方案对比:

1. 使用PageHelper插件

// 引入依赖 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.1</version> </dependency> // 使用示例 PageHelper.startPage(1, 10); // 第1页,每页10条 List<User> users = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<>(users);

2. 手动实现分页

<select id="selectByPage" resultType="User"> SELECT * FROM users ORDER BY id LIMIT #{offset}, #{pageSize} </select>

3. 使用MyBatis-Plus分页

// 配置分页插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } // 使用示例 Page<User> page = new Page<>(1, 10); userMapper.selectPage(page, null); List<User> records = page.getRecords();

5. 生产环境最佳实践

5.1 SQL注入防护

虽然MyBatis使用预编译语句可以有效防止SQL注入,但不当使用仍然存在风险:

危险写法($符号直接拼接)

<select id="selectByOrder" resultType="User"> SELECT * FROM users ORDER BY ${columnName} </select>

安全写法(使用#{}或白名单校验)

// 在Java代码中校验排序字段 private static final Set<String> ALLOWED_SORT_COLUMNS = Set.of("id", "name", "email", "create_time"); public List<User> selectUsers(String sortBy) { if (!ALLOWED_SORT_COLUMNS.contains(sortBy)) { sortBy = "id"; } return userMapper.selectByOrder(sortBy); }
<select id="selectByOrder" resultType="User"> SELECT * FROM users ORDER BY ${columnName} <!-- 注意:即使使用白名单校验,$仍然有风险,最好在应用层排序 --> </select>

5.2 事务管理

Spring与MyBatis整合后,可以使用Spring的声明式事务管理:

@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) public void updateUser(User user) { // 业务逻辑... userMapper.update(user); // 更多数据库操作... } @Transactional(propagation = Propagation.REQUIRES_NEW) public void logOperation(OperationLog log) { // 独立事务记录日志 } }

常见事务问题排查:

  1. 确保使用的是Spring代理对象(调用事务方法的类不能被final修饰)
  2. 检查异常是否被捕获未抛出(默认只回滚RuntimeException)
  3. 确认数据源是否配置了事务管理器
  4. 检查@Transactional是否添加在public方法上

5.3 监控与调优

生产环境必备的监控措施:

1. Druid监控

spring: datasource: druid: filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 1000 web-stat-filter: enabled: true aop-patterns: com.example.mapper.*

2. 慢SQL日志

<!-- mybatis-config.xml --> <settings> <setting name="logImpl" value="SLF4J"/> </settings> <!-- logback.xml --> <logger name="org.mybatis" level="DEBUG"/> <logger name="java.sql" level="DEBUG"/>

3. 连接池监控指标

@RestController public class DruidStatController { @Autowired private DataSource dataSource; @GetMapping("/druid/stat") public Object druidStat() { DruidDataSource druidDataSource = (DruidDataSource) dataSource; Map<String, Object> stat = new HashMap<>(); stat.put("activeCount", druidDataSource.getActiveCount()); stat.put("activePeak", druidDataSource.getActivePeak()); stat.put("poolingCount", druidDataSource.getPoolingCount()); stat.put("connectCount", druidDataSource.getConnectCount()); stat.put("waitThreadCount", druidDataSource.getWaitThreadCount()); return stat; } }

5.4 常见问题解决方案

问题1:Mapper接口无法注入

  • 检查@MapperScan是否配置正确
  • 确认Mapper接口是否有@Repository或@Mapper注解
  • 检查XML文件是否在classpath对应路径

问题2:字段值为null

  • 检查数据库字段名与Java属性名是否匹配(开启mapUnderscoreToCamelCase)
  • 确认resultMap配置是否正确
  • 检查Getter/Setter方法是否存在

问题3:事务不生效

  • 确认是否抛出了RuntimeException
  • 检查是否调用了同类中的其他方法(自调用问题)
  • 确认数据源是否配置了事务管理器

问题4:分页插件冲突

  • 检查是否引入了多个分页插件(如PageHelper和MyBatis-Plus分页)
  • 确认插件执行顺序是否正确

问题5:XML特殊字符处理

<!-- 错误写法 --> <select id="selectActiveUsers"> SELECT * FROM users WHERE status = 1 AND age < 30 </select> <!-- 正确写法 --> <select id="selectActiveUsers"> SELECT * FROM users WHERE status = 1 AND age &lt; 30 </select> <!-- 或者使用CDATA --> <select id="selectActiveUsers"> <![CDATA[ SELECT * FROM users WHERE status = 1 AND age < 30 ]]> </select>

6. 扩展与集成方案

6.1 与MyBatis-Plus整合

MyBatis-Plus是MyBatis的增强工具,提供了大量开箱即用的功能:

// 基础Mapper接口 public interface UserMapper extends BaseMapper<User> { // 已经内置了基本的CRUD方法 } // 服务层示例 @Service public class UserService { @Autowired private UserMapper userMapper; public List<User> selectByCondition(UserQuery query) { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(query.getName()), "name", query.getName()) .eq(query.getStatus() != null, "status", query.getStatus()) .between(query.getStartTime() != null && query.getEndTime() != null, "create_time", query.getStartTime(), query.getEndTime()); return userMapper.selectList(wrapper); } }

6.2 多数据源配置

大型项目往往需要访问多个数据源:

@Configuration @MapperScan(basePackages = "com.example.primary.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory") public class PrimaryDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean public SqlSessionFactory primarySqlSessionFactory( @Qualifier("primaryDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); return factoryBean.getObject(); } @Bean public DataSourceTransactionManager primaryTransactionManager( @Qualifier("primaryDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } // 第二个数据源配置类似,使用不同的包扫描路径和Bean名称

6.3 代码生成器配置

MyBatis Generator可以自动生成基础代码:

<!-- generatorConfig.xml --> <context id="mysql" targetRuntime="MyBatis3"> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis_demo" userId="root" password="123456"/> <javaModelGenerator targetPackage="com.example.entity" targetProject="src/main/java"/> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"/> <table tableName="user" domainObjectName="User"/> <table tableName="order" domainObjectName="Order"/> </context>

运行生成器:

public class MyBatisGenerator { public static void main(String[] args) throws Exception { List<String> warnings = new ArrayList<>(); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration( MyBatisGenerator.class.getResourceAsStream("/generatorConfig.xml")); DefaultShellCallback callback = new DefaultShellCallback(true); MyBatisGenerator generator = new MyBatisGenerator(config, callback, warnings); generator.generate(null); warnings.forEach(System.out::println); } }

6.4 与Spring Boot深度整合

Spring Boot提供了自动配置的MyBatis starter:

# application.yml mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true default-fetch-size: 100 default-statement-timeout: 30

自定义配置类:

@Configuration public class MyBatisCustomConfig { @Bean public ConfigurationCustomizer mybatisConfigurationCustomizer() { return configuration -> { // 添加自定义类型处理器 configuration.getTypeHandlerRegistry() .register(MyEnumTypeHandler.class); // 添加插件 configuration.addInterceptor(new SqlCostTimeInterceptor()); }; } }

7. 实战案例:用户管理系统

7.1 领域模型设计

// 用户实体 @Data public class User { private Long id; private String username; private String password; private String email; private UserStatus status; private Date createTime; private Date updateTime; } // 查询条件封装 @Data public class UserQuery { private String username; private String email; private UserStatus status; private Date createTimeStart; private Date createTimeEnd; private Integer pageNum = 1; private Integer pageSize = 10; }

7.2 核心Mapper实现

public interface UserMapper { // 基础CRUD @Select("SELECT * FROM user WHERE id = #{id}") User selectById(Long id); @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("INSERT INTO user(username,password,email,status) " + "VALUES(#{username},#{password},#{email},#{status})") int insert(User user); @Update("UPDATE user SET username=#{username},email=#{email}," + "status=#{status} WHERE id=#{id}") int update(User user); @Delete("DELETE FROM user WHERE id=#{id}") int delete(Long id); // 条件查询 List<User> selectByCondition(UserQuery query); // 批量操作 int batchInsert(@Param("list") List<User> users); // 关联查询 @Select("SELECT u.* FROM user u JOIN user_role ur ON u.id = ur.user_id " + "WHERE ur.role_id = #{roleId}") List<User> selectByRoleId(Long roleId); }

对应的XML映射文件:

<select id="selectByCondition" resultType="User"> SELECT * FROM user <where> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <if test="email != null and email != ''"> AND email = #{email} </if> <if test="status != null"> AND status = #{status} </if> <if test="createTimeStart != null"> AND create_time >= #{createTimeStart} </if> <if test="createTimeEnd != null"> AND create_time <= #{createTimeEnd} </if> </where> ORDER BY id DESC LIMIT #{offset}, #{pageSize} </select>

7.3 服务层实现

@Service @Transactional(readOnly = true) public class UserService { @Autowired private UserMapper userMapper; public PageInfo<User> queryUsers(UserQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); List<User> users = userMapper.selectByCondition(query); return new PageInfo<>(users); } @Transactional(rollbackFor = Exception.class) public void createUser(User user) { user.setCreateTime(new Date()); user.setUpdateTime(new Date()); userMapper.insert(user); } @Transactional(rollbackFor = Exception.class) public void updateUser(User user) { user.setUpdateTime(new Date()); userMapper.update(user); } @Transactional(rollbackFor = Exception.class) public void deleteUser(Long id) { userMapper.delete(id); } @Transactional(rollbackFor = Exception.class) public void batchCreateUsers(List<User> users) { users.forEach(user -> { user.setCreateTime(new Date()); user.setUpdateTime(new Date()); }); userMapper.batchInsert(users); } }

7.4 控制器层

@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping public Result<PageInfo<User>> listUsers(UserQuery query) { return Result.success(userService.queryUsers(query)); } @PostMapping public Result<Void> createUser(@RequestBody @Valid User user) { userService.createUser(user); return Result.success(); } @PutMapping("/{id}") public Result<Void> updateUser(@PathVariable Long id, @RequestBody @Valid User user) { user.setId(id); userService.updateUser(user); return Result.success(); } @DeleteMapping("/{id}") public Result<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return Result.success(); } }

8. 性能调优实战

8.1 SQL执行计划分析

通过EXPLAIN分析SQL性能:

public interface ExplainMapper { @Select("EXPLAIN ${sql}") List<Map<String, Object>> explain(@Param("sql") String sql); } // 使用示例 public void analyzeQuery() { String sql = "SELECT * FROM user WHERE status = 1 ORDER BY create_time DESC"; List<Map<String, Object>> result = explainMapper.explain(sql); result.forEach(row -> { System.out.println("id: " + row.get("id")); System.out.println("select_type: " + row.get("select_type")); System.out.println("table: " + row.get("table")); System.out.println("type: " + row.get("type")); System.out.println("possible_keys: " + row.get("possible_keys")); System.out.println("key: " + row.get("key")); System.out.println("rows: " + row.get("rows")); System.out.println("Extra: " + row.get("Extra")); }); }

关键指标解读:

  • type:ALL表示全表扫描,应优化为range或ref
  • key:实际使用的索引
  • rows:预估扫描行数
  • Extra:Using filesort表示需要优化排序

8.2 索引优化策略

1. 复合索引设计

-- 为常用查询条件创建复合索引 ALTER TABLE user ADD INDEX idx_status_create_time (status, create_time); -- 覆盖索引优化 ALTER TABLE user ADD INDEX idx_username_email (username, email);

2. 索引使用要点

  • 遵循最左前缀原则
  • 避免在索引列上使用函数
  • 字符串字段考虑前缀索引
  • 区分度低的字段不适合建索引

8.3 连接池优化

Druid连接池关键参数调优:

spring: datasource: druid: # 初始连接数 initial-size: 10 # 最小空闲连接 min-idle: 10 # 最大活跃连接 max-active: 50 # 获取连接等待超时时间(毫秒) max-wait: 60000 # 配置间隔多久检测空闲连接(毫秒) time-between-eviction-runs-millis: 60000 # 连接最小生存时间(毫秒) min-evictable-idle-time-millis: 300000 # 测试连接有效性的SQL validation-query: SELECT 1 # 申请连接时执行validationQuery检测连接有效性 test-on-borrow: false # 归还连接时执行validationQuery检测连接有效性 test-on-return: false # 空闲时检测连接有效性 test-while-idle: true

8.4 缓存策略优化

**多级

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询