redis作为hibernate的二级缓存

hibernate的二级缓存有好多,像ehcache。不过项目的缓存使用的是redis,而redis官方没有实现hibernate的二级缓存接口,只得自己实现。看看公司的高手如何做的吧。

先看配置:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- entityManagerFactory -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        depends-on="cacheManagerFactory">
        ...
        <property name="jpaProperties">
            <props>
                ...
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <!-- <prop key="hibernate.cache.use_query_cache">true</prop> -->
                <prop key="hibernate.cache.region.factory_class">xxx.xxx.framework.cache.hibernate.CacheRegionFactory</prop>
                ...
            </props>
        </property>
    </bean>  
     
    <!-- cache -->
    <bean id="cacheManager" class="xxx.xxx.framework.cache.redis.RedisCacheManager">
        <property name="connectionFactory" ref="redisConnectionFactory"/>
        <property name="namespace" value="payment"/>
    </bean>
     
    <bean id="cacheManagerFactory" class="xxx.xxx.framework.cache.hibernate.CacheManagerFactory">
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

cacheManager是redis缓存的配置,二级缓存实现类CacheRegionFactory里面会用到它,但是hibernate缓存配置的只是配置实现类,没法注入CacheRegionFactory对象,所以这边多了个cacheManagerFactory,注意配置中的depends-on。它的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class CacheManagerFactory implements DisposableBean {
    private static CacheManager CACH_EMANAGER;
 
    public void setCacheManager(CacheManager cacheManager) {
        CACH_EMANAGER = cacheManager;
    }
 
    public static CacheManager getCacheManager() {
        return CACH_EMANAGER;
    }
 
    @Override
    public void destroy() throws Exception {
        CACH_EMANAGER = null;
    }
}
它就是负责生成cacheManager。注意getCacheManager是静态方法。然后我们看redis二级缓存的实现类:


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class CacheRegionFactory implements RegionFactory {
    private static final long serialVersionUID = -1557439471733872383L;
    private CacheManager cacheManager;
    private static final AtomicLong CURRENT = new AtomicLong();
    protected Settings settings;
 
    @Override
    public void start(Settings settings, Properties properties) throws CacheException {
        cacheManager = CacheManagerFactory.getCacheManager();
        this.settings = settings;
        Assert.notNull(cacheManager, "cacheManager is required,CacheManagerFactory must be init first");
    }
 
    @Override
    public void stop() {
        cacheManager = null;
    }
 
    @Override
    public boolean isMinimalPutsEnabledByDefault() {
        return true;
    }
 
    @Override
    public AccessType getDefaultAccessType() {
        return AccessType.NONSTRICT_READ_WRITE;
    }
 
    @Override
    public long nextTimestamp() {
        return CURRENT.incrementAndGet();
    }
 
    @Override
    public EntityRegion buildEntityRegion(String regionName, Properties properties, CacheDataDescription metadata) throws CacheException {
        return new EntityRegionImpl(regionName, cacheManager.getCache(shortRegionName(regionName)), settings, properties, metadata);
    }
 
    @Override
    public NaturalIdRegion buildNaturalIdRegion(String regionName, Properties properties, CacheDataDescription metadata) throws CacheException {
        return new NaturalIdRegionImpl(regionName, cacheManager.getCache(shortRegionName(regionName)), settings, properties, metadata);
    }
 
    @Override
    public CollectionRegion buildCollectionRegion(String regionName, Properties properties, CacheDataDescription metadata) throws CacheException {
        return new CollectionRegionImpl(regionName, cacheManager.getCache(shortRegionName(regionName)), settings, properties, metadata);
    }
 
    @Override
    public QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties) throws CacheException {
        return new QueryResultsRegionImpl(regionName, cacheManager.getCache(shortRegionName(regionName)), settings, properties);
    }
 
    @Override
    public TimestampsRegion buildTimestampsRegion(String regionName, Properties properties) throws CacheException {
        return new TimestampsRegionImpl(regionName, cacheManager.getCache(shortRegionName(regionName)), settings, properties);
    }
 
    private static String shortRegionName(String regionName) {
        ...
    }
}
RegionFactory是hibernate的接口。在start里面通过调用CacheManagerFactory来获取redis的CacheManager。这里主要还是buildXX的几个方法,在这些方法里面通过 CacheManager和region获取cache,然后再生成hibernate需要的缓存对象


