一、問題
1.1、環境
電腦環境:Windows 10;
開發工具:IntelliJ IDEA;
數據庫環境:MySQL 11.0.10;
JDK環境: Jdk1.8;
Maven環境:Maven3.5.0;
1.2、問題
如何使用Mybatis框架實現二級緩存?
二、解答
2.1、基本知識;
1.一級緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該Session中的所有 Cache 就將清空。
2. 二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap存儲,不同在於其存儲作用域爲 Mapper ,並且可自定義存儲源,如 Ehcache
2.2、實戰
首先我們搭建一個簡單的項目:
①、實體類:
如果要實現二級緩存,必須要實現序列化,即實現實現Serializable接口,爲什麼要實現序列化接口?我的理解是,Mybatis如何能判斷你這個User實體類的查詢和另一個User實體類查詢時不一樣的?序列化就像身份證一樣,是標記一個對象是否相同的一個唯一驗證碼;並且,我們再調用外部接口或者傳輸過程中,都需要序列化,也是爲了將一個立體的實體類,在保證對象結構和對象數據的基礎上,做一個線性,二進制化的一個處理;就像一個方陣要快速通過一個窄橋,寬度只能滿足一個人通過,大家按照一定的規則(從前到後,從左到右)依次快速通過,通過窄橋後,再恢復爲原來的方陣(反序列化);總而言之就是:唯一標記和快速傳輸;
package com.example.student.domain;
import java.io.Serializable;
public class User implements Serializable{
private int id;
private int age;
private String userName;
private String userAddress;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString()
{
final StringBuilder builder = new StringBuilder();
builder.append("id=").append(id).append(",");
builder.append("age=").append(age).append(",");
builder.append("userName=").append(userName).append(",");
builder.append("userAddress=").append(userAddress).append(".");
return builder.toString();
}
}
②、mapper.xml文件
mapper.xml文件很簡單,這裏我們再select標籤上新加了一個cache標籤,用來配置緩存配置;eviction表示緩存回收的策略;flushInterval表示清理間隔,滿了多少條之後清空緩存;這裏如果設置1,則第2次查詢都會新開一個sqlSession;你也可以設置大一些;例如60000等;
映射語句文件中的所有select 語句將會被緩存。
映射語句文件中的所有insert,update 和delete 語句會刷新緩存。
緩存會使用Least Recently Used(LRU,最近最少使用的)算法來收回。
根據時間表(比如no Flush Interval,沒有刷新間隔), 緩存不會以任何時間順序來刷新。
緩存會存儲列表集合或對象(無論查詢方法返回什麼)的1024 個引用。
緩存會被視爲是read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
<?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必須是PostsMapper接口的路徑,不然要運行的時候要報錯 “is not known to the MapperRegistry”-->
<mapper namespace="com.example.student.mapper.UserMapper">
<!-- 這兒的resultType是配置在mybatis-config.xml中得別名 -->
<!--<cache/>-->
<!-- 回收策略:
LRU – 最近最少使用的:移除最長時間不被使用的對象。
FIFO – 先進先出:按對象進入緩存的順序來移除它們。
SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。-->
<!--<cache eviction="FIFO" flushInterval="1" size="512" readOnly="true" />-->
<select id="getUserById" parameterType="int" resultType="com.example.student.domain.User">
select * from user where id=#{id}
</select>
</mapper>
③、mapper.java類
package com.example.student.mapper;
import com.example.student.domain.User;
//通過面向接口的mybatis編程方式,需要保證方法名和配置文件中的id名稱一致
public interface UserMapper {
public User getUserById(int id);
}
④、測試類
注意導入的包的版本和路徑;
import com.alibaba.fastjson.JSON;
import com.example.student.domain.User;
import com.example.student.mapper.UserMapper;
import junit.framework.TestCase;
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 org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
public class TestClass extends TestCase {
Logger logger = LoggerFactory.getLogger(TestClass.class);
private static SqlSessionFactory sqlSessionFactoy;
static{
// 獲取mybatis配置文件的方式:
// String resource = "org/mybatis/builder/mybatis-config.xml";
// InputStream inputStream = Resources.getResourceAsStream(resource);
// SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// SqlSessionFactory factory = builder.build(inputStream);
String resource = "mybatis.config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactoy = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testgetUserById() {
// 默認的 openSession()方法沒有參數,它會創建有如下特性的 SqlSession:
// 1.會開啓一個事務(並且不自動提交)。
// 2.將從由當前環境配置的 DataSource 實例中獲取 Connection 對象。
// 3.事務隔離級別將會使用驅動或數據源的默認設置。
// 4.預處理語句不會被複用,也不會批量處理更新。
//這裏開啓兩個SqlSession來模擬兩次請求;
SqlSession sqlSession = sqlSessionFactoy.openSession();
SqlSession sqlSession2 = sqlSessionFactoy.openSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User u1=mapper.getUserById(1);
// 事務控制方法;
// void commit()
// void commit(boolean force)
// void rollback()
// void rollback(boolean force)
sqlSession.commit();
logger.debug(JSON.toJSONString(u1));
User u2=mapper2.getUserById(1);
sqlSession2.commit();
logger.debug(JSON.toJSONString(u2));
} finally {
sqlSession.close();
}
}
}
⑤、建表語句(MySQL):
drop table if exists user;
create table user(
id int primary key auto_increment,
username varchar(50) unique,
password varchar(100),
useraddress varchar(50)
);
select * from user ;
insert into user values(1,"123","123","beijing");
insert into user values(2,"46","456","shanghai");
⑥配置文件
配置mybatis.config.xml文件;這裏要放開settings標籤,讓緩存生效;你可以對比放開前後日誌打印的情況,是否開啓了兩個sqlSession;還是調用2次查詢,但是隻開啓依次sqlSession;下面還要注意一點,現在mysql的Driver廢棄了com.mysql.jdbc.Driver,最好改用com.mysql.cj.jdbc.Driver;並且url後面設置服務器的時區serverTimezone;而且url裏【&
】符號要用轉移字符代替,即【&
】;別忘了修改本地MySQL數據庫的連接信息;關聯的實體類的路徑,關聯的mapper.xml的路徑;一般mapper.xml文件都放在resources下面;我這裏放在resources下面的mapper文件夾裏的;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--必須要配置可緩存-->
<!-- <settings>
<setting name="cacheEnabled" value="true"/>
</settings>-->
<typeAliases>
<typeAlias alias="User" type="com.example.student.domain.User" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/sys?characterEncoding=UTF-8&serverTimezone=GMT%2B8" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
測試:
11:45:00.844 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.11:45:01.099 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
11:45:01.105 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
11:45:02.716 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1496949625.
11:45:02.716 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.718 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Preparing: select * from user where id=?
11:45:02.746 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
11:45:02.769 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <== Total: 1
11:45:02.831 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
11:45:02.831 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
11:45:02.831 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
11:45:02.841 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1665197552.
11:45:02.841 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6340e5f0]
11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Preparing: select * from user where id=?
11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
11:45:02.843 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <== Total: 1
11:45:02.843 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.843 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1496949625 to pool.
Process finished with exit code 0
截圖:
如上圖所示的報文裏,就開啓了請求查詢的兩個事務;
本篇說明內容完畢;
三、總結
1、實體類序列化;
2、mybatis.config.xml(或者你自己起的別的名稱)配置文件裏導入settings標籤,配置setting標籤,屬性name爲cacheEnabled,value值爲true;
<!--必須要配置可緩存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
3、具體的mapper.xml文件標籤上配置cache標籤;配置屬性eviction爲FIFO(先入先出),就是優先移除最早的緩存;flushInterval間隔可以自定義設置,設置爲1則每次查詢過後都會清空緩存;readOnly看你業務是否需要只讀與否;
<cache eviction="FIFO" flushInterval="1" size="512" readOnly="true" />
參考博客:
1、JAVABEAN爲什麼需要序列化
2、mybatis 介紹cache和使用cache
3、mybatis 緩存(cache)的使用
4、mybatis入門教程(九)------mybatis緩存
5、mybatis中文文檔
歡迎關注我的
CSDN博客: https://blog.csdn.net/River_Continent
微信公衆號:幕橋社區
知乎:張牧野, https://www.zhihu.com/people/zhang-mu-ye-37-76/activities
簡書: https://www.jianshu.com/u/02c0096cbfd3