Skip to content

Commit 2f31199

Browse files
authored
Merge pull request #313 from devgateway/feature/jpa-query-cache
#311 Cache hibernate queries from JPA repositories
2 parents 8612369 + b2978bf commit 2f31199

File tree

16 files changed

+291
-10
lines changed

16 files changed

+291
-10
lines changed

checkstyle.xml

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<property name="severity" value="warning"/>
1313
<module name="TreeWalker">
1414
<property name="tabWidth" value="4"/>
15+
<module name="org.devgateway.toolkit.checks.CachableQueryAnnotationCheck" />
1516
<module name="JavadocMethod">
1617
<property name="severity" value="ignore"/>
1718
<property name="suppressLoadErrors" value="true"/>
@@ -42,7 +43,7 @@
4243
<module name="TypeName"/>
4344
<module name="AvoidStarImport"/>
4445
<module name="IllegalImport"/>
45-
<module name="RedundantImport"/>
46+
<module name="RedundantImport"/>
4647
<module name="LineLength">
4748
<property name="max" value="120"/>
4849
</module>
@@ -111,7 +112,7 @@
111112
<property name="severity" value="ignore"/>
112113
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
113114
</module>
114-
<module name="Translation"/>
115+
<module name="Translation"/>
115116
<module name="FileTabCharacter">
116117
<property name="fileExtensions" value="java,xml,html,md"/>
117118
</module>

checkstyle/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target/

