1. 程式人生 > >spring+ mybatis 二級快取使用 redis作為快取

spring+ mybatis 二級快取使用 redis作為快取

springMybatisConfig.xml配置

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" 
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="  
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd  
            http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd  
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
	default-autowire="byName" default-lazy-init="false">

   <!-- 一、使用druid資料庫連線池註冊資料來源 -->  
  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">  
     <!-- 基礎配置 -->  
     <property name="url" value="${jdbc.url}"></property>  
     <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>  
     <property name="username" value="root"></property>  
     <property name="password" value="password"></property>  
  
     <!-- 關鍵配置 -->  
     <!-- 初始化時建立物理連線的個數。初始化發生在顯示呼叫init方法,或者第一次getConnection時 -->   
     <property name="initialSize" value="3" />   
     <!-- 最小連線池數量 -->   
     <property name="minIdle" value="2" />   
     <!-- 最大連線池數量 -->   
     <property name="maxActive" value="15" />  
     <!-- 配置獲取連線等待超時的時間 -->   
     <property name="maxWait" value="10000" />  
  
     <!-- 效能配置 -->  
     <!-- 開啟PSCache,並且指定每個連線上PSCache的大小 -->   
     <property name="poolPreparedStatements" value="true" />   
     <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />  
  
     <!-- 其他配置 -->  
     <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 -->   
     <property name="timeBetweenEvictionRunsMillis" value="60000" />  
     <!-- 配置一個連線在池中最小生存的時間,單位是毫秒 -->   
     <property name="minEvictableIdleTimeMillis" value="300000" />  
     <!--   建議配置為true,不影響效能,並且保證安全性。申請連線的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,  
               執行validationQuery檢測連線是否有效。 -->  
     <property name="testWhileIdle" value="true" />  
     <!-- 這裡建議配置為TRUE,防止取到的連線不可用 ,申請連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能。-->   
     <property name="testOnBorrow" value="true" />   
     <!-- 歸還連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能 -->  
     <property name="testOnReturn" value="false" />  
  </bean>  

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!--dataSource屬性指定要用到的連線池-->
		<property name="dataSource" ref="dataSource" />
        <!-- 開啟快取支援 -->
        <property name="configurationProperties">
            <props>
           		 <!--全域性對映器啟用快取-->
                <prop key="cacheEnabled">true</prop>
                <!-- 查詢時,關閉關聯物件即時載入以提高效能 -->
                <prop key="lazyLoadingEnabled">false</prop>
                <!-- 設定關聯物件載入的形態,此處為按需載入欄位(載入欄位由SQL指定),不會載入關聯表的所有欄位,以提高效能 -->
                <prop key="aggressiveLazyLoading">true</prop>
                <!-- 對於未知的SQL查詢,允許返回不同的結果集以達到通用的效果 -->
                <prop key="multipleResultSetsEnabled">true</prop>
                <!-- 允許使用列標籤代替列名 -->
                <prop key="useColumnLabel">true</prop>
                <!-- 允許使用自定義的主鍵值(比如由程式生成的UUID 32位編碼作為鍵值),資料表的PK生成策略將被覆蓋 -->
                <prop key="useGeneratedKeys">true</prop>
                <!-- 給予被巢狀的resultMap以欄位-屬性的對映支援 -->
                <prop key="autoMappingBehavior">FULL</prop>
                <!-- 對於批量更新操作快取SQL以提高效能 -->
                <prop key="defaultExecutorType">BATCH</prop>
                <!-- 資料庫超過25000秒仍未響應則超時 -->
                <prop key="defaultStatementTimeout">25000</prop>
            </props>
        </property>
		 <!-- 自動掃描mapping.xml檔案 -->
        <property name="mapperLocations" value="classpath:com/dg/mapping/*.xml"></property>
	</bean>

	<!-- (事務管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
	
</beans> 

springRedisConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd     
    http://www.springframework.org/schema/context     
    http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/cache
    http://www.springframework.org/schema/cache/spring-cache-4.3.xsd"
	>

	<!-- redis資料來源 -->
	<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<!-- 最大空閒數 -->
		<property name="maxIdle" value="400" />
		<!-- 最大空連線數 -->
		<property name="maxTotal" value="6000" />
		<!-- 最大等待時間 -->
		<property name="maxWaitMillis" value="1000" />
		<!-- 連線超時時是否阻塞,false時報異常,ture阻塞直到超時, 預設true -->
		<property name="blockWhenExhausted" value="true" />
		<!-- 返回連線時,檢測連線是否成功 -->
		<property name="testOnBorrow" value="true" />
	</bean>

	<!-- Spring-redis連線池管理工廠 -->
	<bean id="jedisConnectionFactory"
		class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
		<!-- IP地址 -->
		<property name="hostName" value="127.0.0.1" />
		<!-- 埠號 -->
		<property name="port" value="6379" />
		<!-- 超時時間 預設2000 -->
		<property name="timeout" value="30000" />
		<!-- 連線池配置引用 -->
		<property name="poolConfig" ref="poolConfig" />
		<!-- usePool:是否使用連線池 -->
		<property name="usePool" value="true" />
	</bean>

	<!-- redis template definition -->
	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory" />
		<property name="keySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="valueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
		</property>
		<property name="hashKeySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer" />
		</property>
		<property name="hashValueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
		</property>
		<!--開啟事務 -->
		<property name="enableTransactionSupport" value="true"></property>
	</bean>
	
	<!-- 已下不是實現mybatis的快取介面 實現spring caches介面的快取配置 -->
    <!--使用實現spring caches的快取方案 快取管理器-->
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <!--自定義的redis快取操作實現-->
                <bean class="com.redisCache.RedisCache">
                    <property name="name" value="myCache"/>
                    <property name="redisTemplate" ref="redisTemplate"/>
                </bean>
            </set>
        </property>
    </bean>
    <!--spring 啟用快取註解-->
 	<cache:annotation-driven cache-manager="cacheManager" />  
 	
</beans>


通大多數ORM層框架一樣,Mybatis自然也提供了對一級快取和二級快取的支援。一下是一級快取和二級快取的作用於和定義。

      1、一級快取是SqlSession級別的快取。在操作資料庫時需要構造 sqlSession物件,在物件中有一個(記憶體區域)資料結構(HashMap)用於儲存快取資料。不同的sqlSession之間的快取資料區域(HashMap)是互相不影響的。

二級快取是mapper級別的快取,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession去操作資料庫得到資料會存在二級快取區域,多個SqlSession可以共用二級快取,二級快取是跨SqlSession的。

 

      2、一級快取的作用域是同一個SqlSession,在同一個sqlSession中兩次執行相同的sql語句,第一次執行完畢會將資料庫中查詢的資料寫到快取(記憶體),第二次會從快取中獲取資料將不再從資料庫查詢,從而提高查詢效率。當一個sqlSession結束後該sqlSession中的一級快取也就不存在了。Mybatis預設開啟一級快取。

      二級快取是多個SqlSession共享的,其作用域是mapper的同一個namespace,不同的sqlSession兩次執行相同namespace下的sql語句且向sql中傳遞引數也相同即最終執行相同的sql語句,第一次執行完畢會將資料庫中查詢的資料寫到快取(記憶體),第二次會從快取中獲取資料將不再從資料庫查詢,從而提高查詢效率。Mybatis預設沒有開啟二級快取需要在setting全域性引數中配置開啟二級快取。

      一般的我們將Mybatis和Spring整合時,mybatis-spring包會自動分裝sqlSession,而Spring通過動態代理sqlSessionProxy使用一個模板方法封裝了select()等操作,每一次select()查詢都會自動先執行openSession(),執行完close()以後呼叫close()方法,相當於生成了一個新的session例項,所以我們無需手動的去關閉這個session(),當然也無法使用mybatis的一級快取,也就是說mybatis的一級快取在spring中是沒有作用的。

      因此我們一般在專案中實現Mybatis的二級快取,雖然Mybatis自帶二級快取功能,但是如果實在叢集環境下,使用自帶的二級快取只是針對單個的節點,所以我們採用分散式的二級快取功能。一般的快取NoSql資料庫如redis,Mancache等,或者EhCache都可以實現,從而更好地服務tomcat叢集中ORM的查詢。

下面主要通過Redis實現Mybatis的二級快取功能。

1、配置檔案中開啟二級快取

[html]  view plain  copy
  1. <setting name="cacheEnabled" value="true"/>  

2、實現Mybatis的Cache介面

package com.redisCache;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.DigestUtils;

public class RedisCache implements Cache {

	private static Logger logger = LoggerFactory.getLogger(RedisCache1.class);

	/** The ReadWriteLock. */
	private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	public final long liveTime = 86400;
	private final String COMMON_CACHE_KEY = "COM:";

	private String id;
	private RedisTemplate<String, Object> redisTemplate;
	private ApplicationContext context;


	public RedisCache() {

	}

	public RedisCache(final String id) {
		if (id == null) {
			throw new IllegalArgumentException("必須傳入ID");
		}
		//springBean 獲取配置好的 RedisTemplate
		context = new ClassPathXmlApplicationContext("spring-redis.xml");
		redisTemplate =(RedisTemplate)context.getBean("redisTemplate");
		logger.debug(">>>>>>>>>>>>>>>>>>>>>MybatisRedisCache:id=" + id);
		this.id = id;
	}

	private String getKey(Object key) {
		StringBuilder accum = new StringBuilder();
		accum.append(COMMON_CACHE_KEY);
		accum.append(this.id).append(":");
		accum.append(DigestUtils.md5DigestAsHex(String.valueOf(key).getBytes()));
		return accum.toString();
	}

	/**
	 * redis key規則字首
	 */
	private String getKeys() {
		return COMMON_CACHE_KEY + this.id + ":*";
	}

	@Override
	public String getId() {
		return id;
	}

	@Override
	public void putObject(Object key, Object value) {
		final String keyf = getKey(key);
		final Object valuef = value;
		redisTemplate.execute(new RedisCallback<Long>() {
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] keyb = keyf.getBytes();
				byte[] valueb = SerializeUtil.serialize(valuef);
				connection.set(keyb, valueb);
				logger.debug("新增快取 key:--------" + keyf + " liveTime seconds: " + liveTime);
				if (liveTime > 0) {
					connection.expire(keyb, liveTime);
				}
				return 1L;
			}
		});

	}

	@Override
	public Object getObject(Object key) {
		final String keyf = getKey(key);
		Object object = null;
		object = redisTemplate.execute(new RedisCallback<Object>() {
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] k = keyf.getBytes();
				byte[] value = connection.get(k);
				if (value == null) {
					return null;
				}
				return SerializeUtil.unserialize(value);
			}
		});
		return object;
	}

	@Override
	public Object removeObject(Object key) {
		final String keyf = getKey(key);
		Object object = null;
		object = redisTemplate.execute(new RedisCallback<Long>() {
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				logger.debug("移除快取 key:--------" + keyf);
				return connection.del(keyf.getBytes());
			}
		});
		return object;
	}

	@Override
	public void clear() {
		redisTemplate.execute(new RedisCallback<Integer>() {
			@Override
			public Integer doInRedis(RedisConnection connection) throws DataAccessException {
				Set<byte[]> keys = connection.keys(getKeys().getBytes());
				int num = 0;
				if (null != keys && !keys.isEmpty()) {
					num = keys.size();
					for(byte[] k:keys) {
						connection.decr(k);
					}
				}
				logger.debug("刪除所有Key字首為 " + getKeys() + "的數目:" + num);
				return 1;
			}

		});

	}

	@Override
	public int getSize() {
		Object object = null;
		object = redisTemplate.execute(new RedisCallback<Integer>() {
			@Override
			public Integer doInRedis(RedisConnection connection) throws DataAccessException {
				Set<byte[]> keys = connection.keys(getKeys().getBytes());
				int num = 0;
				if (null != keys && !keys.isEmpty()) {
					num = keys.size();
				}
				logger.debug("查詢所有Key字首為 " + getKeys() + "的數目:" + num);
				return num;
			}

		});
		return ((Integer) object).intValue();
	}

	@Override
	public ReadWriteLock getReadWriteLock() {
		// TODO Auto-generated method stub
		return readWriteLock;
	}

	public RedisTemplate<String, Object> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	public void setId(String id) {
		this.id = id;
	}

	public static class SerializeUtil {
		public static byte[] serialize(Object object) {
			ObjectOutputStream oos = null;
			ByteArrayOutputStream baos = null;
			try {
				// 序列化
				baos = new ByteArrayOutputStream();
				oos = new ObjectOutputStream(baos);
				oos.writeObject(object);
				byte[] bytes = baos.toByteArray();
				return bytes;
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}

		public static Object unserialize(byte[] bytes) {
			if (bytes == null)
				return null;
			ByteArrayInputStream bais = null;
			try {
				// 反序列化
				bais = new ByteArrayInputStream(bytes);
				ObjectInputStream ois = new ObjectInputStream(bais);
				return ois.readObject();
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}
	}

}

3、二級快取的實用

我們需要將所有的實體類進行序列化,然後在Mapper中新增自定義cache功能。自定義的快取類

	<cache eviction="LRU" type="com.RedisCache.RedisCache" />


Mapper XML檔案配置支援cache後,檔案中所有的Mapper statement就支援了。此時要個別對待某條,需要:
<select id="inetAton" parameterType="string" resultType="integer" useCache=“false”>  
select inet_aton(#{name})
</select>

看如下例子,即一個常用的cache標籤屬性:

<cache 
eviction="FIFO"  <!--回收策略為先進先出-->
flushInterval="60000" <!--自動重新整理時間60s-->
size="512" <!--最多快取512個引用物件-->
readOnly="true"/> <!--只讀-->
  • 1
  • 2
  • 3
  • 4
  • 5

eviction(回收策略)

  1. LRU – 最近最少使用的:移除最長時間不被使用的物件。(預設的屬性)

  2. FIFO – 先進先出:按物件進入快取的順序來移除它們。

  3. SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。

  4. WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的物件。

flushInterval(重新整理間隔)

可以被設定為任意的正整數,而且它們代表一個合理的毫秒形式的時間段。預設情況是不設定,也就是沒有重新整理間隔,快取僅僅呼叫語句時重新整理。

size(引用數目)

可以被設定為任意正整數,要記住你快取的物件數目和你執行環境的可用記憶體資源數目。預設值是1024。

readOnly(只讀)

可以被設定為true或false。只讀的快取會給所有呼叫者返回快取物件的相同例項。因此這些物件不能被修改。這提供了很重要的效能優勢。可讀寫的快取會返回快取物件的拷貝(通過序列化)。這會慢一些,但是安全,因此預設是false。



二、注意的幾個細節
1、如果readOnly為false,此時要結果集物件是可序列化的。
<cache readOnly="false"/>

2、在SqlSession未關閉之前,如果對於同樣條件進行重複查詢,此時採用的是local session cache,而不是上面說的這些cache。

3、MyBatis快取查詢到的結果集物件,而非結果集資料,是將對映的PO物件集合快取起來。


快取使用的註釋方法:

  1. //配置快取  
  2. @CacheNamespace(size=100,eviction=LruCache.class,implementation=org.mybatis.caches.ehcache.EhcacheCache.class)  
  3.   
  4. public interface IUsersMapper {  
  5.     //開啟快取  
  6.     @Options(useCache=true)  
  7.     @Select("select * from users")  
  8.     public List<Map> findAll();  
  9.       
  10.     @Select("select count(1) from users where userName=#{userName}")  
  11.     public int findById(@Param("userName") String userName);  

  12.     //儲存過程  
  13.     @Select("call pp11()")  
  14.     public List<Map> findAll_a();  
  15. }  










當時使用 實現 org.springframework.cache.Cache;

在上面吧這個快取管理交給 org.springframework.cache.support.SimpleCacheManager 來管理 啟用註釋即可
package com.redisCache;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.concurrent.Callable;

import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

public class RedisSpringCache implements Cache {

	private RedisTemplate<String, Object> redisTemplate;
	private String name;

	public RedisTemplate<String, Object> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return this.name;
	}

	@Override
	public Object getNativeCache() {
		// TODO Auto-generated method stub
		return this.redisTemplate;
	}

	@Override
	public ValueWrapper get(Object key) {
		// TODO Auto-generated method stub
		System.out.println("get key");
		final String keyf = key.toString();
		Object object = null;
		object = redisTemplate.execute(new RedisCallback<Object>() {
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] key = keyf.getBytes();
				byte[] value = connection.get(key);
				if (value == null) {
					return null;
				}
				return toObject(value);
			}
		});
		return (object != null ? new SimpleValueWrapper(object) : null);
	}

	@Override
	public void put(Object key, Object value) {
		// TODO Auto-generated method stub
		System.out.println("put key");
		final String keyf = key.toString();
		final Object valuef = value;
		final long liveTime = 86400;
		redisTemplate.execute(new RedisCallback<Long>() {
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] keyb = keyf.getBytes();
				byte[] valueb = toByteArray(valuef);
				connection.set(keyb, valueb);
				if (liveTime > 0) {
					connection.expire(keyb, liveTime);
				}
				return 1L;
			}
		});
	}

	private byte[] toByteArray(Object obj) {
		byte[] bytes = null;
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(obj);
			oos.flush();
			bytes = bos.toByteArray();
			oos.close();
			bos.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return bytes;
	}

	private Object toObject(byte[] bytes) {
		Object obj = null;
		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
			ObjectInputStream ois = new ObjectInputStream(bis);
			obj = ois.readObject();
			ois.close();
			bis.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		} catch (ClassNotFoundException ex) {
			ex.printStackTrace();
		}
		return obj;
	}

	@Override
	public void evict(Object key) {
		// TODO Auto-generated method stub
		System.out.println("del key");
		final String keyf = key.toString();
		redisTemplate.execute(new RedisCallback<Long>() {
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				return connection.del(keyf.getBytes());
			}
		});
	}

	@Override
	public void clear() {
		// TODO Auto-generated method stub
		System.out.println("clear key");
		redisTemplate.execute(new RedisCallback<String>() {
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				connection.flushDb();
				return "ok";
			}
		});
	}

	@Override
	public <T> T get(Object key, Class<T> type) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public ValueWrapper putIfAbsent(Object key, Object value) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
		// TODO Auto-generated method stub
		return null;
	}

}

