spring+ mybatis 二級緩衝使用 redis作為緩衝

來源:互聯網
上載者:User

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"><beanclass="org.springframework.data.redis.serializer.StringRedisSerializer" /></property><property name="valueSerializer"><beanclass="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /></property><property name="hashKeySerializer"><beanclass="org.springframework.data.redis.serializer.StringRedisSerializer" /></property><property name="hashValueSerializer"><beanclass="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 <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 擷取配置好的 RedisTemplatecontext = 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 + ":*";}@Overridepublic String getId() {return id;}@Overridepublic 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;}});}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic void clear() {redisTemplate.execute(new RedisCallback<Integer>() {@Overridepublic 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;}});}@Overridepublic int getSize() {Object object = null;object = redisTemplate.execute(new RedisCallback<Integer>() {@Overridepublic 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();}@Overridepublic ReadWriteLock getReadWriteLock() {// TODO Auto-generated method stubreturn 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(回收策略)

LRU – 最近最少使用的:移除最長時間不被使用的對象。(預設的屬性)

FIFO – 先進先出:按對象進入緩衝的順序來移除它們。

SOFT – 軟引用:移除基於記憶體回收行程狀態和軟引用規則的對象。

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

flushInterval(重新整理間隔)

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

size(引用數目)

可以被設定為任意正整數,要記住你緩衝的對象數目和你運行環境的可用記憶體資源數目。預設值是1024。

readOnly(唯讀)

可以被設定為true或false。唯讀緩衝會給所有調用者返回緩衝對象的相同執行個體。因此這些對象不能被修改。這提供了很重要的效能優勢。可讀寫的緩衝會返回緩衝對象的拷貝(通過序列化)。這會慢一些,但是安全,因此預設是false。



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

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

3、MyBatis緩衝查詢到的結果集對象,而非結果集資料,是將映射的PO對象集合緩衝起來。


緩衝使用的注釋方法:

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.