第三章:MyBatis 高级特性与进阶


文档摘要

第三章:MyBatis 高级特性与进阶 第三章:MyBatis 高级特性与进阶 本章将深入探讨 MyBatis 的高级特性,帮助你更有效地使用 MyBatis 解决复杂的数据访问需求,并提升开发效率和代码质量。我们将涵盖动态 SQL、缓存机制、插件机制、以及 MyBatis 与 Spring 的整合等关键内容。 3.1 动态 SQL 动态 SQL 是 MyBatis 最强大的特性之一,它允许你根据不同的条件构建不同的 SQL 语句,从而避免了大量的硬编码 SQL 和复杂的字符串拼接。 3.1.1 标签 标签是最基础的动态 SQL 元素,用于判断条件是否成立,如果成立则将标签内的 SQL 片段包含到最终的 SQL 语句中。 代码实践: 3.1.

第三章:MyBatis 高级特性与进阶

第三章:MyBatis 高级特性与进阶

本章将深入探讨 MyBatis 的高级特性,帮助你更有效地使用 MyBatis 解决复杂的数据访问需求,并提升开发效率和代码质量。我们将涵盖动态 SQL、缓存机制、插件机制、以及 MyBatis 与 Spring 的整合等关键内容。

3.1 动态 SQL

动态 SQL 是 MyBatis 最强大的特性之一,它允许你根据不同的条件构建不同的 SQL 语句,从而避免了大量的硬编码 SQL 和复杂的字符串拼接。

3.1.1 if 标签

if 标签是最基础的动态 SQL 元素,用于判断条件是否成立,如果成立则将标签内的 SQL 片段包含到最终的 SQL 语句中。

<select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM Blog WHERE state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> </select>

代码实践:

public interface BlogMapper { List<Blog> findActiveBlogWithTitleLike(@Param("title") String title); } // Blog.java (简化) public class Blog { private Integer id; private String title; private String state; // getters and setters } // BlogMapper.xml <mapper namespace="com.example.BlogMapper"> <select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM Blog WHERE state = 'ACTIVE' <if test="title != null and title != ''"> AND title like #{title} </if> </select> </mapper> // 测试 @Test public void testFindActiveBlogWithTitleLike() { SqlSession sqlSession = sqlSessionFactory.openSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); List<Blog> blogs = blogMapper.findActiveBlogWithTitleLike("%My%"); assertNotNull(blogs); sqlSession.close(); }

3.1.2 choosewhenotherwise 标签

choose 标签类似于 Java 中的 switch 语句,它允许你选择多个条件中的一个执行。

<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM Blog WHERE state = 'ACTIVE' <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null"> AND author like #{author} </when> <otherwise> AND featured = 1 </otherwise> </choose> </select>

3.1.3 where 标签

where 标签用于简化 WHERE 子句的编写,它可以智能地处理 ANDOR 前缀。

<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM Blog <where> state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> <if test="author != null"> AND author like #{author} </if> </where> </select>

3.1.4 set 标签

set 标签用于简化 UPDATE 语句的编写,它可以智能地处理逗号前缀。

<update id="updateBlogIfNecessary"> UPDATE Blog <set> <if test="title != null">title=#{title},</if> <if test="author != null">author=#{author}</if> </set> WHERE id = #{id} </update>

3.1.5 foreach 标签

foreach 标签用于遍历集合,它可以用于构建 IN 子句或批量插入数据。

<select id="findBlogIn" resultType="Blog"> SELECT * FROM Blog WHERE id IN <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select>

代码实践:

public interface BlogMapper { List<Blog> findBlogIn(@Param("ids") List<Integer> ids); } // BlogMapper.xml <mapper namespace="com.example.BlogMapper"> <select id="findBlogIn" resultType="Blog"> SELECT * FROM Blog WHERE id IN <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select> </mapper> // 测试 @Test public void testFindBlogIn() { SqlSession sqlSession = sqlSessionFactory.openSession(); BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); List<Integer> ids = Arrays.asList(1, 2, 3); List<Blog> blogs = blogMapper.findBlogIn(ids); assertNotNull(blogs); sqlSession.close(); }

3.1.6 bind 标签

bind 标签允许你创建一个变量,并将其绑定到一个表达式,然后在 SQL 语句中使用该变量。