附上:轉載的sprigcache的使用: https://blog.csdn.net/u011202334/article/details/61923172


記錄下自己專案在用的Spring Cache的使用方式。
Spring的抽象已經做得夠好了,適合於大多數場景,非常複雜的就需要自己AOP實現了。
Spring官網的文件挺不錯的,但是對Cache這塊的介紹不是很詳細,結合網上大牛的博文,彙總下文。

快取概念

快取簡介

快取,我的理解是:讓資料更接近於使用者;工作機制是:先從快取中讀取資料,如果沒有再從慢速裝置上讀取實際資料(資料也會存入快取);快取什麼:那些經常讀取且不經常修改的資料/那些昂貴(CPU/IO)的且對於相同的請求有相同的計算結果的資料。如CPU—L1/L2—記憶體—磁碟就是一個典型的例子,CPU需要資料時先從 L1/L2中讀取,如果沒有到記憶體中找,如果還沒有會到磁碟上找。還有如用過Maven的朋友都應該知道,我們找依賴的時候,先從本機倉庫找,再從本地伺服器倉庫找,最後到遠端倉庫伺服器找;還有如京東的物流為什麼那麼快?他們在各個地都有分倉庫,如果該倉庫有貨物那麼送貨的速度是非常快的。

快取命中率

即從快取中讀取資料的次數 與 總讀取次數的比率,命中率越高越好:
命中率 = 從快取中讀取次數 / (總讀取次數[從快取中讀取次數 + 從慢速裝置上讀取的次數])
Miss率 = 沒有從快取中讀取的次數 / (總讀取次數[從快取中讀取次數 + 從慢速裝置上讀取的次數])

