Drools using large RAM for larger rules - spring

We have started using Drools [V 7.55.0.Final]. We have a requirement where we have large number of rules (~100,000) but have few(1-10) facts to process for every API request. A sample of each rule is as follows:
rule "Rule-000000-2627"
when
discountObject: Discount(purchaseSource == "FLIPKART" && customerType == "NEW" && bank == "HDFC" &&
purchaseType == "BNPL" && apparelType == "SHIRT" && price >= 100 && price < 200)
then
discountObject.setDiscount(2);
end
The discount object is as follows:
public class Discount {
private String name;
private String purchaseSource;
private String customerType;
private String bank;
private String purchaseType;
private String apparelType;
private int price;
private int discount;
}
The Drools configuration is as follows:
public class DroolDiscountConfig {
private KieServices kieServices = KieServices.Factory.get();
#Value( "${drool.rule.file}" )
private String droolConfigFile;
private KieFileSystem getKieFileSystem() throws IOException {
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
Long startTime = Utility.startTime();
String[] droolFiles = this.droolConfigFile.split(",");
for(String droolFile: droolFiles) {
kieFileSystem.write(ResourceFactory.newFileResource(droolFile));
}
Long elapseTime = Utility.elapsedTime(startTime);
System.out.println("Completed loading drool file " + droolConfigFile + " in " + elapseTime);
return kieFileSystem;
}
#Bean
public KieContainer getKieContainer() throws IOException {
System.out.println("Container created...");
getKieRepository();
Long startTimeKieBuilder = Utility.startTime();
KieBuilder kb = kieServices.newKieBuilder(getKieFileSystem());
kb.buildAll();
System.out.println("Kie builder took " + Utility.elapsedTime(startTimeKieBuilder));
Long startTimeKieModule = Utility.startTime();
KieModule kieModule = kb.getKieModule();
System.out.println("Kie Module generation took " + Utility.elapsedTime(startTimeKieModule));
Long startTimeKieContainer = Utility.startTime();
KieContainer kContainer = kieServices.newKieContainer(kieModule.getReleaseId());
System.out.println("KieContainer generation took " + Utility.elapsedTime(startTimeKieContainer));
return kContainer;
}
private void getKieRepository() {
Long startTimeKieRepo = Utility.startTime();
final KieRepository kieRepository = kieServices.getRepository();
kieRepository.addKieModule(new KieModule() {
public ReleaseId getReleaseId() {
return kieRepository.getDefaultReleaseId();
}
});
System.out.println("KieRepo generation took " + Utility.elapsedTime(startTimeKieRepo));
}
}
The Controller is as follows:
#RestController
public class DiscountController {
#Autowired
private KieContainer kieContainer;
#PostMapping("/discount")
public List<Discount> viewDiscount(#RequestBody List<Discount> discounts) {
StatelessKieSession statelessKieSession = kieContainer.newStatelessKieSession();
statelessKieSession.execute(discounts);
return discounts;
}
}
The rule file (single drl file) is of size 40Mb. Whenever, we are starting a service, it occupies around 5-6Gb of RAM. I checked the memory dump using VisualVM but did not find much detail as most of the memory was occupied by bytes[] and Strings[].
I would like to know, are we using Drools in the right way. How can we avoid this huge memory consumption.
Also there are two other issues we are facing:
The first API request takes long time to respond (around 30 seconds).
The service takes long time to start (around 5mins)
If we try to restrict the size of the VM to around 3-4Gb then we get the following error
ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'discountController': Unsatisfied dependency expressed through field 'kieContainer'; nested exception is
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'getKieContainer' defined in class path resource
[load/test/DroolDiscountConfig.class]: Bean instantiation via factory method failed; nested exception is
org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.kie.api.runtime.KieContainer]:
Factory method 'getKieContainer' threw exception; nested exception is java.lang.OutOfMemoryError: Java heap space

Related

Is there a way to make a transactional action in a #Scheduled method?

So, my application is multi-tenants based, and I need to apply a transactional request each Sunday at 1:00 PM.
It basically needs to get All resources and create Usage (which is an entity per week) based on the actual Date.
My transactional Method :
/**
* Scheduled to run at 1:00 every sunday
* It should create capacities for all resources that doesn't have any for next years
*/
#Transactional
public void createNewCapacities() throws Exception {
LocalDate now = LocalDate.now();
System.out.println("Date is :" + now);
System.out.println("Start of capacities creation...");
//List<Resource> resources = resourceService.findAll();
//for(Resource resource : resources){
//Calendar calendar = calendarService.findCalendarById(resource.getSelectedCalendarId());
...
//}
}
My scheduler :
#Service
public class UsageServiceScheduler {
#Autowired
private UsageService usageService;
#Scheduled(cron= "0 0 1 * * SUN")
public void callScheduledTask() throws Exception {
usageService.createNewCapacities();
}
}
This throw an Exception:
org.springframework.transaction.CannotCreateTransactionException:
Could not open JPA EntityManager for transaction; nested exception is
java.lang.IllegalStateException: Cannot determine target DataSource
for lookup key [null].
Is there a way to establish a connection with the database during the #Scheduled method?
EDIT :
I have both #Transactional and #Scheduled enabled.
TENANT DATA SOURCE PROPERTIES :
#Component
#ConfigurationProperties(prefix = "tenants")
public class TenantDataSourceProperties {
private Map<Object, Object> dataSources = new LinkedHashMap<>();
public Map<Object, Object> getDataSources() {
return dataSources;
}
public void setDataSources(Map<String, Map<String, String>> dataSources) {
dataSources.forEach((key, value) -> this.dataSources.put(key, convert(value)));
}
public DataSource convert(Map<String, String> source) {
return DataSourceBuilder.create()
.url(source.get("jdbcUrl"))
.driverClassName(source.get("driverClassName"))
.username(source.get("username"))
.password(source.get("password"))
.build();
}
}
I want my cron job to run for all existing tenants.
Or in other words, get all the dataSources and apply cron job for each db.

Spring dynamic async

Trying to have a dynamic number of async processes running in Spring 2.0.5. I do have it working on a fixed number but need the flexibility.
the current methods are:
in class AsyncFactoryService:
public void doAsyncBetter(long rowsPerBucket) throws InterruptedException, SQLException, ExecutionException {
List<Bucket> buckets = sourceData01Service.bucketsByRowsPerBucket(rowsPerBucket);
AsyncDataLoadService[] asyncDataLoadServiceArray = new AsyncDataLoadService[buckets.size()];
for(int i = 0; i < buckets.size(); i++) {
asyncDataLoadServiceArray[i] = new AsyncDataLoadService();
asyncDataLoadServiceArray[i].loadSourceData01(buckets.get(i));
}
}
in class AsyncDataLoadService
#Autowired SourceData01Service sourceData01Service;
#Async
public Future<String> loadSourceData01(Bucket bucket) throws InterruptedException, SQLException {
LOGGER.info(String.format("(loadSourceData01) [Thread id => %d, bucket => %s]",Thread.currentThread().getId(), bucket.toString()));
List<SourceData01> sourceData01s = null;
sourceData01s = sourceData01Service.rowsByBucket(bucket);
Thread.sleep(5000);
String info = String.format("finish sourceData01s.size() => %d, Thread id => %d", sourceData01s.size(), Thread.currentThread().getId());
return new AsyncResult<>(info);
}
this fails with
Caused by: java.lang.NullPointerException
at demo.service.AsyncDataLoadService.loadSourceData01(AsyncDataLoadService.java:35) ~[classes/:?]
at demo.service.AsyncFactoryService.doAsyncBetter(AsyncFactoryService.java:43) ~[classes/:?]
at demo.RunApp.run(RunApp.java:39) ~[classes/:?]
As it seems in the service the sourceData01Service is null.
When I try with a fixed number like
public void doAsync(long rowsPerBucket) throws InterruptedException, SQLException, ExecutionException {
List<Bucket> buckets = sourceData01Service.bucketsByRowsPerBucket(rowsPerBucket);
Future<String> process0 = asyncDataLoadService.loadSourceData01(buckets.get(0));
Future<String> process1 = asyncDataLoadService.loadSourceData01(buckets.get(1));
Future<String> process2 = asyncDataLoadService.loadSourceData01(buckets.get(2));
Future<String> process3 = asyncDataLoadService.loadSourceData01(buckets.get(3));
Future<String> process4 = asyncDataLoadService.loadSourceData01(buckets.get(4));
while(!(process0.isDone()) && !(process2.isDone()) && !(process3.isDone()) && !(process4.isDone())) {
Thread.sleep(2000);
}
LOGGER.info("Process 0 => " + process0.get());
LOGGER.info("Process 1 => " + process1.get());
LOGGER.info("Process 2 => " + process2.get());
LOGGER.info("Process 3 => " + process3.get());
LOGGER.info("Process 4 => " + process4.get());
}
it works as expected but in my case the number of buckets is dependent on the number of rows in the table so I do not know how many buckets there will be. Any ideas how to do this?
The service (sourceData01Service) injection in AsyncDataLoadService didn't happen as the application is creating the instance of AsyncDataLoadService (in the factory class) instead of spring creating it.
We can modify the implementation to let spring create the required instance.
#Component
class AsyncDataLoadService {
#Autowired SourceData01Service sourceData01Service;
#Async
public Future<String> loadSourceData01(Bucket bucket) throws InterruptedException, SQLException {
LOGGER.info(String.format("(loadSourceData01) [Thread id => %d, bucket => %s]",Thread.currentThread().getId(), bucket.toString()));
List<SourceData01> sourceData01s = null;
sourceData01s = sourceData01Service.rowsByBucket(bucket);
Thread.sleep(5000);
String info = String.format("finish sourceData01s.size() => %d, Thread id => %d", sourceData01s.size(), Thread.currentThread().getId());
return new AsyncResult<>(info);
}
}
We can then change the factory class implementation to autowire AsyncDataLoadService and submit the job to be executed (in this case loadSourceData01).
Since asyncDataLoadService.loadSourceData01 is annotated with #Async, every call to that method will be executed in a separate thread. By default spring use SimpleAsyncTaskExecutor that fires up new thread for every invocation. Configure thread pool implementation if required.
#Component
class AsyncFactoryService {
#Autowired
AsyncDataLoadService asyncDataLoadService;
public void doAsyncBetter(long rowsPerBucket) throws InterruptedException, SQLException, ExecutionException {
List<Bucket> buckets = sourceData01Service.bucketsByRowsPerBucket(rowsPerBucket);
for(int i = 0; i < buckets.size(); i++) {
asyncDataLoadService.loadSourceData01(buckets.get(i));
}
}
}

Tests fail with #Scheduled Task: JdbcSQLSyntaxErrorException Table "USER_ACCOUNT_CREATED_EVENT" not found