checkstyle/pom.xml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<artifactId>checkstyle</artifactId>
8+
<groupId>org.devgateway.toolkit</groupId>
9+
<version>1.0</version>
10+
11+
<properties>
12+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13+
<maven.compiler.source>8</maven.compiler.source>
14+
<maven.compiler.target>8</maven.compiler.target>
15+
<maven-checkstyle-plugin.version>3.0.0</maven-checkstyle-plugin.version>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.apache.maven.plugins</groupId>
21+
<artifactId>maven-checkstyle-plugin</artifactId>
22+
<version>${maven-checkstyle-plugin.version}</version>
23+
</dependency>
24+
</dependencies>
25+
26+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.devgateway.toolkit.checks;
2+
3+
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
4+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
5+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
6+
import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
7+
8+
/**
9+
* Checks that the methods of all classes annotated with @CacheableHibernateQueryResult
10+
* are explicitly annotated to cache or not the query result.
11+
*
12+
* Anytime a new method is defined this will catch a potential missing caching definition.
13+
*
14+
* @author Nadejda Mandrescu
15+
*/
16+
public class CachableQueryAnnotationCheck extends AbstractCheck {
17+
private static final String ERROR_MESSAGE =
18+
"@CacheableHibernateQueryResult must annotate its methods explicitly either with " +
19+
"@CacheHibernateQueryResult or @NoCacheHibernateQueryResult";
20+
21+
@Override
22+
public int[] getDefaultTokens() {
23+
return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
24+
}
25+
26+
@Override
27+
public void visitToken(DetailAST ast) {
28+
if (AnnotationUtility.containsAnnotation(ast, "CacheableHibernateQueryResult")) {
29+
DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
30+
DetailAST methodDef = objBlock.findFirstToken(TokenTypes.METHOD_DEF);
31+
while (methodDef != null) {
32+
if (methodDef.getType() == TokenTypes.METHOD_DEF) {
33+
if (!(AnnotationUtility.containsAnnotation(methodDef, "CacheHibernateQueryResult")
34+
|| AnnotationUtility.containsAnnotation(methodDef, "NoCacheHibernateQueryResult"))) {
35+
log(methodDef.getLineNo(), ERROR_MESSAGE);
36+
}
37+
}
38+
methodDef = methodDef.getNextSibling();
39+
}
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!DOCTYPE checkstyle-packages PUBLIC
4+
"-//Checkstyle//DTD Package Names Configuration 1.0//EN"
5+
"https://checkstyle.org/dtds/packages_1_0.dtd">
6+
7+
<checkstyle-packages>
8+
<package name="org.devgateway.toolkit.checks" />
9+
<package name="com.puppycrawl.tools.checkstyle" />
10+
</checkstyle-packages>

persistence/src/main/java/org/devgateway/toolkit/persistence/application.properties

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ spring.jpa.hibernate.jdbc.batch_versioned_data=true
2727
spring.jpa.hibernate.bytecode.use_reflection_optimizer=true
2828
spring.jpa.hibernate.bytecode.provider=javassist
2929
spring.jpa.hibernate.cache.use_query_cache=true
30+
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
3031
spring.servlet.multipart.enabled = false
3132

3233
#enable modified flag for envers, to track field-level modifications
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.devgateway.toolkit.persistence.checks;
2+
3+
import org.devgateway.toolkit.persistence.repository.CacheableHibernateQueryResult;
4+
import org.reflections.Reflections;
5+
import org.springframework.stereotype.Component;
6+
7+
import javax.annotation.PostConstruct;
8+
import java.util.HashSet;
9+
import java.util.Set;
10+
11+
/**
12+
* @author Nadejda Mandrescu
13+
*/
14+
@Component
15+
public class ValidateCacheableHibernateQueryResult {
16+
private static final String SCAN_PACKAGE = "org.devgateway";
17+
private static final String ERROR_MESSAGE = "Classes inherited from @CacheableHibernateQueryResult base class "
18+
+ "must also be annotated with @CacheableHibernateQueryResult: ";
19+
20+
private Set<String> validated = new HashSet<>();
21+
private Set<String> invalidClasses = new HashSet<>();
22+
23+
@PostConstruct
24+
public void validate() {
25+
Reflections reflections = new Reflections(SCAN_PACKAGE);
26+
Set<Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(CacheableHibernateQueryResult.class);
27+
annotatedClasses.forEach(annotatedBaseClass -> validateClass(null, annotatedBaseClass, reflections));
28+
if (!invalidClasses.isEmpty()) {
29+
throw new RuntimeException(ERROR_MESSAGE + String.join(", ", invalidClasses));
30+
}
31+
}
32+
33+
private void validateClass(final Class<?> parent, final Class<?> clazz, final Reflections reflections) {
34+
boolean notYetValidated = validated.add(clazz.getCanonicalName());
35+
if (notYetValidated) {
36+
if (parent != null) {
37+
CacheableHibernateQueryResult annotation = clazz.getAnnotation(CacheableHibernateQueryResult.class);
38+
if (annotation == null) {
39+
invalidClasses.add(clazz.getCanonicalName());
40+
}
41+
}
42+
reflections.getSubTypesOf(clazz).forEach(subClass -> validateClass(clazz, subClass, reflections));
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package org.devgateway.toolkit.persistence.repository;
22

33
import org.devgateway.toolkit.persistence.dao.AdminSettings;
4-
import org.devgateway.toolkit.persistence.repository.norepository.BaseJpaRepository;
4+
import org.devgateway.toolkit.persistence.repository.norepository.CacheableQueryResultRepository;
55
import org.springframework.transaction.annotation.Transactional;
66

77
/**
88
* @author idobre
99
* @since 6/22/16
1010
*/
1111
@Transactional
12-
public interface AdminSettingsRepository extends BaseJpaRepository<AdminSettings, Long> {
12+
@CacheableHibernateQueryResult
13+
public interface AdminSettingsRepository extends CacheableQueryResultRepository<AdminSettings, Long> {
1314

1415
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.devgateway.toolkit.persistence.repository;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
import javax.persistence.QueryHint;
9+
10+
import org.springframework.data.jpa.repository.QueryHints;
11+
12+
/**
13+
* @author Octavian Ciubotaru
14+
*/
15+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@QueryHints({@QueryHint(name = "org.hibernate.cacheable", value = "true")})
18+
public @interface CacheHibernateQueryResult {
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.devgateway.toolkit.persistence.repository;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* This annotation helps to guarantee that once query result caching is used,
10+
* then all methods across the repo hierarchy explicitly use the caching or not.
11+
*
12+
* Done with:
13+
* 1. Checkstyle CachableQueryAnnotationCheck: validates that such class methods
14+
* explicitly declare @CacheHibernateQueryResult or @NoCacheHibernateQueryResult.
15+
*
16+
* 2. ValidateCacheableHibernateQueryResult: checks at app startup that
17+
* all subclasses of a class annotated with @CacheableHibernateQueryResult
18+
* are also annotated with @CacheableHibernateQueryResult.
19+
* This will guarantee the checkstyle validation for the entire hierarchy.
20+
*/
21+
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
22+
@Retention(RetentionPolicy.RUNTIME)
23+
public @interface CacheableHibernateQueryResult {
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.devgateway.toolkit.persistence.repository;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
9+
@Retention(RetentionPolicy.CLASS)
10+
public @interface NoCacheHibernateQueryResult {
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.devgateway.toolkit.persistence.repository.norepository;
2+
3+
import org.devgateway.toolkit.persistence.repository.CacheHibernateQueryResult;
4+
import org.devgateway.toolkit.persistence.repository.CacheableHibernateQueryResult;
5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
7+
import org.springframework.data.domain.Sort;
8+
import org.springframework.data.jpa.domain.Specification;
9+
import org.springframework.data.repository.NoRepositoryBean;
10+
import org.springframework.lang.Nullable;
11+
12+
import java.io.Serializable;
13+
import java.util.List;
14+
import java.util.Optional;
15+
16+
/**
17+
* @author Octavian Ciubotaru
18+
*/
19+
@NoRepositoryBean
20+
@CacheableHibernateQueryResult
21+
public interface CacheableQueryResultRepository<T, ID extends Serializable> extends BaseJpaRepository<T, ID> {
22+
23+
@Override
24+
@CacheHibernateQueryResult
25+
List<T> findAll();
26+
27+
@Override
28+
@CacheHibernateQueryResult
29+
List<T> findAll(Sort sort);
30+
31+
@Override
32+
@CacheHibernateQueryResult
33+
List<T> findAll(Specification<T> spec);
34+
35+
@Override
36+
@CacheHibernateQueryResult
37+
Page<T> findAll(Specification<T> spec, Pageable pageable);
38+
39+
@Override
40+
@CacheHibernateQueryResult
41+
Page<T> findAll(Pageable pageable);
42+
43+
@Override
44+
@CacheHibernateQueryResult
45+
List<T> findAll(Specification<T> spec, Sort sort);
46+
47+
@Override
48+
@CacheHibernateQueryResult
49+
Optional<T> findOne(@Nullable Specification<T> spec);
50+
51+
@Override
52+
@CacheHibernateQueryResult
53+
long count(Specification<T> spec);
54+
55+
@Override
56+
@CacheHibernateQueryResult
57+
long count();
58+
59+
@Override
60+
@CacheHibernateQueryResult
61+
Optional<T> findById(ID id);
62+
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
# Uncomment for Hibernate SQL statements and parameters
3+
#logging.level.org.hibernate.SQL=DEBUG
4+
#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

persistence/src/main/resources/ehcache.xml

+15
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,19 @@
5050
maxEntriesLocalHeap="100000"
5151
statistics="true">
5252
</cache>
53+
54+
<cache name="default-update-timestamps-region"
55+
eternal="true"
56+
diskPersistent="false"
57+
maxEntriesLocalHeap="10000"
58+
statistics="true">
59+
</cache>
60+
61+
<cache name="default-query-results-region"
62+
eternal="true"
63+
diskPersistent="false"
64+
maxEntriesLocalHeap="10000"
65+
statistics="true">
66+
</cache>
67+
5368
</ehcache>

persistence/src/test/java/org/devgateway/toolkit/persistence/excel/test/TestAddressRepository.java

-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import org.springframework.data.domain.Sort;
88
import org.springframework.data.jpa.domain.Specification;
99

10-
import java.io.Serializable;
1110
import java.util.List;
1211
import java.util.Optional;
1312

@@ -34,7 +33,6 @@ public boolean existsById(Long aLong) {
3433
return false;
3534
}
3635

37-
3836
@Override
3937
public List findAll() {
4038
return null;
@@ -164,6 +162,4 @@ public long count(Example example) {
164162
public boolean exists(Example example) {
165163
return false;
166164
}
167-
168-
169165
}

0 commit comments

Comments
 (0)