Spring boot mongodb transaction rollback - java

Here is the behavior I wish to achieve:
#Transactional(rollbackFor = NullPointerException.class)
#PostMapping(consumes = {"application/json"})
public Employee createEmployee(#Valid #RequestBody Employee employee) {
Optional<Department> departmentOptional = departmentRepository.findById(employee.getDeptId());
EmployeeAggregate employeeAggregate = employeeAggregateRepository.findAll().get(0);
employeeAggregate.setTotalEmployeeCount(employeeAggregate.getTotalEmployeeCount()+1);
employeeAggregateRepository.save(employeeAggregate); <--This line should be rolled back when exception is thrown.
if(departmentOptional.isPresent()) {
Department department = departmentOptional.get();
return employeeRepository.save(employee);
}
else{
throw new NullPointerException();
}
}
There are two documents, Employees, and EmployeeAggregate. I want to be able to rollback the employee count stored in EmployeeAggregate if I am unable to create an employee in Employees document.
Here is my mongo config file:
#Configuration
public class MongoConfig {
#Bean
#Autowired
#ConditionalOnExpression("'${mongo.transactions}'=='enabled'")
MongoTransactionManager mongoTransactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
public #Bean MongoClient mongoClient() {
return new MongoClient("localhost");
}
public #Bean
MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoClient(), "EmployeeDatabase");
}
}
Currently, no rollback takes place if Employee creation fails. The count gets updated by one irrespective of successful employee creation or not. An explanation of multidocument transactions in spring boot would be appreciated, since the documentation is a bit confusing.

Related

Setting Hazelcast Cache for Multi-tenancy

I am currently using the JHipster generator for really boiler plate code which involves HazelCast as a second level cache. I was able to get Multi-tenancy (schema per tenant) working with a header based tenant context. The problem I have now, is that the #Cacheable annotations all share a context. If the cache is hot, I end up with cross-schema data. For example, tenant1 pulls all records from their table which is cached. Tenant 2 goes to pull the same items from their table, the cache is read, and it never goes to the actual tenant db. An easy fix would be disable caching all together but I would like to not do that. I can not for the life of me figure out how to make hazelcast aware of the tenant context - documentation is lacking. Some others have solved this with using custom name resolvers but it doesn't appear to be as dynamic as I was hoping (i.e. you have to know all of the tenants ahead of time). Thoughts?
Current cache config:
#Configuration
#EnableCaching
public class CacheConfiguration implements DisposableBean {
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
private final Environment env;
private final ServerProperties serverProperties;
private final DiscoveryClient discoveryClient;
private Registration registration;
public CacheConfiguration(Environment env, ServerProperties serverProperties, DiscoveryClient discoveryClient) {
this.env = env;
this.serverProperties = serverProperties;
this.discoveryClient = discoveryClient;
}
#Autowired(required = false)
public void setRegistration(Registration registration) {
this.registration = registration;
}
#Override
public void destroy() throws Exception {
log.info("Closing Cache Manager");
Hazelcast.shutdownAll();
}
#Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
log.debug("Starting HazelcastCacheManager");
return new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance);
}
#Bean
public HazelcastInstance hazelcastInstance(JHipsterProperties jHipsterProperties) {
log.debug("Configuring Hazelcast");
HazelcastInstance hazelCastInstance = Hazelcast.getHazelcastInstanceByName("SampleApp");
if (hazelCastInstance != null) {
log.debug("Hazelcast already initialized");
return hazelCastInstance;
}
Config config = new Config();
config.setInstanceName("SampleApp");
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
if (this.registration == null) {
log.warn("No discovery service is set up, Hazelcast cannot create a cluster.");
} else {
// The serviceId is by default the application's name,
// see the "spring.application.name" standard Spring property
String serviceId = registration.getServiceId();
log.debug("Configuring Hazelcast clustering for instanceId: {}", serviceId);
// In development, everything goes through 127.0.0.1, with a different port
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
log.debug("Application is running with the \"dev\" profile, Hazelcast " +
"cluster will only work with localhost instances");
System.setProperty("hazelcast.local.localAddress", "127.0.0.1");
config.getNetworkConfig().setPort(serverProperties.getPort() + 5701);
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
String clusterMember = "127.0.0.1:" + (instance.getPort() + 5701);
log.debug("Adding Hazelcast (dev) cluster member {}", clusterMember);
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(clusterMember);
}
} else { // Production configuration, one host per instance all using port 5701
config.getNetworkConfig().setPort(5701);
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true);
for (ServiceInstance instance : discoveryClient.getInstances(serviceId)) {
String clusterMember = instance.getHost() + ":5701";
log.debug("Adding Hazelcast (prod) cluster member {}", clusterMember);
config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(clusterMember);
}
}
}
config.getMapConfigs().put("default", initializeDefaultMapConfig(jHipsterProperties));
// Full reference is available at: http://docs.hazelcast.org/docs/management-center/3.9/manual/html/Deploying_and_Starting.html
config.setManagementCenterConfig(initializeDefaultManagementCenterConfig(jHipsterProperties));
config.getMapConfigs().put("com.test.sampleapp.domain.*", initializeDomainMapConfig(jHipsterProperties));
return Hazelcast.newHazelcastInstance(config);
}
private ManagementCenterConfig initializeDefaultManagementCenterConfig(JHipsterProperties jHipsterProperties) {
ManagementCenterConfig managementCenterConfig = new ManagementCenterConfig();
managementCenterConfig.setEnabled(jHipsterProperties.getCache().getHazelcast().getManagementCenter().isEnabled());
managementCenterConfig.setUrl(jHipsterProperties.getCache().getHazelcast().getManagementCenter().getUrl());
managementCenterConfig.setUpdateInterval(jHipsterProperties.getCache().getHazelcast().getManagementCenter().getUpdateInterval());
return managementCenterConfig;
}
private MapConfig initializeDefaultMapConfig(JHipsterProperties jHipsterProperties) {
MapConfig mapConfig = new MapConfig();
/*
Number of backups. If 1 is set as the backup-count for example,
then all entries of the map will be copied to another JVM for
fail-safety. Valid numbers are 0 (no backup), 1, 2, 3.
*/
mapConfig.setBackupCount(jHipsterProperties.getCache().getHazelcast().getBackupCount());
/*
Valid values are:
NONE (no eviction),
LRU (Least Recently Used),
LFU (Least Frequently Used).
NONE is the default.
*/
mapConfig.setEvictionPolicy(EvictionPolicy.LRU);
/*
Maximum size of the map. When max size is reached,
map is evicted based on the policy defined.
Any integer between 0 and Integer.MAX_VALUE. 0 means
Integer.MAX_VALUE. Default is 0.
*/
mapConfig.setMaxSizeConfig(new MaxSizeConfig(0, MaxSizeConfig.MaxSizePolicy.USED_HEAP_SIZE));
return mapConfig;
}
private MapConfig initializeDomainMapConfig(JHipsterProperties jHipsterProperties) {
MapConfig mapConfig = new MapConfig();
mapConfig.setTimeToLiveSeconds(jHipsterProperties.getCache().getHazelcast().getTimeToLiveSeconds());
return mapConfig;
}
}
Sample Repository using cacheNames...
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
String USERS_BY_LOGIN_CACHE = "usersByLogin";
String USERS_BY_EMAIL_CACHE = "usersByEmail";
String USERS_BY_ID_CACHE = "usersById";
Optional<User> findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime);
Optional<User> findOneByResetKey(String resetKey);
Optional<User> findOneByEmailIgnoreCase(String email);
Optional<User> findOneByLogin(String login);
#EntityGraph(attributePaths = "roles")
#Cacheable(cacheNames = USERS_BY_ID_CACHE)
Optional<User> findOneWithRolesById(Long id);
#EntityGraph(attributePaths = "roles")
#Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithRolesByLogin(String login);
#EntityGraph(attributePaths = { "roles", "roles.permissions" })
#Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithRolesAndPermissionsByLogin(String login);
#EntityGraph(attributePaths = "roles")
#Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneWithRolesByEmail(String email);
Page<User> findAllByLoginNot(Pageable pageable, String login);
}
I am using tenant per database (MySQL), but as long as you are setting a thread context above here is what I'm doing - I'm using Spring Boot. I've created a custom Cache Key generator which combines the tenant name + class + and method. You can really choose any combination. Whenever I pass that tenant back it pulls the correct entries. In the Hazelcast command center for my AppointmentType map type I see the number of entries increment per tenant.
Some other references that may be helpful:
https://www.javadevjournal.com/spring/spring-cache-custom-keygenerator/
https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/cache.html (search for keyGenerator="myKeyGenerator")
In your class where you want to cache (mine is a service class):
#Service
public class AppointmentTypeService {
private static final Logger LOGGER = LoggerFactory.getLogger(AppointmentTypeService.class);
private final AppointmentTypeRepository appointmentTypeRepository;
#Autowired
AppointmentTypeService(AppointmentTypeRepository appointmentTypeRepository) {
this.appointmentTypeRepository = appointmentTypeRepository;
}
//ADD keyGenerator value. Name is the name of the bean of the class
#Cacheable(value="appointmentType", keyGenerator = "multiTenantCacheKeyGenerator")
public List<AppointmentType> list() {
return this.appointmentTypeRepository.findAll();
}
#CacheEvict(value="appointmentType", allEntries=true)
public Long create(AppointmentType request) {
this.appointmentTypeRepository.saveAndFlush(request);
return request.getAppointmentTypeId();
}
#CacheEvict(value="appointmentType", allEntries=true)
public void delete(Long id) {
this.appointmentTypeRepository.deleteById(id);
}
public Optional<AppointmentType> findById(Long id) {
return this.appointmentTypeRepository.findById(id);
}
}
Create key generator class
//setting the bean name here
#Component("multiTenantCacheKeyGenerator")
public class MultiTenantCacheKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object o, Method method, Object... os) {
StringBuilder sb = new StringBuilder();
sb.append(TenantContext.getCurrentTenantInstanceName()) //my tenant context class which is using local thread. I set the value in the Spring filter.
.append("_")
.append(o.getClass().getSimpleName())
.append("-")
.append(method.getName());
}
return sb.toString();
}
}
One approach to defining different cache keys for the tenants is to override the method getCache in org.springframework.cache.CacheManager, as suggested here: Extended spring cache...
As of Jhipster 7.0.1, the CacheManager for Hazelcast is defined in the class CacheConfiguration as stated bellow:
#Configuration
#EnableCaching
public class CacheConfiguration {
//...
#Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
return new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance);
}
//...
}
To have the cache keys prefixed with the tenant id, the following code may be used as a starting point:
#Configuration
#EnableCaching
public class CacheConfiguration {
#Bean
public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
return new com.hazelcast.spring.cache.HazelcastCacheManager(hazelcastInstance){
#Override
public Cache getCache(String name) {
String tenantId = TenantStorage.getTenantId();
if (StringUtils.isNotBlank(tenantId)){
return super.getCache(String.format("%s:%s", tenantId, name));
}
return super.getCache(name);
}
};
}
}
Note: in the above code, TenantStorage.getTenantId() is a static function one should implement and that returns the current tenant id.
Consider the class posted by the OP:
#Cacheable(cacheNames = "usersByLogin")
Optional<User> findOneWithRolesByLogin(String login);
The following cache values will be used by HazelCast:
tenant1 => tenant1:usersByLogin
tenant2 => tenant2:usersByLogin
null => usersByLogin

Spring batch JPAItemReader performance Issue

Below is the configuration of my spring batch job which takes records from DB, do some processing in item processor, updates status column and writes back to DB.
When I ran for 10k records, I could see its taking every record one by one and updating status in the same manner. Initially I was planning to use multithreading but it doesn't make any sense as my job runs once in a day with number of records ranging from 10 to 100k. ( Records are less than 5k in most of the days and a very few days in a year ( 5 to 10 days) it comes to 50k to 100k).
I don't want to add more cpus and getting charged by Kubernetes just for 10 days of an year. Now the problem is when I ran this job, it takes only 100 records that it runs every select query independently instead of taking 100 at a time. Also update is also one record at a time and it takes 10 mins to process 10k records which is really slow.
How can do a faster read, process and write? I can get rid of multithreading and have a bit more of CPU utilization once in a while. More information is given as comments in code.
#Configuration
#EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer{
public final static Logger logger = LoggerFactory.getLogger(BatchConfiguration.class);
#Autowired
JobBuilderFactory jobBuilderFactory;
#Autowired
StepBuilderFactory stepBuilderFactory;
#Autowired
MyRepository myRepository;
#Autowired
private EntityManagerFactory entityManagerFactory;
#Value("${chunk-size}")
private int chunkSize;
#Value("${max-threads}")
private int maxThreads;
private final DataSource dataSource;
/**
* #param dataSource
* Override to do not set datasource even if a datasource exist during intialization.
* Initialize will use a Map based JobRepository (instead of database) for Spring batch meta tables
*/
#Override
public void setDataSource(DataSource dataSource) {
}
#Override
public PlatformTransactionManager getTransactionManager() {
return jpaTransactionManager();
}
#Autowired
public BatchConfiguration(#Qualifier("dataSource") DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public JpaTransactionManager jpaTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
#Bean
#StepScope
public JdbcPagingItemReader<ModelEntity> importReader() { // I tried using RepositoryItemReader but records were skipped by JPA hence I went for JdbcPagingItemReader
JdbcPagingItemReader<ModelEntity> reader = new JdbcPagingItemReader<ModelEntity>();
final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
sqlPagingQueryProviderFactoryBean.setDataSource( dataSource );
sqlPagingQueryProviderFactoryBean.setSelectClause( "SELECT *" );
sqlPagingQueryProviderFactoryBean.setFromClause( "FROM mytable" );
sqlPagingQueryProviderFactoryBean.setWhereClause( "WHERE STATUS = 'myvalue' " );
sqlPagingQueryProviderFactoryBean.setSortKey( "primarykey" );
try {
reader.setQueryProvider( sqlPagingQueryProviderFactoryBean.getObject() );
} catch (Exception e) {
e.printStackTrace();
}
reader.setDataSource( dataSource );
reader.setPageSize( chunkSize );
reader.setSaveState( Boolean.FALSE );
reader.setRowMapper( new BeanPropertyRowMapper<ModelEntity>(ModelEntity.class ) );
return reader;
}
#Bean
public ItemWriter<ModelEntity> databaseWriter() {
RepositoryItemWriter<ModelEntity> repositoryItemWriter=new RepositoryItemWriter<>();
repositoryItemWriter.setRepository(myRepository);
repositoryItemWriter.setMethodName("save");
return repositoryItemWriter;
}
#Bean
public Myprocessor myprocessor() {
return new Myprocessor();
}
#Bean
public JobExecutionListener jobExecutionListener() {
return new JobExecutionListener();
}
#Bean
public StepExecutionListener stepExecutionListener() {
return new StepExecutionListener();
}
#Bean
public ChunkExecutionListener chunkListener() {
return new ChunkExecutionListener();
}
#Bean
public TaskExecutor taskExecutor() {
SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
taskExecutor.setConcurrencyLimit(maxThreads);
return taskExecutor;
}
#Bean
public Job processJob() {
return jobBuilderFactory.get("myjob")
.incrementer(new RunIdIncrementer())
.start(processStep())
.listener(jobExecutionListener())
.build();
}
#Bean
public Step processStep() {
return stepBuilderFactory.get("processStep")
.<ModelEntity,ModelEntity>chunk(chunkSize)
.reader(importReader())
.processor(myprocessor())
.writer(databaseWriter())
.taskExecutor(taskExecutor())
.listener(stepExecutionListener())
.listener(chunkListener())
.transactionManager(getTransactionManager())
.throttleLimit(maxThreads)
.build();
}
}
Repository that I am using is JpaRepository and code below. (Assuming save method of its parent class CrudRepository will do save)
public interface MyRepository extends JpaRepository<ModelEntity, BigInteger> {
}
Processor is as below
#Component
public class Myprocessor implements ItemProcessor<Myprocessor,Myprocessor> {
#Override
public ModelEntity process(ModelEntity modelEntity) throws Exception {
try {
// This is fast and working fine
if ((myProcessing)) {
modelEntity.setStatus(success);
} else {
modelEntity.setStatus(failed);
}
}
catch (Exception e){
logger.info( "Exception occurred while processing"+e );
}
return modelEntity;
}
// This is fast and working fine
public Boolean myProcessing(ModelEntity modelEntity){
//Processor Logic Here
return processingStatus;
}
}
Properties file below
logging.level.org.hibernate.SQL=DEBUG
logging.level.com.zaxxer.hikari.HikariConfig=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.url=url
spring.datasource.username=username
spring.datasource.password=password
spring.jpa.hibernate.connection.provider_class
=org.hibernate.hikaricp.internal.HikariCPConnectionProvider
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
spring.jpa.show-sql=false
spring.main.allow-bean-definition-overriding=true
spring.batch.initializer.enabled=false
spring.batch.job.enabled=false
spring.batch.initialize-schema=never
chunk-size=100
max-threads=5
You can enable JDBC batch processing for INSERT, UPDATE and DELETE statements with just one configuration property:
spring.jpa.properties.hibernate.jdbc.batch_size
It determines the number of updates that are sent to the database at one time for execution.
For details, see this link
Thank you all for the suggestions. I found the issue myself. I was using JdbcPagingItemReader and RepositoryItemWriter. The reader was working as expected, but the writer was triggering a select query for each record passed after processor. I believe reason behind is that the the record is persistent to JPA only after processor since the reader is not a standard JPA reader. I am not sure about it though. But changing the writer to JdbcBatchItemWriter fixed the issue.

Spring Batch multiple insert for a one read

I've a Spring Batch process that read Report objects from a CSV and insert Analytic objects into a MySQL DB correctly, but the logical has changed for a more than one Analytics insert for each Report readed.
I'm new in Spring Batch and the actually process was very difficult for me, and I don't know how to do this change.
I haven't XML configuration, all is with annotations. Report and Analytics classes have a getter and a setter for two fields, adId and value. The new logic has seven values for an adId and I need to insert seven rows into table.
I hide, delete or supress some code that not contribute for the question.
Here is my BatchConfiguration.java:
#Configuration
#EnableBatchProcessingpublic
class BatchConfiguration {
#Autowired
private transient JobBuilderFactory jobBuilderFactory;
#Autowired
private transient StepBuilderFactory stepBuilderFactory;
#Autowired
private transient DataSource dataSource;
public FlatFileItemReader<Report> reader() {
// The reader from the CSV works fine.
}
#Bean
public JdbcBatchItemWriter<Analytic> writer() {
final JdbcBatchItemWriter<Analytic> writer = new JdbcBatchItemWriter<Analytic>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<Analytic>());
writer.setSql("INSERT INTO TABLE (ad_id, value) VALUES (:adId, :value)");
writer.setDataSource(dataSource);
return writer;
}
#Bean
public AnalyticItemProcessor processor() {
return new AnalyticItemProcessor();
}
#Bean
public Step step() {
return stepBuilderFactory.get("step1").<Report, Analytic> chunk(10000).reader(reader()).processor(processor()).writer(writer()).build();
}
#Bean
public Job process() {
final JobBuilder jobBuilder = jobBuilderFactory.get("process");
return jobBuilder.start(step()).build();
}
}
Then the AnalyticItemProcessor.java
public class AnalyticItemProcessor implements ItemProcessor<Report, Analytic> {
#Override
public Analytic process(final Report report) {
// Creates a new Analytic call BeanUtils.copyProperties(report, analytic) and returns analytic.
}
}
And the Process:
#SpringBootApplication
public class Process {
public static void main(String[] args) throws Exception {
SpringApplication.run(Process.class, args);
}
}
How can I do this change? Maybe with ItemPreparedStatementSetter or ItemSqlParameterSourceProvider? Thanks.
If I'm understanding your question correctly, you can use the CompositeItemWriter to wrap multiple JdbcBatchItemWriter instances (one per insert you need to accomplish). That would allow you to insert multiple rows per item. Otherwise, you'd need to write your own ItemWriter implementation.

Spring Transactional does not roll back

I have a question about xml-free configuration of Spring. Unfortunately it does not roll back my DB changes even if I mark a corresponding method with #Transactional annotation.
First of all I have a controller, which calls a class marked with #Transactional.
#RequestMapping(value="/Device", method=RequestMethod.POST, produces={"application/json"})
#ResponseStatus(HttpStatus.CREATED)
public #ResponseBody List<Device> addDevice(
#RequestBody Device deviceToAdd) {
List<Device> devices= deviceManager.addDevice(deviceToAdd);
return devices;
}
All controllers have an ExceptionResolver that catches DuplicateKeyException:
#ExceptionHandler(DuplicateKeyException.class)
public ResponseEntity<Result> handleBindException(DuplicateKeyException e) {
log.error(e.getMessage(), e);
Result result = new Result("this object already exists");
return new Response(result);
}
Here is my main transactional class which calls two DAO classes. The second one causes DuplicateKeyException, but the results of the first one are not deleted from DB.
#Transactional(propagation = Propagation.REQUIRED, rollbackFor=Exception.class)
public class DeviceManager {
#Autowired
DaoClass1 daoClass1;
#Autowired
DaoClass2 daoClass2;
public List<Device> addDevice(Device device){
Pocket pocket = daoClass1.addPocket(new Pocket());<--is not rolled back after Exception
device.setPocket(pocket);
List<Devices> addedDevices = daoClass2.addDevice(device); <--- causes exception
return devices;
}
I tried also to use #Transactional annotation both for the classes daoClass1 and daoClass2, but it did not change anything.
What could I have done wrong?

My #Cacheable seems to be ignored (Spring)

I have to cache the result of the following public method :
#Cacheable(value = "tasks", key = "#user.username")
public Set<MyPojo> retrieveCurrentUserTailingTasks(UserInformation user) {
Set<MyPojo> resultSet;
try {
nodeInformationList = taskService.getTaskList(user);
} catch (Exception e) {
throw new ApiException("Error while retrieving tailing tasks", e);
}
return resultSet;
}
I also configured Caching here :
#Configuration
#EnableCaching(mode = AdviceMode.PROXY)
public class CacheConfig {
#Bean
public CacheManager cacheManager() {
final SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("tasks"),new ConcurrentMapCache("templates")));
return cacheManager;
}
#Bean
public CacheResolver cacheResolver() {
final SimpleCacheResolver cacheResolver = new SimpleCacheResolver(cacheManager());
return cacheResolver;
}
}
I assert the following :
Cache is initialized and does exist within Spring Context
I used jvisualvm to track ConcurrentMapCache (2 instances), they are
there in the heap but empty
Method returns same values per user.username
I tried the same configuration using spring-boot based project and
it worked
The method is public and is inside a Spring Controller
The annotation #CacheConfig(cacheNames = "tasks") added on top of my
controller
Spring version 4.1.3.RELEASE
Jdk 1.6
Update 001 :
#RequestMapping(value = "/{kinematicId}/status/{status}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public DocumentNodeWrapper getDocumentsByKinematicByStatus(#PathVariable String kinematicId, #PathVariable String status, HttpServletRequest request) {
UserInformation user = getUserInformation(request);
Set<ParapheurNodeInformation> nodeInformationList = retrieveCurrentUserTailingTasks(user);
final List<DocumentNodeVO> documentsList = getDocumentsByKinematic(kinematicId, user, nodeInformationList);
List<DocumentNodeVO> onlyWithGivenStatus = filterByStatus(documentsList);
return new DocumentNodeWrapper("filesModel", onlyWithGivenStatus, user, currentkinematic);
}
Thanks
Is the calling method getDocumentsByKinematicByStatus() in the same bean as the cacheable method ? If true, then this is a normal behavior because you're not calling the cacheable method via proxy but directly.

Categories