巧说 Mybatis
核心配置文件详解
MyBatis 核心配置文件模板
<?xml version="1.0" encoding="utf8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/a.xml"/>
</mappers>
</configuration>
environment
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="test">
<!--
type: JDBC / MANAGER
-->
<transactionManager type="JDBC"></transactionManager>
<!--
type: POOLED / UNPOOLED / JNDI
JNDI: 表示使用上下文中的数据源
-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mysqldemo"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
typeAliases
<typeAliases>
<!--
typeAlias 设置某个类型别名:
属性:
type:设置需要设置别名的类型
alias:设置某个类型别名
-->
<typeAlias type="com.lyq.mybatis.Entity.Category" alias="Category"/>
<!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
<package name="com.lyq.mybatis.Entity"/>
</typeAliases>
properties
引入jdbc.properties,使用${变量名}的形式引入
<properties resource="jdbc.properties" />
mappers
<mappers>
<!--<mapper resource="mapper/CategoryMapper.xml"></mapper>-->
<!--
以包为单位引入映射文件:
要求 1: mapper 接口所在的包要和映射文件所在的包一致
要求 2:mapper 接口要和映射文件的名字一样
-->
<package name="com.lyq.mybatis.mapper"/>
</mappers>
MyBatis 获取参数值的方式(重点)
当Mapper接口方法的参数为单个的字面量
<!--User getUserById(String username);-->
<select id="getUserById" resultType="User">
select * from users where userName = '${userName}
select * from users where userName = #{userName}
</select>
当Mapper接口方法的参数为多个
- 此时 MyBatis 会将这些变量存放在一个Map中,以两种方式存储
- 以arg0,arg1...为键,以参数为值
- 以 param1, param2...为键,以参数为值
<!--User checkLogin(String userName, String password);-->
<select id="checkLogin" resultType="User">
select * from users
where userName = #{arg0} and password = #{arg1}
</select>
mapper 接口方法的参数是实体类类型的参数
- 只需要通过 #{} 以属性的方式访问属性值即可,但是需要注意 ${} 的单引号问题
<!--int insertUserInfo(User user); -->
<insert id="insertUserInfo" parameterType="User">
insert into users values(#{userName}, #{password}, #{phone})
</insert>
特殊 SQL 的执行
模糊查询
<select id="getUserByLike" resultType="User">
select * from users where userName like '%${userName}%'
</select>
批量删除
<delete id="deleteMore" parameterType="string">
delete from users where id in (${id})
</delete>
获取自增主键
<!--
useGeneratedKeys: 设置当前标签中的 sql 使用自增主键
keyProperty: 将自增的主键的值赋值给传输到映射文件中参数的某个属性
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into users values(null, #{userName}, #{password}, #{phone})
</insert>
自定义映射 resultMap
解决字段名和数据库字段名不一致问题
- 为字段起别名
- 添加 MyBatis 全局配置
<settings>
<!--将下划线自动映射为驼峰 emp_name -> empName -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- 通过
resultMap映射(一般用来处理:多对一 和 一对多 关系)
<mapper namespace="com.lyq.mybatis.mapper.EmpMapper">
<resultMap id="empResultMap" type="Emp">
<!--
association :用来处理 多对一 映射关系
collection :用来处理 一对多 映射关系
id : 设置主键映射关系
result : 设置普通字段映射关系
type : 设置映射关系中的实体类类型
property : 该字段是 sql 语句查询出的字段名
column : 该字段是数据库表的字段名
-->
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
</mapper>
多对一映射关系
1. 级联属性赋值
```
<!-- 处理多对一映射关系方式一:级联属性赋值 -->
<resultMap id="empAndDeptResultMapOne" 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>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMapOne">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
```
2. 使用 association标签
```
<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>
<select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
```
3. 分步查询
<!--Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMapTwo">
select * from t_emp where eid = #{eid}
</select>
<resultMap id="empAndDeptByStepResultMapTwo" 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>
<!--
select : 设置分布查询的 sql 的唯一标识(mapper接口的全类名.方法名)
column : 设置分布查询的条件
-->
<association
property="dept"
select="com.lyq.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did"
></association>
</resultMap>
<mapper namespace="com.lyq.mybatis.mapper.DeptMapper">
<!--Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
select * from t_dept where did = #{did}
</select>
</mapper>
一对多映射关系
1. collection 方式
<select id="getDeptAndEmpt" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
<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>
2. 分步查询方式
延迟加载
全局配置文件
<!-- 设置 MyBatis 的全局配置 -->
<settings>
<!--将下划线自动映射为驼峰 emp_name -> empName -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 延迟加载开启 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
mapper文件
<resultMap id="empAndDeptByStepResultMapTwo" 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>
<!--
select : 设置分布查询的 sql 的唯一标识(mapper接口的全类名.方法名)
column : 设置分布查询的条件
fetchType : 当开启了全局的延迟加载之后,可通过此属性手动控制延迟加载的效果
lazy: 标识延迟加载
eager: 表示立即加载
-->
<association
property="dept"
select="com.lyq.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did"
fetchType="eager"
></association>
</resultMap>
动态 SQL
多条件查询 - if 标签
使用 if 标签,可以动态添加 sql 字段名进行查询
<mapper namespace="com.lyq.mybatis.mapper.DynamicSQLMapper">
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where 1=1
<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>
</select>
</mapper>
多条件查询 - where 标签
<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 != ''">
age = #{age}
</if>
<if test="sex != null and sex != ''">
sex = #{sex}
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
</where>
</select>
trim 标签
- prefix: 将
trim标签中内容前面添加指定内容 - suffix: 将
trim标签中内容后面添加指定内容 - prefixOverrides: 将
trim标签中内容前面去除指定内容 - suffixOverrides: 将
trim标签中内容后面去除指定内容
forEach 标签
- collection :集合名称
- item:集合中的每一项
- separator :分隔符号
- open:括号
- close:括号
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," close=")" open="(">
#{eid}
</foreach>
</delete>
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
</foreach>
</insert>
sql 标签
<sql id="testSql">
eid, emp_name
</sql>
<select id="test">
select <include refid="testSql"></include> from t_emp
</select>
MyBatis 的缓存
一级缓存
一级缓存是sqlSession级别的,通过同一个sqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会在次访问数据库
使一级缓存失效的几种情况
- 不同的 SqlSession 对应不同的一级缓存
- 同一个 SqlSession 但是查询条件不同
- 同一个 SqlSession 两次查询期间执行了任何一次增删操作
- 同一个 SqlSession 两次查询期间手动清除缓存
二级缓存
二级缓存是SqlSessionFactory级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若是再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启条件:
- 在核心配置文件中,设置全局配置属性 cacheEnabled = "true", 默认为 true, 不需要设置
- 在映射文件中设置标签
<cache/> - 二级缓存必须在 SqlSession 关闭或提交之后有效
- 查询的数据缩转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:
- 两次查询之间执行了任意的增删改,会让一级缓存和二级缓存同时失效
二级缓存相关配置
在 mapper 配置文件中添加的 cache 标签中可以设置的属性:
-
eviction 属性:缓存回收策略
- LRU
- FIFO
- SOFT :软引用
- WEAK :弱引用
- 默认的是 LRU
-
flushInterval 属性:刷新间隔,单位是
毫秒,默认没有间隔 -
size 属性:引用数目,代表缓存最多可以存储多少个对象
-
readOnly 属性:只读,true / false
- true:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改
- false:会返回缓存对象的拷贝(通过序列化)。安全
MyBatis 缓存查询顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的程序,可以拿来直接使用
- 如果二级缓存没有命中,在查询一级缓存
- 如果一级缓存也没有命中,就查询数据库
- SqlSession 关闭之后,一级缓存中的数据会写入二级缓存
MyBatis 分页插件
- 安装依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
- 配置分页插件
在 MyBatis 的核心配置文件mybatis-config.xml中配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
- 使用分页插件
/**
* limit index pageSize
* index:当前页起始索引
* pageSize:每页显示的条数
* pageNum:当前页的页码
* index = (pageNum - 1) * pageSize
* @throws Exception
*/
@Test
public void testPageHelper() throws Exception{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
// 开启分页
// 第一个参数是:从第几页开始
// 第二个参数是:每一页多少条数据
PageHelper.startPage(1, 4);
List<Emp> allEmp = mapper.getAllEmp();
for (Emp emp : allEmp) {
System.out.println(emp);
}
}