在說正文之前我們先介紹一下redis: redis是當今比較熱門的非關係型資料庫之一,他使用的是key-value的鍵值對來進行存儲,是一個存在於記憶體之中的資料庫,我們一般用於做數據緩存。當我們需要大量的數據查詢時,如果我們都直接訪問資料庫時,會嚴重影響資料庫性能。所以我們一般的操作就是在db層之上 ...
在說正文之前我們先介紹一下redis:
redis是當今比較熱門的非關係型資料庫之一,他使用的是key-value的鍵值對來進行存儲,是一個存在於記憶體之中的資料庫,我們一般用於做數據緩存。當我們需要大量的數據查詢時,如果我們都直接訪問資料庫時,會嚴重影響資料庫性能。所以我們一般的操作就是在db層之上的各級使用多級的no-sql來為db提供緩衝。
因為redis是存在於記憶體之中,那麼問題來了當我們斷電時或者宕機時就會產生數據丟失,所以redis為我們提供了rdb和aof的兩種持久化保存的方式,這也是為什麼同樣是緩存資料庫我們選擇redis而不選擇memcache的原因。而且為了在大流量下提供穩定業務,redis還提供了redis-cluster,twemproxy,codis等集群化方案,為我們搭建分散式系統提供了可能。廢話說了這麼多下麵開始正文。
一.添加redis對應的依賴
<!-- reids緩存--> <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${redis.data.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${redis.clients.version}</version> </dependency>
依賴版本大家可以下用的最多的比較穩定。
二.添加相應的spring配置
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--開啟aop代理--> <aop:aspectj-autoproxy expose-proxy="true"/> <!--spring添加註解掃描--> <context:annotation-config></context:annotation-config> <!--spring 註解掃描但是要排除spring mvc的控制器--> <context:component-scan base-package="com"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter> </context:component-scan> <!--載入資源文件,該標簽全文只能有一個--> <context:property-placeholder location="classpath:jdbc.properties,classpath:redisconfig.properties"/> <!--配置數據源,阿裡數據連接池--> <bean id="dataSource" class="${jdbc.dataType}" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.usename}"/> <property name="password" value="${jdbc.password}"/> <!-- 資料庫連接池配置 --> <property name="initialSize" value="20"/><!-- 初始化連接數量 --> <property name="minIdle" value="5"/> <!-- 最小空閑連接數量 --> <property name="maxActive" value="1500"/> <!-- 最大連接數量 --> <property name="maxWait" value="60000"/> <!-- 最大建立連接等待時間(毫秒)。如果超過此時間將接到異常。設為-1表示無限制--> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 --> <property name="validationQuery" value="SELECT 'x'"/> <property name="testWhileIdle" value="true"/> <!--空閑時是否進行驗證,檢查對象是否有效 --> <property name="testOnBorrow" value="false"/> <!--取得對象時是否進行驗證,檢查對象是否有效 --> <property name="testOnReturn" value="false"/> <!--返回對象時是否進行驗證 --> <!-- 打開PSCache,並且指定每個連接上PSCache的大小 --> <property name="poolPreparedStatements" value="true"/> <!-- 表明是否開啟statement cache,預設為false,也就是不開啟 --> <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/> <!-- statement cache的大小,預設為-1,也就是不限制 --> <!-- 配置監控統計攔截的filters,去掉後監控界面sql無法統計 --> <property name="filters" value="stat"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="dataSource" ref="dataSource"/> <!-- 掃描entity包 使用別名 --> <property name="typeAliasesPackage" value="com.lwc.pojo"/> <!-- 掃描sql配置文件:mapper需要的xml文件 --> <property name="mapperLocations" value="classpath*:com/lwc/dao/mapper/*.xml"/> </bean> <!--根據介面名生成對應的代理類--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.lwc.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 註入資料庫連接池 --> <property name="dataSource" ref="dataSource"/> </bean> <!--配置事務切麵--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lwc.service.*.*(..))"></aop:advisor> </aop:config> <!-- 2 事務詳情(事務通知) , 在aop篩選基礎上,比如對ABC三個確定使用什麼樣的事務。例如:AC讀寫、B只讀 等 <tx:attributes> 用於配置事務詳情(屬性屬性) <tx:method name=""/> 詳情具體配置 propagation 傳播行為 , REQUIRED:必須;REQUIRES_NEW:必須是新的 isolation 隔離級別 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--配置redis緩存--> <!--redis配置--> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!--控制一個pool最多有多少個狀態為空閑的jedis實例--> <property name="maxIdle" value="${redis.maxIdle}"></property> <!--當borrow(引入)一個實例時,最大的等待時間,如果超過則拋出jedisConnectionException--> <property name="maxWaitMillis" value="${redis.maxWaitMillis}"></property> <property name="maxTotal" value="${redis.maxTotal}"></property> <!-- 在在borrow一個jedis實例時,是否提前進行validate操作;如果為true,則得到的jedis實例均是可用的--> <property name="testOnBorrow" value="${redis.testOnBorrow}"></property> </bean> <!--redis連接池--> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg index="0" ref="jedisPoolConfig"></constructor-arg> <constructor-arg index="1" value="${redis.host}"></constructor-arg> <constructor-arg index="2" value="${redis.port}"></constructor-arg> <constructor-arg index="3" value="${redis.timeout}"></constructor-arg> </bean> <!-- redis連接工廠 --> <bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.host}" ></property> <property name="port" value="${redis.port}"></property> <property name="poolConfig" ref="jedisPoolConfig"></property> </bean> <!-- redis操作模板,這裡採用儘量面向對象的模板 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="JedisConnectionFactory"/> <property name="keySerializer" ref="stringReadisSerializer"/> <property name="valueSerializer" ref="stringReadisSerializer"/> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/> </property> <!--開啟事務--> <property name="enableTransactionSupport" value="true"/> </bean> <!--使用字元串進行序列化--> <bean id="stringReadisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <!--使用JDK的序列化器進行轉化--> <bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> <bean id="getCache" class="com.lwc.redis.aspectj.GetCachAop"> <property name="redisTemplate" ref="redisTemplate"/> </bean> </beans>
這裡是spring的基本配置,這裡我沒有分開來,反正都有註釋
三.redis的一些基本資源文件
redis.host=localhost redis.port=6379 redis.maxIdle=50 redis.maxTotal=100 redis.maxWaitMillis=3000 redis.testOnBorrow=true redis.timeout=5000
這裡我沒有配置密碼,所以就沒有寫pass,但是如果有需要的話可以找到redis的配置文件加上如下語句
這樣就可以了,如果沒有配置密碼而配置了<property name="password" value="${redis.password}" />將會報錯
四.利用aop和自定義註解來進行實現環繞
下麵是自定義註解,並且配上了一些註解的解釋,還沒有學過自定註解的小伙伴可以看看
package com.lwc.redis.annotation; import java.lang.annotation.*; /**自定義註解 * 2.1.1 Target:表示註解的作用目標 * * @Target(ElementType.TYPE) //介面、類、枚舉、註解 * * @Target(ElementType.FIELD) //欄位、枚舉的常量 * * @Target(ElementType.METHOD) //方法 * * @Target(ElementType.PARAMETER) //方法參數 * * @Target(ElementType.CONSTRUCTOR) //構造函數 * * @Target(ElementType.LOCAL_VARIABLE)//局部變數 * * @Target(ElementType.ANNOTATION_TYPE)//註解 * * @Target(ElementType.PACKAGE) ///包 * * 2.1.2 @Documented:說明該註解將被包含在javadoc中; * * 2.1.3 @Inherited:說明子類可以繼承父類中的該註解 ; * * 2.1.4 @Retention:註解的保留位置; * * @Retention(RetentionPolicy.SOURCE) //註解僅存在於源碼中,在class位元組碼文件中不包含 * * @Retention(RetentionPolicy.CLASS) // 預設的保留策略,註解會在class位元組碼文件中存在,但運行時無法獲得, * * @Retention(RetentionPolicy.RUNTIME) // 註解會在class位元組碼文件中存在,在運行時可以通過反射獲取到 */ @Target(ElementType.METHOD)//目標為方法 @Retention(RetentionPolicy.RUNTIME)//註解在類中存在,運行時通過反射獲取 @Documented @Inherited public @interface GetCache { String name() default ""; String value() default ""; }
定義這個註解是為了讓aop可以直接根據註解來進行切麵環繞,而不需要根據傳統的方法來進行切點,這樣會方便很多,否則的話就需要
定義介面然後對介面需要的方法進行定義切點,在實現該介面,這樣也可以做到切麵環繞,但是會更麻煩點,有興趣的小伙伴可以自己試試
通知類:
package com.lwc.redis.aspectj; import com.lwc.redis.annotation.GetCache; import com.lwc.util.RedisCache; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.io.Serializable; import java.lang.reflect.Method; @Component @Aspect public class GetCachAop { @Autowired private RedisTemplate<Serializable,Object> redisTemplate; private RedisCache redisCache=new RedisCache(); //定義切點 @Pointcut("@annotation(com.lwc.redis.annotation.GetCache)") public void getCache(){ System.out.println("獲取記憶體數據切入點"); } /*在所有標註了GetCache的地方切入*/ @Around("getCache()") public Object beforeExec(ProceedingJoinPoint joinPoint){ //生成redis中的id,根據自己指定的格式 String redisKey=getCacheKey(joinPoint); //獲取從redis中查詢得到的對象 Object object=redisCache.getDataFromRedis(redisKey); //如果查詢到了 if(null!=object){ System.out.println("從redis中獲取到了數據"); return object; }else{ System.out.println("從資料庫中查詢數據"); //如果沒有查詢到,則在資料庫中進行查詢 try { object=joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } } //在目標方法執行完之後:將查到的數據放入到redis中 redisCache.setDataToRedis(redisKey,object); return object; } /** * 根據方法名參數名類名獲取唯一的一個鍵值 * 格式為yourpackage.classname.methodname(int).argsvalue123 * @param joinPoint * @return */ //變數沒有用到時不讓警告 @SuppressWarnings("unused") private String getCacheKey(ProceedingJoinPoint joinPoint){ //獲取切入方法的一些相關的信息 MethodSignature ms=(MethodSignature) joinPoint.getSignature(); Method method=ms.getMethod(); //獲取註解中設置的對應參數 String ActionName=method.getAnnotation(GetCache.class).value(); String fieldList=method.getAnnotation(GetCache.class).name(); for(String field:fieldList.split(".")) ActionName+="."+field; //獲取切點方法的參數 String id=null; Object[] args=joinPoint.getArgs(); if(args!=null && args.length>0) id=String.valueOf(args[0]); ActionName+="="+id; String redisKey=ms+"."+ActionName; return redisKey; } public void setRedisTemplate( RedisTemplate<Serializable, Object> redisTemplate){ this.redisTemplate = redisTemplate; } }
上面代碼都有詳細註釋這裡就不重覆了
下麵是序列化的工具類:
package com.lwc.util; import java.io.*; public class SerializableUtil { //將位元組數組反序列化為對象 public static Object toObject(byte[] bytes){ Object obj=null; try{ ByteArrayInputStream bis=new ByteArrayInputStream(bytes); ObjectInputStream ois=new ObjectInputStream(bis); obj=ois.read(); ois.close(); bis.close(); } catch (IOException e) { e.printStackTrace(); } return obj; } //將對象序列化為位元組數組 public static 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 e) { e.printStackTrace(); } return bytes; } }
下麵是緩存工具類:
package com.lwc.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; /** * redis緩存工具類 */ public class RedisCache { private static JedisPool jedisPool; //靜態類進行參數的初始化 static { ClassPathXmlApplicationContext cxac=new ClassPathXmlApplicationContext("applicationContext.xml"); jedisPool=(JedisPool) cxac.getBean("jedisPool"); } //從緩存中讀取數據,進行反序列化 public Object getDataFromRedis(String redisKey){ Jedis jedis=jedisPool.getResource(); byte[] result=jedis.get(redisKey.getBytes()); //如果沒有查詢到,就返回空 if(null==result) return null; return SerializableUtil.toObject(result); } //將資料庫中查到的數據放入redis中 public void setDataToRedis(String redisKey,Object obj){ byte[] bytes =SerializableUtil.toByteArray(obj); Jedis jedis=jedisPool.getResource(); String sucess=jedis.set(redisKey.getBytes(),bytes); if("OK".equals(sucess)){ System.out.println("數據保存成功"); } } }
當然我也看到有人繼承cach類來使用redis緩存,下麵貼出別人的代碼併進行解釋:
package com.ssm.redis; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.ibatis.cache.Cache; import org.springframework.beans.factory.annotation.Autowired; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisCache implements Cache { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * Jedis客戶端 */ @Autowired private Jedis redisClient = createClient(); private String id; public RedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("必須傳入ID"); } System.out.println("MybatisRedisCache:id=" + id); this.id = id; } @Override public void clear() { redisClient.flushDB(); } @Override public String getId() { return this.id; } @Override public Object getObject(Object key) { byte[] ob = redisClient.get(SerializeUtil.serialize(key.toString())); if (ob == null) { return null; } Object value = SerializeUtil.unSerialize(ob); return value; } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } @Override public int getSize() { return Integer.valueOf(redisClient.dbSize().toString()); } @Override public void putObject(Object key, Object value) { redisClient.set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value)); } @Override public Object removeObject(Object key) { return redisClient.expire(SerializeUtil.serialize(key.toString()), 0); } protected static Jedis createClient() { try { @SuppressWarnings("resource") JedisPool pool = new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6379); return pool.getResource(); } catch (Exception e) { e.printStackTrace(); } throw new RuntimeException("初始化連接池錯誤"); } }
這個其實是使用mybatis自帶的二級緩存,從寫mybatis中的緩存類來進行實現使用這種方法就不需要額外的添加aop之類的只需要在映射的dao文件中添加<cache type=
"packagename.RedisCache"
/>這樣便可以直接使用redis而不需要aop配置
言歸正傳,我們接著使用aop來實現redis整合,下麵是service使用註解來進行實現redis存取
package com.lwc.service; import com.lwc.dao.UserDao; import com.lwc.pojo.User; import com.lwc.redis.annotation.GetCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private UserDao userDao; @GetCache(name = "user" ,value = "id") public User getUser(Integer id){ return userDao.selectUser(id); } }
這樣我們每次調用service的這個方法就可以實現從redis存取數據了。其他的UserDao和對應的映射文件我這裡就不貼出來了
值得一說的是這裡的實體類要繼承Serializable 介面,不然會報錯,因為他時間實例化對象進行序列化存入記憶體之中。
以上就完成了基本的redis的使用,下篇博文將會介紹redis的持久化