Skip to content

Commit

Permalink
Merge pull request #313 from devgateway/feature/jpa-query-cache
Browse files Browse the repository at this point in the history
#311 Cache hibernate queries from JPA repositories
  • Loading branch information
mpostelnicu authored May 19, 2021
2 parents 8612369 + b2978bf commit 2f31199
Show file tree
Hide file tree
Showing 16 changed files with 291 additions and 10 deletions.
5 changes: 3 additions & 2 deletions checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<property name="severity" value="warning"/>
<module name="TreeWalker">
<property name="tabWidth" value="4"/>
<module name="org.devgateway.toolkit.checks.CachableQueryAnnotationCheck" />
<module name="JavadocMethod">
<property name="severity" value="ignore"/>
<property name="suppressLoadErrors" value="true"/>
Expand Down Expand Up @@ -42,7 +43,7 @@
<module name="TypeName"/>
<module name="AvoidStarImport"/>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="RedundantImport"/>
<module name="LineLength">
<property name="max" value="120"/>
</module>
Expand Down Expand Up @@ -111,7 +112,7 @@
<property name="severity" value="ignore"/>
<metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
</module>
<module name="Translation"/>
<module name="Translation"/>
<module name="FileTabCharacter">
<property name="fileExtensions" value="java,xml,html,md"/>
</module>
Expand Down
1 change: 1 addition & 0 deletions checkstyle/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target/
26 changes: 26 additions & 0 deletions checkstyle/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>checkstyle</artifactId>
<groupId>org.devgateway.toolkit</groupId>
<version>1.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven-checkstyle-plugin.version>3.0.0</maven-checkstyle-plugin.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.devgateway.toolkit.checks;

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;

/**
* Checks that the methods of all classes annotated with @CacheableHibernateQueryResult
* are explicitly annotated to cache or not the query result.
*
* Anytime a new method is defined this will catch a potential missing caching definition.
*
* @author Nadejda Mandrescu
*/
public class CachableQueryAnnotationCheck extends AbstractCheck {
private static final String ERROR_MESSAGE =
"@CacheableHibernateQueryResult must annotate its methods explicitly either with " +
"@CacheHibernateQueryResult or @NoCacheHibernateQueryResult";

@Override
public int[] getDefaultTokens() {
return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
}

@Override
public void visitToken(DetailAST ast) {
if (AnnotationUtility.containsAnnotation(ast, "CacheableHibernateQueryResult")) {
DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
DetailAST methodDef = objBlock.findFirstToken(TokenTypes.METHOD_DEF);
while (methodDef != null) {
if (methodDef.getType() == TokenTypes.METHOD_DEF) {
if (!(AnnotationUtility.containsAnnotation(methodDef, "CacheHibernateQueryResult")
|| AnnotationUtility.containsAnnotation(methodDef, "NoCacheHibernateQueryResult"))) {
log(methodDef.getLineNo(), ERROR_MESSAGE);
}
}
methodDef = methodDef.getNextSibling();
}
}
}
}
10 changes: 10 additions & 0 deletions checkstyle/src/main/resources/packagenames.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE checkstyle-packages PUBLIC
"-//Checkstyle//DTD Package Names Configuration 1.0//EN"
"https://checkstyle.org/dtds/packages_1_0.dtd">

<checkstyle-packages>
<package name="org.devgateway.toolkit.checks" />
<package name="com.puppycrawl.tools.checkstyle" />
</checkstyle-packages>
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ spring.jpa.hibernate.jdbc.batch_versioned_data=true
spring.jpa.hibernate.bytecode.use_reflection_optimizer=true
spring.jpa.hibernate.bytecode.provider=javassist
spring.jpa.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.servlet.multipart.enabled = false

#enable modified flag for envers, to track field-level modifications
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.devgateway.toolkit.persistence.checks;

import org.devgateway.toolkit.persistence.repository.CacheableHibernateQueryResult;
import org.reflections.Reflections;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Set;