<select id="findBlogLikeBind" resultType="Blog"> <bind name="pattern" value="'%' + title + '%'"/> SELECT * FROM Blog WHERE title like #{pattern} </select>

3.1.7 使用 script 标签

在 MyBatis 3.2 及更高版本中,可以使用 <script> 标签来包含动态 SQL,这可以使 XML 文件更易读。

<select id="findActiveBlogWithTitleLikeScript" resultType="Blog"> <script> SELECT * FROM Blog WHERE state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> </script> </select>

3.1.8 动态SQL 的流程图

3.2 缓存机制

MyBatis 提供了两级缓存:一级缓存(本地缓存)和二级缓存(全局缓存)。

3.2.1 一级缓存 (Local Cache)

一级缓存是 SqlSession 级别的缓存,默认开启。当在同一个 SqlSession 中执行相同的查询时,MyBatis 会首先从一级缓存中查找结果,如果找到则直接返回,否则执行查询并将结果放入一级缓存。SqlSession 关闭时,一级缓存会被清空。

3.2.2 二级缓存 (Global Cache)

二级缓存是 SqlSessionFactory 级别的缓存,需要手动配置开启。当一个 SqlSession 关闭时,它的一级缓存中的数据会被转移到二级缓存中。如果多个 SqlSession 使用同一个 Mapper,则它们可以共享二级缓存中的数据。

配置二级缓存:

  1. mybatis-config.xml 中开启二级缓存:

    <settings> <setting name="cacheEnabled" value="true"/> </settings>
  2. 在 Mapper XML 文件中配置 <cache> 标签:

    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    • eviction: 缓存回收策略,常用的有 LRU (Least Recently Used, 最近最少使用) 和 FIFO (First In First Out, 先进先出)。

    • flushInterval: 缓存刷新间隔,单位为毫秒。

    • size: 缓存最多存储的对象数量。

    • readOnly: 是否只读,如果为 true,则返回对象的引用,否则返回对象的副本。

  3. 实现 Serializable 接口:

    放入二级缓存的对象必须实现 java.io.Serializable 接口。

代码实践:

// Blog.java (实现 Serializable) import java.io.Serializable; public class Blog implements Serializable { private Integer id; private String title; private String state; // getters and setters } // BlogMapper.xml <mapper namespace="com.example.BlogMapper"> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <select id="selectBlog" resultType="Blog"> SELECT * FROM Blog WHERE id = #{id} </select> </mapper> // 测试 @Test public void testSecondLevelCache() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); BlogMapper blogMapper1 = sqlSession1.getMapper(BlogMapper.class); Blog blog1 = blogMapper1.selectBlog(1); System.out.println("First query: " + blog1); sqlSession1.close(); // 关闭 session,数据放入二级缓存 SqlSession sqlSession2 = sqlSessionFactory.openSession(); BlogMapper blogMapper2 = sqlSession2.getMapper(BlogMapper.class); Blog blog2 = blogMapper2.selectBlog(1); System.out.println("Second query: " + blog2); // 从二级缓存获取 sqlSession2.close(); }

3.2.3 缓存的流程图

3.2.4 缓存失效

以下情况会导致缓存失效:

  • 执行 UPDATEINSERTDELETE 语句,MyBatis 会自动刷新缓存。

  • 手动调用 SqlSession.clearCache() 方法。

  • flushInterval 到期。

3.3 插件机制

MyBatis 的插件机制允许你在 MyBatis 执行过程中的某些关键点拦截并修改行为,例如:

  • Executor:拦截执行器的方法,可以修改 SQL 语句、修改查询结果等。

  • StatementHandler:拦截语句处理器的方法,可以修改 SQL 语句。

  • ParameterHandler:拦截参数处理器的方法,可以修改参数。

  • ResultSetHandler:拦截结果集处理器的方法,可以修改查询结果。

