MyBatis 缓存
参考:https://blog.csdn.net/jinhaijing/article/details/84230810
缓存是服务器内存的一块区域。经常访问但又不会时时发生变化的数据适合使用缓存。
mybatis也支持缓存:提高查询速度,减轻数据库访问压力。
一级缓存(本地缓存)
MyBatis自带一级缓存且不可卸载
当执行查询以后,查询的结果会同时缓存到SqlSession为我们提供的一块区域中,该区域的结构是一个Map,当再次查询同样的数据时,mybatis会先去sqlsession中查询缓存,有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了,同时一级缓存是SqlSession范围的缓存,当调用SqlSession的修改、添加、删除、commit(),close等方法时,就会清空一级缓存。
特点:随着sqlSession的创建而存在,随着SqlSession的销毁而销毁
简称:sqlSession级别的缓存
一级缓存失效的四种情况:
sqlSession不同(会话不同)
sqlSession相同,查询缓存中没有的数据
sqlSession相同,但两次查询之间执行了增删改操作
说明:为了防止增删改对当前数据的影响,即使查的同一个对象,也会重新查数据库
原因:每个增删改标签都有默认刷新缓存配置:flushCache=“true”(刷新一级和二级缓存)
sqlSession相同,但手动清楚了一级缓存(缓存清空)
手动清空一级缓存:openSession.clearCache();
没有使用到当前一级缓存的情况,就会再次向数据库发出查询
二级缓存(全局缓存)
基于 mapper.xml 中 namespace 命名空间级别的缓存,即同一个 namespace 对应一个二级缓存。两个mapper.xml的namespace如果相同,则这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
也称为SqlSessionFactory级别的缓存,由同一个SqlSessionFactory对象创建的多个SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与第一次存入的对象是不一样的。
注意:不是程序自带的,需要配置。仅做了解,一般不推荐使用(一般使用Redis缓存)。
配置流程:
- 在核心配置文件 SqlMapConfig.xml 中开启二级缓存(可以省略)
<settings> <!--开启对二级缓存的支持 默认是支持的可以不用配置--> <setting name="cacheEnabled" value="true"/> </settings>
若是springboot集成,在yaml配置文件中配置开启二级缓存(可以省略)
mybatis: configuration: cache-enabled: true #打开全局缓存开关(二级环境),默认值就是true
2.在mapper配置文件中开启使用二级缓存
<?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.atguigu.mybatis.dao.EmployeeMapper"> <!-- 声明使用二级缓存 --> <cache><cache/> <!-- <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> --> <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee"> select * from tbl_employee where last_name like #{lastName} </select> </mapper>
<cache>中可以配置一些参数:
eviction:缓存的回收策略:
LRU – 最近最少使用的:移除最长时间不被使用的对象。默认
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval:缓存刷新间隔。缓存多长时间清空一次,默认不清空,可以设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素
type=“”:指定自定义缓存的全类名
3.实体类对象需要实现序列化接口
public class User implements Serializable
4.操作的过程中需要提交之后才会存入到二级缓存
查询数据提交到二级缓存中:sqlsession.commit | sqlsession.close
**注意:**如果 openSession.close(); 在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据,然后就会发送sql去查数据库;
@Test public void testSecondLevelCache() throws IOException{ SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(); SqlSession openSession2 = sqlSessionFactory.openSession(); try{ EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class); EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class); Employee emp01 = mapper.getEmpById(1); System.out.println(emp01); Employee emp02 = mapper2.getEmpById(1); System.out.println(emp02); openSession.close(); openSession2.close(); } }
工作机制:
1.一个会话,查询一条数据,这个数据会默认先被放在当前会话的一级缓存中;
2.当会话关闭或者提交后,一级缓存中的数据会被保存到二级缓存中;
然后当有新的会话查询数据,若是同一个 namespace 就可以获取二级缓存中的内容。
sqlSession ==> EmployeeMapper ==> Employee
不同namespace查出的数据会放在自己对应的缓存中(map)
效果:数据会从二级缓存中获取
缓存有关的设置/属性
- mybatis全局配置文件中配置全局缓存开启和清空
1)控制二级缓存的开启和关闭
<setting name="cacheEnabled" value="true"/> <!-- cacheEnabled=true:开启缓存;false:关闭缓存(二级缓存关闭)(一级缓存一直可用的) -->
2)控制一级缓存的开启和关闭
<setting name="localCacheScope" value="SESSION"/> <!-- localCacheScope:本地缓存作用域(一级缓存SESSION); 当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存 -->
注意:一级缓存关闭后,二级缓存自然也无法使用;
mapper.xml 中也可以配置一级和二级缓存开启和使用
1)每个select标签都默认配置了useCache=“true”,表示会将本条语句的结果进行二级缓存。
如果useCache= false:则表示不使用缓存(一级缓存依然使用,二级缓存不使用)
2)每个 增删改 标签都默认配置了 flushCache=“true”,表示增删改执行完成后就会刷新一级二级缓存。
注意:查询标签 <select> 默认 flushCache=“false”:如果 flushCache=true; 则每次查询之后都会清空缓存;一级和二级缓存都无法使用。
多数据库支持
如果配置了 DatabaseIdProvider,可以在动态代码中使用名为 “_databaseId” 的变量来为不同的数据库构建特定的语句。
示例:
<insert id="insert"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> <if test="_databaseId == 'oracle'"> select seq_users.nextval from dual </if> <if test="_databaseId == 'db2'"> select nextval for seq_users from sysibm.sysdummy1" </if> </selectKey> insert into users values (#{id}, #{name}) </insert>
DatabaseIdProvider 配置
@Bean public DatabaseIdProvider databaseIdProvider() { DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties p = new Properties(); p.setProperty("Oracle", "oracle"); p.setProperty("TiDB", "tidb"); p.setProperty("PostgreSQL", "postgresql"); p.setProperty("DB2", "db2"); p.setProperty("SQL Server", "sqlserver"); databaseIdProvider.setProperties(p); return databaseIdProvider; }
动态 SQL 中的插入脚本语言
MyBatis 从 3.2 版本开始支持插入脚本语言,这允许插入一种语言驱动,并基于这种语言来编写动态 SQL 查询语句。
可以通过实现以下接口来插入一种语言:
public interface LanguageDriver { ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType); SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType); }
实现自定义语言驱动后,就可以在 mybatis-config.xml 文件中将它设置为默认语言:
<typeAliases> <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/> </typeAliases> <settings> <setting name="defaultScriptingLanguage" value="myLanguage"/> </settings>
或者,也可以使用 lang
属性为特定的语句指定语言:
<select id="selectBlog" lang="myLanguage"> SELECT * FROM BLOG </select>
或者,在 mapper 接口上添加 @Lang
注解:
public interface Mapper { @Lang(MyLanguageDriver.class) @Select("SELECT * FROM BLOG") List<Blog> selectBlog(); }
MyBatis 注解开发
单表注解
* @Insert:实现新增,代替了<insert></insert> * @Update:实现更新,代替了<update></update> * @Delete:实现删除,代替了<delete></delete> * @Select:实现查询,代替了<select></select> * @Result:实现结果集封装,代替了<result></result> * @Results:可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap>
注解对用户表实现增删改查操作
import cn.test.doman.User; import org.apache.ibatis.annotations.*; import java.util.List; // 注解的单表crud操作 @Mapper public interface UserMapper { //1 根据id做查询 @Select("select * from user where id =#{id}") public User findById(int id); //2 全查 @Select("SELECT id AS iid,username AS name,birthday AS bir,sex AS se,address AS addr FROM USER") @Results({ /*column:字段 * property:对象属性 * id:默认是false 代表当前不是主键 * */ @Result(column = "iid",property = "id",id=true), @Result(column = "name",property = "username"), @Result(column = "bir",property = "birthday"), @Result(column = "se",property = "sex"), @Result(column = "addr",property = "address") }) public List<User> findAll(); //3 新增 @Insert("insert into user(username,birthday,sex,address) " + "values(#{username},#{birthday},#{sex},#{address})") public void save(User user); //4 修改 @Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} " + "where id=#{id}") public void update(User user); //5 删除 @Delete("delete from user where id=#{id}") public void delete(int id); }
多表注解
注解开发的多表查询是嵌套查询方式
@Results 代替的是标签<resultMap>。该注解中可以使用单个@Result注解,也可以使用@Result集合。 使用格式:@Results({@Result(), @Result()})或@Results(@Result()) @Result 代替<id>标签和<result>标签 @Result中属性介绍: column:数据库的列名 property:需要装配的属性名 one:需要使用@One注解(@Result(one=@One ())) many:需要使用@Many注解(@Result(many=@Many ())) @One(一对一) 代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One注解属性介绍: select:指定用来多表查询的 sqlmapper 使用格式:@Result(column=" ", property=" ", one=@One(select="")) @Many(一对多) 代替了<collection>标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。 使用格式:@Result(column=" ", property=" ", many=@Many(select=""))
1)一对一
import org.apache.ibatis.annotations.One; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; @Mapper public interface OrdersMapper { // 查询订单 @Select("select * from orders where id=#{id}") @Results({ @Result(column = "id",property = "id",id = true), @Result(column = "ordertime",property = "ordertime"), @Result(column = "money",property = "money"), /* 组装用户 * 一对一 one=@one * select:代表要封装的数据方法来源 * column:方法需要的参数 * property:要封装的对象属性名 * javaType:对象属性名的字节码(.class)类型 * */ @Result(one=@One(select = "cn.test.mapper.UserMapper.findById") ,property = "user",javaType = User.class, column = "uid") }) public Orders findOrders(int id); }
@Mapper public interface UserMapper { //1 根据id做查询 @Select("select * from user where id =#{id}") public User findById(int id); }
2)一对多
@Mapper public interface UserMapper { //1 根据id做查询 @Select("select * from user where id =#{id}") @Results({ @Result(column = "id",property = "id",id=true), @Result(column = "username",property = "username"), @Result(column = "birthday",property = "birthday"), @Result(column = "sex",property = "sex"), @Result(column = "address",property = "address"), /*组装订单orders * 一对多 * many=@many * select:代表要封装的数据方法来源 * column:方法需要的参数 * property:要封装的对象属性名 * javaType:对象属性名的字节码(.class)类型 * */ @Result(many = @Many(select = "cn.test.mapper.OrdersMapper.findOrdersList"), property = "ordersList",javaType =List.class,column = "id") }) public User findById(int id); }
@Mapper public interface OrdersMapper { /*根据用户id获取订单集合*/ @Select("select * from orders where uid=#{用户的id}") public List<Orders> findOrdersList(int id); }
3)多对多
@Mapper public interface UserMapper { //1 根据id做查询 @Select("select * from user where id =#{id}") @Results({ @Result(column = "id",property = "id",id=true), @Result(column = "username",property = "username"), @Result(column = "birthday",property = "birthday"), @Result(column = "sex",property = "sex"), @Result(column = "address",property = "address"), // 组装订单orders @Result(many = @Many(select = "cn.test.mapper.OrdersMapper.findOrdersList" , fetchType = FetchType.LAZY) , property = "ordersList",javaType =List.class,column = "id"), // 组装角色 @Result(many = @Many(select = "cn.test.mapper.RoleMapper.findRoles") ,property = "roleList",javaType = List.class,column = "id") }) public User findById(int id); }
@Mapper public interface RoleMapper { /*查询指定用户的角色*/ @Select("SELECT * FROM user_role ur INNER JOIN role r ON ur.rid=r.id WHERE ur.uid=#{id}") @Results({ @Result(column = "id",property = "id",id=true), @Result(column = "role_name",property = "roleName"), @Result(column = "role_desc",property = "roleDesc") }) public List<Role> findRoles(int id); }