一.简介
MyBatis 是一个半自动化的 ORM(对象关系映射)框架,允许开发者手动编写 SQL 语句,从而能够更好地控制 SQL 的执行,提高性能。MyBatis 可以将 SQL 语句与 Java 代码分离,存储在 XML 文件或者注解中,让代码结构更清晰。
二.初步使用
1.导入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency>
2.创建Do对象
此处为基本数据对象
@Data public class EmpDo { private Long id; private String name; private Integer age; private String email; private String gender; private String address; private BigDecimal salary; }
3.Dao层创建Mapper接口
@Mapper注解告诉mybatis这是一个Mapper接口
@Mapper public interface EmpMapper { EmpDo getEmpById(Integer id); }
4.生成xml配置文件
此处可以安装mybatisx插件自动生成一个文件
namespace写Mapper对象的全类名
<?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.zhang.mybatis.dao.EmpMapper"> //此处添加sql方法 </mapper>
还需要在application.properties中指明xml配置文件的位置
mybatis.mapper-locations=classpath:mapper/**.xml
5.sql方法的编写
<select id="getEmpById" resultType="com.zhang.mybatis.bean.EmpDo"> select * from employee where id = #{id} </select> <insert id="addEmp"> insert into employee(last_name,email,gender) values (#{lastName},#{email},#{gender}) </insert> <update id="updateEmp"> update employee set last_name = #{lastName},email = #{email},gender = #{gender} where id = #{id} </update> <delete id="deleteEmpById"> delete from employee where id = #{id} </delete>
-
id对应Mapper对象的方法名
-
resultType对应返回值
-
括号间为sql语言
接下来就可以直接调用Mapper方法了
6.开启自增id
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id"></insert>
-
useGeneratedKeys表示是否开始自增id
-
keyProperty指定自动生成的id在Do中对应的属性,把自动生成的id封装回Emp对象的id属性中
7.命名规则转化
java和mysql数据库采用了不同的命名规则:
-
java采用驼峰命名(userName)
-
数据库采用蛇形命名(user_name)
在配置文件中开启命名规则自动转换,才能使名称一 一对应上
mybatis.configuration.map-underscore-to-camel-case=true
三.参数
1.占位符#{}和${}的区别
select * from employee where id = #{id} select * from employee where id = ${id}
-
#{} :这是MyBatis中预编译的占位符,用于将参数值安全地插入到SQL语句中。在SQL执行前,MyBatis会将#{} 替换为?,然后使用PreparedStatement来设置参数值,有效防止了sql注入问题。
Connection connection = dataSource.getConnection(); String sql = "select * from employee where id=?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1, "admin");
-
${}:这是字符串替换占位符,MyBatis会直接将${} 替换为实际的参数值,而不进行预编译,会引发sql注入问题。
-
只有参数位置动态取值才可以用#{},jdbc层面表名等位置不支持预编译,只能用${}。
-
总结:能用#{}大括号的地方就用,不能用#{}的地方用${}
2.单参数取值
-
对象取值:直接写属性名,不要写对象名
select * from employee where id = #{name}
-
list取值:使用索引
select * from employee where id = #{ids[5]}
-
map取值: 使用key值
select * from employee where id = #{myId}
3.多参数取值
新版mybatis支持直接使用#{参数名}
老版mybatis需要在参数前使用@Param指定参数名
Emp getEmployByIdAndName(@Param("id") Long id, @Param("empName") Employee emp)
select * from Employee where id = #{id} and name = #{emp.name}
四.返回值
1.一般返回:
<select id="getEmpById" resultType="com.zhang.mybatis.bean.EmpDo"> select * from employee where id = #{id} </select>
resultType规定返回值类型
2.返回map:
mapper方法中使用@Mapper("")指定key值,key默认从对象属性值中取
@Mapper("id")//将id作为key值 Map<Integer,Emp> getAllMap();
返回值的value实际上不是指定类(Emp),而是一个HashMap,
resultType不写map,写指定类,value值才为指定类
3.封装规则
默认封装规则(resultType):JavaBean中的属性名 去数据库表中 找对应列名的值,一一映射封装。
自定义封装规则(resultMap):明确指定每一列如何封装到JavaBean中
在xml中写一个ResultMap对照表
<resultMap id="EmpRM" type="com.zhang.mybatis.bean.EmpDo"> <!--声明主键映射规则--> <id property="id" column="id"></id> <!--声明普通列映射规则--> <result property="name" column="name"></result> <result property="age" column="age"></result> </resultMap>
在方法中根据ResultMap的id使用对照规则
<select id="getEmpById" resultMap="EmpRM"> select * from employee where id = #{id} </select>
五.关联查询
1.association单一关联封装
<select id="getOrderById" resultType="com.zhang.mybatis.bean.Order"> select * from orders left join customer on orders.customer_id = customer.id where orders.id = #{id} </select>
使用默认封装(resultType)无法将连接的对象正确装填(customer将返回为null)
Order(id=1, address=chengdu, amount=5000, customerId=1, customer=null)
这时候要在自定义封装(resultMap)中使用association标签
<resultMap id="OrderRM" type="com.zhang.mybatis.bean.Order"> <id property="id" column="id"></id> <result property="address" column="address"></result> <result property="amount" column="amount"></result> <result column="customer_id" property="customerId"></result> <association property="customer" javaType="com.zhang.mybatis.bean.Customer"> <id property="id" column="id"></id> <result property="name" column="name"></result> </association> </resultMap>
左连接时,association标签中定义了右表的封装关系
2.collection集合关联封装
property对应集合,ofType对应单一类
<resultMap id="CustomerRM" type="com.zhang.myb atis.bean.Customer"> <id property="id" column="id"></id> <result property="name" column="name"></result> <collection property="orders" ofType="com.zhang.mybatis.bean.Order"> <id property="id" column="id"></id> <result property="address" column="address"></result> <result property="amount" column="amount"></result> <result column="customer_id" property="customerId"></result> </collection> </resultMap>
六.分步查询
1.手动分步查询
//按照id查询订单 Order order = orderDao.getOrderById(1); //使用订单中的用户id查询用户 Customer customer = OrderDao.getCustomerById(order.getCustomerId()); //将用户设置到订单中 order.setCustomer(customer);
2.自动分步查询
<resultMap id="OrderRM" type="com.zhang.mybatis.bean.Order"> <id property="id" column="id"></id> <result property="address" column="address"></result> <result property="amount" column="amount"></result> <result property="customerId" column="customer_id"></result> <collection property="customers" select="com.zhang.mybatis.dao.OrderDao.getCustomerById" column="customer_id"> </collection> </resultMap>
-
在collection或者association中使用select属性告诉mybatis在装填该项时使用另一个查询方法
-
column指定传递的参数,当出现多个参数时,使用以下格式
column="{userName=user_name,userAge=user_age}"
3.超级分步问题
-
问题:当实体类之间存在循环引用时,例如 A 类关联 B 类,B 类又关联 A 类,分步查询可能会陷入无限循环,最终导致栈溢出错误。
-
解决办法:使用嵌套查询,把分步查询改成嵌套查询,在一个 SQL 语句里完成所有关联查询。不过这样可能会让 SQL 语句变得复杂,降低可读性。
4.延迟加载
-
MyBatis 的延迟加载基于 Java 的动态代理机制。当主查询执行时,关联对象会被代理对象替代。只有当真正访问关联对象的属性或方法时,MyBatis 才会触发关联查询,从数据库中获取实际数据。
-
开启延迟加载:
在application.properties中添加配置
mybatis.configuration.lazy-loading-enabled=true//开启 MyBatis 的延迟加载特性。 mybatis.configuration.aggressive-lazy-loading=false//禁用积极延迟加载。
七.动态sql
默认事务回滚开启
1.if标签
当test中的条件成立时,标签中的语句才存在
<select id="getOrderById" resultMap="OrderRM"> select * from orders <if test="id != null and id!='' "> where orders.id = #{id} </if> </select>
2.where标签
将if标签等都放到where中,会自动解决where后面语法错误问题
<select id="getOrderById" resultMap="OrderRM"> select * from orders <where> <if></if> <if></if> </where> </select>
3.set标签
和where标签相同,解决语法问题
<update id="updateEmp"> update t_emp <set> <if></if> <if></if> </set> <update>
4.trim标签
当标签中存在内容时,为该内容添加前缀(prefix)或者后缀(suffix)
<trim prefix="前缀" suffix="后缀"></trim>
标签体中最终生成的字符串如果以指定前缀(prefixOverrides)或者后缀(suffixOverrides)开始,就将该前缀覆盖为空串
<trim prefixOverrides="前缀" suffixOverrides="后缀"></trim>
5.choose/when/otherwise标签
<choose>:作为根标签,包裹多个 <when> 和一个可选的 <otherwise> 标签,用于表示一组条件选择逻辑。
<choose> <when test="condition1"> <!-- SQL 片段 1 --> </when> <when test="condition2"> <!-- SQL 片段 2 --> </when> <!-- 可以有多个 <when> 标签 --> <otherwise> <!-- 默认 SQL 片段 --> </otherwise> </choose>
6.foreach标签
-
IN子句:查询多ID记录,生成如 (id1, id2)。
<select id="getOrdersByIds" resultMap="OrderRM"> SELECT * FROM orders WHERE id IN <foreach item="item" index="index" collection="orderIds" open="(" separator="," close=")"> #{item} </foreach> </select>
-
批量插入:生成多个 VALUES 子句。
<insert id="batchInsertOrders"> INSERT INTO orders (address, amount, customer_id) VALUES <foreach item="order" index="index" collection="orderList" separator=","> (#{order.address}, #{order.amount}, #{order.customerId}) </foreach> </insert>
-
批量更新:结合 CASE WHEN 语句。
<update id="batchUpdateOrders"> <foreach collection="emps" item="e" separator=";"> update t_emp set emp_name = #{e.empName} where id = #{e.id} </foreach> </update>
7.抽取复用sql片段
-
抽取:
<sql id="column_names"></sql>
-
引用:
<include refid="column_names"></include>
八.xml的转义字符
字符 | 转义序列 | 描述 |
---|---|---|
& | & | 用于表示字符 & 本身,& 在 XML 中有特殊含义,用于表示转义序列的开始,所以需要转义 |
< | < | 用于表示小于号 < ,< 用于标识 XML 标签的开始,因此在文本中使用时需转义 |
> | > | 用于表示大于号 > ,> 用于标识 XML 标签的结束,在文本中使用时需转义 |
" | " | 用于表示双引号 " ,在属性值中常用双引号,当在文本内容中出现双引号时需转义 |
' | &apos | 用于表示单引号 ' ,在属性值中也可以使用单引号,在文本内容中出现单引号时也需转义 |
九.mybatis缓存机制
一级缓存
-
作用域:一级缓存也称为会话(SqlSession)级缓存,它的作用域是单个 SqlSession。在同一个 SqlSession 中,执行相同的 SQL 查询时,MyBatis 会优先从一级缓存中获取数据,而不是再次查询数据库。
-
原理:当一个 SqlSession 执行查询操作时,MyBatis 会将查询结果存储在该 SqlSession 的缓存中。如果后续再次执行相同的查询(相同的 SQL 语句和参数),MyBatis 会直接从缓存中返回结果,而不会再次访问数据库。
-
失效情况:当 SqlSession 执行插入、更新、删除操作,或者调用 SqlSession 的 clearCache() 方法时,一级缓存会被清空。
-
示例代码:
try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); // 第一次查询 User user1 = mapper.getUserById(1); // 第二次查询,使用缓存 User user2 = mapper.getUserById(1); System.out.println(user1 == user2); // 输出 true }
二级缓存
-
作用域:二级缓存也称为命名空间(Mapper)级缓存,它的作用域是同一个命名空间(Mapper 接口)。不同的 SqlSession 可以共享二级缓存中的数据,只要它们操作的是同一个 Mapper 接口。
-
原理:MyBatis 的二级缓存是基于 Mapper 命名空间的,每个 Mapper 都有自己独立的二级缓存。当一个 SqlSession 执行查询操作并将结果存储在二级缓存中后,其他 SqlSession 执行相同的查询时,可以直接从二级缓存中获取数据。
-
开启方式:要开启二级缓存,需要在 Mapper XML 文件中添加 <cache> 标签,或者在 Mapper 接口上添加 @CacheNamespace 注解。
-
失效情况:当 Mapper 对应的 SqlSession 执行插入、更新、删除操作时,该 Mapper 的二级缓存会被清空。
-
示例代码
<mapper namespace="com.example.mapper.UserMapper"> <!-- 开启二级缓存 --> <cache /> <select id="getUserById" resultType="com.example.entity.User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
try (SqlSession session1 = sqlSessionFactory.openSession(); SqlSession session2 = sqlSessionFactory.openSession()) { UserMapper mapper1 = session1.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); // 第一次查询,存入二级缓存 User user1 = mapper1.getUserById(1); // 第二次查询,从二级缓存获取 User user2 = mapper2.getUserById(1); System.out.println(user1 == user2); // 输出 false,但数据内容相同 }
十.插件机制
MyBatis底层使用拦截器机制提供插件功能,方便用户在SQL执行前后进行拦截增强。
拦截器:Interceptor
它允许拦截 Executor(执行器)、ParameterHandler(参数处理器)、ResultSetHandler(结果集处理器)和 StatementHandler(语句处理器)这四个核心接口的方法调用。