Summary & first problem
I am trying to test my user registration mechanism. When a new user account is created via my REST API, a UserAccountCreatedEvent is stored in the database. A scheduled task checks the database every 5 seconds for new UserAccountCreatedEvents and if one is present, sends an email to the registered user. When running my tests I encounter the problem that the table for the UserAccountCreatedEvent can't be found (see exception below). I used to send the email in a blocking manner in the service method, but I recently switched to this async approach. All my tests worked perfectly for the blocking approach and the only thing I changed for the async approach is to include Awaitility in the test.
2019-04-23 11:24:51.605 ERROR 7968 --- [taskScheduler-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [select useraccoun0_.id as id1_0_, useraccoun0_.completed_at as complete2_0_, useraccoun0_.created_at as created_3_0_, useraccoun0_.in_process_since as in_proce4_0_, useraccoun0_.status as status5_0_, useraccoun0_.user_id as user_id1_35_ from user_account_created_event useraccoun0_ where useraccoun0_.status=? order by useraccoun0_.created_at asc limit ?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException:
Table "USER_ACCOUNT_CREATED_EVENT" not found; SQL statement:
select useraccoun0_.id as id1_0_, useraccoun0_.completed_at as complete2_0_, useraccoun0_.created_at as created_3_0_, useraccoun0_.in_process_since as in_proce4_0_, useraccoun0_.status as status5_0_, useraccoun0_.user_id as user_id1_35_ from user_account_created_event useraccoun0_ where useraccoun0_.status=? order by useraccoun0_.created_at asc limit ? [42102-199]
Full stack trace
Second problem
As if that were not enough, the tests behave completely different when running them in debug mode. When I set a breakpoint in the method that is called by the method which is annotated with #Scheduled, it is invoked several times althogh #Scheduled is configured with a fixedDelayString (fixed delay) of 5000ms. Thanks to logging I can even see that several mails were sent. Still, my test SMTP sever (GreenMail) does not receive any emails. How is this even possible? I've intentionally set the transaction isolation to Isolation.SERIALIZABLE so that it should be impossible (as far as I understand transaction isolation) that two scheduled methods access the same Event from the database.
Third problem
To cap it all, when I rerun the failed tests, THEY WORK. But, there are different exceptions on the console (see below). But still, the app starts and the tests finish successfully. There are different test results depending on if I run all tests vs. only the class vs. only the method vs. rerun failed tests. I don't understand how such an indeterministic behaviour can be possible.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Failed to scan classpath for unlisted entity classes
Caused by: java.nio.channels.ClosedByInterruptException: null
Full stack trace
My code
Test class (UserRegistrationTest)
#ActiveProfiles("test")
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public class UserRegistrationTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private Routes routes;
#Autowired
private TestConfig testConfig;
#Resource(name = "validCustomerDTO")
private CustomerDTO validCustomerDTO;
#Resource(name = "validVendorDTO")
private VendorRegistrationDTO validVendorRegistrationDTO;
#Value("${schedule.sendRegistrationConfirmationEmailTaskDelay}")
private Short registrationConfirmationEmailSenderTaskDelay;
private GreenMail smtpServer;
// Setup & tear down
#Before
public void setUp() {
smtpServer = testConfig.getMailServer();
smtpServer.start();
}
#After
public void tearDown() {
smtpServer.stop();
}
// Tests
#Test
public void testCreateCustomerAccount() throws Exception {
mockMvc.perform(
post(routes.getCustomerPath())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(validCustomerDTO)))
.andExpect(status().isCreated());
// When run normally, I get a timeout from the next line
await().atMost(registrationConfirmationEmailSenderTaskDelay + 10000, MILLISECONDS).until(smtpServerReceivedOneEmail());
// Verify correct registration confirmation email was sent
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
// other checks
// ...
}
#Test
public void testCreateVendorAccount() throws Exception {
mockMvc.perform(
post(routes.getVendorPath())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(validVendorRegistrationDTO)))
.andExpect(status().isCreated());
// When run normally, I get a timeout from the next line
await().atMost(registrationConfirmationEmailSenderTaskDelay + 10000, MILLISECONDS).until(smtpServerReceivedOneEmail());
// Verify correct registration confirmation email was sent
MimeMessage[] receivedMessages = smtpServer.getReceivedMessages();
assertThat(receivedMessages).hasSize(1);
// other checks
// ...
}
// Helper methods
private Callable<Boolean> smtpServerReceivedOneEmail() {
return () -> smtpServer.getReceivedMessages().length == 1;
}
// Test configuration
#TestConfiguration
static class TestConfig {
private static final int PORT = 3025;
private static final String HOST = "localhost";
private static final String PROTOCOL = "smtp";
GreenMail getMailServer() {
return new GreenMail(new ServerSetup(PORT, HOST, PROTOCOL));
}
#Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost(HOST);
javaMailSender.setPort(PORT);
javaMailSender.setProtocol(PROTOCOL);
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
}
Task scheduler (BusinessTaskScheduler)
#Component
public class BusinessTaskScheduler {
private final RegistrationTask registrationTask;
#Autowired
public BusinessTaskScheduler(RegistrationTask registrationTask) {
this.registrationTask = registrationTask;
}
#Scheduled(fixedDelayString = "${schedule.sendRegistrationConfirmationEmailTaskDelay}")
public void sendRegistrationConfirmationEmail() {
registrationTask.sendRegistrationConfirmationEmail();
}
}
The code that is called by the scheduled method (RegistrationTask)
#Component
#Transactional(isolation = Isolation.SERIALIZABLE)
public class RegistrationTask {
private final EmailHelper emailHelper;
private final EventService eventService;
private final UserRegistrationService userRegistrationService;
#Autowired
public RegistrationTask(EmailHelper emailHelper, EventService eventService, UserRegistrationService userRegistrationService) {
this.emailHelper = emailHelper;
this.eventService = eventService;
this.userRegistrationService = userRegistrationService;
}
public void sendRegistrationConfirmationEmail() {
Optional<UserAccountCreatedEvent> optionalEvent = eventService.getOldestUncompletedUserAccountCreatedEvent();
if (optionalEvent.isPresent()) {
UserAccountCreatedEvent event = optionalEvent.get();
User user = event.getUser();
RegistrationVerificationToken token = userRegistrationService.createRegistrationVerificationTokenForUser(user);
emailHelper.sendRegistrationConfirmationEmail(token);
eventService.completeEvent(event);
}
}
}
The event service (EventServiceImpl)
#Service
#Transactional(isolation = Isolation.SERIALIZABLE)
public class EventServiceImpl implements EventService {
private final ApplicationEventDAO applicationEventDAO;
private final UserAccountCreatedEventDAO userAccountCreatedEventDAO;
#Autowired
public EventServiceImpl(ApplicationEventDAO applicationEventDAO, UserAccountCreatedEventDAO userAccountCreatedEventDAO) {
this.applicationEventDAO = applicationEventDAO;
this.userAccountCreatedEventDAO = userAccountCreatedEventDAO;
}
#Override
public void completeEvent(ApplicationEvent event) {
if (!event.getStatus().equals(COMPLETED) && Objects.isNull(event.getCompletedAt())) {
event.setStatus(COMPLETED);
event.setCompletedAt(LocalDateTime.now());
applicationEventDAO.save(event);
}
}
#Override
public Optional<UserAccountCreatedEvent> getOldestUncompletedUserAccountCreatedEvent() {
Optional<UserAccountCreatedEvent> optionalEvent = userAccountCreatedEventDAO.findFirstByStatusOrderByCreatedAtAsc(NEW);
if (optionalEvent.isPresent()) {
UserAccountCreatedEvent event = optionalEvent.get();
setEventInProcess(event);
return Optional.of(userAccountCreatedEventDAO.save(event));
}
return Optional.empty();
}
#Override
public void publishEvent(ApplicationEvent event) {
applicationEventDAO.save(event);
}
// Helper methods
private void setEventInProcess(ApplicationEvent event) {
event.setStatus(Status.IN_PROCESS);
event.setInProcessSince(LocalDateTime.now());
}
}
The UserAccountCreatedEvent
application.yml
schedule:
sendRegistrationConfirmationEmailTaskDelay: 5000 # delay between tasks in milliseconds
I am new to scheduling with Spring, so any help is greatly appreciated!

