MyBatis 深度实战:从基础映射到企业级性能优化

在 Java 企业级应用开发中,持久层框架承担着数据访问的核心职责,而 MyBatis 凭借其 "半自动化" 的特性 ------ 既保留 SQL 的灵活性,又简化 JDBC 的繁琐操作 ------ 成为众多项目的首选。与 Hibernate 等全自动 ORM 框架相比,MyBatis 允许开发者直接编写 SQL 语句,更适合复杂查询场景和对性能有极致要求的系统。本文将从 MyBatis 的核心原理出发,系统讲解映射配置、动态 SQL、缓存机制、插件开发等高级特性,并结合电商、金融等实战场景,提供一套完整的 MyBatis 性能优化方案,帮助开发者构建高效、可维护的数据访问层。

一、MyBatis 核心原理:从配置到 SQL 执行的完整链路

要熟练使用 MyBatis,首先需要理解其底层工作流程。MyBatis 的核心在于 "配置驱动 SQL 执行",整个过程可分为初始化、SQL 解析、参数处理、执行与结果映射四个阶段,每个阶段都有明确的组件分工。

1. 初始化阶段:构建核心配置体系

MyBatis 的初始化始于SqlSessionFactory的创建,该过程通过加载核心配置文件(mybatis-config.xml)和映射文件(Mapper.xml),构建起全局唯一的配置体系。核心配置文件主要包含数据源、事务管理器、类型别名、插件等全局配置,而映射文件则定义 SQL 语句与 Java 方法的映射关系。

初始化关键步骤如下:

  1. 通过Resources.getResourceAsStream()加载 mybatis-config.xml 文件;
  1. 创建SqlSessionFactoryBuilder,调用build()方法解析配置文件,生成Configuration对象(MyBatis 的核心配置载体,存储所有配置信息);
  1. 由Configuration对象创建SqlSessionFactory,该对象是线程安全的,通常在应用启动时初始化一次。

示例代码如下:

复制代码

// MyBatis初始化示例

String resource = "mybatis-config.xml";

InputStream inputStream = Resources.getResourceAsStream(resource);

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

在 Spring 整合 MyBatis 的场景中,初始化过程由 Spring 容器托管,通过SqlSessionFactoryBean自动完成配置加载和SqlSessionFactory创建,开发者无需手动处理,只需在配置类中注入数据源即可:

复制代码

@Configuration

@MapperScan("com.example.mapper") // 扫描Mapper接口

public class MyBatisConfig {

@Autowired

private DataSource dataSource;

@Bean

public SqlSessionFactory sqlSessionFactory() throws Exception {

SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

sessionFactory.setDataSource(dataSource);

// 设置映射文件路径

sessionFactory.setMapperLocations(

new PathMatchingResourcePatternResolver()

.getResources("classpath:mapper/**/*.xml")

);

return sessionFactory.getObject();

}

}

2. SQL 执行阶段:动态解析与参数绑定

当调用SqlSession的方法执行 SQL 时,MyBatis 会经历 "SQL 解析 - 参数绑定 - 执行 - 结果映射" 的完整流程,核心依赖Executor、StatementHandler、ParameterHandler、ResultSetHandler四大组件:

  • Executor:执行器,负责管理缓存和调度 SQL 执行,分为BaseExecutor(基础执行器,含一级缓存)和CachingExecutor(缓存执行器,含二级缓存);
  • StatementHandler:处理 JDBC 的Statement对象,负责 SQL 语句的预处理和执行;
  • ParameterHandler:处理 SQL 参数,将 Java 对象转换为 JDBC 参数;
  • ResultSetHandler:处理查询结果,将 JDBC 的ResultSet转换为 Java 对象。

以查询用户信息为例,SQL 执行的关键流程如下:

  1. 通过SqlSession.getMapper(UserMapper.class)获取 Mapper 接口的代理对象(MyBatis 通过 JDK 动态代理生成);
  1. 调用userMapper.selectById(1)时,代理对象会根据方法全限定名(如com.example.mapper.UserMapper.selectById)在Configuration中找到对应的MappedStatement(存储 SQL 语句、参数类型、结果类型等信息);
  1. Executor根据MappedStatement创建StatementHandler,并由ParameterHandler将参数 "1" 绑定到 SQL 的?占位符;
  1. StatementHandler执行 SQL,获取ResultSet,再由ResultSetHandler将结果映射为User对象并返回。

