1. 程式人生 > >redis + spring + hibernate

redis + spring + hibernate

概述

        今天使用redis鎖犯了一個低階的錯誤,鎖的時間設定太短,導致多執行緒跑時,造成對資料的重複處理,對自己編碼習慣做一個深刻的反思,使用redis這種實時儲存的服務,儲存要考慮容量,速度,安全,但是在實時方面,過期時間應該是一個很重要的引數,不應忽略.值此反思之際,順便對redis認知作一個簡陋的總結.

        redis是一個key_value的儲存系統,支援儲存的型別包括string,list,set,zset,hash,對資料的操作有push/pop,add/remove以及交併差集等,重要的一點,這些操作多事原子性.今天主要整理的是redis與spring的整合以及使用.

        spring整合redis(spring-redis-data),現在以常用的模板使用為例.其整合的思路跟之前spring整合hibernate很相似,redis對應資料庫,配置中有池,有工廠,模板內封裝有連線.

spring整合redis 

搭建環境

       環境:可以正常訪問的redis伺服器+spring4.3.4.RELEASE

       redis服務的簡單搭建,用於測試,windows下,搭建簡單redis

   下載,選擇對應的32位或64,我的是64位,cmd視窗下進入64bit的資料夾,執行redis-server.exe redis.conf便可預設啟動redis,測試redis安裝是否成功,雙擊redis-cli.exe,進入新的cmd視窗,插入一條記錄set hello hi,查詢get hello,得到"hi",安裝成功

pom.xml

	<dependencies>
	
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<!--spring整合redis,模板.工廠.連線池等  -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.8.0.RELEASE</version>
		</dependency>
		
		<!--spring測試-->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.3.4.RELEASE</version>
		</dependency>

		<!--redis客戶端,連線redis-->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.9.0</version>
		</dependency>

	</dependencies>  
      這裡有個地方注意,spring-test 版本與junit版本要匹配,不然會測試會報錯

redis.properties

#訪問地址  
redis.host=localhost  
#訪問埠  
redis.port=6379  
#注意,如果沒有password,此處不設定值,但這一項要保留  
redis.password=
  
#最大空閒數,資料庫連線的最大空閒時間。超過空閒時間,資料庫連線將被標記為不可用,然後被釋放。設為0表示無限制。  
redis.maxIdle=100 
#最小空閒數
redis.minIdle=10
#連線池的最大資料庫連線數。設為0表示無限制  
redis.maxActive=600  
#最大建立連線等待時間。如果超過此時間將接到異常。設為-1表示無限制。  
redis.maxWait=1000  
#在borrow一個jedis例項時,是否提前進行alidate操作;如果為true,則得到的jedis例項均是可用的;  
redis.testOnBorrow=true  

spring-redis.xml

    <!--ignore-unresolvable=“true" 設定true找不到不會報錯,預設false-->  
    <context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="false"/>  
  
    <!-- redis連線池 -->  
    <bean id="jedisConfig" class="redis.clients.jedis.JedisPoolConfig">  
    <!--以下所有的引數配置可以在JedisPoolConfig中查詢,都有其相應的預設值-->  
        <property name="maxTotal" value="${redis.maxActive}"></property>  
        <property name="minIdle" value="${redis.minIdle}" />
        <property name="maxIdle" value="${redis.maxIdle}"></property>  
        <property name="maxWaitMillis" value="${redis.maxWait}"></property>  
        <property name="testOnBorrow" value="${redis.testOnBorrow}"></property>  
    </bean>  
    
    <!-- redis連線工廠 -->  
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">  
        <property name="hostName" value="${redis.host}"></property>  
        <property name="port" value="${redis.port}"></property>  
        <property name="password" value="${redis.password}"></property>  
        <property name="poolConfig" ref="jedisConfig"></property>  
    </bean>  
    
    <!-- redis操作模板,這裡採用儘量面向物件的模板 -->  
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">  
        <property name="connectionFactory" ref="connectionFactory"/>  
        <!--     如果不配置Serializer,那麼儲存的時候只能使用String,如果用物件型別儲存,那麼會提示錯誤 can't cast to String!!!-->  
        <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="enableTransactionSupport" value="true"/>  
    </bean>  

        這裡稍微理解一下幾個系列化,你可以制定key或者value序列化機制

               StringRedisSerializer :字串

              JdkSerializationRedisSerializer: jdk序列化(一些jdk物件的序列化,Boolean,Date等)

              Jackson2JsonRedisSerializer:jons系列化

       redis核心,key-value,key 一般為字串,而value卻支援五種型別,可以理解為五種物件,String,List,Hash,Set,Zset一個物件的結構如下:

