什么是Mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
Mybatis是一个半ORM(对象关系映射)框架,底层封装了JDBC,是程序员在开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,严格控制sql执行性能,灵活度高。
数据持久化:将内存中数据模型转换为存储模型。例如:程序员操作数据对数据库进行增删该查操作。
ORM:(Object relation Mapping)对象映射关系;在对象和关系型数据库直接建立对应关系。
MyBatis优缺点
优点:
- 封装了JDBC,只需关注SQL编写,不用处理连接,关闭过程。
- SQL写在xml文件中,从程序代码中彻底分离。
- 提供xml标签,支持动态SQL。
- 提供映射标签,支持数据库与对象字段关系映射。
缺点
- SQL编写工作量大
- SQL依赖于数据库,导致数据库不具有好的移植性
MyBatis功能架构
-
API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。(Mapper接口)
-
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。(mapper.xml)
-
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。(mybatisConfig.xml)
$和#
#{}是预编译处理,会将SQL中#{}替换为?,调用P热怕热的statement的set方法赋值;在加载时SQL格式已经被确定可以防止SQL注入。
${}是字符串的替换,把${}替换为变量值。
$存在原因:例如开发者不知道实际需要操作的表,动态传入table表
#{} select * from #{} where ... ----》 select * from ”table“ where 报错
${} select * from ${} where ... ----》 select * from table where
Mapper接口的工作原理
Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
特殊查询
模糊查询
/**
* 根据用户名进行模糊查询
* @param username
* @return java.util.List<com.atguigu.mybatis.pojo.User>
* @date 2022/2/26 21:56
*/
List<User> getUserByLike(@Param("username") String username);
```
```xml
<!--List<User> getUserByLike(@Param("username") String username);-->
<select id="getUserByLike" resultType="User">
<!--select * from t_user where username like '%${mohu}%'-->
<!--select * from t_user where username like concat('%',#{mohu},'%')-->
select * from t_user where username like "%"#{mohu}"%"
</select>
批量删除
- 只能使用\${},如果使用#{},则解析后的sql语句为`delete from t_user where id in ('1,2,3')`,这样是将`1,2,3`看做是一个整体,只有id为`1,2,3`的数据会被删除。正确的语句应该是`delete from t_user where id in (1,2,3)`,或者`delete from t_user where id in ('1','2','3')`
```java
/**
* 根据id批量删除
* @param ids
* @return int
* @date 2022/2/26 22:06
*/
int deleteMore(@Param("ids") String ids);
```
```xml
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>
添加功能获取自增主键
insert 方法总是返回一个int值 ,这个值代表的是插入的行数。如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
自定义映射ResultMap
- resultMap:设置自定义映射
- 属性:
- id:表示自定义映射的唯一标识,不能重复
- type:查询的数据要映射的实体类的类型
- 子标签:
- id:设置主键的映射关系
- result:设置普通字段的映射关系
- 子标签属性:
- property:设置映射关系中实体类中的属性名
- column:设置映射关系中表中的字段名
- 若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射,即使字段名和属性名一致的属性也要映射,也就是全部属性都要列出来
<resultMap id="empResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>
<!--List<Emp> getAllEmp();-->
<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
```
- 若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性名符合Java的规则(使用驼峰)。此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系
1. 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
<!--List<Emp> getAllEmp();-->
<select id="getAllEmp" resultType="Emp">
select eid,emp_name empName,age,sex,email from t_emp
</select>
```
2. 可以在MyBatis的核心配置文件中的`setting`标签中,设置一个全局配置信息mapUnderscoreToCamelCase,可以在查询表中数据时,自动将_类型的字段名转换为驼峰,例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为userName。[核心配置文件详解](#核心配置文件详解)
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
```
多对一映射处理
- association:处理多对一的映射关系
- property:需要处理多对的映射关系的属性名
- javaType:该属性的类型
```xml
<resultMap id="empAndDeptResultMapTwo" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<association property="dept" javaType="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<!--Emp getEmpAndDept(@Param("eid")Integer eid);-->
<select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
select * from t_emp left join t_dept on t_emp.eid = t_dept.did where t_emp.eid = #{eid}
</select>
一对多映射处理
- collection:用来处理一对多的映射关系
- ofType:表示该属性对饮的集合中存储的数据的类型
```xml
<resultMap id="DeptAndEmpResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<!--Dept getDeptAndEmp(@Param("did") Integer did);-->
<select id="getDeptAndEmp" resultMap="DeptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
动态SQL
If
- if标签可通过test属性(即传递过来的数据)的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行
- 在where后面添加一个恒成立条件`1=1`
- 这个恒成立条件并不会影响查询的结果
- 这个`1=1`可以用来拼接`and`语句,例如:当empName为null时
- 如果不加上恒成立条件,则SQL语句为`select * from t_emp where and age = ? and sex = ? and email = ?`,此时`where`会与`and`连用,SQL语句会报错
- 如果加上一个恒成立条件,则SQL语句为`select * from t_emp where 1= 1 and age = ? and sex = ? and email = ?`,此时不报错
```xml
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where 1=1
<if test="empName != null and empName !=''">
and emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</select>
where
- where和if一般结合使用:
- 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
- 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and/or去掉
```xml
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName != null and empName !=''">
emp_name = #{empName}
</if>
<if test="age != null and age !=''">
and age = #{age}
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
<if test="email != null and email !=''">
and email = #{email}
</if>
</where>
</select>
```
- 注意:where标签不能去掉条件后多余的and/or
```xml
<!--这种用法是错误的,只能去掉条件前面的and/or,条件后面的不行-->
<if test="empName != null and empName !=''">
emp_name = #{empName} and
</if>
<if test="age != null and age !=''">
age = #{age}
</if>
```
trim
- trim用于去掉或添加标签中的内容
- 常用属性
- prefix:在trim标签中的内容的前面添加某些内容
- suffix:在trim标签中的内容的后面添加某些内容
- prefixOverrides:在trim标签中的内容的前面去掉某些内容
- suffixOverrides:在trim标签中的内容的后面去掉某些内容
- 若trim中的标签都不满足条件,则trim标签没有任何效果,也就是只剩下`select * from t_emp`
```xml
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null and empName !=''">
emp_name = #{empName} and
</if>
<if test="age != null and age !=''">
age = #{age} and
</if>
<if test="sex != null and sex !=''">
sex = #{sex} or
</if>
<if test="email != null and email !=''">
email = #{email}
</if>
</trim>
</select>
```
choose、when、otherwise
- `choose、when、otherwise`相当于`if...else if..else`
- when至少要有一个,otherwise至多只有一个
```xml
<select id="getEmpByChoose" resultType="Emp">
select * from t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
emp_name = #{empName}
</when>
<when test="age != null and age != ''">
age = #{age}
</when>
<when test="sex != null and sex != ''">
sex = #{sex}
</when>
<when test="email != null and email != ''">
email = #{email}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</where>
</select>
```
foreach
- 属性:
- collection:设置要循环的数组或集合
- item:表示集合或数组中的每一个数据
- separator:设置循环体之间的分隔符,分隔符前后默认有一个空格,如` , `
- open:设置foreach标签中的内容的开始符
- close:设置foreach标签中的内容的结束符
- 批量删除
```xml
<!--int deleteMoreByArray(Integer[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
```
```java
@Test
public void deleteMoreByArray() {
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
int result = mapper.deleteMoreByArray(new Integer[]{6, 7, 8, 9});
System.out.println(result);
}
```
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种:通过 <resultMap> 来映射字段名和实体类属性名的一一对应的关系。
第二种是使用sql列的别名功能,让字段名的别名和实体类的属性名一致。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
延迟加载
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB.getName,拦截器invoke方法发现a.getB是值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB.getName方法的调用。这就是延迟加载的基本原理。
Mybatis缓存机制
一级缓存
本地缓存,默认启用,不能关闭。
一级缓存存在于SqlSession的生命周期,SqlSession级别的缓存。在同一个SqlSession中查询时,MyBatis会把执行方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。
若同一个SqlSession中执行方法、参数完全一致,通过算法就会生成相同的键值,当Map缓存对象中已存在该键时,返回缓存对象。
默认情况下:insert、delete、update、操作后都会刷新缓存。可通过sqlSession.clearCache()手动清除缓存。
默认缓存范围是在同一个session中共享-----问题:当在多个会话下,读取的数据不准确。
二级缓存
SqlSessionFactory级别缓存(xml中一个命名空间,同一个mapper) 、存在于SqlSessionFactory生命周期,被多个SqlSession共享,是一个全局变量。
手动开启:要求POJO是可序列化的。
当二级缓存开启后:查询流程:二级缓存-----》一级缓存-----》数据库
文件中所有的select会被缓存,当执行insert、delete、update、操作后都会刷新缓存。
当缓存空间满时默认使用LRU算法(最近最少使用)算法回收。其他清除缓存策略:FIFO淘汰策略、JVM软应用淘汰策略、JVM弱引用淘汰策略。
-问题:当在多个SqlSessionFactory下,读取的数据不准确。