MyBatis相关概念回顾
本质就是对JDBC底层代码的封装。使用mybatis一般是两种方式,一种是XML,一种是注解。
ORM:关系-对象映射,操作实体类就相当于操作表
虽然是底层用到了反射,但是真正的原因是,当我们编写User实体的时候,要保证实体的字段名要和数据库的字段名要保持一致。他们保持一致后,执行完sql,拿到sql查询结果之后,则会根据id字段名,去找到User实体中,id这个属性。然后把查询的值赋过去,以此类推。(User实体的字段名和User表的字段名保持一致,就是一种映射关系,底层也是基于这种映射关系来完成结果集的封装)
注:个人理解: 根据底层源码而知,前提是属性名和字段名保持一致,当sql执行后得到结果集返回,就会对结果集进行遍历(也就是预处理对象集合进行遍历),遍历出每一个预处理对象对应的属性名和value值,因为字段名和属性名是一致的,那么此时就可以通过反射技术:根据返回值类型,查询出的字段名,通过反射里面的getDec。。。先得到字段的属性对象,开启对属性的暴力访问,然后对属性进行赋值,并封装到返回类型的对象中。
半自动:在mybatis中还需要手动编写sql,这样可以能够对sql一步优化。优化后的sql执行效率更高
轻量级:框架在启动的过程中,需要的资源比较少。
优势:MyBatis是一个半自动化的持久化层框架。对开发人员而言, 核心sql还是需要自己优化, sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据。
MyBatis开发步骤:
①添加MyBatis的坐标,引入依赖
②创建user数据表
③编写User实体类(要和表的字段属性要对应)
④编写映射文件UserMapper.xml(在工程的resource文件夹下)
⑤编写核心文件SqlMapConfig.xml
⑥编写测试类
1、新建一个maven项目。
2、添加jar依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--引入依赖-->
<dependencies>
<!--mybatis坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--mysql驱动坐标-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<!--单元测试坐标-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--⽇志坐标-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
3、添加一个实体类,创建一个与实体类字段一样的表。
(因为只有当字段和属性名一致时,底层才可以通过反射给属性进行一一对应的映射赋值封装)
4、添加sql的映射文件
5、添加数据库的映射文件
到此,已经完成了1-5,接下来就是第6步。
6、编写测试类。
到此,入门案例已经OK。
MyBatis的CRUD回顾
对于mybatis的增删改查都是写在配置文件中的,下面是具体配置的过程
1、要有约束头
2、要有mapper标签
3、要有执行语句的标签 (标签里面有id,参数类型,返回类型,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">
<!--namespace : 名称空间:与id组成sql的唯一标识
resultType: 表明返回值类型-->
<mapper namespace="user">
<!---查询用户-->
<select id="findAll" resultType="com.shanglin.pojo.User" >
select * from User
</select>
<!--添加用户-->
<!--parameterType:参数类型
(添加操作,那么里面要填什么值,需要传递参数过来) 传递过来的参数就是一个user对象
那么#{}里面的值,应该与传递过来的类的属性名保持一致,这样就可用其属性的get方法取到值-->
<insert id="saveUser" parameterType="com.shanglin.pojo.User" >
insert into user values(#{id},#{username})
</insert>
<!--修改 需要参数的 获取到相应的属性值给占位符赋值-->
<update id="updateUser" parameterType="com.shanglin.pojo.User">
update user set username = #{username} where id = #{id}
</update>
<!--删除需要参数的 因为删除只需要传递一个id,所以只要添加一个相应的类型
如果参数类型是基本数据类型,或者是基本数据类型的包装类,且只是一个的时候,
#{}内的值随便写的-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{abc}
</delete>
</mapper>
4、编写测试类
@Test
public void test() throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
// 1、查询操作
List<User> objects = sqlSession.selectList("user.findAll");
// 设置实体对象传递的参数
User user = new User();
user.setId(3);
user.setUsername("tom");
// 2、增加
sqlSession.insert("user.saveUser",user);
// 3、修改
sqlSession.update("user.updateUser",user);
// 4、删除 只传id
sqlSession.update("user.deleteUser",3);
// 需要调用提交事物,否则不会入库
sqlSession.commit();
sqlSession.close();
}
映射配置文件分析
核心配置文件分析
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂(工厂的作用是生产SqlSession)
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
// openSession是默认开启一个事务,但是该事务不会自动提交
//在进行增删改操作时,要手动提交事务
SqlSession sqlSession = build.openSession();
// 在sqlSession会话对象中封装了与数据库交互的方法。
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
List<User> objects = sqlSession.selectList("user.findAll");
// 如果改为自动提交,则在openSession();里面添加一个true
Mybatis中Dao层的实现(代理模式)
使用代理开发方式。因为这种方式只需要在持久层添加接口就可以,不需要像传统那样添加实现类
它的原理是在执行过程中,对接口使用JDK动态代理,产生一个代理对象,由这个代理对象去执行操作
但是使用代理对象时,
必须准守以下规范
具体用法
1、namespace必须是方法所在接口的全限定类名
2、id必须和方法名保持一致
3、返回类型、参数类型,是最终要分装到哪个类是全限定类名
代理模式getMapper
只需要传递接口,然后通过代理对象调用接口方法
注意:如果不用代理对象,用其他的,则需要填写全路径,eg:如下
如果使用代理模式,就需要调用getMapper方法,获取动态代理对象,通过动态代理对象去调用要操作的方法。
//使用代理模式的时候,一定要namespace的名字和接口的路径名一致。否则找不到接口
@Test
public void test05() throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
// 通过getMapper去获取代理对象
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
// 通过代理对象去调用接口的方法
List<User> all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
sqlSession.close();
}
Mybatis配置文件的深入
1、properties的配置文件使用
将数据源的配置信息单独的抽取成一个properties文件,然后将这个配置文件加载进到映射文件中进行引用
2、typeAliases标签的别名使用
a、单个的别名使用(typeAlias)
b、批量的别名使用(package)
批量就直接写实体类的包名即可,因为实体类都在一个包下,而且它不区分大小写嘛。
Mapper.xml的sql配置
根据实体类的不同取值, 使用不同的SQL语句来进行查询,SQL是动态变化的。
if条件一般是用于多条件组合查询中,一般先判读是否为空,若不为空则拼接下去
1、动态sql——if标签的使用
UserMapper.xml文件中。一定把参数的名字写对咯,不然报错就的debug源码了。
<!--多条件组合查询:演示if-->
<select id="findByCondition" parameterType="user" resultType="user">
select * from User
<where> <!--会自动拼接where,并去掉第一个and-->
<if test="id !=null">
and id = #{id}
</if>
<if test="username !=null">
and username = #{username}
</if>
</where>
</select>
2、动态sql——foreach标签的使用
<!--多值查询:演示foreach-->
<select id="findByIds" parameterType="list" resultType="user">
<!--如: select * from user where id in (id,id)-->
select * from user
<where>
<!--传过来是数组用array,是集合用List;open是where条件拼接的开始字段;item传递过来的数组正在遍历的值
用,进行分割-->
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
foreach测试代码(采用动态代理模式)
@Test
public void test07() throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
// 通过getMapper去获取代理对象
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
int[] arr ={1,2};
// 通过代理对象去调用接口的方法
List<User> all = mapper.findByIds(arr);
for (User user : all) {
System.out.println(user);
}
sqlSession.close();
}
3、将共用的sql进行抽取出来(抽取sql)
将共用的sql抽取出来,给一个id标识。后续有用到这个sql查询的地方,直接引入id标识即可。
Mybatis复杂的映射开发
1、一对一查询
a、先创建一个user、order表,一个订单只属于一个用户
b、然后创建一个项目(进行多表操作)
c、然后和之前的项目一样,添加依赖。
d、然后创建User,Order实体类(实体类的属性要和表的字段对应 )
e、然后添加数据库的配置文件SqlMapConfig.xml
需要注意的地方: 1、使用的是mapper这个标签**(不用同包同名,只要放到resource包下就可以)**<br /> 2、使用的是package这个标签,也就是会加重包下的所有接口。那么就**需要将映射文件 要保**<br />** 持和接口所在的包必须同包同名**。就如下图的com.shanglin.mapper<br />![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1598754266897-e1cb568b-5a4d-45b1-90d6-517f914d6a24.png#height=330&id=g95pz&originHeight=330&originWidth=1020&originalType=binary&ratio=1&size=0&status=done&style=none&width=1020)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1598754271555-bf7f7f7e-8128-43dd-8673-558a4c6f8a77.gif#height=1&id=acO0d&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)
具体的配置文件
<!--根标签-->
<configuration>
<!--加载jdbc.properties的文件信息-->
<properties resource="jdbc.properties" />
<typeAliases>
<!--给单独的实体起别名(适合个数少的时候)-->
<!-- <typeAlias type="com.shanglin.pojo.User" alias="user"/>-->
<!--批量起别名:其别名就是该包下所有的类的本身的类名:别名还不区分大小写 (整个包下的所有实体)-->
<package name="com.shanglin.pojo"/>
</typeAliases>
<!--environments:运行环境-->
<environments default="development">
<environment id="development">
<!--当前失误交由JDBCj进行管理-->
<transactionManager type="JDBC"/>
<!--当前使用mybatis提供的连接池-->
<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="IUserMapper.xml"/>-->
<!--直接引入某个包下所有的接口对应的映射配置文件
注意前提是:配置文件和接口要同包同名,所以要创建同名包-->
<package name="com.shanglin.mapper"/>
<!--如果是用注解的话,也是需要添加映射的。就是接口的全路径名。
但是如果是多个的时候,批量的,那还是要上面的导包形式-->
<!--<mapper class="com.shanglin.mapper.IUserMapper"/>-->
</mappers>
</configuration>
f、然后编写对应的接口,与映射配置文件来实现一对一查询
这样就可以将查询出user的字段这部分,赋给order的user属性上。
按上面的逻辑,使用resultType只能反射获对Order的属性进行封装,获取不能对user的属性进行封装。
所以resultType是不能满足现状的需求,需要改用resultMap来完成。(它可以手动配置封装逻辑)
g、resultMap、association等标签的使用(封装对象)
手动来配置实体属性与表字段的映射关系,的映射配置文件IOrderMapper.xml
//...约束头
<mapper namespace="com.shanglin.mapper.IOrderMapper">
<cache></cache>
<!--手动来配置实体属性与表字段的映射关系,它的id属性就是标识,后面直接用其标识即可
type: 表示当前需要封装到哪个实体对象当中 (当前需要封装到Order中)-->
<resultMap id="orderMap" type="com.shanglin.pojo.Order">
<!--这里配置的是Order这个属性,与sql查询出来的结果的映射关系
property是实体类的属性名,column是数据库表的元素名 -->
<result property="id" column="id"></result>
<result property="orderTime" column="orderTime"></result>
<result property="total" column="total"></result>
<!--如何把查询出的user信息配置到Order中的user属性中呢?
首先:user是Order类中的其中一个属性名,表示用来配置Order中user这个属性了
javaType来表示当前配置这个属性是什么类型,这里是User实体类-->
<association property="user" javaType="com.shanglin.pojo.User">
<!--uid,username是用sql查询出的结果的字段,前面是属性的字段-->
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
</association>
</resultMap>
<!--这个查询结果,是按照上面id=orderMap的映射关系进行封装,因为调用了上面的标识userMap-->
<select id="findOrderAndUser" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
</select>
</mapper>
2、一对多查询
从用户的角度出发就是一对多(一个用户可以具有多个订单)
一对多的需求:查询所有用户,与此同时查询出该用户所具有的订单信息
那么不能用内连接了,需要用左外或者右外
a、一个用户可以有多个订单,订单用List装起来。
b、创建接口
public interface IUserMapper {
public List<TwoUser> findAll();
}
c、resultMap、collection标签的使用(封装集合)
一对多的配置文件
<mapper namespace="com.shanglin.mapper.IUserMapper">
<cache></cache>
<!--这里面是一对多的-->
<!--手动来配置实体属性与表字段的映射关系,它的id属性就是标识,后面直接用其标识即可
type: 表示当前需要封装到哪个实体对象当中 (当前需要封装到TwoUser中)-->
<resultMap id="userMap" type="com.shanglin.pojo.TwoUser">
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
<!--这里对应的是多方,多方要用collection
property:是实体TwoUser中集合的名称orderList,
ofType:表示当前集合泛型的一个全路径,表示将查询结果封装成一个个的全路径里面的对象
(也就是封装成一个个TwoOrder对象)-->
<collection property="orderList" ofType="com.shanglin.pojo.TwoOrder">
<!--uid,username是用sql查询出的结果的字段,前面是属性的字段-->
<result property="id" column="id"></result>
<result property="orderTime" column="orderTime"></result>
<result property="total" column="total"></result>
</collection>
</resultMap>
<!--这个查询,是按照上面的映射关系进行封装,因为调用了上面的标识id=userMap-->
<select id="findAll" resultMap="userMap">
select * from user u left join orders o on u.id = o.uid
</select>
</mapper>
d、测试代码
@Test
public void test02() throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
List<TwoUser> all = mapper.findAll();
for (TwoUser twoUser : all) {
System.out.println(twoUser);
}
}
3、多对多查询
多对多表示一个用户可以有多个角色,一个角色可以被多个用户使用
所以用户和角色是多对多的关系(多对多其实就是两个一对多)
a、多对多的查询模型
b、创建实体类
一个用户可以有多个角色,所以角色就用roleList来装
c、创建接口
d、多对多的配置文件
<mapper namespace="com.shanglin.mapper.IUserMapper">
<cache></cache>
<!--这里面是多对多的-->
<resultMap id="userRoleMap" type="com.shanglin.pojo.ThreeUser">
<result property="id" column="userid"></result>
<result property="username" column="username"></result>
<!--这里对应的是ThreeUser下面的Role实体对应的集合。roleList就是集合的名字-->
<collection property="roleList" ofType="com.shanglin.pojo.Role">
<result property="id" column="roleid"></result>
<result property="roleName" column="roleName"></result>
<result property="roleDesc" column="roleDesc"></result>
</collection>
</resultMap>
<!-- 查询所有用户的同时查询出每一个用户所属角色-->
<select id="findAllUserAndRole" resultMap="userRoleMap">
select * from user u left join sys_user_role ur on u.id = ur.userid
left join sys_role r on r.id = ur.roleid
</select>
</mapper>
Mybatis的注解开发
1、常用的注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result一起使用, 封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
2、用注解进行CRUD操作
a、在接口中添加注解
//添加用户 #{}里面的值要和实体中的属性一致,才可以通过get拿到值
@Insert("insert into user values(#{id},#{username})")
public void addUser(User user);
//更新用户
@Update("update user set username = #{username} where id = #{id}")
public void updateUser(User user);
//查询用户
@Select("select * from user")
public List<User> selectUser();
//删除用户
@Delete("delete from user where id = #{id}")
public void deleteUser(Integer id);
b、动态代理来调用方法进行测试
其他的测试代码以此类推
@Test
public void test04(){
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User user = new User();
user.setId(4);
user.setUsername("测试数据");
mapper.addUser(user);
sqlSession.commit();
}
c、注解的加载与配置文件加载的异同
sql的核心配置文件中,如果是配置文件,用到resource,或者加载执行方法所在包的全路径
如果是注解,用到class,或者加载执行方法所在包的全路径。如下所示:
<!--直接引入某个包下所有的接口对应的映射配置文件
注意前提是:配置文件和接口要同包同名,所以要创建同名包-->
<package name="com.shanglin.mapper"/>
<!--如果是用注解的话,也是需要添加映射的。就是接口的全路径名。
但是如果是多个的时候,批量的,那还是要上面的导包形式-->
<!--<mapper class="com.shanglin.mapper.IUserMapper"/>-->
3、用注解进行复杂映射开发
注解开发也是需要添加映射的,值不过直接添加其注解接口下的包名即可
先看一下配置文件,如同在配置文件的基础上进行转换为注解
resultMap可用@Results替换
result可用@Result替换
association可用@one替换。
collection可用@Many替换
4、一对一的注解
如何用@one进行封装单个对象
public interface IOrderMapper {
// 查询订单的同时,还需查询订单所属的用户
// 先查询出订单的uid,然后再发起查询,查询订单关联的用户
//javaType表示Order实体中 user这个类型是什么
@Results({
@Result(property = "id",column = "id"),
@Result(property = "orderTime",column = "orderTime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",javaType = User.class,
// 这个sql是根据当前查询结果uid的值,再次查询下面那个订单关联的用户
// one这个是对一方的查询。select里面就是一个namespace.id=>接口全路径.方法名
one=@One(select = "com.shanglin.mapper.IUserMapper.findUserById"))
})
@Select("select * from orders") // 先执行这条查询
public List<Order> findOrderAndUser();
}
public interface IUserMapper {
//根据id查询用户
@Options(useCache = true)
@Select({"select * from user where id = #{id}"})
public User findUserById(Integer id);
}
调用方法测试
@Test
public void ontToOne(){
IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
List<Order> orderAndUser = mapper.findOrderAndUser();
for (Order order : orderAndUser) {
System.out.println(order);
}
}
5、一对多的注解
a、定义好实体类
一个用户多个订单,那么就需要在user实体类中,添加List
b、如何使用many进行封装集合
public interface IUserMapper
// 查询所有用户,同时查询每个用户关联的订单信息(一对多)
@Select("select * from user")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "username",column = "username"),
// 第三个property是从user实体中,找到Order集合的属性名orderList
// javaType指明当前property属性的类型,当前是list集合类型的
@Result(property = "orderList",column = "id",javaType = List.class,
// 这个list是一对多的,是根据用户的id进行查询
many=@Many(select = "com.shanglin.mapper.IOrderMapper.findOrderByUid"))
})
public List<TwoUser> findAll();
}
public interface IOrderMapper {
@Select("select * from orders where uid = #{uid}")
public List<Order> findOrderByUid(Integer uid);
}
c、测试
@Test
public void ontToMany(){
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
List<TwoUser> all = mapper.findAll();
for (TwoUser twoUser : all) {
System.out.println(twoUser);
}
}
6、多对多的注解开发
a、创建实体类
b、 @Many如何实现多对多
//查询所用用户,同时查询每个用户关联的角色信息(多对多)
// 先查询出这个sql,因为用户表和角色表有关联关系,这个关系由中间表来维护
// 第一个sql执行后,有了角色id,然后根据角色id值进行查询,查出该用户的角色信息
@Select("select * from user")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "roleList",column = "id",javaType = List.class,
many = @Many(select = "com.shanglin.mapper.IRoleMapper.findRoleByUid"))
})
public List<ThreeUser> findAllUserAndRole();
public interface IRoleMapper {
// 根据用户的id进行关联查询,把用户的角色信息查出也是第二次查询
@Select("select * from sys_role r,sys_user_role ur where r.id = ur.roleid and ur.userid = #{uid}")
public List<Role> findRoleByUid(Integer uid);
}
c、测试
@Test
public void ManyToMany(){
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
List<ThreeUser> allUserAndRole = mapper.findAllUserAndRole();
for (ThreeUser tUser : allUserAndRole) {
System.out.println(tUser);
}
}
Mybatis的缓存
缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存,我们可以避免频繁的与数据库进行交互,进而提高响应速度
mybatis也提供了对缓存的支持, 分为一级缓存和二级缓存, 可以通过下图来理解:
①、一级缓存是Sql Session级别的缓存。在操作数据库时需要构造sql Session对象, 在对象中有一个数据结构(HashMap) 用于存储缓存数据。不同的sql Session之间的缓存数据区域(HashMap) 是互相不影响的。
②、二级缓存是mapper级别的缓存, 多个SqlSession去操作同一个Mapper的sql语句, 多个SqlSession可以共用二级缓存, 二级缓存是跨Sql Session的
一级缓存
通过会话对象SQLSession去查询验证缓存
③、总结
1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2、如果中间sq Session去执行commit操作(执行插入、更新、删除) , 则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
3、第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息
一级缓存原理探究与源码分析
一级缓存到底是什么?一级缓存什么时候被创建?一级缓存的工作流程是怎样的?
上面的一级缓存,都绕不开SqlSession。所以从SqlSession开始找
多看一下代码,点进去。
1、一级缓存就是一个hashMap
2、
二级缓存
二级缓存缓存的是数据,不是对象,因为缓存是数据,当第二次查询时候,如果是相同的,则重新创建一个对象,将数据赋值给它,然后返回。
二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的, 而二级缓存是基于mapper文件的namespace的, 也就是说多个sqlSession可以共享一个mapper中的二级缓存区域, 并且如果两个mapper的namespace相同, 即使是两个mapper, 那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中
二级缓存的使用
1、添加二级缓存标签
2、对实体类进行序列化
开启了二级缓存后, 还需要将要缓存的pojo实现Serializable接口, 为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话, 就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口
3、测试
@Test
public void secondCatch() {
//根据sqlSessionFactory 生产session 会话对象
SqlSession sqlSession1 = sessionFactory.openSession();
SqlSession sqlSession2 = sessionFactory.openSession();
SqlSession sqlSession3 = sessionFactory.openSession();
// String statement = "com.lagou.pojo.UserMapper.selectUserByUserId" ;
IUserMapper userMapper1 = sqlSession1.getMapper(IUserMapper.class );
IUserMapper userMapper2 = sqlSession2.getMapper(IUserMapper.class );
IUserMapper userMapper3 = sqlSession2.getMapper(IUserMapper.class );
//第一次查询,命中率为0,会发出sql,将查询结果放到缓冲中
User u1 = userMapper1.findUserById(1);
System.out.println(u1);
sqlSession1.close(); //清空一级缓存
User user = new User();
user.setId(1);
user.setUsername( "aaa" );
userMapper3.updateUser(user);
sqlSession3.commit(); // 这步提交后,缓存会被清空
User u2 = userMapper2.findUserById(1); // 第二次也需要发送sql
}
4、分析命中率
第一次命中率是0,就是缓存中没有<br /> 第二次命中率是0.5,也就是缓存中有了,两次,命中一次,概率就是50%<br /> 如果期间进行了更改操作,会清空二级缓存,再次查询还需要从数据库查询。
5、useCache的使用(默认是true)
userCache默认是开启的,默认值是true。
userCache是用来设置是否禁用二级缓存的, 在statement中设置useCache=false可以禁用当前select语句的二级缓存, 即每次查询都会发出sql去查询, 默认情况是true, 即该sql默认去使用二级缓存。
如果将设置成useCache=false, 表示禁用二级缓存, 直接从数据库中获取。
配置文件则如下设置禁用:
<select id="select User By UserId" useCache="false"
resultType="com.shanglin.pojo.User" parameterType="int">
select * from user where id=#{id}
</select>
注解的按如下设置禁用:
6、flushCache的使用(默认是true)
在mapper的同一个namespace中, 如果有其它insert、update、delete操作数据后需要刷新缓
存,如果不执行刷新缓存会出现脏读。
也就是增删改后是需要刷新缓存的,而配置中flushCache=”true”属性, 默认情况下为true, 即刷新缓存, 如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。所以呢,这个值一般是不修改的,默认让他刷新缓存
分布式二级缓存整合redis
a、mybatis与redis的整合
刚刚提到过, mybatis提供了一个cache接口, 如果要实现自己的缓存逻辑, 实现cache接口开发即可。
mybatis本身默认实现了一个, 但是这个缓存的实现无法实现分布式缓存, 所以我们要自己来实现。
redis分布式缓存就可以, mybatis提供了一个针对cache接口的redis实现类, 该类存在mybatis-redis包中
b、在pom.xml中添加jar包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
c、基于redis实现二级缓存
1、配置文件的实现
<?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.shanglin.mapper.IUserMapper">
<cache type="org.mybatis.caches.redis.RedisCache" /><!--基于redis实现二级缓存-->
<select id="findAll" resultType="com.shanglin.pojo.User" useCache="true">
select * from user
</select>
d、在Resource下面创建redis.properties
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
e、代码测试
@Test
public void SecondLevelCache(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);
User user1 = mapper1.findUserById(1);
sqlSession1.close(); //清空一级缓存
User user = new User();
user.setId(1);
user.setUsername("lisi");
mapper3.updateUser(user);
sqlSession3.commit();
User user2 = mapper2.findUserById(1);
System.out.println(user1==user2);
}
执行后,出现一次查询,第二次没有查询,而是命中率为0.5。也就是使用redis作为二级缓存已经OK。