Spring profiles not getting resolved when using it with the spring web based project

in application.properties given : spring.profiles.active=DEV
and in dev config file : mentioned all the mongo connection properties
and added the configuration java file like
#Configuration
#PropertySource("classpath:userIdentity_Dev.properties")
#Profile("DEV")
public class UserIdentityConfigDev
{
}
when running the application the spring profiler is not getting resolved the
below stack trace is received
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userIdentityService': Unsatisfied dependency expressed through field 'userIdentityBusiness'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userIdentityBusiness': Unsatisfied dependency expressed through field 'userIdentityRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userIdentityRepositoryImpl': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'mongodb.userIdentity.host' in string value "${mongodb.userIdentity.host}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
Saying that the ${mongodb.userIdentity.host} property is not resolved
when creating war and jar file for the project the spring profile is not resolved
This is main class:
` #SpringBootApplication(exclude= {DataSourceAutoConfiguration.class ,MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
#PropertySource("classpath:application.properties")
public class ApplicationStart extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(ApplicationStart.class,args);
}
}`
below is the property file:
## MongoDB Connection Properties-----------------
MongoDB database
mongodb.userIdentity.database = UserIdentity_CS
isConnectionStringUsed is true then application creates connection as per connectionString else it will use MongoDB single server properties.
mongodb.userIdentity.isConnectionStringUsed = false
connectionString with authentication
mongodb.connectionString = mongodb://sa:Test%40123#SPT-CPU-0259:27017,SPT-CPU-0173:27017/admin?replicaSet=surveillens
connectionString without authentication
mongodb.userIdentity.connectionString = mongodb://localhost:27017/?replicaSet=surveillens
MongoDB single server properties.
mongodb.userIdentity.host = localhost
mongodb.userIdentity.port = 27017
Authentication properties
mongodb.userIdentity.isAuthenticationEnable = false
mongodb.userIdentity.userName = sa
mongodb.userIdentity.password = Test#123
mongodb.userIdentity.authDB = admin
Collection Name for user Identity
mongodb.userIdentity.collectionName = CreditScore
Other properties -----------------------
userIdentity.ValidKeySet = email;phonenumber;_id
userIdentity.logsFolder = ./IdentityLogs/
userIdentity.insertBatchSize = 100
and below is the file .java file where all this properties are used
`
#Configuration
public abstract class MongoDbRepository {
private Class<T> clazz;
private static MongoClient mongoClient = null;
private static MongoDatabase mongoDatabase = null;
private static ObjectMapper mapper = null;
#Value("${mongodb.userIdentity.host}")
private String mongoHost;
#Value("${mongodb.userIdentity.port}")
private int mongoPortNumber;
#Value("${mongodb.userIdentity.database}")
private String mongoDatabaseName;
#Value("${mongodb.userIdentity.userName}")
private String mongoUserName;
#Value("${mongodb.userIdentity.authDB}")
private String mongoAuthDB;
#Value("${mongodb.userIdentity.password}")
private String mongoPassword;
#Value("${mongodb.userIdentity.isAuthenticationEnable}")
private boolean mongoIsAuthEnable;
#Value("${mongodb.userIdentity.isConnectionStringUsed}")
private boolean mongoIsConnectionStringUsed;
#Value("${mongodb.userIdentity.connectionString}")
private String mongoConnectionString;
public final void setClazz(Class<T> clazzToSet)
{
this.clazz = clazzToSet;
}
/**
* Instantiates a new mongo base repository.
* #throws Exception
*/
public MongoDbRepository()
{
//Trigger MongoDB Connection initialization
if(mongoClient == null)
{
prepareMongoConnection();
}
else
{
// Trigger any method to check MongoDB client is connected
mongoClient.getAddress();
}
// Trigger ObjectMapper initialization
if(mapper == null)
prepareObjectMapper();
}
/**
* Instantiates a new mongoDB connection.
* #throws Exception
*/
private void prepareMongoConnection()
{
if (mongoConnectionString != null && !mongoConnectionString.isEmpty())
{
boolean isConnectionStringUsed = mongoIsConnectionStringUsed;
if(isConnectionStringUsed)
{
MongoClientURI clientUri = new MongoClientURI(mongoConnectionString);
mongoClient = new MongoClient(clientUri);
}
else
{
if(mongoIsAuthEnable)
{
MongoCredential credential = MongoCredential.createCredential(mongoUserName, mongoAuthDB, mongoPassword.toCharArray());
mongoClient = new MongoClient( new ServerAddress(mongoHost, mongoPortNumber), Arrays.asList(credential));
}
else
mongoClient = new MongoClient(mongoHost, mongoPortNumber);
}
// Trigger any method to check MongoDB client is connected
mongoClient.getAddress();
// Get Database from mongoClient.
mongoDatabase = mongoClient.getDatabase(mongoDatabaseName);
}
}
/**
* Get an objectMapper.
*/
private void prepareObjectMapper()
{
mapper = CommonFunctions.getObjectMapper();
}
/**
* Get the MongoDB collection object from MongoDB.
*
* #param collectionName is Name of a MongoDB collection
* #return Collection object
* #throws Exception
*/
private MongoCollection<Document> getCollection(String collectionName) throws Exception
{
if(mongoClient == null)
prepareMongoConnection();
return mongoDatabase.getCollection(collectionName);
}
/* ------- Find functions ------- */
/**
* Find one documents from mongoDB collection.
*
* #param collectionName the collection name
* #param query the query document - set to empty document means no query filtering.
*
* #return entityObj the entity Object
* #throws Exception the exception
*/
public T findOne(String collectionName, Object query) throws Exception
{
if(clazz == null)
throw new NullPointerException("ST224 - Generic class is null - set the generic class before perform MongoDB operation");
MongoCollection<Document> collection = getCollection(collectionName);
Document mongoDoc = collection.find(convertToBsonDocument(query)).first();
String jsonStr = mapper.writeValueAsString(mongoDoc);
T entityObj = mapper.readValue(jsonStr, clazz);
return entityObj;
}
}`

