当前位置: 首页 > news >正文

【Java实战㉚】深入MyBatis:从动态SQL到缓存机制的进阶之旅

目录

  • 一、MyBatis 动态 SQL
    • 1.1 动态 SQL 标签大揭秘
    • 1.2 if 与 where 标签的默契配合
    • 1.3 foreach 标签的批量操作魔法
    • 1.4 动态 SQL 实战案例解析
  • 二、MyBatis 关联查询
    • 2.1 一对一关联的巧妙映射
    • 2.2 一对多关联的高效处理
    • 2.3 多对多关联的中间表处理
    • 2.4 关联查询性能优化之道
  • 三、MyBatis 缓存机制
    • 3.1 一级缓存的 SqlSession 级魔法
    • 3.2 二级缓存的 Mapper 级强化
    • 3.3 缓存失效场景剖析
    • 3.4 MyBatis 与 Spring 整合初步探索


一、MyBatis 动态 SQL

1.1 动态 SQL 标签大揭秘

在实际的数据库操作中,我们经常会遇到需要根据不同的条件来动态生成 SQL 语句的情况。比如,在查询用户信息时,可能需要根据用户输入的姓名、年龄、性别等条件来构建查询语句。如果使用传统的 SQL 语句编写方式,我们需要编写大量的条件判断代码来拼接 SQL 语句,这不仅繁琐,而且容易出错。而 MyBatis 的动态 SQL 功能则可以很好地解决这个问题。

MyBatis 提供了一系列的动态 SQL 标签,包括<if>、<where>、<choose>、<foreach>、<set>等 ,这些标签基于 OGNL 表达式,让我们可以在 SQL 语句中实现各种逻辑,从而根据不同的条件生成不同的 SQL 语句。比如,<if>标签用于条件判断,当指定的条件为真时,才会将标签内的 SQL 片段拼接到最终的 SQL 语句中;<where>标签主要用于生成WHERE子句,它会自动在第一条条件前面添加WHERE关键字,并在后续条件前添加AND,如果所有条件都不满足,它不会生成WHERE关键字,避免了生成无效的WHERE 1=1的情况。

1.2 if 与 where 标签的默契配合

<if>标签和<where>标签是 MyBatis 动态 SQL 中非常常用的两个标签,它们经常配合使用来实现条件查询。<if>标签用于根据条件动态拼接 SQL 片段,<where>标签则用于自动处理WHERE子句开头多余的AND/OR,避免语法错误。

假设有一个用户查询的场景,我们需要根据用户输入的姓名和年龄来查询用户信息。代码示例如下:

<select id="selectUser" resultType="User">SELECT * FROM user<where><if test="name != null and name != ''">AND name = #{name}</if><if test="age != null">AND age = #{age}</if></where>
</select>

在上述代码中,<where>标签会自动判断其内部的<if>标签是否有返回值。如果有返回值,它会在前面添加WHERE关键字,并自动去除<if>标签中条件前面多余的AND。例如,如果name不为空,而age为空,最终生成的 SQL 语句将是SELECT * FROM user WHERE name =?,而不会出现多余的AND关键字。

1.3 foreach 标签的批量操作魔法

<foreach>标签在 MyBatis 动态 SQL 中主要用于遍历集合(如List、Set、数组、Map等),并动态生成 SQL 片段 ,常用于IN条件或批量操作。比如,在进行批量插入或批量删除操作时,<foreach>标签就发挥着重要的作用。

先来看批量插入的场景。假设我们有一个用户列表,需要将这些用户批量插入到数据库中。Mapper 接口方法和 XML 映射文件的配置如下:

// Mapper接口方法
void batchInsertUsers(@Param("users") List<User> userList);<insert id="batchInsertUsers">INSERT INTO user (name, age, email) VALUES<foreach collection="users" item="user" separator=",">(#{user.name}, #{user.age}, #{user.email})</foreach>
</insert>

在上述代码中,collection属性指定了要遍历的集合参数,item属性指定了遍历过程中每个元素的别名,separator属性指定了每次循环之间的分隔符。通过这种方式,我们可以将用户列表中的每个用户数据插入到数据库中。

再看批量删除的场景。假设我们要根据用户 ID 列表来批量删除用户,代码如下:

// Mapper接口方法
void batchDeleteUsers(@Param("ids") List<Long> idList);<delete id="batchDeleteUsers">DELETE FROM user WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>

这里,open属性指定了循环开始时的字符串,close属性指定了循环结束时的字符串。通过<foreach>标签,我们可以将用户 ID 列表中的每个 ID 拼接到IN条件中,实现批量删除操作。

1.4 动态 SQL 实战案例解析

现在,我们通过一个完整的实战案例来展示如何运用动态 SQL 标签实现多条件筛选查询。假设我们有一个商品管理系统,需要根据商品的名称、价格范围、库存数量等条件来查询商品信息。

首先,定义 Mapper 接口方法:

List<Product> selectProductsByCondition(@Param("name") String name,@Param("minPrice") BigDecimal minPrice,@Param("maxPrice") BigDecimal maxPrice,@Param("minStock") Integer minStock);

然后,在 XML 映射文件中编写动态 SQL 语句:

<select id="selectProductsByCondition" resultType="Product">SELECT * FROM product<where><if test="name != null and name != ''">AND name LIKE CONCAT('%', #{name}, '%')</if><if test="minPrice != null">AND price >= #{minPrice}</if><if test="maxPrice != null">AND price <= #{maxPrice}</if><if test="minStock != null">AND stock >= #{minStock}</if></where>
</select>

在这个案例中,我们根据传入的参数动态生成查询条件。如果用户输入了商品名称,就会添加商品名称的模糊查询条件;如果输入了价格范围,就会添加价格的范围查询条件;如果输入了库存数量,就会添加库存数量的查询条件。通过这种方式,我们可以灵活地实现多条件筛选查询,满足不同用户的查询需求。

二、MyBatis 关联查询

2.1 一对一关联的巧妙映射

在数据库设计中,一对一关联关系指的是一个表中的一条记录与另一个表中的一条记录相关联。比如在一个用户管理系统中,一个用户有一个唯一的地址信息,这就是典型的一对一关联关系。在 MyBatis 中,我们可以使用<association>标签来实现一对一关联查询。

以用户与订单详情为例,假设我们有两个表:user表和order_detail表。user表存储用户的基本信息,order_detail表存储订单的详细信息,并且一个用户对应一个订单详情。数据库表设计如下:

CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(50),age INT
);CREATE TABLE order_detail (id INT PRIMARY KEY,order_number VARCHAR(50),user_id INT,FOREIGN KEY (user_id) REFERENCES user(id)
);

实体类设计如下:

public class User {private Integer id;private String name;private Integer age;private OrderDetail orderDetail;// Getters and Setters
}public class OrderDetail {private Integer id;private String orderNumber;// Getters and Setters
}

MyBatis Mapper XML 配置如下:

<mapper namespace="com.example.UserMapper"><resultMap id="userMap" type="User"><id property="id" column="id" /><result property="name" column="name" /><result property="age" column="age" /><association property="orderDetail" column="id" javaType="OrderDetail" select="selectOrderDetailByUserId" /></resultMap><select id="selectUserById" resultMap="userMap">SELECT * FROM user WHERE id = #{id}</select><select id="selectOrderDetailByUserId" resultType="OrderDetail">SELECT * FROM order_detail WHERE user_id = #{id}</select>
</mapper>

在上述配置中,<association>标签的property属性指定了实体类中关联对象的属性名,column属性指定了关联查询的条件列,javaType属性指定了关联对象的类型,select属性指定了嵌套查询的 SQL 语句。通过这种方式,我们可以在查询用户信息的同时,关联查询出对应的订单详情信息。

2.2 一对多关联的高效处理

一对多关联关系在实际应用中也非常常见,它指的是一个表中的一条记录与另一个表中的多条记录相关联。比如在一个电商系统中,一个用户可以有多个订单,这就是一对多关联关系。在 MyBatis 中,我们使用<collection>标签来处理一对多关联查询。

以用户与多个订单为例,假设我们有user表和order表,user表存储用户信息,order表存储订单信息,一个用户可以有多个订单。数据库表设计如下:

CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(50),age INT
);CREATE TABLE order (id INT PRIMARY KEY,order_number VARCHAR(50),user_id INT,FOREIGN KEY (user_id) REFERENCES user(id)
);

实体类设计如下:

public class User {private Integer id;private String name;private Integer age;private List<Order> orders;// Getters and Setters
}public class Order {private Integer id;private String orderNumber;// Getters and Setters
}

MyBatis Mapper XML 配置如下:

<mapper namespace="com.example.UserMapper"><resultMap id="userWithOrdersMap" type="User"><id property="id" column="id" /><result property="name" column="name" /><result property="age" column="age" /><collection property="orders" ofType="Order" column="id" select="selectOrdersByUserId" /></resultMap><select id="selectUserById" resultMap="userWithOrdersMap">SELECT * FROM user WHERE id = #{id}</select><select id="selectOrdersByUserId" resultType="Order">SELECT * FROM order WHERE user_id = #{id}</select>
</mapper>

这里,<collection>标签的property属性指定了实体类中存储关联对象集合的属性名,ofType属性指定了集合中元素的类型,column属性指定了关联查询的条件列,select属性指定了嵌套查询的 SQL 语句。通过这种配置,我们可以在查询用户信息时,同时获取该用户的所有订单信息。

2.3 多对多关联的中间表处理

多对多关联关系是指两个表之间的多条记录相互关联,这种关系在数据库设计中也经常遇到。比如在一个教育系统中,一个学生可以选修多门课程,一门课程也可以被多个学生选修,这就是多对多关联关系。在 MyBatis 中处理多对多关联,通常需要借助中间表来建立两个表之间的关联。

以学生与课程多对多关联为例,我们需要三个表:student表、course表和中间表student_course。student表存储学生信息,course表存储课程信息,student_course表存储学生与课程的关联关系。数据库表设计如下:

CREATE TABLE student (id INT PRIMARY KEY,name VARCHAR(50),age INT
);CREATE TABLE course (id INT PRIMARY KEY,course_name VARCHAR(50),credit INT
);CREATE TABLE student_course (student_id INT,course_id INT,PRIMARY KEY (student_id, course_id),FOREIGN KEY (student_id) REFERENCES student(id),FOREIGN KEY (course_id) REFERENCES course(id)
);

实体类设计如下:

public class Student {private Integer id;private String name;private Integer age;private List<Course> courses;// Getters and Setters
}public class Course {private Integer id;private String courseName;private Integer credit;// Getters and Setters
}

MyBatis Mapper XML 配置如下:

<mapper namespace="com.example.StudentMapper"><resultMap id="studentWithCoursesMap" type="Student"><id property="id" column="id" /><result property="name" column="name" /><result property="age" column="age" /><collection property="courses" ofType="Course" column="id" select="selectCoursesByStudentId" /></resultMap><select id="selectStudentById" resultMap="studentWithCoursesMap">SELECT * FROM student WHERE id = #{id}</select><select id="selectCoursesByStudentId" resultType="Course">SELECT c.*FROM course cJOIN student_course sc ON c.id = sc.course_idWHERE sc.student_id = #{id}</select>
</mapper>

在这个配置中,通过中间表student_course建立了学生与课程的多对多关联。<collection>标签用于将查询到的课程信息映射到Student实体类的courses属性中。通过这种方式,我们可以查询出某个学生选修的所有课程信息。

2.4 关联查询性能优化之道

在进行关联查询时,随着数据量的增大,查询性能可能会受到影响。为了提高关联查询的性能,MyBatis 提供了延迟加载和按需加载等优化手段。

延迟加载,顾名思义,就是在需要用到关联数据时才进行加载,而不是在执行主查询时就加载所有相关数据。这样可以避免在查询主对象时,不必要地加载大量关联对象,从而提高查询性能。在 MyBatis 中,要启用延迟加载,需要在配置文件中进行相关设置:

<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>

lazyLoadingEnabled属性控制是否启用延迟加载,设置为true表示启用;aggressiveLazyLoading属性控制是否对所有关联对象进行积极加载,设置为false表示按需加载。当启用延迟加载后,MyBatis 会在查询主对象时,不立即查询关联对象,而是在实际访问关联对象的属性时才进行查询。

例如,在查询用户信息时,如果启用了延迟加载,只有在调用user.getOrders()方法时,才会触发对该用户订单信息的查询,而不是在查询用户信息时就同时查询出所有订单信息。这样可以减少数据库的负载,提高系统的响应速度。

除了延迟加载,还可以通过合理设计 SQL 语句、建立索引等方式来优化关联查询性能。比如,在多对多关联查询中,为中间表的外键字段创建索引,可以加快关联查询的速度;在编写 SQL 语句时,尽量避免使用子查询,而是使用连接查询来提高查询效率。通过综合运用这些优化手段,可以有效地提升 MyBatis 关联查询的性能,使系统更加高效地运行。

三、MyBatis 缓存机制

3.1 一级缓存的 SqlSession 级魔法