这一流程的核心优势在于 "动态性"------MyBatis 会根据参数类型和 SQL 语句的结构,自动处理参数绑定和结果映射,避免了 JDBC 中手动设置参数、遍历结果集的繁琐代码。

二、MyBatis 核心特性:从基础映射到动态 SQL

MyBatis 的灵活性体现在其丰富的配置特性上,从基础的结果映射到复杂的动态 SQL,再到高级的关联查询,每一项特性都针对实际开发中的痛点设计,帮助开发者高效编写数据访问代码。

1. 结果映射:解决 "字段名与属性名不匹配" 问题

在数据库表设计中,字段名通常采用 "下划线命名法"(如user_name),而 Java 实体类属性采用 "驼峰命名法"(如userName),这种不匹配会导致 MyBatis 无法自动映射结果。MyBatis 提供了三种解决方案:

(1)通过resultMap手动配置映射

这是最灵活的方式,适用于字段与属性映射关系复杂的场景,例如:

复制代码

UserResultMap" type="com.example.entity.User">

_id" property="userId"/> _name" property="userName"/> -->

="user_age" property="userAge"/>

" property="createTime" jdbcType="TIMESTAMP"/>

<select id="selectById" resultMap="UserResultMap">

SELECT user_id, user_name, user_age, create_time

FROM t_user

WHERE user_id = #{userId}

(2)开启驼峰命名自动映射

在 mybatis-config.xml 中配置mapUnderscoreToCamelCase为true,MyBatis 会自动将 "下划线命名" 的字段转换为 "驼峰命名" 的属性,无需手动配置resultMap:

复制代码

batis-config.xml -->

<settings>

name="mapUnderscoreToCamelCase" value="true"/>

>

语句可直接使用resultType -->

selectById" resultType="com.example.entity.User">

SELECT user_id, user_name, user_age, create_time

FROM t_user

WHERE user_id = #{userId}

</select>

(3)使用别名映射

在 SQL 语句中通过AS为字段设置别名,使其与 Java 属性名一致,适用于临时映射场景:

复制代码

selectById" resultType="com.example.entity.User">

SELECT

user_id AS userId,

user_name AS userName,

user_age AS userAge,

create_time AS createTime

FROM t_user

WHERE user_id = #{userId}

</select>

2. 动态 SQL:应对复杂查询条件的 "利器"

在实际开发中,查询条件往往是动态变化的(如电商平台的多条件筛选),若通过 Java 代码拼接 SQL,不仅繁琐且易引发 SQL 注入风险。MyBatis 的动态 SQL 通过<if>、<choose>、、,允许在 XML 中编写逻辑判断,动态生成 SQL 语句,既安全又高效。

(1)<if>与<where>:条件判断与 SQL 拼接

例如,实现 "根据用户名和年龄查询用户" 的动态查询,当用户名或年龄为空时,自动忽略对应的条件:

复制代码

id="selectByCondition" resultType="com.example.entity.User">

SELECT * FROM t_user

<where> AND/OR,避免SQL语法错误 -->

<if test="userName != null and userName != ''">