3.3.1 开发插件

  1. 实现 Interceptor 接口:

    import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) }) public class ExamplePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 拦截逻辑 System.out.println("Interceptor is running!"); return invocation.proceed(); // 继续执行 } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); // 包装目标对象 } @Override public void setProperties(Properties properties) { // 设置属性 System.out.println("Properties: " + properties); } }
  2. 使用 @Intercepts 注解指定拦截点:

    • type: 被拦截的接口类型。

    • method: 被拦截的方法名。

    • args: 被拦截的方法参数类型。

  3. 实现 intercept() 方法:

    • invocation.proceed(): 调用目标方法。
  4. 实现 plugin() 方法:

    • Plugin.wrap(target, this): 使用当前拦截器包装目标对象。
  5. 实现 setProperties() 方法:

    • 用于设置插件的属性。

3.3.2 配置插件

mybatis-config.xml 中配置插件:

<plugins> <plugin interceptor="com.example.ExamplePlugin"> <property name="someProperty" value="someValue"/> </plugin> </plugins>

代码实践:

// 分页插件 @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class PaginationInterceptor implements Interceptor { private static final String DEFAULT_PAGE_SQL_ID = ".*Page$"; // 需要拦截的SQL ID @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类) while (metaStatementHandler.hasGetter("h")) { Object object = metaStatementHandler.getValue("h"); metaStatementHandler = SystemMetaObject.forObject(object); } // 分离最后一个代理对象的目标类 while (metaStatementHandler.hasGetter("target")) { Object object = metaStatementHandler.getValue("target"); metaStatementHandler = SystemMetaObject.forObject(object); } MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); //只重写需要分页的sql。 if (!mappedStatement.getId().matches(DEFAULT_PAGE_SQL_ID)) { return invocation.proceed(); } BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); Object parameterObject = boundSql.getParameterObject(); if (parameterObject == null) { throw new NullPointerException("parameterObject尚未实例化!"); } else { Connection connection = (Connection) invocation.getArgs()[0]; String sql = boundSql.getSql(); // 构建分页 SQL String pageSql = buildPageSql(sql, parameterObject); metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数 metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET); metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT); return invocation.proceed(); } } private String buildPageSql(String sql, Object parameterObject) { // 示例:假设 parameterObject 中包含 page 和 pageSize // 实际实现需要根据你的分页参数进行处理 int page = 1; int pageSize = 10; if (parameterObject instanceof Map) { Map<?, ?> params = (Map<?, ?>) parameterObject; if (params.containsKey("page")) { page = (int) params.get("page"); } if (params.containsKey("pageSize")) { pageSize = (int) params.get("pageSize"); } } int offset = (page - 1) * pageSize; return sql + " LIMIT " + offset + ", " + pageSize; // MySQL 分页示例 } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }

3.4 MyBatis 与 Spring 的整合

MyBatis 可以与 Spring 框架无缝整合,利用 Spring 的依赖注入和事务管理等特性,简化开发。

3.4.1 配置 Spring 集成

  1. 添加依赖:

    <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency>
  2. 配置 SqlSessionFactoryBean

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean>
  3. 配置 MapperScannerConfigurer

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>
  4. 配置数据源 (DataSource):

    使用 Spring 的数据源配置,例如 DriverManagerDataSource 或连接池。

  5. 配置事务管理器 (TransactionManager):

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>

3.4.2 使用 MyBatis Mapper 接口

在 Spring 管理的 Bean 中,可以直接注入 MyBatis Mapper 接口。

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class BlogService { @Autowired private BlogMapper blogMapper; public Blog getBlogById(Integer id) { return blogMapper.selectBlog(id); } }

代码实践:

完整 Spring 集成示例 (简化):

<!-- applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_db?useSSL=false&amp;serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <mybatis-spring:scan base-package="com.example.mapper"/> </beans>
// BlogService.java import com.example.mapper.BlogMapper; import com.example.model.Blog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class BlogService { @Autowired private BlogMapper blogMapper; @Transactional public Blog getBlogById(Integer id) { return blogMapper.selectBlog(id); } }

3.4.3 Spring 集成的流程图

总结

本章介绍了 MyBatis 的高级特性,包括动态 SQL、缓存机制、插件机制以及与 Spring 的整合。掌握这些特性可以帮助你更有效地使用 MyBatis,解决复杂的数据访问需求,提高开发效率和代码质量。 在实际项目中,应根据具体情况选择合适的特性,并结合 Spring 等框架,构建高效稳定的数据访问层。


发布者: 作者: 转发
评论区 (0)
U