這是一個非常重要的監控指標,如果做快取一定要健康這個指標來看快取是否工作良好;

快取策略

Eviction policy

移除策略,即如果快取滿了,從快取中移除資料的策略;常見的有LFU、LRU、FIFO:

  • FIFO(First In First Out):先進先出演算法,即先放入快取的先被移除;
  • LRU(Least Recently Used):最久未使用演算法,使用時間距離現在最久的那個被移除;
  • LFU(Least Frequently Used):最近最少使用演算法,一定時間段內使用次數(頻率)最少的那個被移除;

TTL(Time To Live )

存活期,即從快取中建立時間點開始直到它到期的一個時間段(不管在這個時間段內有沒有訪問都將過期)

TTI(Time To Idle)

空閒期,即一個數據多久沒被訪問將從快取中移除的時間。

到此,基本瞭解了快取的知識,在Java中,我們一般對呼叫方法進行快取控制,比如我呼叫”findUserById(Long id)”,那麼我應該在呼叫這個方法之前先從快取中查詢有沒有,如果沒有再掉該方法如從資料庫載入使用者,然後新增到快取中,下次呼叫時將會從快取中獲取到資料。

自Spring 3.1起,提供了類似於@Transactional註解事務的註解Cache支援,且提供了Cache抽象;在此之前一般通過AOP實現;使用Spring Cache的好處:

  • 提供基本的Cache抽象,方便切換各種底層Cache;
  • 通過註解Cache可以實現類似於事務一樣,快取邏輯透明的應用到我們的業務程式碼上,且只需要更少的程式碼就可以完成;
  • 提供事務回滾時也自動回滾快取;
  • 支援比較複雜的快取邏輯;

