目录
1.创建一个maven项目,添加mybatis,mysql依赖的jar
4.创建mybatis全局配置文件,配置数据库连接信息编辑
5.创建sql映射文件 定义一个与接口方法名相同的查询语句,配置sql映射文件
【前言】本文只作为作者的学习记录,如果能帮到各位技术萌新,是我的荣幸,如果有专业失误,也烦请大佬指出。
mybatis简介
mybatis是什么?
原来是Apache的一个 开源项目iBatis,2010年转投谷歌,从ibatis3.0开始更名为MyBatis。
是一个优秀的数据持久层(dao)
这里牵扯到java后端的一个三层架构
servlet(web) 接受请求,调用其他java程序处理,响应
service 业务逻辑层
dao 数据访问/持久
mybatis是对jdbc进行一个轻量级封装
提供一些自己定义的类和接口来实现功能
提供专门xml文件来进行配置,以及可以自动的对查询结果进行封装
是一个ORM(java对象与数据库映射)实现的数据持久层的框架
支持动态sql,以及数据缓存
这里附上Mybatis中文网站
mybatis搭建1.0
前三步可视为准备工作
1.创建一个maven项目,添加mybatis,mysql依赖的jar
下载到本地仓库(更新)
2.创建本地的git仓库
3.创建一个数据库表,以及一个应的java模型类
创建过滤文件(忽略)
4.创建mybatis全局配置文件,配置数据库连接信息
5.创建sql映射文件 定义一个与接口方法名相同的查询语句,配置sql映射文件
6.创建一个访问接口 定义一个方法
7.测试mybatis
接口代码:
import com.ffyc.mybatis.model.Admin;
public interface AdminDao {
public Admin FindAdminByid(int id);
}
测试类代码:mybatis搭建(第一代)
import com.ffyc.mybatis.dao.AdminDao;
import com.ffyc.mybatis.model.Admin;
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.Reader;
public class TestAdmin {
public static void main(String[] args) throws IOException {
//1.读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
//2.创建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//3.创建 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.获得接口代理对象
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
//5.使用这个对象去调用对应方法
Admin admin = adminDao.FindAdminByid(1);
System.out.println(admin);
//6.关闭SqlSession
sqlSession.close();
}
}
mybatis搭建2.0
1.环境
指定环境俩个实现方式
1.创建SqlSession时指定
代码:
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader,"development");
2.配置环境是指定为默认环境
代码:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!--事物管理类型-->
<dataSource type="POOLED"><!--配置数据源 type="POOLED" 是否使用数据库连接池-->
<property name="driver" value="${driveName}" />
<property name="url" value="${url}" />
<property name="username" value="${uname}" />
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="work">
<transactionManager type="JDBC"/><!--事物管理类型-->
<dataSource type="POOLED"><!--配置数据源 type="POOLED" 是否使用数据库连接池-->
<property name="driver" value="${driveName}" />
<property name="url" value="${url}" />
<property name="username" value="${uname}" />
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
2.事务管理
数据库事务
是对一次数据库操作过程中的多条执行sql进行管理控制
保证一次执行中的多条sql能够作为一个整体,要么都执行成功,要么都执行失败。
3.配置数据源
pool数据库连接池(缓冲池)
现在每与数据库交互一次,创建一次数据库连接对象(Connection,SqlSession),用完就会关闭销毁
下一次需要,重复出现这过程,
问题:频繁创建销毁对象,需要开销(内存,效率)
池的概念:可以理解为一个集合
数据库连接池思想
可以在启动时设置一个容器,在里面初始化好一些数据库连接对象
有请求到来时,可以不用每次都创建销毁,可以重复使用。
减少了频繁创建销毁连接对象的开销
一般的设置
初始连接对象的数量
最大连接对象的数量
最大等待时间
4.1创建并导入配置属性文件
创建属性文件,多组键值对
配置属性文件
4.2调整设置
代码:
<settings>
<!--日志功能-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
4.3别名
5.TestAdmin 解释每一操作步骤的含义
1.
//读取mybatis核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
2.
//创建SqlSessionFactory
//SqlSessionFactory封装了所有的配置信息,
//SqlSessionFactory负责生成一个数据库连接的会话对象SqlSession对象
//SqlSessionFactory创建开销比较大,所以在整个项目中只需要创建一次即可,不用销毁
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
3.
//创建SqlSession session会话,一次与数据库交互,类似于之前使用的Connection
//SqlSessionFactory中的openSession()方法用来创建一个SqlSession对象,默认无参的默认设置事物提交为false(手动提交)
SqlSession sqlSession = sessionFactory.openSession();
4.
//创建访问接口的代理对象
//现在mybatis建议使用接口化访问, 先定义一个接口(规范),把每个接口与sql映射文件进行绑定
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
5.
//使用代理对象访问接口中对应的方法,本质是调用的是接口对应的sql映射文件中的那个sql
Admin admin = adminDao.findAdminById(2);
6.
//关闭与数据库连接会话对象
//
sqlSession.close();
6.进行一个简单的封装处理
将读取配置文件,创建SqlSessionFactory以及创建一个SqlSession对象封装成一个类
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.Reader;
public class MybatisUtil {
static SqlSessionFactory sessionFactory = null;
static{
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSession getSqlSession(){
return sessionFactory.openSession();
}
}
测试
API说明
#{}与${}的区别
#{参数名}: 首先是采用预编译的方式传值,一般用于向sql中传值使用,更加的安全
${参数名}: '${参数名}' 使用字符串拼接方式传值,不安全, 一般可以用于动态向sql中传列名
举个例子:
购物网站的排序,需要直接读取到属性名时,直接使用'${属性名}'就行。
<select>
select * from goods order by ${列名}
</select>
mybatis测试
增删改
添加
代码:
<!--增,改,删-->
<insert id="insertAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into admins(account,password) value (#{account},#{password})
</insert>
<update id="updateAdmin" parameterType="Admin">
update admins set account=#{account},password=#{password} where id = #{id}
</update>
<delete id="deleteAdmin" parameterType="int">
delete from admins where id = #{id}
</delete>
代码:
import com.ffyc.mybatis.model.Admin;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface AdminDao {
public Admin findAdminByid(int id);
//public void saveAdmin1(@Param("acc") String account, @Param("pwd") String password);
//void saveAdmin(Admin admin);
void insertAdmin(Admin admin);
void updateAdmin(Admin admin);
void deleteAdmin(int id);
//List<Admin> findAdmin();
//List<Admin> findAdmins();//resultMapdemo
}
Test代码:
import com.ffyc.mybatis.dao.AdminDao;
import com.ffyc.mybatis.model.Admin;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.io.IOException;
public class TestAdmin3 {
public static void main(String[] args) throws IOException {
//增加
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
//String account = "tom";
//String password = "123";
//adminDao.saveAdmin1(account,password);
String account = "jack";
String password = "222";
Admin admin = new Admin();
admin.setAccount(account);
admin.setPassword(password);
adminDao.insertAdmin(admin);
sqlSession.commit();
System.out.println(admin.getId());
sqlSession.close();
}
}
修改
import com.ffyc.mybatis.dao.AdminDao;
import com.ffyc.mybatis.model.Admin;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.io.IOException;
public class TestAdmin3 {
public static void main(String[] args) throws IOException {
//修改
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
String account = "JACK";
String password = "000";
Admin admin = new Admin();
admin.setId(4);
admin.setAccount(account);
admin.setPassword(password);
adminDao.updateAdmin(admin);
sqlSession.commit();
sqlSession.close();
}
}
删除
import com.ffyc.mybatis.dao.AdminDao;
import com.ffyc.mybatis.model.Admin;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.io.IOException;
public class TestAdmin3 {
public static void main(String[] args) throws IOException {
//删除
SqlSession sqlSession = MybatisUtil.getSqlSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
adminDao.deleteAdmin(3);
sqlSession.commit();
sqlSession.close();
}
}
结果处理
一般查询结果
<select id="findAdminByid" resultType="int">
select count(*) from admins
</select>
返回值一般是简单类型
无条件查询结果
一般情况下会查询到多条数据,这是就需要集合来接收。
List<Admin> findAdmin();
对象映射
自动映射
当数据库列名与java模型中的属性不一致时,该属性值无法被封装到对象中。
解决办法:
1.将不匹配的列名as成java模型的属性名,就可以正常封装到对象中。
2.Mybatis中的对象映射同样可以解决这样的问题,当然前提是,数据库中使用标准的下划线格式像user_name,并且java模型类中使用标准的驼峰命名格式的时候,像userName。在mybatis.xml中设置自动映射即可。
代码:
<settings>
<!--日志功能-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>
编程小tip:类名的首字母要大写,目的就是为了与创建的对象区分,还有创建类之后,如果生成了有参的构造方法,要将无参的构造方法写出来,方便后续使用,养成良好的变成习惯。
手动映射
也就是特殊定义处理
1. resutlMap 的 id 属性是 resutlMap 的唯一标识
2.resutlMap 的 id 属性是映射的 POJO 类
3.id 标签映射主键,result 标签映射非主键
4.property 设置 POJO 的属性名称,column 映射查询结果的列名称
代码:
<!--特殊处理定义 手动配置映射管理 id是唯一的 使用resultMap时会使用id type是类型-->
<resultMap id="searchAdmin" type="Admin">
<!--<id column="" property=""></id>--><!--id标签指针对数据库表中的主键-->
<result column="us" property="userName"></result>
</resultMap>
<!--使用resultMap 的一个demo-->
<select id="findAdmins" resultType="Admin" resultMap="searchAdmin">
select id,account,password,user_name as us from admins
</select>
多表关联处理
准备工作
1.创建表
2.创建模型类
3.创建接口,定义抽象方法
关联查询
代码:
<resultMap id="searchStudent" type="Student">
<result column="id" property="id"></result>
<result column="num" property="num"></result>
<result column="name" property="name"></result>
<result column="gender" property="gender"></result>
<association property="dorm" javaType="Dorm">
<result column="dnum" property="num"></result>
</association>
<association property="admin" javaType="Admin">
<result column="account" property="account"></result>
</association>
</resultMap>
<select id="findStudentByid" resultMap="searchStudent" parameterType="int">
SELECT
s.id,
s.num,
s.name,
s.gender,
d.num dnum,
a.account
FROM student s LEFT JOIN dorm d ON s.dormid = d.id
LEFT JOIN admins a ON s.adminid = a.id
WHERE s.id = #{id}
</select>
嵌套查询
可以看做是多个单表查询,将一个多表关联查询拆分为多次查询,先查询主表数据,然后查询关联表数据。
代码:
<resultMap id="searchStudent1" type="Student">
<result column="id" property="id"></result>
<result column="num" property="num"></result>
<result column="name" property="name"></result>
<result column="gender" property="gender"></result>
<association property="dorm" javaType="Dorm" select="findDormByid" column="dormid"></association>
<association property="admin" javaType="Admin" select="findAdminByid" column="adminid"></association>
</resultMap>
<select id="findStudentByid1" resultType="Student" parameterType="int" resultMap="searchStudent1">
SELECT id,num,NAME,gender,dormid,adminid FROM student WHERE id = #{id}
</select>
<select id="findDormByid" resultType="Dorm">
select num from dorm where id = #{dormid}
</select>
<select id="findAdminByid" resultType="Admin">
select account from admins where id = #{adminid}
</select>
<resultMap>配置自动映射,id有且唯一,type,类型这里是Student类,
<result column=" ",property = " " > column 数据库中对应的列名,property是java模型的属性
查询宿舍
1.一个宿舍中有多个学生
代码:
<resultMap id="searchDorm" type="Dorm">
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<collection property="list" javaType="list" ofType="Student">
<result column="snum" property="num"></result>
<result column="name" property="name"></result>
<result column="gender" property="gender"></result>
</collection>
</resultMap>
<select id="findDorm" resultMap="searchDorm" parameterType="int">
SELECT
d.id,
d.num,
s.num snum,
s.name,
s.gender
FROM dorm d LEFT JOIN student s ON d.id = s.dormid WHERE d.id= #{id}
</select>
测试代码:
import com.ffyc.mybatis.dao.DormDao;
import com.ffyc.mybatis.model.Dorm;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.Date;
import java.util.List;
public class TestDorm {
public static void main(String[] args) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
DormDao dormDao = sqlSession.getMapper(DormDao.class);
Dorm dorm = dormDao.findDorm(1);
System.out.println(dorm);
//List<Dorm> list = dormDao.findAllDorm();
//List<Dorm> list = dormDao.findAllDorm1();
//System.out.println(list);
/*
for(Dorm dorm:list){
System.out.print(dorm.getNum());
for(Student student: dorm.getList()){
System.out.println(student.getName());
}
}
*/
sqlSession.close();
}
}
2.查询所有的宿舍
嵌套查询
配置映射:
代码:
<resultMap id="searchAll" type="Dorm">
<id column="id" property="id"></id>
<result column="num" property="num"></result>
<collection property="list" javaType="list" ofType="Student" select="findStudentBydormid" column="id"></collection>
</resultMap>
<select id="findAllDorm1" resultMap="searchAll">
SELECT id,num FROM dorm
</select>
<select id="findStudentBydormid" parameterType="int" resultType="Student">
select id,num,name,gender from student where dormid =#{id}
</select>
Test测试
测试代码:
import com.ffyc.mybatis.dao.DormDao;
import com.ffyc.mybatis.model.Dorm;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.Date;
import java.util.List;
public class TestDorm {
public static void main(String[] args) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
DormDao dormDao = sqlSession.getMapper(DormDao.class);
//Dorm dorm = dormDao.findDorm(1);
//System.out.println(dorm);
//List<Dorm> list = dormDao.findAllDorm();
List<Dorm> list = dormDao.findAllDorm1();
System.out.println(list);
for(Dorm dorm:list){
System.out.print(dorm.getNum());
for(Student student: dorm.getList()){
System.out.println(student.getName());
}
}
sqlSession.close();
}
}
测试结果
通过日志可以很清楚的看到宿舍对应学生的关系
结果:
[Dorm{id=1, num=101, list=[Student{id=1, num=1001, name='张三', gender='男', dorm=null, admin=null}, Student{id=3, num=1003, name='王五', gender='男', dorm=null, admin=null}, Student{id=5, num=1005, name='jim', gender='男', dorm=null, admin=null}]}, Dorm{id=2, num=102, list=[Student{id=2, num=1002, name='李四', gender='女', dorm=null, admin=null}]}, Dorm{id=3, num=103, list=[Student{id=4, num=1004, name='赵六', gender='男', dorm=null, admin=null}, Student{id=6, num=1006, name='tom', gender='男', dorm=null, admin=null}]}]
所有宿舍封装到一个结果中,每一个宿舍中又封装了一个学生的集合,集合中包括多个学生。
101张三
王五
jim
102李四
103赵六
tom
注解方式
代码:
import com.ffyc.mybatis.model.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface StudentDao_back {
Student findStudentByid(int id);
Student findStudentByid1(int id);
List<Student> findAllStudent();
@Delete("delete from student where id = #{id}")
void deleteStudentByid(int id);
@Insert("insert into student(id,num,name,gender) value(#{id},#{num},#{name},#{gender})")
void addStudent(Student student);
@Update("update student set gender=#{gender} where id = #{id}")
void updateStudent(int id,String gender);
}
mybatis的动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
没有动态Sql之前这里会出现一个非常令人头疼的问题。
if
<select id="findAllStudent" resultType="Student" parameterType="Student">
select id,num,name,gender
from student
where num =#{num} and name =#{name} and gender =#{gender}
</select>
但是如果没有其中的某一个条件呢?就不会查到任何记录,if元素就可以解决这一问题。使用if元素进行判断一下。但是又出现了新的问题。
什么问题呢?请先看下面这段代码。
问题代码:
<select id="findAllStudent" resultType="Student" parameterType="Student">
select id,num,name,gender from student
where
<if test="num!=0">
num =#{num}
</if>
<if test="name!=null">
and name =#{name}
</if>
<if test="gender!=null">
and gender =#{gender}
</if>
</select>
我们要筛选数据时,通常对属性加以限制,但是又不是全部都加,当对num加以限制时,默认num!=0,这时可以正常执行sql语句,但是如果num==0时,sql语句变为
select num,name,gender from student where and name = #{name} and gender = #{gender}
这时可以很清楚的看到这条sql语句是有问题的,系统就会报错,这是就出现这样一种写法,这个问题就迎刃而解了。
代码:
<select id="findAllStudent" resultType="Student" parameterType="Student">
select id,num,name,gender from student
where 1=1
<if test="num!=0">
num =#{num}
</if>
<if test="name!=null">
and name =#{name}
</if>
<if test="gender!=null">
and gender =#{gender}
</if>
</select>
就是这一个小小的1=1,解决这个大大的问题。那么mybatis又是怎么解决的呢?
where,set
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。这样就解决了这一问题。
mybatis中的动态sql就可以很好的解决这一问题。
先来看代码:
<select id="findAllStudent" resultType="Student" parameterType="Student">
select id,num,name,gender from student
<where>
<if test="num!=0">
num =#{num}
</if>
<if test="name!=null">
and name =#{name}
</if>
<if test="gender!=null">
and gender =#{gender}
</if>
</where>
</select>-->
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateStudent" parameterType="Student">
update student
<set>
<if test="num!=0">
num = #{num},
</if>
<if test="name!=null">
name = #{name},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</set>
where id=#{id}
</update>
mybatis还对此做了优化,也就是trim元素
trim
trim元素可以代替where元素,也就是把where设置成前缀,当 WHERE 后紧随 AND 或则 OR 的 时候,就去除 AND 或者 OR。prefix 前缀,prefixOverrides 覆盖首部指定内容。
<select id="findAllStudent" resultType="Student" parameterType="Student">
select id,num,name,gender from student
<trim prefix="where" prefixOverrides="and | or">
<if test="num!=0">
num =#{num}
</if>
<if test="name!=null">
and name =#{name}
</if>
<if test="gender!=null">
and gender =#{gender}
</if>
</trim>
</select>
当然前缀也可以设置成set,比如
<update id="updateStudent1" parameterType="Student">
update student
<trim prefix="set" suffixOverrides=",">
<if test="num!=0">
num = #{num},
</if>
<if test="name!=null">
name = #{name},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</trim>
where id=#{id}
</update>
choose,when
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="findAllStudent" resultType="Student" parameterType="Student">
select id,num,name,gender from student
<trim prefix="where" prefixOverrides="and | or">
<choose>
<when test="num!=0">
num = #{num}
</when>
<when test="name!=null">
and name = #{name}
</when>
<otherwise>
and gender = #{gender}
</otherwise>
</choose>
</trim>
</select>
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:删除id在某个区间的多个学生信息。
<delete id="deleteStudent">
delete from student
<where>
<foreach collection="array" item="item" open="id in (" separator="," close=")">
#{item}
</foreach>
</where>
</delete>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符。
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
补充:
比如说:要删除id小于某一个值的学生信息时,正常书写 mybatis 会报错,需要对这些符号进行转义。这里的"<"与就是标签的左半部分,又能表示小于的意思,就会出错,当然有问题,肯定就有解决办法。
方法一:
特殊字符 | 转义字符 |
---|---|
< | < |
> | > |
" | " |
, | ' |
& | & |
<select id="findStudents" resultType="com.ffyc.mybatis.model.Student" parameterType="int">
select id,num,name,gender from student where id < #{id}
</select>
遇到上述特殊字符时,使用对应的转义字符即可。
方法二:
<select id="findStudentts" resultType="com.ffyc.mybatis.model.Student" parameterType="int" flushCache="true">
select id,num,name,gender from student where id <![CDATA[<]]> #{id}
</select>
mybatis的一级缓存和二级缓存
引入
首先,什么是缓存?
缓存用来存储一些常用或即将用到的数据或指令,当CPU需要这些数据或指令的时候直接从缓存中读取,这样比CPU到内存甚至硬盘中读取要快得多,能够大幅度提升CPU的处理速度(节省时间/提高效率)。
缓存有什么用处?
为让程序更快的访问到数据,同时也是为了减少数据库的访问压力,可以将数据缓存到内存,手机内存,客户端硬盘中。
举个例子:同时有100个用户访问同一个数据库,容易造成数据库崩溃,但如果将第一个用户访问到的数据加入到缓存中,后面的用户直接访问第一个用户访问拿到的数据,之后定时再从数据库中查询以更新缓存,这样就能缓解数据库访问量过大的问题,减少数据库压力,提高查询效率。
缓存是怎么实现的?
mybatis一级缓存
Mybatis 有一级缓存和二级缓存,一级缓存的作用域是同一个 SqlSession,如果在同一个SqlSession中执行两次一样的sql,那么第一次执行完毕后会将在数据库中查到的数据写入缓存,第二次查询时就会直接从缓存中查询,提高查询效率,当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存。
,一级缓存只是相对于同一个 SqlSession 而言,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession 都会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
生命周期
@Test
public void test3(){
//测试一级缓存
//结果只查询1次 没有设置缓存更新,也没有中间强出缓存
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
studentDao.findStudents(13);
studentDao.findStudents(13);
sqlSession.commit();
sqlSession.close();
}
//结果只查询1次 没有设置缓存更新,也没有中间强出缓存
@Test
public void test2(){
//测试一级缓存 中间清除一次缓存
//结果只查询2次
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
studentDao.findStudents(13);
sqlSession.clearCache();
studentDao.findStudents(13);
sqlSession.commit();
sqlSession.close();
}
//测试一级缓存 中间清除一次缓存
//结果只查询2次
@Test
public void test(){
//测试一级缓存
//在select中设置缓存更新
//结果只查询2次
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
studentDao.findStudentts(13);
studentDao.findStudents(13);
sqlSession.commit();
sqlSession.close();
}
//测试一级缓存
//在select中设置缓存更新
//结果只查询2次
mybatis二级缓存
二级缓存是多个SqlSession共享的,是SqlSessionFactory级别的,如果将查询到的数据放到二级缓存中,那么就可以实现多个sqlsession共享。
如果执行两次相同的sql,第一次查询到的数据后关闭sqlsession,就会将数据放到二级缓存中。
也可以理解为根据 mapper 的 namespace 划分区域 的,相同 namespace 的 mapper 查询的数据缓存在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理解为二级缓存区域是根据 mapper 划分。
Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。
二级缓存配置:
1.启用二级缓存
<setting name="cacheEnabled" value="true"/>
2.对象序列化
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示 缓存只作用于 cache 标签所在的映射文件中的语句。
<cache flushInterval="1"></cache>
public void testTwo(){
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
StudentDao studentDao1 = sqlSession1.getMapper(StudentDao.class);
studentDao1.findStudents(10);
sqlSession1.close();
//模拟刷新时间
int j=0;
for(int i=0;i<=1000000;i++){
j=j+1;
}
System.out.println(j);
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
StudentDao studentDao2 = sqlSession2.getMapper(StudentDao.class);
studentDao2.findStudents(10);
sqlSession2.close();
}
这里可以看到结果查询了两次。因为第二次查询过了自动刷新时间。
我们再来看一下两次连续查询的,也就是将模拟间隔时间去掉
这里明显只查询了一次。
总体架构: