MyBatis 作为一款优秀的持久层框架,在 Java 开发中占据着重要地位。它简化了数据库操作,提供了灵活且高效的数据访问方式。本文将深入探讨 MyBatis 的核心功能,包括分页查询、联表查询、动态 SQL 以及代码自动生成,并结合实际案例进行详细分析,帮助读者更好地理解和应用这些功能。
一、MyBatis 框架回顾
在深入学习 MyBatis 的高级特性之前,我们先来回顾一下 MyBatis 框架的基础知识。MyBatis 框架主要用于数据库访问,它通过将 SQL 语句与 Java 代码分离,实现了数据持久化层的解耦。以下是 MyBatis 框架的一些关键知识点:
(一)注解方式实现单表操作
MyBatis 提供了一系列注解,如@Select
、@Insert
、@Delete
和@Update
,用于在接口方法上直接编写 SQL 语句,实现对单表的增删改查操作。这种方式简单直观,适用于简单的数据库操作场景。
(二)MyBatis 的优化策略
- 日志添加
- 引入
log4j
的jar
包,并添加log4j.properties
配置文件,以便在开发过程中更好地跟踪和调试 MyBatis 的执行过程,帮助开发者快速定位问题。
- 引入
- 实体类别名设置
- 为实体类起别名,在编写 SQL 语句时可以直接使用别名代替完整的类名,使 SQL 语句更加简洁易读,提高代码的可维护性。
- 数据源信息抽取
- 将数据源的信息抽取到属性文件中,通过
${key}
的方式引用属性值。这样,在切换数据源或修改数据库连接信息时,无需修改代码,只需调整属性文件即可,增强了程序的灵活性和可配置性。
- 将数据源的信息抽取到属性文件中,通过
(三)其他常用功能
- 获取递增 ID 值
- 在执行插入操作时,通过设置
<insert useGeneratorKey="true" keyProperty="属性">
,可以方便地获取数据库自动生成的递增 ID 值,并将其赋值给实体类的相应属性。
- 在执行插入操作时,通过设置
- 多参处理
- 使用
@Param("名称")
注解为方法参数命名,然后在 SQL 语句中通过#{名称}
引用参数,解决了多参数传递的问题,使 SQL 语句与方法参数的对应关系更加清晰。
- 使用
- 特殊符号处理
- 当 SQL 语句中包含特殊符号时,可以使用转义符或者
<![CDATA[sql]]>
的方式进行处理,避免因特殊符号导致的 SQL 语法错误。
- 当 SQL 语句中包含特殊符号时,可以使用转义符或者
- 模糊查询实现
- 通过
concat("%",#{},"%")
函数,在查询条件中实现模糊查询,方便根据关键字搜索相关数据。
- 通过
- 属性名与列名不一致处理
- 提供了两种解决方案。一是为查询的列起别名,使其与属性名一致;二是通过
ResultMap
标签建立列名和属性名的映射关系,确保数据的正确封装。
- 提供了两种解决方案。一是为查询的列起别名,使其与属性名一致;二是通过
二、分页查询
在实际应用中,分页查询是常见的需求。MyBatis 本身没有提供分页功能,但可以通过集成分页插件来实现。以 PageHelper 插件为例,详细介绍分页查询的实现步骤。
(一)引入 PageHelper 依赖
在项目的pom.xml
文件中添加 PageHelper 的依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>6.0.0</version>
</dependency>
(二)配置 MyBatis
在 MyBatis 的配置文件中,添加如下插件配置:
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="param1" value="value1"/>
</plugin>
</plugins>
<select id="selectAll" resultType="java.util.Map">
select * from tbl_emp e join tbl_dept d on e.did = d.dept_id
</select>
- 使用 association 标签(第二种方案)
- 优点:通过定义实体类之间的关联关系,使代码结构更加清晰,属性调用更加直观。
- 缺点:需要创建额外的实体类和映射配置。
- 示例代码:
public List<Emp> selectAll01();
<resultMap id="EmpMapper" type="org.example.entity.Emp">
<id column="emp_id" property="id"/>
<result column="emp_name" property="name"/>
<result column="emp_job" property="job"/>
<result column="emp_salary" property="salary"/>
<result column="did" property="di"/>
<!-- association:表示多对一的标签 property: 该类中实体类的属性名 javaType:该属性所属于的类型 -->
<association property="dept" javaType="org.example.entity.Dept">
<id column="dept_id" property="id"/>
<result column="dept_name" property="name"/>
<result column="dept_loc" property="loc"/>
</association>
</resultMap>
<select id="selectAll01" resultMap="EmpMapper">
select * from tbl_emp e join tbl_dept d on e.did = d.dept_id
</select>
(二)一对多关系查询
-
使用 Map 类(第一种方案,省略)
- 与多对一关系中使用 Map 集合类类似,但在一对多场景下,使用 Map 可能会导致数据结构不够清晰,维护困难。
-
使用 Collection 标签(第二种方案)
- 优点:能够清晰地表达一对多的关系,通过配置
Collection
标签,可以方便地获取关联的多个对象集合。 - 缺点:配置相对复杂,需要仔细定义映射关系。
- 示例代码(查询部门信息并携带部门对应的员工信息):
- 优点:能够清晰地表达一对多的关系,通过配置
public Dept selectById(int id);
public List<Dept> selectAll();
<resultMap id="DeptMapper" type="org.example.entity.Dept">
<id column="dept_id" property="id" />
<result column="dept_name" property="name"/>
<result column="dept_loc" property="loc"/>
<!-- collection:表示一对多的标签 property:集合对应的属性名 ofType:该属性集合的泛型类型 -->
<collection property="emp" ofType="org.example.entity.Emp">
<id column="emp_id" property="id"/>
<result column="emp_name" property="name"/>
<result column="emp_job" property="job"/>
<result column="emp_salary" property="salary"/>
<result column="did" property="did"/>
</collection>
</resultMap>
<select id="selectById" resultMap="DeptMapper">
select * from tbl_emp e join tbl_dept d on e.did = d.dept_id where dept_id = #{id}
</select>
<select id="selectAll" resultMap="DeptMapper">
select * from tbl_emp e join tbl_dept d on e.did = d.dept_id
</select>
(三)联表查询的 SQL 优化
- 合理选择连接方式
- 根据业务需求和数据特点,选择合适的连接方式(如
INNER JOIN
、LEFT JOIN
、RIGHT JOIN
),避免不必要的数据冗余和查询性能问题。
- 根据业务需求和数据特点,选择合适的连接方式(如
- 建立合适的索引
- 对关联字段建立索引,可以提高联表查询的效率。例如,在员工表的
did
字段和部门表的dept_id
字段上建立索引。
- 对关联字段建立索引,可以提高联表查询的效率。例如,在员工表的
四、动态 SQL 标签
在实际应用中,查询条件往往是动态变化的。MyBatis 提供了动态 SQL 标签,用于根据不同的条件动态拼接 SQL 语句,提高代码的灵活性和可维护性。
(一)动态 SQL 标签介绍
<trim>
标签- 通过修剪 SQL 语句的开头和结尾来动态生成 SQL 片段。可以去除不必要的 SQL 关键字或条件语句,并根据属性定义修剪规则。
<where>
标签- 用于在生成的 SQL 语句中添加
WHERE
子句。它能自动处理条件语句的前缀,在有条件语句存在时添加WHERE
关键字,并去除 SQL 中的第一个AND
标签。
- 用于在生成的 SQL 语句中添加
<set>
标签- 用于在生成的 SQL 语句中添加
SET
子句,主要用于更新操作,根据条件动态生成需要更新的列。
- 用于在生成的 SQL 语句中添加
<foreach>
标签- 用于在 SQL 语句中进行循环操作,可遍历集合或数组,并根据指定模板将元素插入到 SQL 语句中,常用于批量删除和批量添加操作。
<if>
标签- 根据指定条件决定是否包含某个 SQL 语句片段,实现条件判断。
<choose>
、<when>
和<otherwise>
标签- 类似于 Java 中的
switch
语句,<choose>
根据条件选择执行不同的 SQL 语句片段,<when>
定义条件分支,<otherwise>
在所有<when>
条件不匹配时执行的 SQL 语句片段。
- 类似于 Java 中的
(二)示例代码
- 使用
<if>
标签实现多条件查询
public List<Emp> selectByCondition01(@Param("name") String name, @Param("job") String job, @Param("salary") Double salary);
<select id="selectByCondition01" resultMap="EmpMapper">
select * from tbl_emp
<where>
<if test="name!=null and name!=''">
and emp_name like concat('%',#{name},'%')
</if>
<if test="job!=null and job!=''">
and emp_job=#{job}
</if>
<if test="salary!=null">
and emp_salary=#{salary}
</if>
</where>
</select>
- 使用
<choose>
、<when>
和<otherwise>
标签实现条件分支查询
<select id="selectByCondition02" resultMap="EmpMapper">
select * from tbl_emp
<where>
<choose>
<when test="name!=null and name!=''">
and emp_name like concat('%', #{name}, '%')
</when>
<when test="job!=null and job!=''">
and emp_job=#{job}
</when>
<when test="salary!=null">
and emp_salary=#{salary}
</when>
</choose>
</where>
</select>
- 使用
<foreach>
标签实现批量删除和批量添加- 批量删除示例:
public int batchDelete(@Param("ids") Integer[] ids);
<delete id="batchDelete">
delete from tbl_emp where id in
<foreach collection="ids" item="i" open="(" close=")" separator=", ">
#{id}
</foreach>
</delete>
- 批量添加示例:
public int batchInsert(@Param("emps") List<Emp> emps);
<insert id="batchInsert">
insert into tbl_emp(emp_name,emp_job,emp_salary,did) values
<foreach collection="emps" item="e" separator=",">
(#{e.name},#{e.job},#{e.salary},#{e.did})
</foreach>
</insert>
(三)动态 SQL 的应用场景
- 多条件组合查询
- 根据用户输入的不同查询条件,动态生成包含相应条件的 SQL 语句,如电商平台中的商品搜索功能,根据品牌、价格范围、规格等多个条件进行筛选查询。
- 动态更新操作
- 根据业务规则,只更新部分字段。例如,用户信息修改功能,仅更新用户修改的字段,而不是全部字段。
- 批量操作
- 如批量删除选中的记录或批量插入多条数据,提高操作效率,减少数据库交互次数。
五、MyBatis 代码自动生成
随着项目的不断发展,编写大量的 MyBatis 映射文件和实体类会变得繁琐且容易出错。MyBatis 提供了代码自动生成工具,可以根据数据库表结构快速生成实体类、映射文件和 DAO 接口等代码,提高开发效率。
(一)使用 MyBatis Generator 工具
- 配置文件编写
- 创建一个
generatorConfig.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>
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC"
userId="root"
password="123456">
</jdbcConnection>
<javaModelGenerator targetPackage="com.example.entity" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.example.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<table tableName="tbl_emp" domainObjectName="Emp"/>
<table tableName="tbl_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
- 执行生成命令
- 在命令行中执行 MyBatis Generator 的命令,或者在项目中通过 Maven 插件执行。执行后,将根据配置生成相应的实体类、映射文件和 DAO 接口。
(二)代码生成器的优势与注意事项
- 优势
- 大大提高开发效率,减少手动编写代码的工作量,降低出错率。
- 保证代码结构的一致性,便于团队协作和项目维护。
- 注意事项
- 生成的代码可能需要根据实际需求进行适当调整,如添加自定义的方法、修改注释等。
- 当数据库表结构发生变化时,需要及时重新生成代码,以确保代码与数据库的一致性。