/* 
 * Redis 物件 
 */  
typedef struct redisObject {  
  
    // 型別  
    unsigned type:4;          
  
    // 不使用(對齊位)  
    unsigned notused:2;  
  
    // 編碼方式  
    unsigned encoding:4;  
  
    // LRU 時間(相對於 server.lruclock)  
    unsigned lru:22;  
  
    // 引用計數  
    int refcount;  
  
    // 指向物件的值  
    void *ptr;  
  
} robj;  
我比較關心其中的編碼,一個物件,底層資料結構的實現可能不止一種,看看編碼常量與資料結構對應關係


         String用的比較多,主要以String為例,String 對應int,raw額embstr(3.0之後),當字元可以轉為long,則會被轉為long型別,對應編碼常量為int,對於一般的普通字串,長度小於39位元組(REDIS_ENCODING_EMBSTR_SIZE_LIMIT),用embstr,否則用raw,raw與embstr區別就不細說.(object encoding key 可以檢視資料編碼)

        java通過jedis實現與redis互動,jedis通過Tcp協議與redis以位元組流的形式進行資料傳輸(redis命令根據其通訊協議轉為相應的資料格式),也就是無論是key或者value都要進行在客戶端轉為位元組位元組陣列(因此,序列化是很重要的一環),然後傳遞給redis處理,可以理解為key或者value都是位元組陣列的形式傳遞到redis伺服器,redis根據指定的資料型別進行儲存(二者都是在應用層進行資料轉換,編碼).我們使用redis-cli 客戶端檢視都是字元展示,但是中文是不能正確顯示的,需要進行相應的編碼設定(--raw或--embstr).

        回到我們上面的序列化,很重要,但使用很簡單,其內部有兩個方法serialize(序列化),deserialize(反序列化),set類的命令呼叫序列化成位元組陣列,get反序列化成String(或json或jdk物件).

測試

測試,已StringRedisTemplate版本為例,順便重溫一下redis的用法

import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-redis.xml"})  
public class TestSpringRedis {
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Test  
    public void testSpringRedis() {
        //stringRedisTemplate的基本操作
		
        // String讀寫  (redis不區分大小寫)
        redisTemplate.delete("myStr");  //del
        redisTemplate.opsForValue().set("myStr", "skyLine"); //set
        System.out.println(redisTemplate.opsForValue().get("myStr"));  //get
        System.out.println("---------------");  
        //列印skyLine
  
        // List讀寫  
        redisTemplate.delete("myList");  
        redisTemplate.opsForList().rightPush("myList", "T");//rpush
        redisTemplate.opsForList().rightPush("myList", "L");//rpush
        redisTemplate.opsForList().leftPush("myList", "A");//lpush
        List<String> listCache = redisTemplate.opsForList().range(  
                "myList", 0, -1);  
        for (String s : listCache) {  
            System.out.println(s);  
        }  
        System.out.println("---------------");  
        //列印A T L
  
        // Set讀寫  
        redisTemplate.delete("mySet");  
        redisTemplate.opsForSet().add("mySet", "A");//sadd
        redisTemplate.opsForSet().add("mySet", "B");  
        redisTemplate.opsForSet().add("mySet", "C");  
        Set<String> setCache = redisTemplate.opsForSet().members(  //smembers 
                "mySet");  
        for (String s : setCache) {  
            System.out.println(s);  
        }  
        System.out.println("---------------");  
        //列印A B C
  
        // Hash讀寫  
        redisTemplate.delete("myHash");  
        redisTemplate.opsForHash().put("myHash", "BJ", "北京");//hset
        redisTemplate.opsForHash().put("myHash", "SH", "上海");  
        redisTemplate.opsForHash().put("myHash", "HN", "河南");  
        Map<Object, Object> hashCache = redisTemplate.opsForHash().entries("myHash");
        
        for (Map.Entry entry : hashCache.entrySet()) {  
            System.out.println(entry.getKey() + " - " + entry.getValue());  
        }  
        System.out.println("---------------");  
        //列印 BJ-北京  SH-上海 HN-河南
    }  
}

         至此以上,基本調通,以下來看看看看redis其他一些感興趣的東西.

redis事務

        redis的事務.先寫一個測試用例,test測試需要第三方的測試包(Groboutils-5-core.jar我使用的這個,這個地方工具包maven上沒有,下載連結),引用第三方包,打包要注意需要在pom,xml中新增相關說明

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
					<encoding>UTF-8</encoding>
					<compilerArguments>
						<extdirs>webapp\lib</extdirs>
					</compilerArguments>
				</configuration>
			</plugin>
		</plugins>
	</build>

  然後通過 

  mvn clean:clean
  mvn -Dmaven.test.skip=true package 

  完成打包即可!

