文章目录
- 特殊SQL的执行
- 模糊查询
- 批量操作
- 动态设置表名
- 添加功能获取自增的主键
- 自定义映射resultMap
- 处理字段和属性的映射关系
- 在SQL语句中使用别名
- 使用核心配置文件中的驼峰对应方法
- 使用resultMap自定义映射处理
- 一对一映射处理
- 级联方式处理
- association标签
- 分步查询
- 分步查询的优点:可以实现延迟加载
- 一对多映射处理
- collection
- 分步查询
- 动态SQL
- if标签
- 使用1=1解决and问题
- 使用where标签解决问题
- 利用trim解决问题
- choose、when、otherwise
- foreach
- set标签
- SQL片段
- Mybatis缓存
- 一级缓存
- 使一级缓存失效的四种情况:
- MyBatis的二级缓存
- 二级缓存开启的条件
- 二级缓存的相关配置
- MyBatis缓存查询的顺序
- 总结
- Mybatis逆向工程
- 创建逆向工程的步骤
- 添加依赖和插件
- 创建MyBatis的核心配置文件
- 创建逆向工程的配置文件
- 执行MBG插件的generate目标
- 最后的效果
- 功能测试
- 分页插件
- 分析分页需要哪些数据
- 使用步骤
- 添加依赖
- 配置分页插件
- 分页插件的使用
特殊SQL的执行
模糊查询
<mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
<!-- List<User> getUserByLike(@Param("mohu") String mohu);-->
<select id="getUserByLike" resultType="User">-->
select * from t_user where username like "%${mohu}%"-->
</select>-->
<select id="getUserByLike" resultType="User">-->
select * from t_user where username like "%"#{mohu}"%"-->
</select>-->
<select id="getUserByLike" resultType="User">
select * from t_user where username like concat('%',#{mohu},'%')
</select>
</mapper>
/**
* 通过用户名模糊查询用户信息
* @param mohu
* @return
*/
List<User> getUserByLike(@Param("mohu") String mohu);
- 最主要是对于我们这种%%和我们的参数进行拼接
- 第一种直接使用${},不会自动加’’
- 第二种"%" “%”
- 第三种利用concat函数进行拼接
批量操作
<mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
<delete id="deleteMoreUser">
delete from t_user where id in (${ids})
</delete>
</mapper>
/**
* 批量删除
* @param ids
*/
int deleteMoreUser(@Param("ids") String ids);
动态设置表名
/**
* 动态设置表名,查询用户信息
* @param tableName
* @return
*/
List<User> getUserList(@Param("tableName") String tableName);
<mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
<!-- List<User> getUserList(@Param("tableName") String tableName);-->
<select id="getUserList" resultType="User">
select * from ${tableName}
</select>
</mapper>
添加功能获取自增的主键
场景模拟:
t_clazz(clazz_id,clazz_name)
t_student(student_id,student_name,clazz_id)
1、添加班级信息
2、获取新添加的班级的id
3、为班级分配学生,即将某学的班级id修改为新添加的班级的id
- 所以有时候对新增数据的主键是很有必要的
/**
* 添加用户信息并获取自增的主键
* @param user
*/
void insertUser(User user);
<mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
<!-- void insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{pwd},#{age},#{gender},#{email})
</insert>
</mapper>
- useGeneratedKeys:设置使用自增的主键
- keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
@Test
public void insertUserTest(){
SqlSession sqlSession= SqlSessionUtil.getSqlSession();
SpecialSQLMapper specialSQLMapper=sqlSession.getMapper(SpecialSQLMapper.class);
User user=new User(null,"小寿司","123",1,"男","xss@163.com");
specialSQLMapper.insertUser(user);
System.out.println(user);
}
//输出结果
//User{id=13, username='小寿司', pwd='123', age=1, gender='男', email='xss@163.com'}
- 我们进行执行的User的id是null.但是执行insert后的User中有了对应的id属性的值
自定义映射resultMap
resultMap:设置自定义的映射关系
- id:唯一标识
- type:处理映射关系的实体类的类型
- 常用的标签:
- id:处理主键和实体类中属性的映射关系
- column:设置映射关系中的字段名,必须是sql查询出的某个字段
- property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
- result:处理普通字段和实体类中属性的映射关系
- column:设置映射关系中的字段名,必须是sql查询出的某个字段
- property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
- association:处理一对一的映射关系(处理实体类类型的属性)
- property:设置需要处理映射关系的属性的属性名
- javaType:设置要处理的属性的类型
- collection:处理一对多的映射关系(处理集合类型的属性)
- property:设置需要处理映射关系的属性的属性名
- ofType:设置collection标签所处理的集合属性中存储数据的类型
- id:处理主键和实体类中属性的映射关系
- 这里的一对一和一对多前面的表示属性,后面表示记录
处理字段和属性的映射关系
- 在前面的例子中,我们的属性名称和字段名称是一样的,所以不需要特意设置,但是也会有属性名和字段名不一样的情况,就需要我们主动去设置
/**
* 通过Id查询对应的Emp数据
* @param id
* @return
*/
Emp getEmpById(@Param("id")Integer id);
在SQL语句中使用别名
<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
<!-- Emp getEmpById(@Param("id")Integer id);-->
<select id="getEmpById" resultType="Emp">
select emp_id as empId, emp_name as empName,age,gender,dept_id as deptId from t_emp where emp_id = #{id}
</select>
</mapper>
- 为查询的字段设置别名,和属性名保持一致
使用核心配置文件中的驼峰对应方法
mybatis对应的核心配置文件的设置
<settings>
<!--将下划线映射为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
EmpMapper.xml的配置
<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
<!-- Emp getEmpById(@Param("id")Integer id);-->
<select id="getEmpById" resultType="Emp">
select * from t_emp where emp_id= #{id}
</select>
</mapper>
- 当我们开启了这个配置,不需要取别名也能成功获取数据到对应的属性上
- 当
字段符合MySQL
的要求使用_,而属性符合java的要求使用驼峰
- 此时可以在MyBatis的核心配置文件中设置一个全局配置,可以自动将下划线映射为驼峰
- emp_id->empId , emp_name->empName dept_id->deptId
- 此时可以在MyBatis的核心配置文件中设置一个全局配置,可以自动将下划线映射为驼峰
使用resultMap自定义映射处理
<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<result column="dept_id" property="deptId"></result>
</resultMap>
<!-- Emp getEmpById(@Param("id")Integer id);-->
<select id="getEmpById" resultMap="empResultMap">
select * from t_emp where emp_id= #{id}
</select>
</mapper>
<resultMap id="empResultMap" type="Emp">
id表示resultMap的id标志 type表示表示返回的实体类型,Emp是别名<id column="emp_id" property="empId"></id>
id表示处理主键和实体类中属性的映射关系- column表示表中的主键名称, property表示实体类的属性名,表示对应
<result column="emp_name" property="empName"></result>
result表示处理普通字段和实体类中属性的映射关系- column表示表中的字段名称, property表示实体类的属性名,表示对应
一对一映射处理
public interface EmpMapper {
/**
* 通过Id查询对应的Emp数据
* @param id
* @return
*/
Emp getEmpById(@Param("id")Integer id);
}
级联方式处理
<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
<resultMap id="empResultMap" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
</resultMap>
<!-- Emp getEmpById(@Param("id")Integer id);-->
<select id="getEmpById" resultMap="empResultMap">
select emp.*,dept.* from t_emp as emp left join t_dept as dept on emp.emp_id=dept.dept_id where emp_id=#{id}
</select>
</mapper>
- 利用联表查询到两张表中的所有数据 ,然后利用级联方式进行对应数据
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
association标签
<resultMap id="empResultMapTwo" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<association javaType="Dept" property="dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>
<!-- Emp getEmpById(@Param("id")Integer id);-->
<select id="getEmpById" resultMap="empResultMapTwo">
select emp.*,dept.* from t_emp as emp left join t_dept as dept on emp.emp_id=dept.dept_id where emp_id=#{id}
</select>
- assocation标签就是专门处理这种一个属性对应表中一列记录的情况
<association javaType="Dept" property="dept">
- javaType表示转换成的Java对象的类型 Dept是别名
- property表示对应的属性名称
分步查询
EmpMapper
public interface EmpMapper {
/**
* 通过Id分步查询出对应的Emp数据
* @param empId
* @return
*/
Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
}
DeptMapper
public interface DeptMapper {
/**
* 根据DeptId查询出对应Dept数据
* @param DeptId
* @return
*/
Dept getDept(@Param("DeptId") Integer DeptId);
}
DeptMapper.xml
<mapper namespace="com.lsc.mybatis.mappers.DeptMapper">
<!-- Dept getDept(@Param("id") Integer id);-->
<select id="getDept" resultType="Dept">
select * from t_dept where dept_id=#{id}
</select>
</mapper>
EmpMapper.xml
<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
<resultMap id="empResultMapThree" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<association property="dept" fetchType="lazy"
select="com.lsc.mybatis.mappers.DeptMapper.getDept" column="dept_id">
</association>
</resultMap>
<!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByStepOne" resultMap="empResultMapThree">
select * from t_emp where emp_id = #{empId}
</select>
</mapper>
-
property:设置需要处理映射关系的属性的属性名
-
select:设置分步查询的sql的唯一标识 也就是这个方法的全名成(包名+类名+方法名)
-
column:将查询出的某个字段作为分步查询的sql的条件
-
fetchType:在开启了延迟加载的环境中,通过该属性设置当前的分步查询是否使用延迟加载
- fetchType=“eager(立即加载)|lazy(延迟加载)”
在核心配置文件中的设置
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
分步查询的优点:可以实现延迟加载
但是必须在核心配置文件中设置全局配置信息:
- lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
- aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载
- 两个属性这样设置,此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql
Mybatis仅支持association关联对象,和collection关联集合对象的延迟加载,association指的是一对一,collection指的是一对多(这里是指对象和表中记录的关系)
原理
- 使用CGLIB创建目标对象的代理对象,当调用目标方法时候,进入拦截器方法,比如调用a.getB().getName()方法,拦截器的invoke方法发现a.getB()是null值,那么就会单独发送事先保存好的关联B对象的sql,把B查询上来,然后调用a.setB(b),,于是a的对象b属性有值了,接着完成a.getB().getName()方法的调用
- 几乎所有延迟加载的原理都是这样
假设我们使用测试方法测试延迟加载,先调用一个查询,查询得出结果赋给对象a,里面包含未加载的null值项,假设这一项为类B的引用——b,并且假设类B拥有get方法getName()。当我们调用a.getB().getName()时,此时对象a中的成员对象b为null,无法调用其的getName()方法,此时Mybatis意识到要使用延迟加载来使此次调用不出Bug。
Mybatis使用CGLib生成目标对象a的代理对象,当我们在测试方法里调用a.getB()方法时,结果为null,不可行。于是,Mybatis调用拦截器方法,使用事先在resultMap中的association或collection标签里设定好的select查询语句来获取数据库中的数据并映射到对象b上,此时b不再为null,a.getB().getName()顺利调用,实现了按需加载、延迟加载,节省了宝贵的计算资源。
一对多映射处理
/**
* 获得部门信息和部门对应的所有人的信息
* @param deptId
* @return
*/
Dept getEmpAndDeptResultMap(@Param("deptId") Integer deptId);
collection
<mapper namespace="com.lsc.mybatis.mappers.DeptMapper">
<resultMap id="deptAndEmpResultMap" type="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<!--
ofType:设置集合类型的属性中存储的数据的类型
-->
<collection property="empList" ofType="Emp">
<id property="empId" column="emp_id"></id>
<result property="empName" column="emp_name"></result>
<result property="gender" column="gender"></result>
<result property="age" column="age"></result>
</collection>
</resultMap>
<!-- Dept getEmpAndDeptResultMap(@Param("deptId") Integer deptId);-->
<select id="getEmpAndDeptResultMap" resultMap="deptAndEmpResultMap">
select *
from t_dept
left join t_emp
on t_dept.dept_id=t_emp.dept_id
where t_dept.dept_id=#{deptId}
</select>
</mapper>
- connection处理一对多的这种映射关系,也就是说一个属性对应多条数据
- ofType设置集合类型的属性中存储的数据的类型
分步查询
DeptMapper
/**
* 分步获得部门信息和部门对应的所有人的信息
* @param deptId
* @return
*/
Dept getEmpAndDeptTwoStep(@Param("deptId") Integer deptId);
EmpMapper
/**
* 根据deptId来获取所有的Emp
* @param deptId
* @return
*/
List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);
<mapper namespace="com.lsc.mybatis.mappers.DeptMapper">
<resultMap id="deptAndEmpResultMapByStep" type="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<collection property="empList"
select="com.lsc.mybatis.mappers.EmpMapper.getDeptAndEmpByStepTwo"
column="dept_id"></collection>
</resultMap>
<!-- Dept getEmpAndDeptTwoStep(@Param("deptId") Integer deptId);-->
<select id="getEmpAndDeptTwoStep" resultMap="deptAndEmpResultMapByStep">
select * from t_dept where dept_id=#{deptId}
</select>
</mapper>
动态SQL
动态 sql 是Mybatis的强大特性之一,能够完成不同条件下不同的 sql 拼接。
-
如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦
-
它存在的意义是为了 解决 拼接SQL语句字符串时的痛点问题。
if标签
if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行
<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="gender != null and gender != ''">
and gender=#{gender}
</if>
</select>
- 如果empName为null,那么开头会多出来一个and
- 如果都为null,那么会只出现一个where
使用1=1解决and问题
DynamicMapper
/**
* 根据条件查询员工信息
* @param emp
* @return
*/
List<Emp> getEmpByCondition(Emp emp);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!-- 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="gender != null and gender != ''">
and gender=#{gender}
</if>
</select>
</mapper>
使用where标签解决问题
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<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="gender != null and gender != ''">
and gender=#{gender}
</if>
</where>
</select>
</mapper>
where和if一般结合使用:
- 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
- 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
注意:where标签不能去掉条件最后多余的and
利用trim解决问题
trim用于去掉或添加标签中的内容
常用属性:
- prefix:表示整个语句块,以prefix的值作为前缀
- suffix:表示整个语句块,以suffix的值作为后缀
- prefixOverrides:表示整个语句块要去除掉的前缀
- suffixOverrides:表示整个语句块要去除掉的后缀
如果为内容为空串,那么都不会生效
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="empName != null and empName != ''">
emp_name = #{empName} and
</if>
<if test="age != null and age != ''">
age = #{age} and
</if>
<if test="gender != null and gender != ''">
gender = #{gender}
</if>
</trim>
</select>
</mapper>
- 在前面加一个 prefix=“where”
choose、when、otherwise
相当于java中的if…else if…else
DynamicMapper
/**
* 使用choose查询员工信息
* @param emp
* @return
*/
List<Emp> getEmpByChoose(Emp emp);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!-- List<Emp> getEmpByChoose(Emp emp);-->
<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="gender != null and gender !=''">
gender= #{gender}
</when>
</choose>
</where>
</select>
</mapper>
- choose是when和otherwise的父标签
- when只要满足了一个,后面的就不会执行,如果when都不满足,就执行ontherwise,很类似我们swtich case带break的结构
foreach
用于迭代的标签
/**
* 批量添加员工信息
* @param emps
*/
void insertMoreEmp(@Param("emps") List<Emp> emps);
/**
* 批量删除
* @param emplds
*/
void deleteMoreEmp(@Param("emplds") Integer[] emplds);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!-- void insertMoreEmp(@Param("emps") List<Emp> emps);-->
<insert id="insertMoreEmp" >
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.gender},null)
</foreach>
</insert>
<!-- void deleteMoreEmp(@Param("empIds") Integer[] empIds);-->
<delete id="deleteMoreEmp">
delete from t_emp where emp_id in
<foreach collection="emplds" item="empId" separator="," open="(" close=")">
#{empId}
</foreach>
</delete>
<!-- 一样的效果-->
<!-- void deleteMoreEmp(@Param("empIds") Integer[] empIds);-->
<delete id="deleteMoreEmp">
delete from t_emp where
<foreach collection="empIds" item="empId" separator="or">
emp_id = #{empId}
</foreach>
</delete>
</mapper>
- collection:设置要循环的数组或集合
- item:用一个字符串表示数组或集合中的每一个数据
- separator:设置每次循环的数据之间的分隔符
- open:循环的所有内容以什么开始
- close:循环的所有内容以什么结束
set标签
根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容。
/**
* 利用set标签更新
* @param emp
*/
void updateEmpBySet( Emp emp);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!-- void updateEmpBySet(@Param("emp") Emp emp);-->
<update id="updateEmpBySet">
update t_emp
<set>
<if test="empName!=null and empName !=''">
emp_name=#{empName}
</if>
<if test="age != null and age != ''">
,age=#{age}
</if>
<if test="gender != null and gender != ''">
,gender=#{gender}
</if>
</set>
where emp_id= #{empId}
</update>
</mapper>
- 跟where标签很相似
SQL片段
sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
<sql id="empColumns">
emp_id,emp_name,age,gender,dept_id
</sql>
select <include refid="empColumns"></include> from t_emp
Mybatis缓存
- 我们的浏览器也有缓存,当我们访问一些web资源,会把一些资源存储在本地,下次再次访问这些资源,就不需要从服务器获取,直接从本地获取
- Mybatis查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
CacheMapper接口
public interface CacheMapper {
/**
* 根据员工Id查询员工信息
* @param empId
* @return
*/
Emp getEmpById(@Param("empId") Integer empId);
/**
* 添加员工信息
* @param emp
*/
void insertEmp(Emp emp);
}
Cachemapper.xml文件
<mapper namespace="com.lsc.mybatis.mappers.CacheMapper">
<!-- Emp getEmpById(@Param("empId") Integer empId);-->
<select id="getEmpById" resultType="Emp">
select * from t_Emp where emp_id=#{empId}
</select>
<!-- void insertEmp(Emp emp);-->
<insert id="insertEmp" >
insert into t_emp values(null,#{empName},#{age},#{gender},null)
</insert>
</mapper>
一级缓存
一级缓存是SqlSession级别的
public void getEmpByIdTest() throws IOException {
InputStream is= Resources.getResourceAsStream("mybatis_config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession=sqlSessionFactory.openSession();
CacheMapper cacheMapper=sqlSession.getMapper(CacheMapper.class);
Emp emp1=cacheMapper.getEmpById(1);
Emp emp2=cacheMapper.getEmpById(1);
System.out.println(emp1);
System.out.println(emp2);
// SqlSession sqlSession1=sqlSessionFactory.openSession();
}
DEBUG 01-11 23:01:39,518 ==> Preparing: select * from t_Emp where emp_id=? (BaseJdbcLogger.java:137)
DEBUG 01-11 23:01:39,545 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:01:39,563 <== Total: 1 (BaseJdbcLogger.java:137)
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
- 我们发现我们通过同一个sqlSession来执行了两次getEmpById,但是从结果中我们看出来的只从数据库中查询了一次
- 说明了第二次查询是从缓存中获得数据
- 每个SqlSession中都持有Excutor,每个Excutor中有一个LocalCache。当用户发起询问时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
- Mybatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺
使一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 缓存也是从数据库查出来的,如果缓存中都没有数据,那么肯定用不了缓存,需要从数据库查
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 因为发生增删改,可能会导致缓存中的数据跟我们的数据库中不一样
- 同一个SqlSession两次查询期间手动清空了缓存
@Test
public void getEmpByIdTest() throws IOException {
InputStream is= Resources.getResourceAsStream("mybatis_config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1=sqlSessionFactory.openSession(true);
CacheMapper cacheMapper1=sqlSession1.getMapper(CacheMapper.class);
Emp emp1=cacheMapper1.getEmpById(1);
SqlSession sqlSession2=sqlSessionFactory.openSession(true);
CacheMapper cacheMapper2=sqlSession2.getMapper(CacheMapper.class);
Emp emp2=cacheMapper2.getEmpById(1);
System.out.println(emp1);
System.out.println(emp2);
// SqlSession sqlSession1=sqlSessionFactory.openSession();
}
//输出结果
DEBUG 01-11 23:13:45,519 ==> Preparing: select * from t_Emp where emp_id=? (BaseJdbcLogger.java:137)
DEBUG 01-11 23:13:45,547 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:13:45,564 <== Total: 1 (BaseJdbcLogger.java:137)
DEBUG 01-11 23:13:45,600 ==> Preparing: select * from t_Emp where emp_id=? (BaseJdbcLogger.java:137)
DEBUG 01-11 23:13:45,600 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:13:45,603 <== Total: 1 (BaseJdbcLogger.java:137)
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
- 我们发现从不同的SqlSession中查询,一级缓存失效了
@Test
public void getEmpByIdTest() throws IOException {
InputStream is= Resources.getResourceAsStream("mybatis_config.xml");
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1=sqlSessionFactory.openSession(true);
CacheMapper cacheMapper1=sqlSession1.getMapper(CacheMapper.class);
Emp emp1=cacheMapper1.getEmpById(1);
cacheMapper1.insertEmp(new Emp(null,"ABC",null,null,null));
Emp emp2=cacheMapper1.getEmpById(1);
System.out.println(emp1);
System.out.println(emp2);
sqlSession1.close();
sqlSession2.close();
}
DEBUG 01-11 23:20:30,168 ==> Preparing: select * from t_Emp where emp_id=? (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,191 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,209 <== Total: 1 (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,211 ==> Preparing: insert into t_emp values(null,?,?,?,null) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,213 ==> Parameters: ABC(String), null, null (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,218 <== Updates: 1 (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,219 ==> Preparing: select * from t_Emp where emp_id=? (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,219 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:20:30,221 <== Total: 1 (BaseJdbcLogger.java:137)
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
- 我们在同一个sqlSession下,查询相同的数据,因为中间有一个插入操作,就导致了一级缓存失效
MyBatis的二级缓存
- 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
- 我们的一级缓存是默认开启的,但是我们的二级缓存需要我们手动配置
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存开启的条件
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中设置标签
<cache/>
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
public void TestCacheTwo() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis_config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.getEmpById(1);
System.out.println(emp1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
Emp emp2 = mapper2.getEmpById(1);
System.out.println(emp2);
sqlSession2.close();
}
DEBUG 01-11 23:45:06,117 ==> Preparing: select * from t_Emp where emp_id=? (BaseJdbcLogger.java:137)
DEBUG 01-11 23:45:06,143 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-11 23:45:06,161 <== Total: 1 (BaseJdbcLogger.java:137)
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
DEBUG 01-11 23:45:06,178 Cache Hit Ratio [com.lsc.mybatis.mappers.CacheMapper]: 0.5 (LoggingCache.java:60)
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
①eviction属性:缓存回收策略,默认的是 LRU。
- LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
②flushInterval属性:刷新间隔,单位毫秒
- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
③size属性:引用数目,正整数
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
④readOnly属性:只读, true/false
-
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。
-
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
总结
- MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
- MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。
Mybatis逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
说白了就是自动生成代码,主要生成一些CURD的代码
创建逆向工程的步骤
添加依赖和插件
<!-- 打包方式-->
<packaging>jar</packaging>
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
创建MyBatis的核心配置文件
创建逆向工程的配置文件
文件名必须是: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>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/ssm"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.lsc.mybatis.pojo"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.lsc.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.lsc.mybatis.mappers" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
- 配置数据库连接的信息
- 驱动信息
- URL
- 用户名
- 密码
- 配置JavaBean的信息
- 配置JavaBean生成下的目录
- SQLXML文件配置的信息
- Mapper接口的生成的配置信息
- 配置表名和对应的类名的映射关系
执行MBG插件的generate目标
最后的效果
功能测试
public class EmpMapperTest {
@Test
public void mbgTest(){
SqlSession sqlSession= SqlSessionUtil.getSqlSession();
EmpMapper empMapper=sqlSession.getMapper(EmpMapper.class);
// 根据Id查询数据
Emp emp=empMapper.selectByPrimaryKey(1);
//select emp_id, emp_name, age, gender, dept_id from t_emp where emp_id = ?
System.out.println(emp);
// 查询所有数据
List<Emp> empList = empMapper.selectByExample(null);
empList.forEach(System.out::println);
// select emp_id, emp_name, age, gender, dept_id from t_emp
// 根据条件查询数据
EmpExample example=new EmpExample();
example.createCriteria().andEmpNameEqualTo("刘颂成").andAgeEqualTo(22);
example.or().andGenderEqualTo("男");
List<Emp> empList1 = empMapper.selectByExample(example);
//select emp_id, emp_name, age, gender, dept_id from t_emp WHERE ( emp_name = ? and age = ? ) or( gender = ? )
empList.forEach(System.out::println);
Emp emp1 = new Emp(1,"小成",23,"男",null);
//测试普通修改功能
empMapper.updateByPrimaryKey(emp);
//update t_emp set emp_name = ?, age = ?, gender = ?, dept_id = ? where emp_id = ?
//测试选择性修改
empMapper.updateByPrimaryKeySelective(emp);
//update t_emp SET emp_name = ?, age = ?, gender = ? where emp_id = ?
}
}
分页插件
分析分页需要哪些数据
当前在第一页
首页 上一页 2 3 4 5 6 下一页 末页
-
假如上面是我们实际的需求
-
我们要确定每页显示的条数,和当前页面的页码,这些数据是从前端传过来的
- pageSize:每页显示的条数
- pageNum:当前页的页码
-
我们还需要知道总页面数,因为需要判断当前是不是最后一页,如果是最后一页,那么末页和下一页都不能使用
- totalPage:总页数
totalPage = count / pageSize; if(count % pageSize != 0){ totalPage += 1; }
-
总页数=总记录数/pageSize
- count:总记录数
-
当前页的起始索引,因为我们要通过limit index,pageSize来查询当前页面需要的数据
- index=(pageNum-1)*pageSize
例子
pageSize=4,pageNum=1,index=0 limit 0,4
pageSize=4,pageNum=3,index=8 limit 8,4
pageSize=4,pageNum=6,index=20 limit 8,4
使用步骤
添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
配置分页插件
在MyBatis的核心配置文件中配置插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
分页插件的使用
@Test
public void Test(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询功能之前开启分页功能
Page<Object> page= PageHelper.startPage(5,4);//第一个参数表第几页 第二个参数表示每页有几条数据
List<Emp> empList = mapper.selectByExample(null);
PageInfo<Emp> pageInfo = new PageInfo<>(empList, 5);//这个5表示当前页面应该显示的页面导航页的集合的大小
empList.forEach(System.out::println);
System.out.println(pageInfo);
}
PageInfo{pageNum=5, pageSize=4, size=4, startRow=17, endRow=20, total=37, pages=10,
list=Page{count=true, pageNum=5, pageSize=4, startRow=16, endRow=20, total=37, pages=10, reasonable=false, pageSizeZero=false}
[ Emp{empId=17, empName='a', age=null, gender='null', deptId=null},
Emp{empId=18, empName='a', age=null, gender='null', deptId=null},
Emp{empId=19, empName='aa', age=null, gender='null', deptId=null},
Emp{empId=20, empName='a', age=null, gender='null', deptId=null}
],
prePage=4, nextPage=6, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=3, navigateLastPage=7, navigatepageNums=[3, 4, 5, 6, 7]}
- pageInfo里有我们分页需要的信息
- pageNum表示这是第几页 pageSize表示每页有几条数据 表示这一页的起始记录行数和结尾记录行数 startRow=17, endRow=20 total表示总记录行数,pages表示总共有多少页
- 上一页的页数和下一页的页数 prePage=4, nextPage=6,
- isFirstPage=false, isLastPage=false表示是不是最后一页 是不是第一页
- hasPreviousPage=true, hasNextPage=true 是否有上一页 是否有下一页
- 表示应该显示的页数 navigatepageNums=[3, 4, 5, 6, 7]
没有开启分页插件
DEBUG 01-12 02:12:19,579 ==> Preparing: select emp_id, emp_name, age, gender, dept_id from t_emp (BaseJdbcLogger.java:137)
DEBUG 01-12 02:12:19,615 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 01-12 02:12:19,639 <== Total: 37 (BaseJdbcLogger.java:137)
开启了分页插件
DEBUG 01-12 02:05:24,147 Cache Hit Ratio [SQL_CACHE]: 0.0 (LoggingCache.java:60)
DEBUG 01-12 02:05:24,195 ==> Preparing: SELECT count(0) FROM t_emp (BaseJdbcLogger.java:137)
DEBUG 01-12 02:05:24,219 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 01-12 02:05:24,233 <== Total: 1 (BaseJdbcLogger.java:137)
DEBUG 01-12 02:05:24,235 ==> Preparing: select emp_id, emp_name, age, gender, dept_id from t_emp LIMIT ?, ? (BaseJdbcLogger.java:137)
DEBUG 01-12 02:05:24,236 ==> Parameters: 16(Long), 4(Integer) (BaseJdbcLogger.java:137)
DEBUG 01-12 02:05:24,237 <== Total: 4 (BaseJdbcLogger.java:137)
- 我们发现开启了分页插件,我们的sql语句发生了变化
- 分页插件的基本原理就是使用Mybatis提供的插件接口,实现了自定义插件,在插件的拦截方法内拦截主执行是SQL,然后重写SQL,添加对应的物理分页语句和物理分页参数