對於Spring Cache抽象,主要從以下幾個方面學習:

  • Cache API及預設提供的實現
  • Cache註解
  • 實現複雜的Cache邏輯
快取簡介 開濤的部落格

Spring Cache簡介

Spring3.1開始引入了激動人心的基於註釋(annotation)的快取(cache)技術,它本質上不是一個具體的快取實現方案(例如EHCache 或者 OSCache),而是一個對快取使用的抽象,通過在既有程式碼中新增少量它定義的各種 annotation,即能夠達到快取方法的返回物件的效果。

Spring的快取技術還具備相當的靈活性,不僅能夠使用 SpEL(Spring Expression Language)來定義快取的key和各種condition,還提供開箱即用的快取臨時儲存方案,也支援和主流的專業快取例如EHCache、 memcached整合。

其特點總結如下:

  • 通過少量的配置 annotation 註釋即可使得既有程式碼支援快取
  • 支援開箱即用 Out-Of-The-Box,即不用安裝和部署額外第三方元件即可使用快取
  • 支援 Spring Express Language,能使用物件的任何屬性或者方法來定義快取的 key 和 condition
  • 支援 AspectJ,並通過其實現任何方法的快取支援
  • 支援自定義 key 和自定義快取管理者,具有相當的靈活性和擴充套件性