以buildEntityRegion为例,返回的对象EntityRegion是hibernate的接口,EntityRegionImpl怎是我们的实现。先看看它的实现:

?
1
2
3
4
5
6
7
8
9
10
11
public class EntityRegionImpl extends AbstractTransactionalDataRegion implements EntityRegion {
 
    public EntityRegionImpl(String regionName, Cache cache, Settings settings, Properties properties, CacheDataDescription metadata) {
        super(regionName,cache, settings, properties, metadata);
    }
 
    @Override
    public EntityRegionAccessStrategy buildAccessStrategy(AccessType accessType) throws CacheException {
        return new EntityRegionAccessStrategyImpl(this, settings);
    }
}
这边可以分两部分看了一个是它的父类AbstractTransactionalDataRegion,另外一个是由EntityRegion继承而来的buildAccessStrategy(Hibernate EntityRegion的接口实现)。

先看它的父类,父类实现了hibernate的TansactionalDateRegion:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AbstractTransactionalDataRegion extends AbstractRegion implements TransactionalDataRegion {
    private final CacheDataDescription metadata;
 
    public AbstractTransactionalDataRegion(String regionName, Cache cache, Settings settings, Properties properties, CacheDataDescription metadata) {
        super(regionName, cache, settings, properties);
        this.metadata = metadata;
    }
 
    @Override
    public boolean isTransactionAware() {
        return false;
    }
 
    @Override
    public CacheDataDescription getCacheDataDescription() {
        return metadata;
    }
}
isTransactionAware是表示是否支持jta事务。然后再看它的父类AbstractRegion:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class AbstractRegion implements Region {
    private static final AtomicLong CURRENT = new AtomicLong();
    private String regionName;
    protected final Cache cache;
    protected final Properties properties;
    protected final Settings settings;
    protected final KeyGenerator keyGenerator = DefaultKeyGenerator.INSTANCE;
 
    public AbstractRegion(String regionName, Cache cache, Settings settings, Properties properties) {
        this.regionName = regionName;
        this.cache = cache;
        this.settings = settings;
        this.properties = properties;
    }
 
    @Override
    public String getName() {
        return regionName;
    }
 
    @Override
    public void destroy() throws CacheException {
    }
 
    @Override
    public boolean contains(Object key) {
        return cache.exists(key);
    }
 
    @Override
    public long getSizeInMemory() {
        return -1;
    }
 
    @Override
    public long getElementCountInMemory() {
        return cache.getStatistics().getSize();
    }
 
    @Override
    public long getElementCountOnDisk() {
        return -1;
    }
 
    @Override
    public Map toMap() {
        return Collections.EMPTY_MAP;
    }
 
    @Override
    public long nextTimestamp() {
        return CURRENT.incrementAndGet();
    }
 
    @Override
    public int getTimeout() {
        return 300;
    }
 
    public Object get(Object key) throws CacheException {
        try {
            return postGet(cache.get(toKey(key)));
        } catch (Throwable e) {
            throw new CacheException(e);
        }
    }
 
    public void put(Object key, Object value) throws CacheException {
        try {
            cache.put(toKey(key), value);
        } catch (Exception e) {
            throw new CacheException(e);
        }
    }
 
    public void evict(Object key) throws CacheException {
        try {
            cache.evict(toKey(key));
        } catch (Exception e) {
            throw new CacheException(e);
        }
    }
 
    public void evictAll() throws CacheException {
        try {
            cache.clear();
        } catch (Exception e) {
            throw new CacheException(e);
        }
    }
 
    private Object toKey(Object key) {
        if (key instanceof CacheKey) {
            key = ((CacheKey) key).getKey();
        }
        return keyGenerator.generate(key);
    }
......
这里实现了hibernate region的一些接口,另外一些对于缓存的一些操作方法如:put\get\evict等也做了实现。

现在我们回过头看看EntityRegionImpl里面的EntityRegionAccessStrategyImpl实现:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class EntityRegionAccessStrategyImpl extends AbstractAccessStrategy<EntityRegionImpl> implements EntityRegionAccessStrategy {
 
 
    public EntityRegionAccessStrategyImpl(EntityRegionImpl region, Settings settings) {
        super(region, settings);
    }
 
 
    @Override
    public EntityRegion getRegion() {
        return region;
    }
}
它也实现了hibernate的接口:EntityRegionAccessStrategy,字面意思就是缓存的访问策略。

先看看hibernate的这个接口的定义:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public interface EntityRegionAccessStrategy extends RegionAccessStrategy{
 
    /**
     * Get the wrapped entity cache region
     *
     * @return The underlying region
     */
    public EntityRegion getRegion();
 
    /**
     * Called after an item has been inserted (before the transaction completes),
     * instead of calling evict().
     * This method is used by "synchronous" concurrency strategies.
     *
     * @param key The item key
     * @param value The item
     * @param version The item's version value
     * @return Were the contents of the cache actual changed by this operation?
     * @throws CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region}
     */
    public boolean insert(Object key, Object value, Object version) throws CacheException;
 
    /**
     * Called after an item has been inserted (after the transaction completes),
     * instead of calling release().
     * This method is used by "asynchronous" concurrency strategies.
     *
     * @param key The item key
     * @param value The item
     * @param version The item's version value
     * @return Were the contents of the cache actual changed by this operation?
     * @throws CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region}
     */
    public boolean afterInsert(Object key, Object value, Object version) throws CacheException;
 
    /**
     * Called after an item has been updated (before the transaction completes),
     * instead of calling evict(). This method is used by "synchronous" concurrency
     * strategies.
     *
     * @param key The item key
     * @param value The item
     * @param currentVersion The item's current version value
     * @param previousVersion The item's previous version value
     * @return Were the contents of the cache actual changed by this operation?
     * @throws CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region}
     */
    public boolean update(Object key, Object value, Object currentVersion, Object previousVersion) throws CacheException;
 
    /**
     * Called after an item has been updated (after the transaction completes),
     * instead of calling release().  This method is used by "asynchronous"
     * concurrency strategies.
     *
     * @param key The item key
     * @param value The item
     * @param currentVersion The item's current version value
     * @param previousVersion The item's previous version value
     * @param lock The lock previously obtained from {@link #lockItem}
     * @return Were the contents of the cache actual changed by this operation?
     * @throws CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region}
     */
    public boolean afterUpdate(Object key, Object value, Object currentVersion, Object previousVersion, SoftLock lock) throws CacheException;
}
它的父接口里面还有一些其他的接口如get,remove等可以直接看看源码注释上面都写了。

我们的实现类同时有个父类AbstractAccessStrategy<EntityRegionImpl>,很多EntityRegionAccessStrategy因为都是公用的所以在AbstractAccessStrategy中实现了。看代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public abstract class AbstractAccessStrategy<T extends AbstractTransactionalDataRegion> {
    protected final T region;
    protected final Settings settings;
 
    public AbstractAccessStrategy(T region, Settings settings) {
        this.region = region;
        this.settings = settings;
    }
 
    public boolean insert(Object key, Object value) throws CacheException {
        return insert(key, value, null);
    }
 
    public boolean insert(Object key, Object value, Object version) throws CacheException {
        return false;
    }
 
    public boolean afterInsert(Object key, Object value) throws CacheException {