SpringBoot+Mybatis環境下如何用Redis做Mybatis的二級快取
mybatis的一級快取和二級快取?
一級快取是SqlSession級別的快取。在操作資料庫時需要構造 sqlSession物件,在物件中有一個(記憶體區域)資料結構(HashMap)用於儲存快取資料。不同的sqlSession之間的快取資料區域(HashMap)是互相不影響的。 一級快取的作用域是同一個SqlSession,在第一個sqlSession執行相同的sql語句後結果放在記憶體中,第二次會從快取中獲取資料將不再從資料庫查詢,從而提高查詢效率。當一個sqlSession結束後該sqlSession中的一級快取也就不存在了。Mybatis預設開啟一級快取。
本地快取不能被關閉
二級快取是mapper級別的快取,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession去操作資料庫得到資料會存在二級快取區域,多個SqlSession可以共用二級快取,二級快取是跨SqlSession的。
二級快取是多個SqlSession共享的,其作用域是mapper的同一個namespace,即在不同的sqlsession中,相同的namespace下,相同的sql語句,並且sql模板中引數也相同的,會命中快取。第一次執行完畢會將資料庫中查詢的資料寫到快取(記憶體),第二次會從快取中獲取資料將不再從資料庫查詢,從而提高查詢效率。
Mybatis預設沒有開啟二級快取需要在setting全域性引數中配置(mybatis-config.xml)開啟二級快取。
在springboot+mybatis整合中非常簡單,但是在加入二級快取時候,我們要考慮mybatis的配置mybatis-config.xml。這個原本是不需要的。
整合步驟
在pom.xml檔案中引入redis依賴
redis客戶端選用Jedis。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.0</version> </dependency>
Redis相關配置如下
#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=15
spring.redis.timeout=0
spring.redis.pool.maxTotal=8
spring.redis.pool.maxWaitMillis=1000
spring.redis.pool.maxIdle=8
spring.redis.pool.minIdle=0
Redis訪問使用的Bean
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private String database;
@Value("${spring.redis.timeout}")
private String timeout;
@Value("${spring.redis.pool.maxTotal}")
private String maxTotal;
@Value("${spring.redis.pool.maxWaitMillis}")
private String maxWaitMillis;
@Value("${spring.redis.pool.maxIdle}")
private String maxIdle;
@Value("${spring.redis.pool.minIdle}")
private String minIdle;
@Bean
public JedisPoolConfig getRedisConfig(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(maxIdle);
config.setMaxTotal(maxTotal);
config.setMaxWaitMillis(maxWaitMillis);
config.setMinIdle(minIdle);
return config;
}
@Bean(name = "jedisConnectionFactory")
public JedisConnectionFactory getConnectionFactory(){
JedisConnectionFactory factory = new JedisConnectionFactory();
JedisPoolConfig config = getRedisConfig();
factory.setPoolConfig(config);
factory.setHostName(host);
factory.setPort(port);
factory.setDatabase(database);
factory.setPassword(password);
factory.setTimeout(timeout);
return factory;
}
@Bean(name = "redisTemplate")
public RedisTemplate<?, ?> getRedisTemplate(){
RedisTemplate<?,?> template = new
StringRedisTemplate(getConnectionFactory());
return template;
}
}
配置Redis作為Mybatis的快取
實現Mybatis的一個介面org.apache.ibatis.cache.Cache,這個介面設計了寫快取,讀快取,銷燬快取的方式,和訪問控制讀寫鎖
public class MybatisRedisCache implements Cache {
private static JedisConnectionFactory jedisConnectionFactory;
private final String id;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public void clear() {
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
connection.flushDb();
connection.flushAll();
} catch (JedisConnectionException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
}
}
}
@Override
public String getId() {
return this.id;
}
@Override
public Object getObject(Object key) {
Object result = null;
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
result = serializer.deserialize(connection.get(serializer.serialize(key)));
} catch (JedisConnectionException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
}
}
return result;
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
@Override
public int getSize() {
int result = 0;
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
result = Integer.valueOf(connection.dbSize().toString());
} catch (JedisConnectionException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
}
}
return result;
}
@Override
public void putObject(Object key, Object value) {
RedisConnection connection = null;
try {
connection = jedisConnectionFactory.getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
connection.set(serializer.serialize(key), serializer.serialize(value));
} catch (JedisConnectionException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
}
}
}
@Override
public Object removeObject(Object key) {
RedisConnection connection = null;
Object result = null;
try {
connection = jedisConnectionFactory.getConnection();
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
result = connection.expire(serializer.serialize(key), 0);
} catch (JedisConnectionException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.close();
}
}
return result;
}
public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
MybatisRedisCache.jedisConnectionFactory = jedisConnectionFactory;
}
}
這個類並不是由Spring虛擬機器管理的類,但是,其中有一個靜態屬性jedisConnectionFactory需要注入一個Spring bean,也就是在RedisConfig中生成的bean。
在一個普通類中使用Spring虛擬機器管理的Bean,一般使用Springboot自省的SpringContextAware。
這裡使用了另一種方式,靜態注入的方式。這個方式是通過RedisCacheTransfer來實現的。
靜態注入
@Component
public class RedisCacheTransfer {
@Autowired
public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
MybatisRedisCache.setJedisConnectionFactory(jedisConnectionFactory);
}
}
RedisCacheTransfer是一個springboot bean,在容器建立之初進行初始化的時候,會注入jedisConnectionFactory bean給setJedisConnectionFactory方法的傳參。
而setJedisConnectionFactory通過呼叫靜態方法設定了類MybatisRedisCache的靜態屬性jedisConnectionFactory。
這樣就把spring容器管理的jedisConnectionFactory注入到了靜態域。
Mybatis二級快取的全域性開關 mybatis-config.xml
<?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="mapUnderscoreToCamelCase" value="true" />
<!-- 這個配置使全域性的對映器啟用或禁用快取。系統預設值是true-->
<setting name="cacheEnabled" value="true" />
<!-- 全域性啟用或禁用延遲載入。當禁用時,所有關聯物件都會即時載入 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 允許或不允許多種結果集從一個單獨的語句中返回(需要適合的驅動)-->
<setting name="multipleResultSetsEnabled" value="true" />
<!--使用列標籤代替列名。不同的驅動在這方便表現不同。參考驅動文件或充分測試兩種方 法來決定所使用的驅動。 系統預設值是true-->
<setting name="useColumnLabel" value="true" />
<!--允許 JDBC 支援生成的鍵。需要適合的驅動。如果設定為 true 則這個設定強制生成的鍵被使用,儘管一些驅動拒絕相容但仍然有效(比如Derby)。 系統預設值是false, -->
<setting name="useGeneratedKeys" value="false" />
<!--配置預設的執行器。SIMPLE 執行器沒有什麼特別之處。REUSE執行器重用預處理語句。BATCH 執行器重用語句和批量更新 系統預設值是SIMPLE -->
<setting name="defaultExecutorType" value="SIMPLE" />
<!--設定超時時間,它決定驅動等待一個數據庫響應的時間。 系統預設值是null,-->
<setting name="defaultStatementTimeout" value="25000" />
</settings>
</configuration>
全域性配置的載入在dataSource中可以是這樣的。
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));
指定了mapper.xml的存放路徑,在mybatis-mapper路徑下,所有後綴是.xml的都會讀入。
bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
指定了mybatis-config.xml的存放路徑,直接放在Resource目錄下即可。
@Bean(name = "moonlightSqlSessionFactory")
@Primary
public SqlSessionFactory moonlightSqlSessionFactory(@Qualifier("moonlightData") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));
bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
return bean.getObject();
}
配置mapper作用域namespace
<mapper namespace="com.kangaroo.studio.moonlight.dao.mapper.MoonlightMapper">
<cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/>
<resultMap id="geoFenceList" type="com.kangaroo.studio.moonlight.dao.model.GeoFence">
<constructor>
<idArg column="id" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="name" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="type" javaType="java.lang.Integer" jdbcType="INTEGER" />
<arg column="group" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="geo" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="createTime" javaType="java.lang.String" jdbcType="VARCHAR" />
<arg column="updateTime" javaType="java.lang.String" jdbcType="VARCHAR" />
</constructor>
</resultMap>
<select id="queryGeoFence" parameterType="com.kangaroo.studio.moonlight.dao.model.GeoFenceQueryParam" resultMap="geoFenceList">
select <include refid="base_column"/> from geoFence where 1=1
<if test="type != null">
and type = #{type}
</if>
<if test="name != null">
and name like concat('%', #{name},'%')
</if>
<if test="group != null">
and `group` like concat('%', #{group},'%')
</if>
<if test="startTime != null">
and createTime >= #{startTime}
</if>
<if test="endTime != null">
and createTime <= #{endTime}
</if>
</select>
</mapper>
namespace下的cache標籤就是載入快取的配置,快取使用的正式我們剛才實現的MybatisRedisCache。
這裡只實現了一個查詢queryGeoFence,你可以在select標籤中,開啟或者關閉這個sql的快取。使用屬性值useCache=true/false。