脚本宝典收集整理的这篇文章主要介绍了Mybatis,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache Software foundation 迁移到了GOOGLE code,并且改名为MyBatis 。2013年11月迁移到GIThub。
简单来说,MyBatis能帮助我们快速开发基于Java + 数据库的程序,能帮助我们快速映射POJO对象和数据库中的数据 同时支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。
导入mybatis的jar包(包括DTD文档)
编写配置文件
<?XMl version="1.0" encoding="UTF-8" standalone="no"?>
<!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="username" value="root"/>
<property name="password" value="5863385"/>
<property name="url" value="jdbc:MySQL://localhost:3306/mysqltest?useSSL=false"/>
<property name="driver" value="com.mysql.jdbc.Driver"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis02test/mappers/student-mapper.xML"></mapper>
</mappers>
</configuration>
编写SQL映射文件
**注意名字有一一对应关系 **
<?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="mybatis02test.DAO.StudentDAO">
<insert id="insertStudent" parameterType="mybatis02test.entity.Student">
INSERT INTO student VALUES (#{studentNo},#{loginPwd},#{studentName},#{sex},#{gradeiD},#{phone},#{address});
</insert>
<delete id="deleteByID" parameterType="java.lang.Integer">
DELETE From student WHERE studentNo = (#{studentNo});
</delete>
<update id="update" parameterType="java.util.HashMap">
UPDATE student SET LOGinPwd = (#{loginPwd}),studentName = (#{studentName}),sex = (#{sex}),gradeID = (#{gradeID}),phone = (#{phone}),address = (#{address})WHERE studentNo = (#{studentNo});
</update>
<update id="updateStudent" parameterType="mybatis02test.entity.Student">
UPDATE student
<set>
<if test="studentNo!=null">
studentNo = (#{studentNo}),
</if>
<if test="loginPwd!=null">
loginPwd = (#{loginPwd})
</if>
</set>
WHERE studentNo = (#{studentNo});
</update>
<select id="queryStudents" resultType="mybatis02test.entity.Student">
SELECT * From student;
</select>
<select id="getStudentByNo" parameterType="java.lang.Integer" resultType="mybatis02test.entity.Student">
SELECT * FROM student WHERE studentNo = (#{studentNo})
</select>
<select id="queryStudentByName" parameterType="java.lang.String" resultType="mybatis02test.entity.Student">
SELECT * FROM student WHERE studentName LIKE '%' #{name} '%'
</select>
<select id="queryStudentByStudentNo" parameterType="java.util.HashMap" resultType="mybatis02test.entity.Student">
SELECT * FROM student WHERE studentNo Between #{begin} AND #{end};
</select>
<select id="queryStudentByGradeID" parameterType="java.util.List" resultType="mybatis02test.entity.Student">
SELECT * FROM student
<if test="list!=null and list.size>0">
WHERE gradeID IN
<foreach collection="list" open="(" close=")" separator="," item="aa">
#{aa}
</foreach>
</if>
</select>
</mapper>
代码实现
//1.加载mybatis数据库的配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建SqlSessionFactoryBuilder 构建者,来创建SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.一个数据库对应一个SqlSessionFactory,是线程安全的对象,创建起来比较耗费资源。
SqlSessionFactory factory = builder.build(in);
//4. 获取SqlSession
SqlSession session = factory.openSession();
System.out.println(session);
session.delete("test.deleteEmp");
session.COMmit();
session.close();
batis工具包的编写:(通过将线程和会话绑定,解决了线程问题)
package mybatis02test.dao.impl;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @author hzl
* @Description: 工具类
* @Date 15:39$ 2021/11/4$
*/
public class MybaseUtil {
//线程绑定会话
static ThreadLocal<SqlSession> threadLocale = new ThreadLocal<>();
//将路径设定默认值
private static final String CONFIG_FILE = "mybatis02test/mybatis-config.xml";
static SqlSessionFactory factory;
static {
initFactory(CONFIG_FILE);
}
//实例化工厂
public static void initFactory(String path){
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
try {
InputStream inputStream = Resources.getResourceAsStream(path);
factory = builder.build(inputStream);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("配置文件加载错误");
}
}
//获取会话
public static SqlSession getSession(){
SqlSession session = threadLocale.get();
if (session == null){
session=factory.openSession();
threadLocale.set(session);
}
return session;
}
//关闭会话
public static void close(){
SqlSession session = threadLocale.get();
if (session!=null){
session.close();
threadLocale.set(null);
}
}
//提交
public static void commit(){
SqlSession session = threadLocale.get();
if(session!=null) {
session.commit();
}
}
//回滚
public static void rollBack(){
SqlSession session = threadLocale.get();
if(session!=null) {
session.rollback();
}
}
}
实现类的编写
package mybatis02test.dao.impl;
import mybatis02test.dao.StudentDAO;
import mybatis02test.entity.Student;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
import java.util.Map;
/**
* @author hzl
* @Description: 实现类
* @Date 15:39$ 2021/11/4$
*/
public class StudentImpl implements StudentDAO {
//插入学生对象
@override
public int insertStudent(Student student) {
try {
SqlSession session = MybaseUtil.getSession();
int a = session.insert("mybatis02test.dao.StudentDAO.insertStudent",student);
MybaseUtil.commit();
return a;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("插入失败");
}finally {
MybaseUtil.close();
}
}
//根据ID删除 表
@Override
public int deleteById(int id) {
try {
SqlSession session = MybaseUtil.getSession();
int a = session.delete("mybatis02test.dao.StudentDAO.deleteByID",id);
MybaseUtil.commit();
return a;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("删除失败");
}finally {
MybaseUtil.close();
}
}
//更新一个对象 传入实体类的n个属性
@Override
public int update(Map map) {
try{
SqlSession session = MybaseUtil.getSession();
int a = session.update("mybatis02test.dao.StudentDAO.update",map);
MybaseUtil.commit();
return a;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("更新失败");
}finally {
MybaseUtil.close();
}
}
//更新一个对象,传入的对象覆盖
@Override
public int updateStudent(Student e) {
try {
SqlSession session = MybaseUtil.getSession();
int a = session.update("mybatis02test.dao.StudentDAO.updateStudent",e);
MybaseUtil.commit();
return a;
}catch (Exception e1){
MybaseUtil.rollBack();
e1.printStackTrace();
throw new RuntimeException("更新失败");
}finally {
MybaseUtil.close();
}
}
//查找全部 返回List
@Override
public List<Student> queryStudent() {
try{
List<Student> list;
SqlSession session = MybaseUtil.getSession();
list = session.selectList("mybatis02test.dao.StudentDAO.queryStudents");
MybaseUtil.commit();
return list;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("查询失败");
}finally {
MybaseUtil.close();
}
}
//根据ID指定查找 返回一个实体类
@Override
public Student getStudentByNo(Integer i) {
try {
SqlSession session = MybaseUtil.getSession();
Student student= session.selectOne("mybatis02test.dao.StudentDAO.getStudentByNo",i);
MybaseUtil.commit();
return student;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("查询失败");
}finally {
MybaseUtil.close();
}
}
//名字模糊查找 返回List
@Override
public List<Student> queryStudentByName(String name) {
try{
List<Student> list;
SqlSession session = MybaseUtil.getSession();
list = session.selectList("mybatis02test.dao.StudentDAO.queryStudentByName",name);
MybaseUtil.commit();
return list;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("查询失败");
}finally {
MybaseUtil.close();
}
}
//根据集合查找 返回List
@Override
public List<Student> queryStudentByGradeID(List<Integer> list) {
try{
SqlSession session = MybaseUtil.getSession();
List<Student> list2;
list2 = session.selectList("mybatis02test.dao.StudentDAO.queryStudentByGradeID",list);
MybaseUtil.commit();
return list2;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("查询失败");
}finally {
MybaseUtil.close();
}
}
//根据编号区间查找 返回List
@Override
public List<Student> queryStudentByStudentNo(Map map) {
try {
SqlSession session = MybaseUtil.getSession();
List<Student> list= session.selectList("mybatis02test.dao.StudentDAO.queryStudentByStudentNo",map);
MybaseUtil.commit();
return list;
}catch (Exception e){
MybaseUtil.rollBack();
e.printStackTrace();
throw new RuntimeException("查询失败");
}finally {
MybaseUtil.close();
}
}
}
接口类:
import mybatis02test.entity.Student;
import java.util.List;
import java.util.Map;
/**
* @author hzl
* @Description: 增删改查接口
* @Date 15:37$ 2021/11/4$
*/
public interface StudentDAO {
//插入
int insertStudent(Student student);
//删除
int deleteById(int id);
//修改
int update(Map map);
int updateStudent(Student student);
//查询
List<Student> queryStudent();
Student getStudentByNo(Integer i);
List<Student> queryStudentByName (String name);
List<Student> queryStudentByGradeID(List<Integer> list);
List<Student> queryStudentByStudentNo(Map map);
}
}
//保存员工对象
int saveEmployee(Employee employee);
<!-- 保存对象 -->
<!-- Employee 是别名 -->
<insert id="saveEmployee" parameterType="Employee">
insert into employees(employee_ID,last_name,First_name,hiredate) values(#{employeeID},#{lastName},#{firstName},#{hiredate})
</insert>
//更新员工特定的几个属性
int update(HashMap map);
<!--更新特定数据-->
<!--sQL驼峰映射主要是解决数据库读取问题,读取的值无法赋给实体类,这里改写_还得写 -->
<update id="update" parameterType="hashMap">
update employees
<set>
<if test="lastName != null">
last_Name = #{lastName},
</if>
<if test="job != null">
job_ID = #{jobID},
</if>
<if test="hiredate != null">
hiredate = #{hiredate}
</if>
</set>
<where>
employee_ID = #{employeeID};
</where>
</update>
//删除
public void delete(int id);
<delete id="delete" parameterType="int">
delete from user where id = #{id}
</delete>
UserDao:
public List<User> findAll();
UserDao.xml:
<select id="findAll" resultType="com.domain.User">
select * from user
</select>
UserDao:
public List<User> findAll();
UserDao.xml:
<select id="findAll" resultType="com.domain.User">
select * from user
</select>
//根据员工姓名模糊查询
List<Employee> queryEmployee(HashMap fuzzyName);
<select id="queryEmployee" parameterType="hashMap" resultType="Employee">
select * from employees where last_name like '%' #{lastName} '%';
</select>
UserDao:
public int count();
UserDao.xml:
<select id="count" resultType="int">
select count(*) from user
</select>
//根据薪水区间查询
List<Employee> queryBySal(HashMap codition);
<!--根据薪水区间查询-->
<select id="queryBySal" parameterType="HashMap" resultType="Employee">
select * from employees where salary <![CDATA[ >= ]]> #{minSal} and salary <![CDATA[ <= ]]> #{maxSal};
</select>
方式一:手动指定
//查询所有员工姓名,薪水,工作(给列取别名 封装映射返回结果)
List<Employee> query();
<!-- 给列定义别名 ,查询结果使用自定义的resultMap类型来封装数据-->
<select id="query" resultMap="EmployeeMap">
select employee_ID,job_ID,hiredate,salary from employees;
</select>
<!-- type的值,使用的是别名 -->
<resultMap type="Employee" id="EmployeeMap">
<id column="employee_ID" property="employeeID"/>
<result column="job_ID" property="jobID"/>
</resultMap>
方式二:开启驼峰自动转型
在config.xml配置
<settings>
<!--开启mybatis驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!--取别名 全局配置 -->
<typeAliases>
<typeAlias type="com.pers.mybatistest.entity.Employee" alias="Employee"></typeAlias>
<!--在mappers里面,参数类型和返回值类型都是实体类,每次都要写这么长长的一串com.pers.mybatistest.entity.Employee,就会非常麻烦。 -->
<!--这里采用取别名的方式解决 -->
</typeAliases>
//多条件动态查询
List<Employee> queryByCondition(HashMap map);
<!-- 动态查询 -->
<select id="queryByCondition" parameterType="HashMap" resultType="Employee">
select * from employees
<where>
<if test="lastName !=null">
and last_name like '%' #{lastName} '%'
</if>
<if test="employeeID != null">
and employee_ID = #{employeeID}
</if>
<if test="begin != null">
<if test="end!=null">
and hiredate between #{begin} and #{end}
</if>
<if test="end == null">
and hiredate ![CDATA[ >= ]] #{begin}
</if>
</if>
<if test="begin == null">
<if test="end != null">
and hiredate <![CDATA[ <= ]]> #{end}
</if>
</if>
</where>
</select>
//多条件choose查询(只有一个条件会查询)
List<Employee> chooseQuery(HashMap map);
<!-- choose 判断之会选择其中的一个条件 -->
<select id="chooseQuery" parameterType="hashMap" resultType="Employee">
select * from employees
<where>
<choose>
<when test="lastName != null">
and last_name like '%' #{lastName} '%'
</when>
<when test="jobID != null">
and job_ID = #{jobID}
</when>
<otherwise>
and department_ID = 100
</otherwise>
</choose>
</where>
</select>
//分页查询
List<Employee> selectByPage(HashMap map);
<!-- 分页 -->
<select id="selectByPage" parameterType="hashMap" resultType="Employee">
select * from employees limit #{start},#{limit}
</select>
//实体类
package com.pers.mybatistest.entity;
import java.util.Date;
/**
* @author hzl
* @Description: 实体类
* @Date 11:52$ 2021/11/5$
*/
public class Employee {
private Integer employeeID;
private Integer managerID;
private Integer departmentID;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private String jobID;
private Double salary;
private Double commissionPct;
private Date hiredate;
private Department department;
public Employee(Integer employeeID, Integer managerID, Integer departmentID, String firstName, String lastName, String email, String phoneNumber, String jobID, Double salary, Double commissionPct, Date hiredate, Department department) {
this.employeeID = employeeID;
this.managerID = managerID;
this.departmentID = departmentID;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.phoneNumber = phoneNumber;
this.jobID = jobID;
this.salary = salary;
this.commissionPct = commissionPct;
this.hiredate = hiredate;
this.department = department;
}
public Employee() {
}
public Integer getEmployeeID() {
return employeeID;
}
public void setEmployeeID(Integer employeeID) {
this.employeeID = employeeID;
}
public Integer getManagerID() {
return managerID;
}
public void setManagerID(Integer managerID) {
this.managerID = managerID;
}
public Integer getDepartmentID() {
return departmentID;
}
public void setDepartmentID(Integer departmentID) {
this.departmentID = departmentID;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getJobID() {
return jobID;
}
public void setJobID(String jobID) {
this.jobID = jobID;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Double getCommissionPct() {
return commissionPct;
}
public void setCommissionPct(Double commissionPct) {
this.commissionPct = commissionPct;
}
public Date getHiredate() {
return hiredate;
}
public void setHiredate(Date hiredate) {
this.hiredate = hiredate;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
@Override
public String toString() {
return "Employee{" +
"employeeID=" + employeeID +
", managerID=" + managerID +
", departmentID=" + departmentID +
", firstName='" + firstName + ''' +
", lastName='" + lastName + ''' +
", email='" + email + ''' +
", phoneNumber='" + phoneNumber + ''' +
", jobID='" + jobID + ''' +
", salary=" + salary +
", commissionPct=" + commissionPct +
", hiredate=" + hiredate + (department != null? (", department=" + department.toString()): "") +
'}';
}
}
// 多表查询
List<Employee>selectByJoin();
<!-- 多表查询 -->
<select id="selectByJoin" resultMap="EmpDeptMap">
select a.*,b.department_ID dno,b.department_name from employees a left join departments b on a.department_ID = b.department_ID;
</select>
<resultMap id="EmpDeptMap" type="Employee" autoMapping="true">
<!-- id用来定义主键标识列
在resultMap标签中使用association标签
标签功能:对一对一映射的返回结果进行封装,即封装从属实体类对象
标签属性:javaType属性 指定所封装对象的全限定类名-->
<id column="department_ID" property="departmentID"/>
<!-- 关联映射 -->
<!-- property 都是实体类的属性 -->
<association column="department_ID" property="department" javaType="com.pers.mybatistest.entity.Department">
<!--需要查询几个附加的表就添加几列result -->
<id column="dno" property="departmentID"/>
<result column="department_name" property="departmentName"/>
</association>
</resultMap>
package com.pers.mybatistest.entity;
import java.util.List;
/**
* @author hzl
* @Description:
* @Date 20:07$ 2021/11/5$
*/
public class Department {
private Integer departmentID;
private String departmentName;
private Integer managerID;
private Integer locationID;
private List<Employee> employeeList;
public Department(Integer departmentID, String departmentName, Integer managerID, Integer locationID, List<Employee> employeeList) {
this.departmentID = departmentID;
this.departmentName = departmentName;
this.managerID = managerID;
this.locationID = locationID;
this.employeeList = employeeList;
}
public Department() {
}
public Integer getDepartmentID() {
return departmentID;
}
public void setDepartmentID(Integer departmentID) {
this.departmentID = departmentID;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public Integer getManagerID() {
return managerID;
}
public void setManagerID(Integer managerID) {
this.managerID = managerID;
}
public Integer getLocationID() {
return locationID;
}
public void setLocationID(Integer locationID) {
this.locationID = locationID;
}
public List<Employee> getEmployeeList() {
return employeeList;
}
public void setEmployeeList(List<Employee> employeeList) {
this.employeeList = employeeList;
}
@Override
public String toString() {
return "Department{" +
"departmentID=" + departmentID +
", departmentName='" + departmentName + ''' +
", managerID=" + managerID +
", locationID=" + locationID + (employeeList!=null?( ", employeeList=" + employeeList.toString()):"")+
'}';
}
}
List<Department> queryByJoin();
<?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.pers.mybatistest.dao.DepartmentDAO">
<select id="queryByJoin" resultMap="DeptMap">
select a.department_ID,a.department_name, b.department_ID dno,b.first_name,b.last_name from departments a left join employees b on a.department_ID = b.department_ID
</select>
<!--采用Map映射的话 需要全部都映射上,不映射都为null-->
<resultMap id="DeptMap" type="com.pers.mybatistest.entity.Department">
<id column="department_ID" property="departmentID"></id>
<result column="department_name" property="departmentName"></result>
<collection property="employeeList" ofType="com.pers.mybatistest.entity.Employee" resultMap="EmployeeMap"></collection>
</resultMap>
<resultMap id="EmployeeMap" type="com.pers.mybatistest.entity.Employee">
<id column="dno" property="departmentID"></id>
<result column="first_name" property="firstName"></result>
<result column="last_name" property="lastName"></result>
</resultMap>
</mapper>
添加记录并获取其id值
UserDao:
public void add2(User user);
UserDao.xml:
<insert id="add2" parameterType="com.domain.User">
<!--
keyColumn:表中的字段名
keyProperty:类中的属性名
order:
BEFORE:在之前执行
AFTER:在之后执行
-->
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user values(null, #{name})
</insert>
缓存是一般的ORM ( 对象关系映射(英语:Object Relational Mapping))框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
cache
MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,只有一个默认的实现类PerpetualCache,它是用HashMap 实现的。
除此之外,还有很多的装饰器,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。但是无论怎么装饰,经过多少层装饰,最后使用的还是基本的实现类(默认PerpetualCache)。可以通过 CachingExecutor 类 Debug 去查看。
所有的缓存实现类总体上可分为三类:基本缓存、淘汰算法缓存、装饰器缓存。
缓存实现类 | 描述 | 作用 | 装饰条件 |
---|---|---|---|
基本缓存 | 缓存基本实现类 | 默认是PerpetualCache,也可以自定义比如RedisCache、EhCache 等,具备基本功能的缓存类 | 无 |
LruCache | LRU策略的缓存 | 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use) | eviction="LRU”(默认) |
FifoCache | FIFO策略的缓存 | 当缓存到达上限时候,删除最先入队的缓存 | eviction= "FIFO" |
SoftCache WeakCache | 带清理策略的缓存 | 通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference | eviction= "SOFT' eviction= "WEAK" |
LoggingCache | 带日志功能的缓存 | 比如:输出缓存命中率 | 基本 |
SynchronizedCache | 同步缓存 | 基于synchronized关键字实现,解决并发问题 | 基本 |
BlockingCache | 阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现 | blocking=true |
SerializedCache | 支持序列化的缓存 | 将对象序列化以后存到缓存中,取出时反序列化 | readOnly=false(默认) |
ScheduledCache | 定时调度的缓存 | 在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时) .如果是则清空缓存- -即每隔一-段时间清空一次缓存 | flushInterval不为空 |
TransactionalCache | 事务缓存 | 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 | TransactionalCacheManager中用Map维护对应关系 |
一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。首先我们必须去弄清楚一个问题,在MyBatis 执行的流程里面,涉及到这么多的对象,那么缓存PerpetualCache 应该放在哪个对象里面去维护?如果要在同一个会话里面共享一级缓存,这个对象肯定是在SqlSession 里面创建的,作为SqlSession 的一个属性。
defaultsqlSession 里面只有两个属性,Configuration 是全局的,所以缓存只可能放在Executor 里面维护——SimpleExecutor/ReuseExecutor/BatchExecutor 的父类BaseExecutor 的构造函数中持有PerpetualCache。在同一个会话里面,多次执行相同的SQL 语句,会直接从内存取到缓存的结果,不会再发送SQL 到数据库。但是不同的会话里面,即使执行的SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。
每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。
在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。
为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
如下图所示,MyBatis会在一次会话的表示----一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。
一级缓存的生命周期有多长?
SqlSession 一级缓存的工作流程:
对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果
判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
如果命中,则直接将缓存结果返回;
如果没命中:
一级缓存的不足:
使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选:
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。
作为一个作用范围更广的缓存,它肯定是在SqlSession 的外层,否则不可能被多个SqlSession 共享。而一级缓存是在SqlSession 内部的,所以第一个问题,肯定是工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。第二个问题,二级缓存放在哪个对象中维护呢? 要跨会话共享的话,SqlSession 本身和它里面的BaseExecutor 已经满足不了需求了,那我们应该在BaseExecutor 之外创建一个对象。
实际上MyBatis 用了一个装饰器的类来维护,就是CachingExecutor。如果启用了二级缓存,MyBatis 在创建Executor 对象的时候会对Executor 进行装饰。CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,如果没有委派交给真正的查询器Executor 实现类,比如SimpleExecutor 来执行查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户。
开启二级缓存的方法
第一步:配置 mybatis.configuration.cache-enabled=true,只要没有显式地设置cacheEnabled=false,都会用CachingExecutor 装饰基本的执行器。
<setting name="cacheEnabled" value="true"/>
第二步:在Mapper.xml 中配置< cache/>标签:
<!--缓存类型可以不指定 默认是这个 -->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024"<!--二级缓存中最多可以存放对象的个数,默认是1024 -->
eviction="LRU"<!-- 当二级缓存中的对象达到最大值时,就需要通过逐出策略将缓存中的对象移出缓存-->
<!-- FIFO: First In First Out 先进先出 -->
<!--LRU: Least Recently Used 未被使用时间最长的,默认-->
flushInterval="120000"<!--刷新时间 刷新缓存及清空缓存,一般不指定,即当执行增删改时刷新缓存 -->
readOnly="false"/><!-- 设置缓存中数据是否只读的,true表示缓存中的对象不能被修改,提升性能-->
基本上就是这样。这个简单语句的效果如下:
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
注:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
Mapper.xml 配置了< cache>之后,select()会被缓存。update()、delete()、insert()会刷新缓存。如果cacheEnabled=true,Mapper.xml 没有配置标签,还有二级缓存吗?(没有)还会出现CachingExecutor 包装对象吗?(会,创建Session对象的时候创建)
只要cacheEnabled=true 基本执行器就会被装饰。有没有配置< cache>,决定了在启动的时候会不会创建这个mapper 的Cache 对象,只是最终会影响到CachingExecutorquery 方法里面的判断。如果某些查询方法对数据的实时性要求很高,不需要二级缓存,怎么办?我们可以在单个查询语句 ID 上显式关闭二级缓存(默认是true):
<!-- useCache使用二级缓存 -->
<select id="select" resultMap="EmpMap" useCache="false">
select * from emp
</select>
为什么增删改操作会清空缓存?在CachingExecutor 的update()方法里面会调用flushCacheIfRequired(ms),isFlushCacheRequired 就是从标签里面渠道的flushCache 的值。而增删改操作的flushCache 属性默认为true。
什么时候开启二级缓存?
一级缓存默认是打开的,二级缓存需要配置才可以开启。那么我们必须思考一个问题,在什么情况下才有必要去开启二级缓存?
如果要让多个namespace 共享一个二级缓存,应该怎么做?跨namespace 的缓存共享的问题,可以使用< cache-ref>来解决:
<cache-ref namespace="com.wuzz.crud.dao.DepartmentMapper" />
cache-ref 代表引用别的命名空间的Cache 配置,两个命名空间的操作使用的是同一个Cache。在关联的表比较少,或者按照业务可以对表进行分组的时候可以使用。
注意:在这种情况下,多个Mapper 的操作都会引起缓存刷新,缓存的意义已经不大了.
第三方缓存做二级缓存
除了MyBatis 自带的二级缓存之外,我们也可以通过实现Cache 接口来自定义二级缓存。MyBatis 官方提供了一些第三方缓存集成方式,比如ehcache 和redis:https://github.com/mybatis/redis-cache ,这里就不过多介绍了。当然,我们也可以使用独立的缓存服务,不使用MyBatis 自带的二级缓存。
自定义缓存:
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
这个示例展示了如何使用一个自定义的缓存实现。type 属性指定的类必须实现 org.mybatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
为对你的缓存进行配置,只需要简单地在你的缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如,下面的例子将在你的缓存实现上调用一个名为 setCacheFile(String file)
的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
你可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。 你也可以使用占位符(如 ${cache.file}
),以便替换成在配置文件属性中定义的值。从版本 3.4.2 开始,MyBatis 已经支持在所有属性设置完毕之后,调用一个初始化方法。 如果想要使用这个特性,请在你的自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject
接口。
public interface InitializingObject {
void initialize() throws Exception;
}
请注意,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。 因此,同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。 默认情况下,语句会这样来配置:
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>
鉴于这是默认行为,显然你永远不应该以这样的方式显式配置一条语句。但如果你想改变默认的行为,只需要设置 flushCache 和 useCache 属性。比如,某些情况下你可能希望特定 select 语句的结果排除于缓存之外,或希望一条 select 语句清空缓存。类似地,你可能希望某些 update 语句执行时不要刷新缓存。
以上是脚本宝典为你收集整理的Mybatis全部内容,希望文章能够帮你解决Mybatis所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。