Spring Cache 介紹 Spring Cache 介紹 - Rollen Holt - 部落格園

API介紹

Cache介面

理解這個介面有助於我們實現自己的快取管理器

[java]  view plain  copy
  1. package org.springframework.cache;  
  2.   
  3. public interface Cache {  
  4.   
  5.     /** 
  6.      * 快取的名字 
  7.      */  
  8.     String getName();  
  9.   
  10.     /** 
  11.      * 得到底層使用的快取 
  12.      */  
  13.     Object getNativeCache();  
  14.   
  15.     /** 
  16.      * 根據key得到一個ValueWrapper,然後呼叫其get方法獲取值  
  17. 相關推薦

    spring+ mybatis 二級快取使用 redis作為快取

    springMybatisConfig.xml配置 <?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="ht

    spring,springmvc,mybatis整合redisredis作為快取使用

    環境 1,windows7 2,mysql 3,eclipse 4,redis 5,tomcat7 注意:啟動redis的方式,已經把redis做成windows服務,以windows服務的方式啟動 把redis做成windows服務的命令列 redis-s

    mybatis plus使用redis作為二級快取

    建議快取放到 service 層,你可以自定義自己的 BaseServiceImpl 重寫註解父類方法,繼承自己的實現。為了方便,這裡我們將快取放到mapper層。mybatis-plus整合redis作為二級快取與mybatis整合redis略有不同。 1. mybatis-plus開啟二級快取 mybat

    REDIS學習(3.2)spring boot 使用redis作為快取

    一,指定主鍵的生成規則 在3.1的基礎上修改RedisConfig @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport {     @Be

    spring-boot整合redis作為快取(3)——自定義key

            分幾篇文章總結spring-boot與Redis的整合         4、自定義key         5、spring-boot引入Redis         在上一篇文章中說道key是用來分辨同一個快取中的快取資料的。key是可以自己制定的,也

    從零搭建Spring Boot腳手架(6):整合Redis作為快取

    ![](https://img2020.cnblogs.com/other/1739473/202008/1739473-20200819095515507-188579634.png) ## 1. 前言 [上一文](https://mp.weixin.qq.com/s/9uVsi9yfE0QheEKCU

    Redis作為快取實現工具類

    使用Redis作為快取物件,常用的儲存格式為字串,所以在儲存快取時,將物件轉為字串儲存.由於存的時候為字串,所以取出的也為json字串. 此工具類在設值時只需要將key與物件傳入即可 取值時只需要將key與要取的物件型別傳入即可 public class CacheUtilImpl im

    讀取大檔案資料進入redis作為快取:贈(廣播變數)

    在專案中使用Redis做快取檔案(目的等同於廣播變數): package com.app import com.utils.{JedisConnectionPool, RptUtils} import org.apache.commons.lang.StringUtils import

    Spring boot如何使用redis快取快取註解的用法總結

    1. 概述 本文介紹Spring boot 如何使用redis做快取,如何對redis快取進行定製化配置(如key的有效期)以及spring boot 如

    SpringBoot 2.x 使用Redis作為快取 設定有效時間

    redis 配置 redis: database: 0 host: localhost port: 6379 password: jedis: pool: max-active: 8 max-wait:

    Spring boot中使用Redis實現快取功能

    1. Redis簡介 Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。 Redis 可以儲存鍵與5種不同資料結構型別之間的對映,這5種資料結構型別分別為String(

    springboot2+shiro+jwt整合(二)細粒度許可權控制+使用redis作為快取

    簡單來說,當專案啟動起來後,我們的後臺介面的許可權控制就應該起作用了,那麼如何使用shiro來實現呢?我這裡使用的是 如何使用註解來配置細粒度許可權。 首先,shiro預設不支援使用註解方式,需要在ShiroConfig中新增以下程式碼 /** * 下面

    Shiro使用redis作為快取(解決shiro頻繁訪問Redis)(十一)

    之前寫過一篇部落格,使用的一個開源專案,實現了redis作為快取 快取使用者的許可權 和 session資訊,還有兩個功能沒有修改,一個是使用者併發登入限制,一個是使用者密碼錯誤次數.本篇中幾個類 也是使用的開源專案中的類,只不過是拿出來了,redis單獨做

    springboot配置redis作為快取空間

    package com.ty.tyzxtj.util; import java.io.Serializable; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.sprin

    redis作為快取的簡單理解

    來源:http://doushini.iteye.com/blog/1879616?utm_source=tuicool&utm_medium=referral Redis快取伺服器筆記 redis是一個高效能的key-value儲存系統,能夠作為快取框架和佇列

    springboot2.x使用redis作為快取(使用fastjson序列化的方式,並除錯反序列化異常

    1.redis是記憶體資料庫,可以單獨作為資料庫(有持久化方案),也可以作為快取(一般為MySQL搭配)        1.1 可以通過jedis,程式碼的方式手動將其傳入redis作為快取;        1.2 也可以通過註解的方式,和spring boo

    nodejs使用redis作為快取介質,封裝快取

    // cache.js const redis = require('redis'); const config = require('config'); const logger = require('winston'); const redisObj = { client: null,

    大型分布式項目項目實戰Springmvc+Spring+Mybatis+Maven+CMS+Redis+Solr+Linux+Nginx+單點登錄、分布式緩存、負載均衡視頻課程

    edi mina img solr 技術 性能提升 登錄 rom nginx * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架構師、集群、高可用、高可擴 展、高性能、高

    redis作為mybatis二級快取

    redis作為二級快取伺服器,來替代mybatis的二級快取,至於二級快取有什麼缺點我想大家都懂吧,   [service] 2016-08-31 21:01:32,912 - com.erp.dao.TestMybatisMapper.selectByPrimaryKey -19

    Spring Boot + Mybatis + 二級快取例項(Ehcache,Redis

    使用Mybatis自帶二級快取 MyBatis 包含一個非常強大的查詢快取特性,它可以非常方便地配置和定製。MyBatis 3 中的快取實現的很多改進都已經實現了,使得它更加強大而且易於配置。 預設情況下是沒有開啟快取的,除了區域性的 session 快取,可以增強