1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.cache;
26
27 import java.io.Serializable;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Properties;
31 import java.util.Set;
32
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import org.hibernate.HibernateException;
37 import org.hibernate.UnresolvableObjectException;
38 import org.hibernate.cfg.Settings;
39 import org.hibernate.engine.SessionImplementor;
40 import org.hibernate.type.Type;
41 import org.hibernate.type.TypeFactory;
42
43 /**
44 * The standard implementation of the Hibernate QueryCache interface. This
45 * implementation is very good at recognizing stale query results and
46 * and re-running queries when it detects this condition, recaching the new
47 * results.
48 *
49 * @author Gavin King
50 * @author Steve Ebersole
51 */
52 public class StandardQueryCache implements QueryCache {
53
54 private static final Logger log = LoggerFactory.getLogger( StandardQueryCache.class );
55
56 private QueryResultsRegion cacheRegion;
57 private UpdateTimestampsCache updateTimestampsCache;
58
59 public void clear() throws CacheException {
60 cacheRegion.evictAll();
61 }
62
63 public StandardQueryCache(
64 final Settings settings,
65 final Properties props,
66 final UpdateTimestampsCache updateTimestampsCache,
67 String regionName) throws HibernateException {
68 if ( regionName == null ) {
69 regionName = StandardQueryCache.class.getName();
70 }
71 String prefix = settings.getCacheRegionPrefix();
72 if ( prefix != null ) {
73 regionName = prefix + '.' + regionName;
74 }
75 log.info( "starting query cache at region: " + regionName );
76
77 this.cacheRegion = settings.getRegionFactory().buildQueryResultsRegion( regionName, props );
78 this.updateTimestampsCache = updateTimestampsCache;
79 }
80
81 public boolean put(
82 QueryKey key,
83 Type[] returnTypes,
84 List result,
85 boolean isNaturalKeyLookup,
86 SessionImplementor session) throws HibernateException {
87 if ( isNaturalKeyLookup && result.size() == 0 ) {
88 return false;
89 }
90 else {
91 Long ts = new Long( session.getTimestamp() );
92
93 if ( log.isDebugEnabled() ) {
94 log.debug( "caching query results in region: " + cacheRegion.getName() + "; timestamp=" + ts );
95 }
96
97 List cacheable = new ArrayList( result.size() + 1 );
98 cacheable.add( ts );
99 for ( int i = 0; i < result.size(); i++ ) {
100 if ( returnTypes.length == 1 ) {
101 cacheable.add( returnTypes[0].disassemble( result.get( i ), session, null ) );
102 }
103 else {
104 cacheable.add(
105 TypeFactory.disassemble(
106 ( Object[] ) result.get( i ), returnTypes, null, session, null
107 )
108 );
109 }
110 }
111
112 cacheRegion.put( key, cacheable );
113
114 return true;
115
116 }
117
118 }
119
120 public List get(
121 QueryKey key,
122 Type[] returnTypes,
123 boolean isNaturalKeyLookup,
124 Set spaces,
125 SessionImplementor session) throws HibernateException {
126 if ( log.isDebugEnabled() ) {
127 log.debug( "checking cached query results in region: " + cacheRegion.getName() );
128 }
129
130 List cacheable = ( List ) cacheRegion.get( key );
131 if ( cacheable == null ) {
132 log.debug( "query results were not found in cache" );
133 return null;
134 }
135
136 Long timestamp = ( Long ) cacheable.get( 0 );
137 if ( !isNaturalKeyLookup && !isUpToDate( spaces, timestamp ) ) {
138 log.debug( "cached query results were not up to date" );
139 return null;
140 }
141
142 log.debug( "returning cached query results" );
143 for ( int i = 1; i < cacheable.size(); i++ ) {
144 if ( returnTypes.length == 1 ) {
145 returnTypes[0].beforeAssemble( ( Serializable ) cacheable.get( i ), session );
146 }
147 else {
148 TypeFactory.beforeAssemble( ( Serializable[] ) cacheable.get( i ), returnTypes, session );
149 }
150 }
151 List result = new ArrayList( cacheable.size() - 1 );
152 for ( int i = 1; i < cacheable.size(); i++ ) {
153 try {
154 if ( returnTypes.length == 1 ) {
155 result.add( returnTypes[0].assemble( ( Serializable ) cacheable.get( i ), session, null ) );
156 }
157 else {
158 result.add(
159 TypeFactory.assemble(
160 ( Serializable[] ) cacheable.get( i ), returnTypes, session, null
161 )
162 );
163 }
164 }
165 catch ( UnresolvableObjectException uoe ) {
166 if ( isNaturalKeyLookup ) {
167 //TODO: not really completely correct, since
168 // the uoe could occur while resolving
169 // associations, leaving the PC in an
170 // inconsistent state
171 log.debug( "could not reassemble cached result set" );
172 cacheRegion.evict( key );
173 return null;
174 }
175 else {
176 throw uoe;
177 }
178 }
179 }
180 return result;
181 }
182
183 protected boolean isUpToDate(Set spaces, Long timestamp) {
184 if ( log.isDebugEnabled() ) {
185 log.debug( "Checking query spaces for up-to-dateness: " + spaces );
186 }
187 return updateTimestampsCache.isUpToDate( spaces, timestamp );
188 }
189
190 public void destroy() {
191 try {
192 cacheRegion.destroy();
193 }
194 catch ( Exception e ) {
195 log.warn( "could not destroy query cache: " + cacheRegion.getName(), e );
196 }
197 }
198
199 public QueryResultsRegion getRegion() {
200 return cacheRegion;
201 }
202
203 public String toString() {
204 return "StandardQueryCache(" + cacheRegion.getName() + ')';
205 }
206
207 }