AND user_name LIKE CONCAT('%', #{userName}, '%')

</if>

<if test="userAge != null">

AND user_age = #{userAge}

</select>

移除条件中多余的AND或OR,避免生成SELECT * FROM t_user WHERE AND user_age = 20` 这类语法错误。

(2)<foreach>:批量操作与集合参数处理

批量查询(如根据多个用户 ID 查询)或批量插入场景中,可将Java集合转换为SQL中的IN` 条件或值列表:

复制代码

-->

selectByIds" resultType="com.example.entity.User">

SELECT * FROM t_user

WHERE user_id IN

="list" item="id" open="(" separator="," close=")">

#{id}

</foreach>

Insert">

INSERT INTO t_user (user_name, user_age, create_time)

VALUES

item="user" separator=",">

(#{user.userName}, #{user.userAge}, #{user.createTime})

其中,`collection`指定集合参数的名称(若为List则默认`list`,若为数组则默认`array`,若为Map则指定Key),`item`为集合元素的别名,`open`和`close`指定SQL片段的开头和结尾,`separator`指定元素间的分隔符。

#### (3)`分支判断

类似Java中的`if-else`,适用于"多个条件中仅满足一个"的场景,例如"优先根据用户ID查询,若ID为空则根据用户名查询":

```````xml````

id="selectByChoose" resultType="com.example.entity.User">

SELECT * FROM t_user

<where>

>

="userId != null">

AND user_id = #{userId}

>

="userName != null and userName != ''">

AND user_name = #{userName}

>

AND user_age > 18

</otherwise>

</choose>

>

3. 关联查询:处理一对一与一对多关系

在数据库设计中,表与表之间常存在关联关系(如 "用户 - 订单" 的一对多关系、"订单 - 支付记录" 的一对一关系)。MyBatis 通过>(一对一)和<collection>(一对多)标签,实现关联对象的嵌套映射,避免手动编写多表查询后的结果组装代码。

(1)一对一关联:association

以 "订单 - 支付记录" 为例,一个订单对应一条支付记录,映射如下:

复制代码

实体类:包含Payment属性 -->

public class Order {

private Long orderId;

private String orderNo;

private Payment payment; // 一对一关联的支付记录

// getter/setter

}

OrderWithPaymentResultMap" type="com.example.entity.Order">

="order_id" property="orderId"/>

<result column="order_no" property="orderNo"/>

-->

property="payment" javaType="com.example.entity.Payment">

="pay_id" property="payId"/>

<result column="pay_amount" property="payAmount"/>

_time" property="payTime"/>

>

OrderWithPayment" resultMap="OrderWithPaymentResultMap">

SELECT

o.order_id, o.order_no,

p.pay_id, p.pay_amount, p.pay_time

FROM t_order o

LEFT JOIN t_payment p ON o.order_id = p.order_id

WHERE o.order_id = #{orderId}

>

(2)一对多关联:collection

以 "用户 - 订单" 为例,一个用户对应多个订单,映射如下:

复制代码

:包含List属性 -->

public class User {

private Long userId;

private String userName;

private List<Order> orders; // 一对多关联的订单列表

// getter/setter

}

Mapper.xml -->

Map id="UserWithOrdersResultMap" type="com.example.entity.User">

" property="userId"/>

_name" property="userName"/>

多关联映射 -->

ofType="com.example.entity.Order">

<id column="order_id" property="orderId"/>

" property="orderNo"/>

order_time" property="orderTime"/>

>

UserWithOrders" resultMap="UserWithOrdersResultMap">

SELECT

u.user_id, u.user_name,

o.order_id, o.order_no, o.order_time

FROM t_user u

LEFT JOIN t_order o ON u.user_id = o.user_id

WHERE u.user_id = #{userId}

</select>

需注意,association的javaType指定关联对象的类型,而collection的ofType指定集合中元素的类型,避免混淆。

三、MyBatis 缓存机制:提升查询性能的关键手段

缓存是优化数据库查询性能的核心手段,MyBatis 提供了一级缓存(本地缓存)和二级缓存(全局缓存),通过减少数据库访问次数,大幅提升高频查询的响应速度。

1. 一级缓存:SqlSession 级别的本地缓存

一级缓存是 MyBatis 的默认缓存,作用域为SqlSession(即一次数据库会话)。当在同一个SqlSession中执行相同的 SQL 查询时,MyBatis 会将第一次查询的结果存入一级缓存,后续查询直接从缓存中获取,无需再次访问数据库。

一级缓存的核心特性

  • 默认开启,无需额外配置;
  • 缓存的 key 由MappedStatement的 ID(即 SQL 的唯一标识)、SQL 语句、参数、分页信息等共同决定;
  • 当执行insert、update、delete操作或调用SqlSession.clearCache()时,一级缓存会被清空,避免缓存数据与数据库数据不一致。

示例代码验证一级缓存:

复制代码

try (SqlSession session = sqlSessionFactory.openSession()) {

UserMapper mapper = session.getMapper(UserMapper.class);

// 第一次查询:访问数据库,结果存入一级缓存

User user1 = mapper.selectById(1);

// 第二次查询:相同SqlSession,从一级缓存获取,不访问数据库

User user2 = mapper.selectById(1);

System.out.println(user1 == user2); // true(同一对象)

}

2. 二级缓存:SqlSessionFactory 级别的全局缓存

二级缓存的作用域为SqlSessionFactory,即多个SqlSession共享缓存数据,适用于多线程共享高频查询结果的场景(如电商平台的商品列表查询)。二级缓存需要手动开启,且实体类需实现Serializable接口(缓存数据需序列化存储)。

(1)二级缓存的开启步骤
  1. 在 mybatis-config.xml 中开启二级缓存全局配置(默认已开启,可显式配置):
复制代码

>

="cacheEnabled" value="true"/>

  1. 在需要使用二级缓存的 Mapper.xml 中添加 ` 标签:
复制代码

<!-- UserMapper.xml -->

<cache

eviction="LRU" 缓存淘汰策略:LRU(最近最少使用) -->

flushInterval="60000" 缓存刷新间隔:60秒 -->

size="1024" -->

readOnly="false"/> false表示可写入 -->

  1. 确保实体类实现Serializable接口:
复制代码

public class User implements Serializable {

// 字段与 getter/setter

}

(2)二级缓存的执行流程
  1. 第一个SqlSession执行查询后,结果会先存入一级缓存;
  1. 当SqlSession提交(commit())或关闭(close())时,一级缓存的数据会被同步到二级缓存;
  1. 第二个SqlSession执行相同查询时,会先从二级缓存获取数据,若缓存命中则直接返回,否则访问数据库并更新缓存。

示例代码验证二级缓存:

复制代码

// 第一个SqlSession

try (SqlSession session1 = sqlSessionFactory.openSession()) {

UserMapper mapper1 = session1.getMapper(UserMapper.class);

User user1 = mapper1.selectById(1); // 访问数据库

session1.commit(); // 提交后,一级缓存同步到二级缓存

}

// 第二个SqlSession

try (SqlSession session2 = sqlSessionFactory.openSession()) {

UserMapper mapper2 = session2.getMapper(UserMapper.class);

User user2 = mapper2.selectById(1); // 从二级缓存获取,不访问数据库

}

(3)二级缓存的注意事项
  • 二级缓存不适用于数据频繁修改的场景(如订单表),否则会因缓存频繁刷新导致性能下降;
  • 若 Mapper.xml 中某条 SQL 不需要使用二级缓存,可在>标签中设置useCache="false":
复制代码

="selectById" resultType="User" useCache="false">

SELECT * FROM t_user WHERE user_id = #{userId}

</select>

  • 执行insert/update/delete操作时,MyBatis 会自动清空当前 Mapper 的二级缓存,可通过flushCache="false"禁用(不推荐,可能导致数据不一致)。

四、MyBatis 性能优化:从 SQL 到配置的全方位调优

在高并发场景下(如电商秒杀、金融交易),MyBatis 的性能直接影响系统整体吞吐量。性能优化需从 SQL 优化、参数配置、插件使用三个维度入手,结合实际业务场景制定方案。

1. SQL 优化:减少数据库访问开销

SQL 优化是 MyBatis 性能优化的基础,低效的 SQL 会导致数据库压力剧增,即使缓存配置完善也无法弥补。常见的 SQL 优化手段包括:

(1)避免全表扫描,使用索引

确保查询条件中的字段(如WHERE、ORDER BY、JOIN关联字段)已建立索引,例如 "根据用户 ID 查询订单" 时,t_order表的user_id字段应建立索引。同时,避免使用SELECT *,而是显式指定需要查询的字段,减少数据传输量:

复制代码

优化前:全表扫描,查询所有字段 -->

ByUserId" resultType="Order">

SELECT * FROM t_order WHERE user_id = #{userId}

</select>

后:使用索引,只查询必要字段 -->

<select id="selectByUserId" resultType="Order">

SELECT order_id, order_no, order_time

FROM t_order

WHERE user_id = #{userId} 索引 -->

(2)优化关联查询,避免笛卡尔积

多表关联查询时,需确保JOIN条件明确,避免因关联条件缺失导致笛卡尔积(数据量呈指数级增长)。例如 "用户 - 订单 - 支付记录" 三表关联,需指定清晰的JOIN条件:

复制代码

="selectUserOrderPayment" resultMap="UserOrderPaymentResultMap">

SELECT

u.user_id, u.user_name,

o.order_id, o.order_no,

p.pay_id, p.pay_amount

FROM t_user u

LEFT JOIN t_order o ON u.user_id = o.user_id 明确关联条件 -->

LEFT JOIN t_payment p ON o.order_id = p.order_id 条件 -->

WHERE u.user_id = #{userId}

#### (3)使用分页查询,避免大量数据加载

查询结果集较大时(如超过100条),需使用分页查询,避免一次性加载大量数据导致内存溢出。MyBatis的分页可通过`RowBounds`(内存分页,不推荐)或插件(如PageHelper,物理分页,推荐)实现:

```````java````

// 使用PageHelper实现物理分页(推荐)

PageHelper.startPage(1, 10); // 第1页,每页10条

List> userList = userMapper.selectAll(); // 自动拼接LIMIT语句

PageInfo new PageInfo);

System.out.println("总记录数:" + pageInfo.getTotal());

System.out.println("总页数:" + pageInfo.getPages());

物理分页通过在 SQL 语句后拼接LIMIT(MySQL)或ROWNUM(Oracle),直接从数据库获取分页数据,避免内存分页的性能问题。

2. 配置优化:提升 MyBatis 运行效率

MyBatis 的核心配置参数对性能影响显著,合理配置可减少资源消耗,提升执行效率。

(1)数据源配置:使用连接池

MyBatis 默认使用简易的连接池,生产环境中需替换为性能更优的第三方连接池(如 HikariCP、Druid),减少数据库连接创建与销毁的开销。以 HikariCP 为例,配置如下:

复制代码

-->

production">

Manager type="JDBC"/>

="POOLED">

CP的数据源类 -->

SourceClassName" value="com.zaxxer.hikari.HikariDataSource"/>

<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis_db"/>

value="root"/>

value="123456"/>

<property name="maximumPoolSize" value="20"/>

minimumIdle" value="5"/> 数 -->

name="idleTimeout" value="300000"/> 连接超时时间 -->

</dataSource>

</environment>

HikariCP 是目前性能最优的 Java 连接池,相比 C3P0、DBCP,其连接获取速度更快,内存消耗更低。

(2)懒加载配置:延迟加载关联对象

在关联查询中,若暂时不需要关联对象的数据,可开启懒加载(延迟加载),仅当访问关联对象时才执行关联查询,减少不必要的数据库访问。配置如下:

复制代码

Enabled" value="true"/> -->

="aggressiveLazyLoading" value="false"/> 关闭积极懒加载,按需加载 -->

>

例如,查询用户时不立即加载订单列表,仅当调用user.getOrders()时才执行订单查询 SQL,适用于 "大部分场景只需用户基本信息" 的业务需求。

(3)参数映射优化:使用#而非$

MyBatis 的参数占位符有#和$两种:

  • #:预编译占位符,会将参数转换为?,通过PreparedStatement设置参数,可防止 SQL 注入,性能更优;
  • $:字符串替换占位符,直接将参数拼接到 SQL 中,存在 SQL 注入风险,仅适用于动态表名、动态字段名等特殊场景。

开发中应优先使用#,避免$的安全隐患:

复制代码

:使用#,预编译 -->

selectById" resultType="User">

SELECT * FROM t_user WHERE user_id = #{userId}

</select>

推荐:使用$,存在SQL注入风险(仅特殊场景使用) -->

<select id="selectByTableName" resultType="User">

SELECT * FROM ${tableName} WHERE user_id = #{userId}

</select>

3. 插件开发:扩展 MyBatis 功能

MyBatis 的插件机制允许开发者在 SQL 执行的关键节点(如参数处理、结果映射、SQL 执行)插入自定义逻辑,实现日志记录、性能监控、数据加密等功能。MyBatis 插件基于 JDK 动态代理实现,可拦截的四大核心组件为:Executor、StatementHandler、ParameterHandler、ResultSetHandler。

(1)插件开发步骤

以 "SQL 执行时间监控插件" 为例,开发流程如下:

  1. 实现Interceptor接口,重写intercept()(核心逻辑)、plugin()(生成代理对象)、setProperties()(配置参数)方法:
复制代码

@Intercepts({

@Signature(

type = Executor.class, // 拦截的组件类型

method = "query", // 拦截的方法

args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} // 方法参数

)

})

public class SqlExecutionTimeInterceptor implements Interceptor {

@Override

public Object intercept(Invocation invocation) throws Throwable {

// 1. 记录开始时间

long startTime = System.currentTimeMillis();

try {

// 2. 执行原方法(SQL查询)

return invocation.proceed();

} finally {

// 3. 计算执行时间并打印日志

long costTime = System.currentTimeMillis() - startTime;

MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

String sqlId = mappedStatement.getId(); // 获取SQL的唯一标识

System.out.printf("SQL [%s] 执行时间:%d ms%n", sqlId, costTime);

}

}

@Override

public Object plugin(Object target) {

// 生成代理对象,仅对Executor组件生效

return Plugin.wrap(target, this);

}

@Override

public void setProperties(Properties properties) {

// 读取插件配置参数(如日志级别)

}

}

  1. 在 mybatis-config.xml 中注册插件:
复制代码

>

ceptor="com.example.plugin.SqlExecutionTimeInterceptor">

) -->

name="logLevel" value="INFO"/>

</plugin>

  1. 启动应用后,执行 SQL 时会自动打印执行时间,便于定位慢查询。
(2)插件使用注意事项
  • 插件会生成代理对象,过度使用可能导致性能下降,建议仅在必要场景(如监控、加密)使用;
  • 拦截方法时需明确参数类型,避免因参数不匹配导致拦截失败;
  • 多个插件同时拦截同一组件时,需通过@Order注解指定执行顺序,避免逻辑冲突。

五、MyBatis 实战案例:电商订单系统的数据访问层设计

以电商订单系统为例,结合前文讲解的 MyBatis 特性,设计一套高效、可维护的数据访问层,涵盖订单查询、创建、批量操作等核心场景。

1. 需求分析与表设计

订单系统的核心表包括t_order(订单主表)、t_order_item(订单明细表)、t_payment(支付记录表),表结构简化如下:

  • t_order:order_id(主键)、order_no(订单编号)、user_id(用户 ID)、order_amount(订单金额)、order_status(订单状态)、create_time(创建时间);
  • t_order_item:item_id(主键)、order_id(订单 ID)、product_id(商品 ID)、product_name(商品名称)、quantity(购买数量)、price(商品单价);
  • t_payment:pay_id(主键)、order_id(订单 ID)、pay_amount(支付金额)、pay_type(支付方式)、pay_time(支付时间)。

2. Mapper 接口与映射文件设计

(1)订单查询:关联查询订单、明细、支付记录
复制代码

// OrderMapper.java

public interface OrderMapper {

// 查询订单及其明细、支付记录

OrderVO selectOrderDetail(@Param("orderId") Long orderId);

}

// OrderMapper.xml

OrderDetailResultMap" type="com.example.vo.OrderVO">

<id column="order_id" property="orderId"/>

" property="orderNo"/>

order_amount" property="orderAmount"/>

column="order_status" property="orderStatus"/>

-->

property="payment" javaType="com.example.entity.Payment">

="pay_id" property="payId"/>

<result column="pay_amount" property="payAmount"/>

_type" property="payType"/>

>

多关联订单明细 -->

Items" ofType="com.example.entity.OrderItem">

_id" property="itemId"/>

="product_id" property="productId"/>

<result column="product_name" property="productName"/>

" property="quantity"/>

" property="price"/>

</collection>

</resultMap>

id="selectOrderDetail" resultMap="OrderDetailResultMap">

SELECT

o.order_id, o.order_no, o.order_amount, o.order_status,

p.pay_id, p.pay_amount, p.pay_type,

i.item_id, i.product_id, i.product_name, i.quantity, i.price

FROM t_order o

LEFT JOIN t_payment p ON o.order_id = p.order_id

LEFT JOIN t_order_item i ON o.order_id = i.order_id

WHERE o.order_id = #{orderId}

(2)订单创建:插入订单主表与明细表(事务控制)
复制代码

// OrderMapper.java

public interface OrderMapper {

// 插入订单主表

int insertOrder(Order order);

// 批量插入订单明细表

int batchInsertOrderItem(@Param("orderItems") ListItems);

}

// OrderMapper.xml

id="insertOrder" useGeneratedKeys="true" keyProperty="orderId">

INSERT INTO t_order (order_no, user_id, order_amount, order_status, create_time)

VALUES (#{orderNo}, #{userId}, #{orderAmount}, #{orderStatus}, #{createTime})

InsertOrderItem">

INSERT INTO t_order_item (order_id, product_id, product_name, quantity, price)

VALUES

" item="item" separator=",">

(#{item.orderId}, #{item.productId}, #{item.productName}, #{item.quantity}, #{item.price})

</foreach>

</insert>

// Service层(事务控制)

@Service

public class OrderService {

@Autowired

private OrderMapper orderMapper;

@Transactional(rollbackFor = Exception.class)

public void createOrder(Order order, List<OrderItem> orderItems) {

// 插入订单主表,获取自增主键orderId

orderMapper.insertOrder(order);

// 为明细设置orderId

orderItems.forEach(item -> item.setOrderId(order.getOrderId()));

// 批量插入明细

orderMapper.batchInsertOrderItem(orderItems);

}

}

(3)订单筛选:动态 SQL 多条件查询
复制代码

// OrderMapper.java

public interface OrderMapper {

// 多条件筛选订单(支持用户ID、订单状态、时间范围)

List<OrderVO> selectOrderByFilter(OrderFilter filter);

}

// OrderFilter.java(筛选条件实体)

public class OrderFilter {

private Long userId;

private Integer orderStatus;

private Date startTime; // 下单开始时间

private Date endTime; // 下单结束时间

// getter/setter

}

// OrderMapper.xml

id="selectOrderByFilter" resultType="com.example.vo.OrderVO">

SELECT order_id, order_no, order_amount, order_status, create_time

FROM t_order

<where>

test="userId != null">

AND user_id = #{userId}

</if>

test="orderStatus != null">

AND order_status = #{orderStatus}

!= null and endTime != null">

AND create_time BETWEEN #{startTime} AND #{endTime}

ORDER BY create_time DESC

</select>

3. 性能优化措施

  1. 缓存优化:对 "订单状态查询" 等高频且不常修改的场景,开启二级缓存,缓存刷新间隔设置为 5 分钟;
  1. SQL 优化:为t_order表的user_id、order_status、create_time字段建立联合索引,提升多条件查询效率;
  1. 连接池优化:使用 HikariCP,设置最大连接数为 30,最小空闲连接数为 10,适配订单系统的并发需求;
  1. 分页优化:订单列表查询使用 PageHelper 实现物理分页,避免一次性加载大量订单数据。

六、MyBatis 常见问题与解决方案

在实际开发中,MyBatis 常遇到 "参数绑定错误""结果映射失败""缓存数据不一致" 等问题,以下是高频问题的解决方案:

1. 参数绑定错误:There is no getter for property named 'xxx' in 'class java.lang.Integer'

原因:当方法参数为单个基本类型或包装类型时,MyBatis 默认将参数名称视为param1,若在 XML 中使用#{xxx}(如#{userId}),会因无法找到对应的 getter 方法报错。

解决方案

  • 使用@Param注解为参数指定名称:
复制代码

// 错误写法

List<User> selectByUserId(Integer userId); // XML中使用#{userId}会报错

// 正确写法

List<User> selectByUserId(@Param("userId") Integer userId); // XML中可使用#{userId}

  • 在 XML 中使用#{param1}替代#{xxx}:
复制代码
相关推荐
仟濹2 小时前
【Java 基础】面向对象 - 继承
java·开发语言
6***83052 小时前
微服务搭建----springboot接入Nacos2.x
java
likuolei3 小时前
XML 元素 vs. 属性
xml·java·开发语言
自不量力的A同学3 小时前
Spring Boot 4.0.0 正式发布
java·spring boot·后端
d***29243 小时前
【spring】Spring事件监听器ApplicationListener的使用与源码分析
java·后端·spring
5***b973 小时前
解决报错net.sf.jsqlparser.statement.select.SelectBody
java
q***95223 小时前
Tomcat下载,安装,配置终极版(2024)
java·tomcat
2***d8853 小时前
详解tomcat中的jmx监控
java·tomcat
无敌最俊朗@3 小时前
Qt事件循环队列剖析!!!
java