/**
* @author Nadejda Mandrescu
*/
@Component
public class ValidateCacheableHibernateQueryResult {
private static final String SCAN_PACKAGE = "org.devgateway";
private static final String ERROR_MESSAGE = "Classes inherited from @CacheableHibernateQueryResult base class "
+ "must also be annotated with @CacheableHibernateQueryResult: ";

private Set<String> validated = new HashSet<>();
private Set<String> invalidClasses = new HashSet<>();

@PostConstruct
public void validate() {
Reflections reflections = new Reflections(SCAN_PACKAGE);
Set<Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(CacheableHibernateQueryResult.class);
annotatedClasses.forEach(annotatedBaseClass -> validateClass(null, annotatedBaseClass, reflections));
if (!invalidClasses.isEmpty()) {
throw new RuntimeException(ERROR_MESSAGE + String.join(", ", invalidClasses));
}
}

private void validateClass(final Class<?> parent, final Class<?> clazz, final Reflections reflections) {
boolean notYetValidated = validated.add(clazz.getCanonicalName());
if (notYetValidated) {
if (parent != null) {
CacheableHibernateQueryResult annotation = clazz.getAnnotation(CacheableHibernateQueryResult.class);
if (annotation == null) {
invalidClasses.add(clazz.getCanonicalName());
}
}
reflections.getSubTypesOf(clazz).forEach(subClass -> validateClass(clazz, subClass, reflections));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package org.devgateway.toolkit.persistence.repository;

import org.devgateway.toolkit.persistence.dao.AdminSettings;
import org.devgateway.toolkit.persistence.repository.norepository.BaseJpaRepository;
import org.devgateway.toolkit.persistence.repository.norepository.CacheableQueryResultRepository;
import org.springframework.transaction.annotation.Transactional;

/**
* @author idobre
* @since 6/22/16
*/
@Transactional
public interface AdminSettingsRepository extends BaseJpaRepository<AdminSettings, Long> {
@CacheableHibernateQueryResult
public interface AdminSettingsRepository extends CacheableQueryResultRepository<AdminSettings, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.devgateway.toolkit.persistence.repository;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.persistence.QueryHint;

import org.springframework.data.jpa.repository.QueryHints;

/**
* @author Octavian Ciubotaru
*/
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@QueryHints({@QueryHint(name = "org.hibernate.cacheable", value = "true")})
public @interface CacheHibernateQueryResult {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.devgateway.toolkit.persistence.repository;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This annotation helps to guarantee that once query result caching is used,
* then all methods across the repo hierarchy explicitly use the caching or not.
*
* Done with:
* 1. Checkstyle CachableQueryAnnotationCheck: validates that such class methods
* explicitly declare @CacheHibernateQueryResult or @NoCacheHibernateQueryResult.
*
* 2. ValidateCacheableHibernateQueryResult: checks at app startup that
* all subclasses of a class annotated with @CacheableHibernateQueryResult
* are also annotated with @CacheableHibernateQueryResult.
* This will guarantee the checkstyle validation for the entire hierarchy.
*/
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheableHibernateQueryResult {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.devgateway.toolkit.persistence.repository;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface NoCacheHibernateQueryResult {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.devgateway.toolkit.persistence.repository.norepository;

import org.devgateway.toolkit.persistence.repository.CacheHibernateQueryResult;
import org.devgateway.toolkit.persistence.repository.CacheableHibernateQueryResult;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.lang.Nullable;

import java.io.Serializable;
import java.util.List;
import java.util.Optional;

/**
* @author Octavian Ciubotaru
*/
@NoRepositoryBean
@CacheableHibernateQueryResult
public interface CacheableQueryResultRepository<T, ID extends Serializable> extends BaseJpaRepository<T, ID> {

@Override
@CacheHibernateQueryResult
List<T> findAll();

@Override
@CacheHibernateQueryResult
List<T> findAll(Sort sort);

@Override
@CacheHibernateQueryResult
List<T> findAll(Specification<T> spec);

@Override
@CacheHibernateQueryResult
Page<T> findAll(Specification<T> spec, Pageable pageable);

@Override
@CacheHibernateQueryResult
Page<T> findAll(Pageable pageable);

@Override
@CacheHibernateQueryResult
List<T> findAll(Specification<T> spec, Sort sort);

@Override
@CacheHibernateQueryResult
Optional<T> findOne(@Nullable Specification<T> spec);

@Override
@CacheHibernateQueryResult
long count(Specification<T> spec);

@Override
@CacheHibernateQueryResult
long count();

@Override
@CacheHibernateQueryResult
Optional<T> findById(ID id);

}
4 changes: 4 additions & 0 deletions persistence/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

# Uncomment for Hibernate SQL statements and parameters
#logging.level.org.hibernate.SQL=DEBUG
#logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
15 changes: 15 additions & 0 deletions persistence/src/main/resources/ehcache.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,19 @@
maxEntriesLocalHeap="100000"
statistics="true">
</cache>

<cache name="default-update-timestamps-region"
eternal="true"
diskPersistent="false"
maxEntriesLocalHeap="10000"
statistics="true">
</cache>

<cache name="default-query-results-region"
eternal="true"
diskPersistent="false"
maxEntriesLocalHeap="10000"
statistics="true">
</cache>

</ehcache>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import java.io.Serializable;
import java.util.List;
import java.util.Optional;

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


@Override
public List findAll() {
return null;
Expand Down Expand Up @@ -164,6 +162,4 @@ public long count(Example example) {
public boolean exists(Example example) {
return false;
}


}
Loading

0 comments on commit 2f31199

Please sign in to comment.