經典的銀行轉賬:

		redisTemplate.delete("A");
		redisTemplate.delete("B");
		// 初始化

		// 賬戶A
		redisTemplate.opsForValue().set("A", "1000");
		// 賬戶B
		redisTemplate.opsForValue().set("B", "1000");

		// 轉賬
		Integer numA = Integer.parseInt(redisTemplate.opsForValue().get("A"));
		Integer numB = Integer.parseInt(redisTemplate.opsForValue().get("A"));
		numA = numA - 100;
		numB = numB + 100;

		// redisTemplate.multi();開啟事務
		redisTemplate.opsForValue().set("A", numA + "");
		int i = 1 / 0;
		redisTemplate.opsForValue().set("B", numB + "");
		// List<Object> exec = redisTemplate.exec();//結束事務
		// System.out.println(exec.get(0));
        結果 A=900 B=1000

        加上事務(放開事務註釋),結果A=1000,B=1000,異常回滾了.注意multi()方法的位置,放在get之前會報空指標,redis的事務跟我們通常的資料的事務有些不一樣的地方,redis開啟事務以後,get與set都是在執行exec()方法後一次性提交,也就是執行multi()開啟事務後,之後對redis的操作都會儲存在快取中,等到事務提交後一次性提交.而資料庫的的事務,每次事務裡都相當於有個資料映象,是可以獲取到資料的.

       redistemplate還有另一種方法watch(),這實現的相當於是一種樂觀鎖,比如有個業務需求,我在操作資料時,資料有變動,我需要回滾

		redisTemplate.delete("A");
		redisTemplate.opsForValue().set("A", "1000");
		
		 TestRunnable[] trs = new TestRunnable [2];  
		 //給A賦值+500(此執行緒先執行)
		 trs[0] = new TestRunnable() {
			@Override
			public void runTest() throws Throwable {
				try {
					TimeUnit.SECONDS.sleep(2);
					Integer numA = Integer.parseInt(redisTemplate.opsForValue().get("A"));
					numA = numA+500;
					redisTemplate.opsForValue().set("A",numA+"");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		
		//給A賦值+100(此執行緒後執行)
		trs[1] = new TestRunnable() {
			@Override
			public void runTest() throws Throwable {
				redisTemplate.watch("A");//開啟樂觀鎖
				//賬戶A
				Integer numA = Integer.parseInt(redisTemplate.opsForValue().get("A"));
				numA = numA+100;
				
				redisTemplate.multi();//開啟事務
				try {
					TimeUnit.SECONDS.sleep(5);
					System.out.println("2:"+numA);
					redisTemplate.opsForValue().set("A",numA+"");
					redisTemplate.exec();//結束事務
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
	    // 用於執行多執行緒測試用例的Runner,將前面定義的單個Runner組成的陣列傳入 
	     MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs);  
	     // 開發併發執行數組裡定義的內容 
	     mttr.runTestRunnables();  
結果A=1500,先執行的執行緒1賦值500成功,執行緒2加了watch(),提交時發現已經被修改了,此次提交全部回滾.(此處舉例僅供參考,)

        以上是事務實現的一種方式,還有就是結合spring的@transactional註解,此處打算結合hibernate(4.3.4.RELEASE)+mysql,相當於對hibernate作個簡單的總結

整合hibernate

pom.xml中需要加上hibernate相關maven

	<!--資料來源 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.18</version>
	</dependency>
	<dependency>
		<groupId>c3p0</groupId>
		<artifactId>c3p0</artifactId>
		<version>0.9.1.2</version>
	</dependency>

	<!--hibernate所需依賴 -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-core</artifactId>
		<version>${hibernate.version}</version>
	</dependency>

	<!-- for JPA, use hibernate-entitymanager instead of hibernate-core -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-entitymanager</artifactId>
		<version>${hibernate.version}</version>
	</dependency>

	<!--使用hibernate jpa 依賴-->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-orm</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<!--hibernate log4j依賴-->
	<dependency>
		<groupId>org.apache.logging.log4j</groupId>
		<artifactId>log4j-api</artifactId>
		<version>2.6.2</version>
	</dependency>

spring-redis.xml新增配置
    	<!-- 資料來源 -->
	<bean id="C3P0DataSourceFather" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		destroy-method="close" abstract="true">
		<!--設定資料庫連線 -->
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="autoCommitOnClose" value="false" />
		<!-- 當連線池中的連線耗盡的時候c3p0一次同時獲取的連線數。Default: 3 -->
		<property name="acquireIncrement" value="2" />
		<!-- 檢測pool內的連線是否正常,此引數就是Task執行的頻率 -->
		<property name="idleConnectionTestPeriod" value="60" />
		<!-- true表示每次把連線checkin到pool裡的時候測試其有效性,非同步操作,會造成至少多一倍的資料庫呼叫 -->
		<property name="testConnectionOnCheckin" value="false" />
		<!-- 定義在從資料庫獲取新連線失敗後重復嘗試的次數。Default: 30 -->
		<property name="acquireRetryAttempts" value="600" />
		<!-- 兩次連線中間隔時間,單位毫秒。Default: 1000 -->
		<property name="acquireRetryDelay" value="1000" />
		<!-- 當連線池用完時客戶端呼叫getConnection()後等待獲取新連線的時間,超時後將丟擲SQLException,如設為0則無限期等待。單位毫秒。Default:0 -->
		<property name="checkoutTimeout" value="10000" />
		<property name="maxStatements" value="0" />
		<!-- 初始化時獲取三個連線,取值應在minPoolSize與maxPoolSize之間。 Default: 3 -->
		<property name="initialPoolSize" value="20" />
		<!-- 最小連線數 -->
		<property name="minPoolSize" value="3" />
		<!-- 連線池中保留的最大連線數。Default: 15 -->
		<property name="maxPoolSize" value="20" />
		<!-- 最大空閒時間,60秒內未使用則連線被丟棄。若為0則永不丟棄。Default: 0 -->
		<property name="maxIdleTime" value="60" />
		<!-- How long to hang on to excess unused connections after traffic spike -->
		<property name="maxIdleTimeExcessConnections" value="600" />
	</bean>

	<!-- 主資料庫 -->
	<bean id="dataSource" parent="C3P0DataSourceFather">
		<property name="jdbcUrl" value="${db.jdbcUrl}" />
		<property name="user" value="${db.user}" />
		<property name="password" value="${db.password}" />
	</bean>

	<!-- 主資料庫 sessionFactory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="packagesToScan">
			<list>
				<value>spring.hibernate.model</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<!--資料庫方言 -->
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
				<!--命名策略 -->
				<!-- <prop key="hibernate.implicit_naming_strategy">org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl</prop> 
					<prop key="hibernate.physical_naming_strategy">com.sc.privatecloud.util.ScPhysicalNamingStrategy</prop> -->
				<!--開發除錯使用 -->
				<!--控制檯列印sql語句 -->
				<prop key="hibernate.show_sql">true</prop>
				<!--格式化語句 -->
				<prop key="hibernate.format_sql">true</prop>
				<!--如果開啟, Hibernate將在SQL中生成有助於除錯的註釋資訊, 預設值為false -->
				<prop key="hibernate.use_sql_commants">false</prop>
				<!--自動建表策略,create,update,create-drop,validate,執行時期此屬性不建議設定 -->
				<!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->

				<!--批量處理 -->
				<!--Hibernate每次從資料庫中取出並放到JDBC的Statement中的記錄條數。Fetch Size設的越大,讀資料庫的次數越少,速度越快,Fetch 
					Size越小,讀資料庫的次數越多,速度越慢 -->
				<prop key="jdbc.fetch_size">50</prop>
				<!--Hibernate批量插入,刪除和更新時每次操作的記錄數。Batch Size越大,批量操作的向資料庫傳送Sql的次數越少,速度就越快,同樣耗用記憶體就越大 -->
				<prop key="jdbc.batch_size">50</prop>
				<!--是否允許Hibernate用JDBC的可滾動的結果集。對分頁的結果集。對分頁時的設定非常有幫助 -->
				<prop key="jdbc.use_scrollable_resultset">false</prop>

				<!--連線資料庫 -->
				<!--useUnicode連線資料庫時是否使用Unicode編碼 -->
				<prop key="Connection.useUnicode">true</prop>
				<!--連線資料庫時資料的傳輸字符集編碼方式 -->
				<prop key="connection.characterEncoding">utf-8</prop>
				<!--二級快取配置 -->
<!-- 				開啟查詢快取
				<prop key="hibernate.cache.use_query_cache">true</prop>
				開啟二級快取
				<prop key="hibernate.cache.use_second_level_cache">false</prop>
				快取記憶體提供程式
				由於spring也使用了Ehcache, 保證雙方都使用同一個快取管理器
				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
				</prop> -->
			</props>
		</property>
	</bean>

	<!--開啟註解-->
  	<tx:annotation-driven transaction-manager="transactionManager"/> 
  	
  	<!--掃描-->
  	<context:component-scan base-package="spring.redis.test"/>
  	
  	<!--事務-->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate5.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<!--hibernate模板-->	
	<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

hibernate的model

package spring.hibernate.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
//@Table 不設定有預設值
@Table(name="student")
public class Student {
	private Integer id;
	private String name;
	private Date createTime = new Date();
	
	//自增id
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	@Column(name = "create_time", nullable = false, updatable = false)
	@Temporal(TemporalType.TIMESTAMP)
	public Date getCreateTime() {
		return createTime;
	}
	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}
	
}

測試事務的service

package spring.redis.test.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import spring.hibernate.model.Student;
import spring.redis.test.service.TestTxService;

@Service
@Transactional
public class TestTxServiceImpl implements TestTxService{
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private HibernateTemplate hibernateTemplate;
	@Override
	public void testTx() {
		//spring 回滾
		Student s = new Student();
		s.setName("test");
		hibernateTemplate.save(s);
		
		//redis事務回滾
		//redisTemplate.delete("A");
		//redisTemplate.delete("B");
		//初始化
		//賬戶A 
		//redisTemplate.opsForValue().set("A", "1000");
		//賬戶B
		//redisTemplate.opsForValue().set("B", "1000");
		
		//轉賬(提前初始化好A=1000,B=1000)
		Integer numA = Integer.parseInt(redisTemplate.opsForValue().get("A"));
		Integer numB = Integer.parseInt(redisTemplate.opsForValue().get("B"));
		numA = numA-100;
		numB = numB+100;
		
		//redisTemplate.multi();開啟事務
		redisTemplate.opsForValue().set("A",numA+"");
		int i=1/0;//回滾事件
		redisTemplate.opsForValue().set("B",numB+"");
		//List<Object> exec = redisTemplate.exec();//結束事務
		//System.out.println(exec.get(0));
		
	}

}

test中測試

	@Test
	public void getValue() {
		try {
			testTxService.testTx();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(redisTemplate.opsForValue().get("A"));
		System.out.println(redisTemplate.opsForValue().get("B"));
	}
此處測試有個小插曲,當時我想直接在test中使用@transactional 註解,結果,異常事務不會滾,原因是我使用註解事務,掃描的包不會去掃描test中測試,一般要通過繼承AbstractTransactionalJUnit4SpringContextTests使用,但還是沒有用service來的方便直接.

        測試結果很有趣,結果列印的都是1000,資料庫也沒有插入新資料.結果顯示的資料庫的事務生效了,redis的事務也是生效了,但是我帶程式碼中並沒有顯示的開啟事務(我註釋了multi()或exec()).原因是在@transactional註解的時候,執行redis相關操作,進行了隱士開啟了事務(詳見RedisConnectionUtils,如何判斷是否是@transactional註解),事務中每次讀都是一個新connection 這保證能獲取到最新的資料(解決了上面的空指標的問題),而寫資料共用一個connection,保證事務中一致性(詳見ConnectionSplittingInterceptor.intercept).當時你在@transactional中顯示執行exec(),事務會直接提交.

        所以使用了@transactional註解,沒有特別的業務需求是不需要進行顯示提交事務,再來看看對watch的影響,在@transactional標註的方法鍾假如watch(),直接報java.lang.UnsupportedOperationException異常,出現異常的原因對一個key進行watch,其不應處在事務狀態,也就是watch必須在multi()方法之前,來看下衝突的原始碼

JedisConnection中

	public void watch(byte[]... keys) {
		if (isQueueing()) {
			throw new UnsupportedOperationException();
		}
		try {
			for (byte[] key : keys) {
				if (isPipelined()) {
					pipeline(new JedisStatusResult(pipeline.watch(key)));
				} else {
					jedis.watch(key);
				}
			}
		} catch (Exception ex) {
			throw convertJedisAccessException(ex);
		}
	}
//#######
	public boolean isQueueing() {
		return client.isInMulti();
	}
原始碼解釋的很明白,事實上,對於一個在事務執行的key,進行監控是沒有什麼意義的,監控造成的事務之間的相互巢狀只會更麻煩

小聊模板

        基本的使用聊完了,來稍微看下kanxiredisTemplate與hibernateTemplate的異曲同工之妙.模板型別的操作,大底最後都是獲取一個connection,通過各種factory,connection pool等實現connection的高效利用.模板的好處,在實現原有功能的基礎上,整合spring進一步昇華,而且使用模板基本不需要考慮釋放connection的問題,模板都會幫你釋放connection,但是值得一提的是hibernateTemplate一般的操作必須有事務的支援(hibernate 開啟session的時候,自動設定conn.setAutoCommit(false),一般jdbc預設是true,不使用事務,是不會提交的).