Caching Java 8 Optional with Spring Cache

I have a method:
#Cacheable(key = "#jobId")
public Optional<JobInfo> getJobById(String jobId) {
log.info("Querying for job " + jobId);
counterService.increment("queryJobById");
Job job = jobsRepository.findOne(jobId);
if (job != null) {
return Optional.of(createDTOFromJob(job));
}
return Optional.empty();
}
When I am trying to retrieve the cached item I am getting the following exception:
2016-01-18 00:01:10 ERROR [trace=,span=] http-nio-8021-exec-2 [dispatcherServlet]:182 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [java.util.Optional]] with root cause
java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [java.util.Optional]
Just implement the Serializable interface in your DTO
#Document(collection = "document_name")
public class Document implements Serializable {
private static final long serialVersionUID = 7156526077883281623L;
Spring supports caching Optional. The issue is your Redis serializer (JdkSerializationRedisSerializer probably). It uses Java based serialization which requires the classes to be Serializable. You can solve this by configuring the RedisCacheManager to use another serializer that doesn't have this limitation. For example you can use Kryo (com.esotericsoftware:kryo:3.0.3):
#Bean
RedisCacheManager redisCacheManager (RedisTemplate<Object, Object> redisOperations) {
// redisOperations will be injected if it is configured as a bean or create it: new RedisTemplate()...
redisOperations.setDefaultSerializer(new RedisSerializer<Object>() {
//use a pool because kryo instances are not thread safe
KryoPool kryoPool = new KryoPool.Builder(Kryo::new).build();
#Override
public byte[] serialize(Object o) throws SerializationException {
ByteBufferOutput output = new ByteBufferOutput();
Kryo kryo = kryoPool.borrow();
try {
kryo.writeClassAndObject(output, o);
} finally {
kryoPool.release(kryo);
output.close();
}
return output.toBytes();
}
#Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(bytes.length == 0) return null;
Kryo kryo = kryoPool.borrow();
Object o;
try {
o = kryo.readClassAndObject(new ByteBufferInput(bytes));
} finally {
kryoPool.release(kryo);
}
return o;
}
});
RedisCacheManager redisCacheManager = new RedisCacheManager(redisOperations);
redisCacheManager.setCachePrefix(new DefaultRedisCachePrefix("app"));
redisCacheManager.setTransactionAware(true);
return redisCacheManager;
}
Note that this is just an example, I didn't test this imeplementation. But I use the Kryo serializer in production in the same manner for redis caching with Spring.
Because your serialized object is not implement RedisSerializer, or you can extend class JdkSerializationRedisSerializer, which have implement RedisSerializer.
example code:
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
public class YourDTOObject extends JdkSerializationRedisSerializer implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
....
}
More details and principle, please visit my blog

Resources