MyBatis 的缓存机制是提升应用性能的重要手段之一,其中一级缓存是最基础的缓存级别。一级缓存是基于SqlSession级别的缓存,它的作用范围仅限于同一个SqlSession。当我们在同一个SqlSession中执行相同的查询操作时,MyBatis 会首先检查一级缓存中是否已经存在该查询结果。如果存在,就直接从缓存中获取数据并返回,而不再执行实际的数据库查询操作。

这种缓存机制的原理基于HashMap实现,它将查询语句和参数作为键,查询结果作为值存储在缓存中。例如,当我们执行一个查询用户信息的操作时,MyBatis 会根据查询的 SQL 语句、传入的参数等信息生成一个唯一的缓存键。如果后续在同一个SqlSession中再次执行相同的查询,MyBatis 会根据这个缓存键去一级缓存中查找对应的结果。如果找到了,就直接返回缓存中的用户信息,避免了重复查询数据库带来的开销。

默认情况下,MyBatis 的一级缓存是自动开启的,无需额外的配置。这使得我们在日常开发中可以轻松享受到一级缓存带来的性能提升。比如在一个业务逻辑中,多次查询某个用户的基本信息,如果没有一级缓存,每次查询都需要与数据库进行交互,这会消耗一定的时间和资源。而有了一级缓存,第一次查询后,后续的相同查询都可以直接从缓存中获取数据,大大提高了查询效率。

3.2 二级缓存的 Mapper 级强化

二级缓存是 MyBatis 中比一级缓存更高级的缓存机制,它是基于Mapper级别的缓存。与一级缓存不同,二级缓存的作用范围是多个SqlSession共享,也就是说,只要是同一个Mapper的查询操作,无论在哪个SqlSession中执行,都可以共享二级缓存中的数据。

二级缓存的实现原理与一级缓存类似,但它的生命周期更长,作用范围更广。当一个SqlSession执行查询操作时,如果在一级缓存中没有命中,MyBatis 会继续检查二级缓存。如果二级缓存中有对应的查询结果,就会直接返回,而不需要再次查询数据库。二级缓存默认是关闭的,需要我们手动进行配置才能启用。在Mapper XML 文件中,通过添加<cache>标签来启用二级缓存,如下所示:

<mapper namespace="com.example.UserMapper"><cache /><!-- 其他SQL映射语句 -->
</mapper>

<cache>标签还可以配置一些属性,如eviction指定缓存的回收策略,默认是LRU(最近最少使用);flushInterval设置缓存刷新的时间间隔(毫秒);size定义缓存的最大缓存对象数;readOnly指定缓存是否只读,默认是false,即可读可写。通过合理配置这些属性,可以更好地满足不同业务场景的需求。

3.3 缓存失效场景剖析

缓存虽然能够提高查询性能,但在某些情况下,缓存中的数据可能会与数据库中的实际数据不一致,这就需要了解缓存失效的场景。

当执行增删改操作(INSERT、UPDATE、DELETE)时,MyBatis 会认为数据库中的数据已经发生了变化,为了保证数据的一致性,会自动清空一级缓存和二级缓存中相关的数据。例如,当我们执行一个更新用户信息的操作后,再查询该用户的信息,如果缓存没有失效,就会返回旧的数据,这显然是不符合要求的。所以,MyBatis 通过清空缓存来确保下次查询能够获取到最新的数据。

不同的SqlSession之间,一级缓存是相互独立的。也就是说,在一个SqlSession中查询的数据不会缓存到另一个SqlSession的一级缓存中。如果在不同的SqlSession中执行相同的查询,即使查询条件相同,也会重新查询数据库,因为每个SqlSession都有自己独立的一级缓存。而二级缓存是跨SqlSession共享的,但如果不同的Mapper之间进行增删改操作,也可能导致相关的二级缓存失效。

另外,当查询条件不同时,即使是同一条 SQL 语句,缓存也不会生效。因为缓存是根据查询语句和参数来进行存储和匹配的,如果参数发生了变化,就会被认为是不同的查询,不会命中缓存。在实际开发中,我们需要根据业务场景和数据更新频率,合理地利用缓存,并注意缓存失效的情况,以确保数据的准确性和系统的性能。

3.4 MyBatis 与 Spring 整合初步探索

在实际的企业级开发中,MyBatis 通常会与 Spring 框架整合使用,以充分发挥两者的优势。SqlSessionFactoryBean是 MyBatis 与 Spring 整合的关键配置类,它负责创建SqlSessionFactory对象,而SqlSessionFactory是 MyBatis 的核心组件,用于创建SqlSession。

