I am using Springboot 2.1 and spring data-jpa for persistence using @RepositoryRestResource. I have enabled caching for my API calls and that works well with @Cacheable, But now I want to enable second-level cache for all my JPA entities and have below configurations but still any query on these entities are firing hibernate queries and not using cache. Please let me know what I am missing for this Entity caching.
Gradle Dependency:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
implementation 'org.springframework.boot:spring-boot-starter-jersey'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.ehcache:ehcache:3.7.1'
implementation 'javax.cache:cache-api'
compile group: 'org.hibernate', name: 'hibernate-jcache', version: '5.3.10.Final'
runtimeOnly 'mysql:mysql-connector-java'
}
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mar_db
spring.datasource.username=root
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE
spring.cache.jcache.config=classpath:ehcache.xml
ehcache.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.ehcache.org/v3"
xmlns:jsr107="http://www.ehcache.org/v3/jsr107">
<service>
<jsr107:defaults enable-statistics="true" />
</service>
<cache alias="readOnlyEntityData">
<key-type>java.lang.Object</key-type>
<expiry>
<ttl unit="minutes">360</ttl>
</expiry>
<listeners>
<listener>
<class>com.tfsc.ilabs.selfservice.common.utils.CacheLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>UPDATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>REMOVED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">1000</heap>
<offheap unit="MB">256</offheap>
</resources>
</cache>
</config>
Config.java
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import java.util.Objects;
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "readOnlyEntityData")
public class Config {
@Id
@NotNull
@Column(unique = true)
private String code;
@NotNull
private String value;
@NotNull
@Column(columnDefinition = "boolean default true")
private boolean status;
@NotNull
private String type;
......
}
Service class:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class DBConfigServiceImpl implements DBConfigService {
@Autowired
private DBConfigRepository dbConfigRepository;
@Override
public List<ConfigDTO> findAll() {
return dbConfigRepository.findAll().stream().map(Config::toDTO).collect(Collectors.toList());
}
@Cacheable(value = "readOnlyEntityData", keyGenerator = "cacheKeyGenerator")
@Override
public ConfigDTO findByCode(String code) {
Optional<Config> config = dbConfigRepository.findById(code);
if(config.isPresent()){
return config.get().toDTO();
}else {
throw new NoSuchResourceException(new ErrorObject("Config not found {0}", code));
}
}
}
JpaRepository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource
public interface DBConfigRepository extends JpaRepository<Config, String> {
}
Only Methods marked with @Cacheable responses are cached and works well, But when It comes to Entity level caching of data, always db query is fired to pull the data. Please let me know what I am missing here.
Edit: Found that only JPA's findById() returns from the cache as hibernate cache is stored in hydrated form as key value pair, where id is the key. Whereas findAll() and findByType() etc methods always fires DB queries to get data. How to make them store and return from cache.?
findAll()
,findByType()
(any other methods where you want to use second level cache) mehtods toDBConfigRepository
and annotate them with@QueryHints(value = { @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_CACHEABLE, value = "true")})
(since this annotation can not be applied to the whole repository)? – Seale