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
這裡有個地方注意,spring-test 版本與junit版本要匹配,不然會測試會報錯<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>
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,不使用事務,是不會提交的).