在 Spring 的配置文件中,我们可以通过如下方式配置SqlSessionFactoryBean:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="classpath*:mapper/*.xml"/>
</bean>

其中,dataSource属性指定了数据库连接数据源,它可以是各种类型的数据源,如DruidDataSource、HikariDataSource等,具体的配置方式与 Spring 中配置数据源的方式一致。mapperLocations属性用于指定 MyBatis 映射文件的位置,可以使用 Ant 样式的路径表达式来指定多个映射文件的位置。通过这样的配置,Spring 在启动时会创建SqlSessionFactory对象,并将其注入到需要的组件中。

在整合过程中,还需要注意事务管理的配置。通常会使用 Spring 的声明式事务管理来管理 MyBatis 的数据库事务。通过配置事务管理器和事务注解,我们可以方便地控制事务的边界和传播行为。例如,可以使用@Transactional注解来标记需要事务管理的方法,当方法被调用时,Spring 会自动开启、提交或回滚事务。通过合理配置SqlSessionFactoryBean和事务管理,我们可以实现 MyBatis 与 Spring 的无缝整合,为开发高效、稳定的企业级应用提供有力支持。


文章转载自:

http://8CI3RsGx.qhczg.cn
http://Ws725zuV.qhczg.cn
http://TR9W4Xgs.qhczg.cn
http://Lez8lkiI.qhczg.cn
http://6AIHPIT9.qhczg.cn
http://L9MqULcs.qhczg.cn
http://29E9zvyD.qhczg.cn
http://a7Cpg6ju.qhczg.cn
http://Lfh7pWoa.qhczg.cn
http://pgq9QXx6.qhczg.cn
http://x1LQlO2E.qhczg.cn
http://czsheDnx.qhczg.cn
http://Jfqi4r8a.qhczg.cn
http://8A7hsceu.qhczg.cn
http://hwO5Zwgu.qhczg.cn
http://GNW4RhAr.qhczg.cn
http://cnGFKj4M.qhczg.cn
http://bP3dJPwA.qhczg.cn
http://iwas2zHh.qhczg.cn
http://hGF4GP7M.qhczg.cn
http://l02wMpVl.qhczg.cn
http://Yg6V3ruT.qhczg.cn
http://Z6cArWL1.qhczg.cn
http://6JPHdZk4.qhczg.cn
http://cRk2PJdC.qhczg.cn
http://0s0WOvZ9.qhczg.cn
http://XJSwYe3w.qhczg.cn
http://THIFtuYq.qhczg.cn
http://BYDvsccV.qhczg.cn
http://i0zRE8cm.qhczg.cn
http://www.dtcms.com/a/372375.html

相关文章:

  • 腾讯云EdgeOne免费套餐:零成本开启网站加速与安全防护
  • Cookie-Session 认证模式与Token认证模式
  • Redis哨兵模式在Spring Boot项目中的使用与实践
  • [工作表控件13] 签名控件在合同审批中的应用
  • 【图像理解进阶】MobileViT-v3核心技术解析和应用场景说明
  • 前端拖拽功能实现全攻略
  • AI赋能软件开发|智能化编程实战与未来机会有哪些?
  • 335章:使用Scrapy框架构建分布式爬虫
  • Docker|“ssh: connect to host xxx.xxx.xxx.xxx port 8000: Connection refused“问题解决
  • OneCode 可视化揭秘系列(三):AI MCP驱动的智能工作流逻辑编排
  • 数据结构深度解析:二叉树的基本原理
  • Supabase02-速通
  • LLM学习:大模型基础——视觉大模型以及autodl使用
  • 嵌入式Secure Boot安全启动详解
  • 【倍增】P3901 数列找不同|普及+
  • 数据结构:堆
  • 继续优化基于树状数组的cuda前缀和
  • 数组常见算法
  • 数仓建模理论
  • 致远A8V5 9.0授权文件
  • 【New Phytologist】​​单细胞多组学揭示根毛对盐胁迫的特异性响应文献分享
  • MyBatis 拦截器让搞定监控、脱敏和权限控制
  • 20250907-0101:LangChain 核心价值补充
  • 论CMD、.NET、PowerShell、cmdlet四者关系
  • 从IFA展会看MOVA的“全维进阶”如何重新定义智能家居边界
  • SpringBoot 数据脱敏实战: 构建企业级敏感信息保护体系
  • 公链分析报告 - 模块化区块链1
  • 20250907-01:理解 LangChain 是什么 为什么诞生
  • 做一个鉴权系统
  • Javaweb - 14.5 Vue3 路由机制