SpringAOP and Redis build cache and springaopredis build
Recently, it is too slow for the project to query the database, and the persistent layer does not enable the secondary cache. Now we want to use Redis as the cache. In order not to rewrite the original code, we use AOP + Redis here.
Currently, only query is required for the project:
Data needs to be queried from the database every time during data query. The database is under great pressure and the query speed is slow. Therefore, the cache layer is set to query data first from redis. If the query fails, then, query the data in the database and place the data queried in the database into redis. The data can be queried directly from redis next time without querying the database.
Advantages of redis as a cache:
1. Memory-level cache, the query speed is beyond doubt.
2. High-Performance K-V storage system, supports String, Hash, List, Set, Sorted Set and other data types, can be applied in many scenarios.
3. redis3.0 and later versions support cluster deployment.
4. redis supports data persistence, AOF, and RDB.
Entity classes and tables:
public class RiskNote implements Serializable { private static final long serialVersionUID = 4758331879028183605L; private Integer ApplId; private Integer allqyorg3monNum; private Double loanF6endAmt; private String isHighRisk1; private Date createDate; private String risk1Detail; private Integer risk2; private String risk3; private String creditpaymonth; ......
Redis and Spring integration parameters:
Redis. properties
#redis settingsredis.minIdle=5redis.maxIdle=10redis.maxTotal=50redis.maxWaitMillis=1500redis.testOnBorrow=trueredis.numTestsPerEvictionRun=1024redis.timeBetweenEvictionRunsMillis=30000redis.minEvictableIdleTimeMillis=1800000redis.softMinEvictableIdleTimeMillis=10000redis.testWhileIdle=trueredis.blockWhenExhausted=false#redisConnectionFactory settingsredis.host=192.168.200.128redis.port=6379
Integration configuration file: applicationContext_redis.xml
<! -- Load configuration data --> <bean class = "org. springframework. beans. factory. config. propertyPlaceholderConfigurer "> <property name =" systemPropertiesModeName "value =" canonical "/> <property name =" ignoreResourceNotFound "value =" true "/> <property name =" locations "> <list> <value> classpath *: /redis. properties </value> </list> </property> </bean> <! -- Annotation scan --> <context: component-scan base-package = "com. club. common. redis"/> <! -- Jedis connection pool configuration --> <bean id = "poolConfig" class = "redis. clients. jedis. JedisPoolConfig"> <! -- Minimum number of idle connections --> <property name = "minIdle" value = "$ {redis. minIdle}"/> <! -- Maximum number of idle connections --> <property name = "maxIdle" value = "$ {redis. maxIdle}"/> <! -- Max connections --> <property name = "maxTotal" value = "$ {redis. maxTotal}"/> <! -- Get the maximum number of milliseconds to wait for the connection. The value is smaller than zero: The uncertain blocking time. Default value:-1 --> <property name = "maxWaitMillis" value = "$ {redis. maxWaitMillis} "/> <! -- Check the validity when obtaining the connection. The default value is false --> <property name = "testOnBorrow" value = "$ {redis. testOnBorrow}"/> <! -- Maximum number of connections released each time --> <property name = "numTestsPerEvictionRun" value = "$ {redis. numTestsPerEvictionRun}"/> <! -- Release connection scan interval (MS) --> <property name = "timeBetweenEvictionRunsMillis" value = "$ {redis. timeBetweenEvictionRunsMillis}"/> <! -- Minimum idle connection time --> <property name = "minEvictableIdleTimeMillis" value = "$ {redis. minEvictableIdleTimeMillis}"/> <! -- After how long the connection is idle, it is released directly when the idle time> This value and the maximum number of idle connections> <property name = "softMinEvictableIdleTimeMillis" value = "$ {redis. softMinEvictableIdleTimeMillis} "/> <! -- Check validity when idle. The default value is false --> <property name = "testWhileIdle" value = "$ {redis. testWhileIdle}"/> <! -- Indicates whether the connection is blocked when the connection is exhausted. If it is false, an exception is reported. If the connection times out, the default value is true. --> <property name = "blockWhenExhausted" value = "$ {redis. blockWhenExhausted} "/> </bean> <! -- Redis connection pool --> <bean id = "jedisPool" class = "redis. clients. jedis. jedisPool "destroy-method =" close "> <constructor-arg name =" poolConfig "ref =" poolConfig "/> <constructor-arg name =" host "value =" $ {redis. host} "/> <constructor-arg name =" port "value =" $ {redis. port} "/> </bean> <bean id =" redisCache "class =" com. club. common. redis. redisCache "> <property name =" jedisPool "ref =" jedisPool "> </property> </bean> <bean I D = "testDao" class = "com. club. common. redis. testDao "> </bean> <bean id =" testService "class =" com. club. common. redis. service. testService "> </bean> <! -- Enable Aspect support --> <aop: aspectj-autoproxy/> </beans>
Test, so there is no write interface at all levels.
The DAO layer queries data and encapsulates objects:
Public class TestDao {// query public RiskNote getByApplId (Integer applId) throws Exception {Class. forName ("oracle. jdbc. driver. oracleDriver "); Connection connection = DriverManager. getConnection ("jdbc: oracle: thin: @ 192.168.11.215: 1521: MFTEST01", "datacenter", "datacenter"); PreparedStatement = connection. prepareStatement ("select * from TEMP_RISK_NOTE where appl_id =? "); // Execute statement. setInt (1, applId); ResultSet resultSet = statement.exe cuteQuery (); RiskNote riskNote = new RiskNote (); // parse while (resultSet. next () {riskNote. setApplId (resultSet. getInt ("APPL_ID"); riskNote. setAllqyorg3monNum (resultSet. getInt ("ALLQYORG3MON_NUM"); riskNote. setLoanF6endAmt (resultSet. getDouble ("LOAN_F6END_AMT"); riskNote. setIsHighRisk1 (resultSet. getString ("IS_HIGH_RISK_1"); riskNote. setCreateDate (resultSet. getDate ("CREATE_DATE"); riskNote. setRisk1Detail (resultSet. getString ("RISK1_DETAIL"); riskNote. setRisk2 (resultSet. getInt ("RISK2"); riskNote. setRisk3 (resultSet. getString ("RISK3"); riskNote. setCreditpaymonth (resultSet. getString ("CREDITPAYMONTH");} return riskNote ;}}
The Service layer calls DAO:
@Servicepublic class TestService { @Autowired private TestDao testDao; public Object get(Integer applId) throws Exception{ RiskNote riskNote = testDao.getByApplId(applId); return riskNote; }}
Test:
public class TestQueryRiskNote { @Test public void testQuery() throws Exception{ ApplicationContext ac = new FileSystemXmlApplicationContext("src/main/resources/spring/applicationContext_redis.xml"); TestService testService = (TestService) ac.getBean("testService"); RiskNote riskNote = (RiskNote)testService.get(91193); System.out.println(riskNote); }}
The test code outputs the queried RiskNote object. You can rewrite the toString method to view the result.
The result is as follows: the final output object
Set up Redis on a virtual machine Linux system. For more information, see Baidu
Redis supports multiple data structures. The queried objects can be directly stored in redis using the hash structure.
Because the data queried by different methods in the project is inconsistent, such as simple objects, List sets, Map sets, and Map sets of objects in the List. To achieve uniformity and universality, redis also provides the set (byte [], byte []) method, so you can serialize the object and save it to redis. Then, deserialize the object.
Serialization and deserialization tools:
/***** @ Description: serialization deserialization tool */public class SerializeUtil {/***** serialization */public static byte [] serialize (Object obj) {ObjectOutputStream oos = null; ByteArrayOutputStream baos = null; try {// serialization baos = new ByteArrayOutputStream (); oos = new ObjectOutputStream (baos); oos. writeObject (obj); byte [] byteArray = baos. toByteArray (); return byteArray;} catch (IOException e) {e. printStackTrace ();} return null;}/***** deserialization * @ param bytes * @ return */public static Object unSerialize (byte [] bytes) {ByteArrayInputStream bais = null; try {// deserialization to object bais = new ByteArrayInputStream (bytes); ObjectInputStream ois = new ObjectInputStream (bais); return ois. readObject ();} catch (Exception e) {e. printStackTrace ();} return null ;}}
Slice analysis:
Aspect: redis is queried before query. If no penetration to the database is found, the data is queried from the database and saved to redis. Then, the next query can directly hit the cache.
The target method is to query the database. redis needs to be queried before query.
If the query result is not found in redis, the database is queried. After the target method is executed, you need to put the queried data in redis so that you do not need to query it in the database next time.
Therefore, you can set the notification in the Section as a surround notification.
The partition class is written as follows:
/*** @ Description: This interface is used to query redis before query. If no penetration to the database is found, the data is queried from the database and then saved to redis, then, you can directly hit the cache in the next query */@ Component @ Aspectpublic class RedisAspect {@ Autowired @ Qualifier ("redisCache") private RedisCache redisCache; // set the cut point: use xml, configure @ Pointcut ("execution (* com. club. common. redis. service. testService. get (java. lang. integer) and args (applId) ") // used for testing. The method name, method parameter type, method form parameter, and so on are also specified here. A complete cut-point expression is provided.
Public void myPointCut () {}@ Around ("myPointCut ()") public Object around (ProceedingJoinPoint joinPoint) {// front: query the cache System in redis. out. println ("Call the method queried from redis... "); // obtain the target method parameter String applId = null; Object [] args = joinPoint. getArgs (); if (args! = Null & args. length> 0) {applId = String. valueOf (args [0]);} // key format in redis: applId String redisKey = applId; // obtain the Object objectFromRedis = redisCache queried from redis. getDataFromRedis (redisKey); // if the if (null! = ObjectFromRedis) {System. out. println ("Data queried from redis... no need to query database "); return objectFromRedis;} System. out. println ("no data found from redis... "); // not found, the query database Object = null; try {object = joinPoint. proceed ();} catch (Throwable e) {e. printStackTrace ();} System. out. println ("Data queried from the database... "); // post: Put the data queried in the database into redis System. out. println ("Call the method used to store the data queried by the database to redis... "); redisCache. setDataToRedis (redisKey, object); // return the queried data to return object ;}}
How to query data from redis and save the data queried by the database to redis:
/*** @ Description: Redis cache */public class RedisCache {@ Resource private JedisPool jedisPool; public JedisPool getJedisPool () {return jedisPool;} public void setJedisPool (JedisPool jedisPool) {this. jedisPool = jedisPool;} // query from redis cache and deserialize public Object getDataFromRedis (String redisKey) {// query Jedis jedis = jedisPool. getResource (); byte [] result = jedis. get (redisKey. getBytes (); // if the query is not empty, if (null = result) {return null;} // The return SerializeUtil is deserialized. unSerialize (result);} // put the data queried in the database into redis public void setDataToRedis (String redisKey, Object obj) {// serialize byte [] bytes = SerializeUtil. serialize (obj); // save to redis Jedis jedis = jedisPool. getResource (); String success = jedis. set (redisKey. getBytes (), bytes); if ("OK ". equals (success) {System. out. println ("data is successfully saved to redis... ");}}}
Test 1: No data of the object is queried in redis.
The result is: first query in redis, no data found, then the proxy executes the query from the database, and then stores the data in redis, then the next query can be directly queried from redis.
Test 2: At this time, redis has a previous query of data from the database.
After testing in the project: the results are still very obvious. There is a super complex query. After formatting, the SQL statement is 688 rows. Every time you refresh the page, you need to re-query it, which takes about 10 seconds.
After the first query is placed in redis, the query results in redis can be obtained within 2 seconds, which is very fast.
The above is a Demo written before the project transformation. The actual project is complicated, and the cut-point expression is composed of two or three values. It also focuses on the writing of the Cut-point expression.
For example:
@ Pointcut ("(execution (* com. club. risk. center. service. impl. *. * (java. lang. string) | (execution (* com. club. risk. py. service. impl. pyServcieImpl. queryPyReportByApplId (java. lang. string) | (execution (* com. club. risk. zengxintong. service. impl. ZXTServiceImpl. queryZxtReportByApplId (..))) ")
This is a combination of multiple cut points. | connection.
Keys used in my project are also more complex than applId, because key conflicts may occur if only applId is used,
So the key used in the project is applId: The method is fully qualified, so that the key can be inconsistent.
As follows:
// First obtain the target method parameter String applId = null; Object [] args = joinPoint. getArgs (); if (args! = Null & args. length> 0) {applId = String. valueOf (args [0]);} // obtain the class of the target method String target = joinPoint. getTarget (). toString (); String className = target. split ("@") [0]; // method name for obtaining the target method String methodName = joinPoint. getSignature (). getName (); // key format in redis: applId: method name String redisKey = applId + ":" + className + ". "+ methodName;
Therefore, the above is a general process, which depends on the specific situation in the project.
I didn't write the AOP code myself before. This time, I suddenly found that AOP is really powerful. In the whole process, I didn't change any source code except the configuration file. All the functions were introduced.
This Demo basically meets the requirements. You only need to set the cut point to apply the cache to various query methods, or set the cut point to service. impl package, which acts directly on all service methods.