Difference between Spring Boot QuartzAutoConfiguration 2.1.6.RELEASE & 2.2.2.RELEASE - spring

We were using Spring Boot 2.1.6.RELEASE.
after that we updated spring version to 2.2.2.RELEASE. When we change the version we noticed our quartz jobs not working. We have multiple jobs and we configured them like below. After some reasearch i found some differences between in QuartzAutoConfiguration class.
How can i inject my triggers in spring 2.2.2.RELEASE. Is there any easy way? I dont want to write to many triggers and trigger details.
MyConfig
import io.rkpc.commons.util.ApplicationReflectionUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
#Slf4j
#Configuration
#ConfigurationProperties(prefix = "quartz")
#Profile("quartz")
#Data
public class JobConfig {
private List<Job> jobs;
#Bean
public JobDetail[] jobDetail() throws SchedulerConfigException {
Set<Class<QuartzJobBean>> subClasses = ApplicationReflectionUtil.getSubClasses(QuartzJobBean.class, "io.rkpc");
List<JobDetail> jobDetails = new ArrayList<>();
for (Class<QuartzJobBean> quartzJobBeanClass : subClasses) {
Job job = getJob(quartzJobBeanClass.getSimpleName());
if (job.isEnabled()) {
JobDetail jobDetail = JobBuilder.newJob(quartzJobBeanClass)
.withIdentity(quartzJobBeanClass.getSimpleName())
.storeDurably()
.build();
jobDetails.add(jobDetail);
}
}
return jobDetails.toArray(new JobDetail[0]);
}
#Bean
public Trigger[] jobATrigger(JobDetail[] jobADetails) throws SchedulerConfigException {
List<Trigger> triggers = new ArrayList<>();
for (JobDetail jobDetail : jobADetails) {
Job job = getJob(jobDetail.getKey().getName());
CronTrigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity(jobDetail.getKey().getName().concat("Trigger"))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
.build();
triggers.add(trigger);
}
return triggers.toArray(new Trigger[0]);
}
private Job getJob(String name) throws SchedulerConfigException {
List<Job> filteredJobs = jobs.stream().filter(job -> job.getName().equals(name)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(filteredJobs) || filteredJobs.size() > 1) {
log.error("{} is not configured", name);
throw new SchedulerConfigException("Job is not configured");
}
return filteredJobs.get(0);
}
#Data
public static class Job {
private String name;
private String cron;
private boolean enabled;
}
}
QuartzAutoConfiguration.java Spring version 2.1.6 github url ; https://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java
QuartzAutoConfiguration.java Spring version 2.2.2 github url https://github.com/spring-projects/spring-boot/blob/v2.2.2.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java
Main difference i notice is ; in 2.1.6 version Quartz AutoConfiguration was "Trigger" array but 2.2.2 doesn't have "Trigger" array.

Spring has always some magic :)
import io.rkpc.commons.util.ApplicationReflectionUtil;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.validation.constraints.NotNull;
import java.text.ParseException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
#Slf4j
#Configuration
#ConfigurationProperties(prefix = "quartz")
#Profile("quartz")
#Data
#AutoConfigureBefore({QuartzAutoConfiguration.class})
#RequiredArgsConstructor(onConstructor = #__({#Autowired, #NotNull}))
public class JobConfig {
private final List<Job> jobs;
private final DefaultListableBeanFactory beanFactory;
#PostConstruct
public void init() throws SchedulerConfigException, ParseException {
Set<Class<QuartzJobBean>> subClasses = ApplicationReflectionUtil.getSubClasses(QuartzJobBean.class, "io.rkpc");
for (Class<QuartzJobBean> quartzJobBeanClass : subClasses) {
Job job = getJob(quartzJobBeanClass.getSimpleName(), jobs);
if (job.isEnabled()) {
JobDetailImpl jobDetail = (JobDetailImpl) JobBuilder.newJob(quartzJobBeanClass)
.withIdentity(quartzJobBeanClass.getSimpleName())
.storeDurably()
.build();
CronTriggerImpl trigger = (CronTriggerImpl) TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity(jobDetail.getKey().getName().concat("Trigger"))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
.build();
GenericBeanDefinition jobBeanDefinition = new GenericBeanDefinition();
jobBeanDefinition.setBeanClass(JobDetailImpl.class);
jobBeanDefinition.getPropertyValues().addPropertyValue("jobClass", quartzJobBeanClass);
jobBeanDefinition.getPropertyValues().addPropertyValue("key", jobDetail.getKey());
jobBeanDefinition.getPropertyValues().addPropertyValue("durability", jobDetail.isDurable());
beanFactory.registerBeanDefinition(quartzJobBeanClass.getSimpleName(), jobBeanDefinition);
GenericBeanDefinition triggerBeanDefinition = new GenericBeanDefinition();
triggerBeanDefinition.setBeanClass(CronTriggerImpl.class);
triggerBeanDefinition.getPropertyValues().addPropertyValue("jobKey", trigger.getJobKey());
triggerBeanDefinition.getPropertyValues().addPropertyValue("key", trigger.getKey());
triggerBeanDefinition.getPropertyValues().addPropertyValue("cronExpression", new CronExpression(trigger.getCronExpression()));
beanFactory.registerBeanDefinition(trigger.getName(), triggerBeanDefinition);
}
}
}
public Job getJob(String name, List<Job> jobs) throws SchedulerConfigException {
List<Job> filteredJobs = jobs.stream().filter(job -> job.getName().equals(name)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(filteredJobs) || filteredJobs.size() > 1) {
log.error("{} is not configured", name);
throw new SchedulerConfigException("Job is not configured");
}
return filteredJobs.get(0);
}
#Data
public static class Job {
private String name;
private String cron;
private boolean enabled;
}
}

You are exposing a single Trigger[] bean rather than multiple Trigger beans. You should define one bean per Trigger. You should also do the same for each JobDetail. This was working by accident with Spring Boot 2.1.x as you were relying on the auto-configuration using ObjectProvider<Trigger[]>. The intent of the auto-configuration was to consume all Trigger beans, with the application context turning those beans into an array before injecting them.
If you don't want to define multiple Trigger and JobDetail beans, you may be better configuring the SchedulerFactoryBean yourself rather than relying on the auto-configuration.

Related

How to use two data source for same entity in spring boot application running on tomcat

I have one micro-service named order-service in spring boot.
:: Spring Boot :: (v2.0.5.RELEASE)
in that micro-service, in service layer I have 2 service class 1. OrderManagerService and OrderService. From OrderService I call Repository Interface IOrderRequestRepository which extends PagingAndSortingRepository interface. so The call is basically OrderManagerService -> OrderService -> IOrderRequestRepository.
Right now I am using one data source for the micro-service. Now my requirement is I have to use 2 data source because in OrderManagerService there is one method getCustomerVisitsForMonth(Long customerID) which frequently used and we don't want to give load on prod DB server. Instead we will use prod-replica server for this one particular method.
I have googled a lot of blogs and tried to implement but ending up getting different exception.
below are exceptions:
could not execute query; SQL [SELECT * FROM ....(this is the actual query)]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute query",
Could not open JPA EntityManager for transaction; nested exception is org.hibernate.TransactionException: JDBC begin transaction failed:
Below are my sample code:
application-env.properties
spring.datasource.url=jdbc:mysql://someurl:3306/orders?zeroDateTimeBehavior=convertToNull&useSSL=false
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.datasource.driverClassName=com.mysql.jdbc.Driver
#replica datasource details
spring.replica.datasource.url=jdbc:mysql://someurl:3306/order?zeroDateTimeBehavior=convertToNull&useSSL=false
spring.replica.datasource.username=<username>
spring.replica.datasource.password=<password>
spring.replica.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.tomcat.initial-size=15
spring.datasource.tomcat.max-wait=20000
spring.datasource.tomcat.max-active=5
spring.datasource.tomcat.max-idle=4
spring.datasource.tomcat.min-idle=2
spring.datasource.tomcat.default-auto-commit=true
#replica data source details
spring.replica.datasource.tomcat.initial-size=15
spring.replica.datasource.tomcat.max-wait=20000
spring.replica.datasource.tomcat.max-active=5
spring.replica.datasource.tomcat.max-idle=4
spring.replica.datasource.tomcat.min-idle=2
spring.replica.datasource.tomcat.default-auto-commit=true
Main class
package in.demo.order.web.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#SpringBootApplication(scanBasePackages = "in.demo.order",
exclude = { SecurityAutoConfiguration.class })
public class OrderServiceApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(OrderServiceApplication.class);
}
}
I have created 2 different packages for Repository classes and for Entity classes, however my Entity and Repository are same.
packages
in.demo.order.dao
in.demo.order.repodao
Configuration classes
MainDataSourceConfiguration
package in.demo.order.web.config;
import in.demo.order.replicadao.IOrderRequestRepositoryForReplica;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "in.demo.order.dao",
entityManagerFactoryRef = "mainEntityManagerFactory",
transactionManagerRef = "mainTransactionManager"
)
public class MainDataSourceConfiguration {
#Autowired
private Environment environment;
#Primary
#Bean(name = "mainDataSourceProperties")
#ConfigurationProperties("spring.datasource")
public DataSourceProperties mainDataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name = "mainDataSource")
#ConfigurationProperties("spring.datasource.configuration")
public DataSource mainDataSource(
#Qualifier("mainDataSourceProperties") DataSourceProperties mainDataSourceProperties) {
return mainDataSourceProperties
.initializeDataSourceBuilder()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.build();
}
#Primary
#Bean(name = "mainEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean mainEntityManagerFactory(
EntityManagerFactoryBuilder mainEntityManagerFactoryBuilder,
#Qualifier("mainDataSource") DataSource mainDataSource) {
Map<String, String> mainJpaProperties = new HashMap<>();
mainJpaProperties.put("hibernate.dialect",
environment.getProperty("spring.jpa.properties.hibernate.dialect"));
mainJpaProperties.put("hibernate.hbm2ddl.auto",
environment.getProperty("spring.jpa.hibernate.ddl-auto"));
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = mainEntityManagerFactoryBuilder
.dataSource(mainDataSource)
.packages("in.demo.order.dao.model")
.persistenceUnit("mainDataSource")
.properties(mainJpaProperties)
.build();
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return localContainerEntityManagerFactoryBean;
}
#Primary
#Bean(name = "mainTransactionManager")
public PlatformTransactionManager mainTransactionManager(
#Qualifier("mainEntityManagerFactory") EntityManagerFactory mainEntityManagerFactory) {
return new JpaTransactionManager(mainEntityManagerFactory);
}
}
ReplicaDataSourceConfiguration
package in.demo.order.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "in.demo.order.replicadao",
entityManagerFactoryRef = "replicaEntityManagerFactory",
transactionManagerRef = "replicaTransactionManager"
)
public class ReplicaDataSourceConfiguration {
#Autowired
private Environment environment;
#Bean(name = "replicaDataSourceProperties")
#ConfigurationProperties("spring.replica.datasource")
public DataSourceProperties replicaDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "replicaDataSource")
#ConfigurationProperties("spring.replica.datasource.configuration")
public DataSource replicaDataSource(
#Qualifier("replicaDataSourceProperties") DataSourceProperties replicaDataSourceProperties) {
return replicaDataSourceProperties
.initializeDataSourceBuilder()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.build();
}
#Bean(name = "replicaEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean replicaEntityManagerFactory(
EntityManagerFactoryBuilder replicaEntityManagerFactoryBuilder,
#Qualifier("replicaDataSource") DataSource replicaDataSource) {
Map<String, String> replicaJpaProperties = new HashMap<>();
replicaJpaProperties.put("hibernate.dialect",
environment.getProperty("spring.jpa.properties.hibernate.dialect"));
replicaJpaProperties.put("hibernate.hbm2ddl.auto",
environment.getProperty("spring.jpa.hibernate.ddl-auto"));
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = replicaEntityManagerFactoryBuilder
.dataSource(replicaDataSource)
.packages("in.demo.order.replicadao.model")
.persistenceUnit("replicaDataSource")
.properties(replicaJpaProperties)
.build();
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return localContainerEntityManagerFactoryBean;
}
#Bean(name = "replicaTransactionManager")
public PlatformTransactionManager replicaTransactionManager(
#Qualifier("replicaEntityManagerFactory") EntityManagerFactory replicaEntityManagerFactory) {
return new JpaTransactionManager(replicaEntityManagerFactory);
}
}
OrderManagerService
package in.demo.order.service;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Transactional
public class OrderManagerService{
#Autowired
private OrderService orderService;
public CustomerVisitsResponse getCustomerVisitsForMonthForReplicaDataSource(Long customerId) {
return orderService.getTotalCustomerVisitsForMonth(customerId);
}
public CustomerVisitsResponse getCustomerVisitsForMonthForMainDataSource(Long customerId) {
return orderService.getTotalCustomerVisitsForMonthForMainDataSource(customerId);
}
}
OrderService
package in.demo.order.service;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import in.demo.order.replicadao.IOrderRequestRepositoryForReplica;
import in.demo.order.dao.IOrderRequestRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
#Transactional(rollbackFor = Exception.class)
public class OrderService {
#Autowired
private IOrderRequestRepositoryForMain orderRequestRepositoryForMain;
#Autowired
private IOrderRequestRepositoryForReplica orderRequestRepositoryForReplica;
public CustomerVisitsResponse getTotalCustomerVisitsForMonthForReplicaDataSource(Long customerId) {
List<Integer> statusIdList= new ArrayList<>();
CustomerVisitsResponse response= new CustomerVisitsResponse();
statusIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
List<Integer> sellerIdList= new ArrayList<>();
sellerIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
Date date = new Date();
String month= dateFormat.format(date)+"%";
try {
List<in.demo.order.replicadao.model.OrderEntity> orders =
orderRequestRepositoryForReplica.getOrderBycustomerIdAndBdMonth(customerId,statusIdList,month,sellerIdList);
if(orders!=null && !orders.isEmpty())
{
double total=0d;
for(in.demo.order.replicadao.model.OrderEntity order:orders)
{
total+=order.getPayableAmount();
}
response.setTotalSpent(total);
response.setTotalVisits((long)(orders.size()));
}
else {
response.setTotalSpent(0d);
response.setTotalVisits(0l);
}
} catch (Exception e) {
logger.info("Error occured while fetching getTotalCustomerVisitsForMonth customerid: " + customerId);
response.setTotalSpent(0d);
response.setTotalVisits(0l);
return response;
}
return response;
}
public CustomerVisitsResponse getTotalCustomerVisitsForMonthForMainDataSource(Long customerId) {
List<Integer> statusIdList= new ArrayList<>();
CustomerVisitsResponse response= new CustomerVisitsResponse();
statusIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
List<Integer> sellerIdList= new ArrayList<>();
sellerIdList.add(OrderServiceConstants.SOME_CONSTANT_VALUE);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
Date date = new Date();
String month= dateFormat.format(date)+"%";
try {
List<in.demo.order.dao.model.OrderEntity> orders =
orderRequestRepositoryForMain.getOrderBycustomerIdAndBdMonth(customerId,statusIdList,month,sellerIdList);
if(orders!=null && !orders.isEmpty())
{
double total=0d;
for(in.demo.order.dao.model.OrderEntity order:orders)
{
total+=order.getPayableAmount();
}
response.setTotalSpent(total);
response.setTotalVisits((long)(orders.size()));
}
else {
response.setTotalSpent(0d);
response.setTotalVisits(0l);
}
} catch (Exception e) {
logger.info("Error occured while fetching getTotalCustomerVisitsForMonth customerid: " + customerId);
response.setTotalSpent(0d);
response.setTotalVisits(0l);
return response;
}
return response;
}
}
Repository classes
package in.demo.order.replicadao;
IOrderRequestRepositoryForReplica
package in.demo.order.replicadao;
import in.demo.order.replicadao.model.OrderEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public interface IOrderRequestRepositoryForReplica extends PagingAndSortingRepository<OrderEntity, Long> {
#Query(nativeQuery = true,
value = "SELECT * FROM orders ...<some query>")
public List<OrderEntity> getOrderBycustomerIdAndBdMonth(
Long customerId, List<Integer> statusIdList, String month, List<Integer> sellerIdList);
}
package in.demo.order.dao;
IOrderRequestRepositoryForMain
package in.demo.order.dao;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import in.demo.order.dao.model.OrderEntity;
#Repository
public interface IOrderRequestRepositoryForMain extends PagingAndSortingRepository<OrderEntity, Long> {
#Query(nativeQuery = true,
value = "SELECT * FROM orders ...<some query>")
public List<OrderEntity> getOrderBycustomerIdAndBdMonth(Long customerId,
List<Integer> statusIdList, String month, List<Integer> sellerIdList);
}
Same Entity in different package with different serialVersionUID
packages are:
package in.demo.order.replicadao.model;
package in.demo.order.dao.model;
OrderEntity
package in.demo.order.replicadao.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "orders")
public class OrderEntity implements Serializable {
/**
*
*/
private static final long serialVersionUID = 4667868888563693990L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String orderId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
Controller class
OrderController
package in.demo.order.controller;
import java.util.List;
import javax.sql.DataSource;
import in.demo.order.service.OrderManagerService;
#RestController
#RequestMapping("/api/order")
public class OrderController {
#Autowired
private OrderManagerService orderManagerService;
#Autowired
private DataSource mainDataSource;
#Autowired
#Qualifier("replicaDataSource")
private DataSource replicaDataSource;
#GetMapping("/replica")
public CustomerVisitsResponse getCustomerVisitResponseForReplica() {
Long customerId = 123L;
return orderManagerService.getCustomerVisitsForMonthForReplicaDataSource(customerId);
}
#GetMapping("/main")
public CustomerVisitsResponse getCustomerVisitResponseForMain() {
Long customerId = 123L;
return orderManagerService.getCustomerVisitsForMonthForMainDataSource(customerId);
}
#GetMapping("/maindatasource")
public String getMainDataSource() {
return mainDataSource.toString();
}
#GetMapping("/replicadatasource")
public String getReplicaDataSource() {
return replicaDataSource.toString();
}
}
I have checked manually in both Database for the query I am running. Data is present. But when I am running from postman to test
for replica, I am getting
Method threw 'org.springframework.dao.InvalidDataAccessResourceUsageException' exception. caused by org.hibernate.exception.SQLGrammarException: could not execute query
for main, I am getting
Transaction was marked for rollback only; cannot commit; nested exception is org.hibernate.TransactionException: Transaction was marked for rollback only; cannot commit
When I am using one datasource, the query is working fine.
The issue was with Entity class column name mapping issue, previously we were not mentioning #Column(name="column_name") , once I added the annotation, it started to work. But still I am confused how it is working fine when using single Data source

Detecting the insertion of data into the table and calling the method

I have an issue with detecting adding a new row to the table. I want to trigger a method from some service (Spring boot) when somebody executes an insert query on the database (Postgres)
Somebody told me I can use #Scheduled annotation and check if something was added using a repository. I have to make some changes instantly (by using another method). The scheduled method should run every 5 seconds to do this instantly. Of course, this is a really bad idea because it will kill the database someday and it's not efficient.
How can I do this better?
You can write concrete implementer of org.hibernate.integrator.spi.Integrator. and give it to hibernate.integrator_provider
From ServiceRegistry we can get EventListenerRegistry and then append listener of type EventType.POST_INSERT. More events here.
Main Reference Hibernate Integrator Ref
As per the query, I have also added how to call the service method from the listener class.
Here is how I have done it:
package com.example.samplejdbctemplatecall;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.jpa.boot.spi.IntegratorProvider;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
#RequestMapping(path = "/entity-listener")
#RestController
public class SampleLogController {
private final SampleLogRepository sampleLogRepository;
private final SampleLogEntries sampleLogEntiries;
#Autowired
public SampleLogController(SampleLogRepository sampleLogRepository, SampleLogEntries sampleLogEntiries) {
this.sampleLogRepository = sampleLogRepository;
this.sampleLogEntiries = sampleLogEntiries;
}
// This is usually post method but for test purpose creating new log with uuid random and inserting
#GetMapping(path = "insert")
public SampleLog insertNewEntry() {
final String uuid = UUID.randomUUID().toString();
final SampleLog sampleLog = new SampleLog();
sampleLog.setMessage(uuid);
return sampleLogRepository.save(sampleLog);
}
#GetMapping(path = "list-recent-inserts")
public Map<Long, String> entries() {
return sampleLogEntiries.data();
}
}
#Slf4j
#Component
class HibernateConfig implements HibernatePropertiesCustomizer {
private final JpaEventListenerIntegrator jpaEventListenerIntegrator;
#Autowired
HibernateConfig(JpaEventListenerIntegrator jpaEventListenerIntegrator) {
this.jpaEventListenerIntegrator = jpaEventListenerIntegrator;
}
#Override
public void customize(Map<String, Object> hibernateProperties) {
log.warn("Called hibernate configuration");
hibernateProperties.put("hibernate.integrator_provider",
(IntegratorProvider) () -> Collections.singletonList(jpaEventListenerIntegrator));
}
}
#Configuration
class SampleConfiguration {
#Bean
SampleLogEntries sampleEntries() {
return new SampleLogEntries();
}
}
class SampleLogEntries {
private final ConcurrentMap<Long, String> map = new ConcurrentHashMap<>();
public void add(SampleLog sampleLog) {
this.map.put(sampleLog.getId(), sampleLog.getMessage());
}
public Map<Long, String> data() {
return Collections.unmodifiableMap(this.map);
}
}
#Repository
interface SampleLogRepository extends CrudRepository<SampleLog, Long> {
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
class SampleLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String message;
}
#Service
#Slf4j
class JpaEventListenerIntegrator implements Integrator, PostInsertEventListener {
private final SampleLogEntries sampleLogEntiries;
#Autowired
JpaEventListenerIntegrator(SampleLogEntries sampleLogEntiries) {
this.sampleLogEntiries = sampleLogEntiries;
}
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry
.appendListeners(EventType.POST_INSERT, this);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
EventListenerGroup<PostInsertEventListener> eventListenerGroup = eventListenerRegistry
.getEventListenerGroup(EventType.POST_INSERT);
log.info("listener attached were: " + eventListenerGroup.getClass().getSimpleName());
log.error("disintegrate : " + getClass().getCanonicalName());
eventListenerGroup.clearListeners();
}
#Override
public void onPostInsert(PostInsertEvent event) {
log.info("Inserted : " + event.getEntity());
final Object entity = event.getEntity();
if (entity instanceof SampleLog) {
sampleLogEntiries.add((SampleLog) entity);
}
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
The answer from #silentsudo is the best one if you only ever use JPA to update the table in question, and if you only have one process updating it.
The issue is that since you are being notified via the JPA interceptor, you won't be notified of any updates that happen outside of your JAP repository.
If you need these other notifications, then you can use Postgres' LISTEN/NOTIFY without polling by using an alternate postgresql JDBC driver, pgjdbc-ng, which implements async notifications.
With this method, you create a trigger in the database to send the notification, so you will be notified of other's updates as well. See https://www.openmakesoftware.com/postgresql-listen-notify-events-example

Encountered error "Consider defining a bean of type 'java.util.concurrent.atomic.AtomicReference' in your configuration"

I am getting the below error while starting spring boot application.
The injection point has the following annotations:
#org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type
'java.util.concurrent.atomic.AtomicReference' in your configuration.
Below is the code .
package de.summer.sampleapplayerv1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#SpringBootApplication(scanBasePackages = {"de.summer.sampleapplayerv1"})
#EnableConfigurationProperties
#EnableJpaRepositories (basePackages ="de.summer.sampleapplayerv1.repository")
#EnableTransactionManagement
public class Sampleapplayerv1Application {
public static void main(String[] args) {
SpringApplication.run(Sampleapplayerv1Application.class, args);
}
}
package de.summer.sampleapplayerv1.service;
import de.summer.sampleapplayerv1.domain.QueueAndPublish;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
#Slf4j
#Service
public class QueueAndPublishServiceImpl implements QueueAndPublishService{
private final AtomicReference<List<QueueAndPublish>> currentJob;
public QueueAndPublishServiceImpl(
#Qualifier("currentJob") AtomicReference<List<QueueAndPublish>> currentJob
){
this.currentJob=currentJob;
}
#Override
public QueueAndPublish getJobStatus(UUID jobId) {
return (QueueAndPublish) currentJob.get().stream()
.filter(j -> j.getJobId()==jobId)
.collect(Collectors.toList());
}
#Override
public List<QueueAndPublish> getAllJobStatus() {
return currentJob.get();
}
#Override
public QueueAndPublish getCategoryDataProcess() {
List<QueueAndPublish> processList=new ArrayList<QueueAndPublish>();
QueueAndPublish process=QueueAndPublish.builder()
.jobId(UUID.randomUUID())
.jobName("Name for Job")
.jobStatus("Not Yet Started")
.build();
Thread t1=new Thread(process.getJobId().toString()){
#Override
public void run(){
log.info("How are you doing");
process.setJobStatus("Completed");
}
};
t1.start();
processList.add(process);
currentJob.set(processList);
return process;
}
#Override
public QueueAndPublish getCatgeoryDataProcessStatus() {
return null;
}
}
package de.summer.sampleapplayerv1.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.io.Serializable;
import java.util.UUID;
#Getter
#Setter
#Builder
#Entity
public class QueueAndPublish implements Serializable {
#Id
private UUID jobId;
private String jobName;
private String jobStatus;
}
If I remove the constructor, spring boot application is starting up without any errors. If included , start up is failing with unsatisfied dependency errors.
Can someone please help on what is wrong with config?
You expect Spring to create an instance of class QueueAndPublishServiceImpl for the implementation of QueueAndPublishService. This instance needs a constructor parameter of type AtomicReference<List<QueueAndPublish>> injected.
But you obviously do not define any Spring bean (Bean, Component, Service, ...) of that type.
Edit:
public QueueAndPublishServiceImpl(
#Qualifier("currentJob") AtomicReference<List<QueueAndPublish>> currentJob
){
this.currentJob=currentJob;
}
Here you define a constructor parameter to have a AtomicReference<List<QueueAndPublish>>, and even specify it with a #Qualifier. So you need to provide a Spring bean of this class with this qualifier, otherwise Spring cannot inject it into the constructor call.
Consider defining a bean of type 'java.util.concurrent.atomic.AtomicReference' in your configuration.
Means "something like" adding this to your Sampleapplayerv1Application:
#Bean("currentJob") AtomicReference<List<QueueAndPublish>> currentJob() {
// or a list implementation of your choice.
return new AtomicReference<>(new java.util.ArrayList<>());
}

Is it a good idea to declare an injector and pass it as a state parameter when integrating typed Akka Actor with Spring

I could not find a neat way to integrate Spring with typed Actors.
Instead of using extensions I had declared an injector service
import akka.actor.typed.ActorSystem;
import com.akka.demo.a010.ASimpleSpringService;
import com.akka.demo.a010.AkkaSimpleBehaviour;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Data
public class ActorInjector {
#Autowired
private ASimpleSpringService aSimpleSpringService;
#Autowired
private ActorInjector _self;
public ActorSystem<AkkaSimpleBehaviour.Command> createAkkaSimpleBehaviour(String actorName) {
return ActorSystem.create(AkkaSimpleBehaviour.create(_self), actorName);
}
}
This service autowires itself and pass that reference to a simple actor.
My Actor definition is as follows.
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.Receive;
import com.akka.demo.a010.config.ActorInjector;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
#Slf4j
public class AkkaSimpleBehaviour extends AbstractBehavior<AkkaSimpleBehaviour.Command> {
private final List<String> messages = new ArrayList<>();
private ActorRef<List<String>> sender;
private ActorInjector actorInjector;
public interface Command extends Serializable {
}
#AllArgsConstructor
public static class TellMeSomething implements Command {
private static final long serialVersionUID = -7796709831949054890L;
#Getter
private final String message;
}
#AllArgsConstructor
public static class CollectTheResults implements Command {
private static final long serialVersionUID = 1643210899551075153L;
#Getter
private final ActorRef<List<String>> sender;
}
private AkkaSimpleBehaviour(ActorContext<Command> context, ActorInjector actorInjector) {
super(context);
this.actorInjector = actorInjector;
}
public static Behavior<Command> create(ActorInjector actorInjector) {
return Behaviors.setup(ctx -> new AkkaSimpleBehaviour(ctx,actorInjector));
}
#Override
public Receive<Command> createReceive() {
return newReceiveBuilder().onMessage(TellMeSomething.class, message -> {
messages.add(message.getMessage());
actorInjector.getASimpleSpringService().logSomething("*-*-*-*-*-*-*-*-*-*");
return Behaviors.same();
}).onMessage(CollectTheResults.class, message -> {
this.sender = message.getSender();
if (messages.size() == 4) {
this.sender.tell(messages);
}
return Behaviors.same();
}).build();
}
}
After passing the injector service I can get my autowired dependencies from that service like :
actorInjector.getASimpleSpringService().logSomething("*-*-*-*-*-*-*-*-*-*");
My ASimpleService is just a dummy service which logs an output.
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
#Service
#Slf4j
public class ASimpleSpringService {
public void logSomething(String message){
log.info(message);
}
}
Then in a simple RestController I am using the system as follows:
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.AskPattern;
import com.akka.demo.a010.config.ActorInjector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
#RestController
#Slf4j
public class MyRestController {
#Autowired
private ActorInjector actorInjector;
#GetMapping("/hello-akka")
public void greetings() {
ActorSystem<AkkaSimpleBehaviour.Command> exampleActor = actorInjector.createAkkaSimpleBehaviour("anActor");
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("hello"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("Who are you"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("create a child"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("Here is some message"));
CompletionStage<List<String>> result = AskPattern.ask(exampleActor, AkkaSimpleBehaviour.CollectTheResults::new, Duration.ofSeconds(10), exampleActor.scheduler());
result.whenComplete((reply, failure) -> {
if(reply != null){
log.info("The system responds in time");
} else {
log.error("The system does not respond in time");
exampleActor.terminate();
throw new RuntimeException("The system does not respond in time");
}
exampleActor.terminate();
});
try{
List<String> messages = result.toCompletableFuture().get();
messages.forEach(log::info);
} catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
}
}
My question is : I am planning to put all of my services into my ActorInjector service and these services will be injected into my actors. I am not familiar with Akka states and its side-effects however I know that it may be a bad idea to store all of these singleton services as actor states. Is it a good idea to store these services as an actor parameter ? What kind of side effects can I experience by doing this way? Can you point me the way?

How to create same Bean using Constructor Injection in Spring boot for different property values

I have to create multiple beans of same type for different property value which is to be injected using constructor.
Currently I have used Bean scope as Prototype & created multiple methods to read different properties to create new object. How to combine all the different methods into single method and to supply different values at run time to create new bean.
package com.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
#Configuration
public class ConfigClass {
#Bean(name="RC")
public JavaClient getClient(#Autowired Environment env)
{
String sName=env.getProperty("RCSName");
String aUrl=env.getProperty("RCAUrl");
String dUrl=env.getProperty("RCDUrl");
return new JavaClient(sName,aUrl,dUrl);
}
#Bean(name="O")
public JavaClient getOClient(#Autowired Environment env)
{
String sName=env.getProperty("OSName");
String aUrl=env.getProperty("OAUrl");
String dUrl=env.getProperty("ODUrl");
return new JavaClient(sName,aUrl,dUrl);
}
}
Now it is creating 2 beans as per above code. Expectation: How to combine getClient & getOClient into single method, but the property to be supplied in a loop to create multiple beans of same type JavaClient
I have modified my ConfigClass as below & created ApplicationContextAware to inject beans by reading file properties.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.EnableScheduling;
#Configuration
#EnableScheduling
public class ConfigClass {
#Bean
#Scope("prototype")
public JavaClient getClient(String sName,String aUrl,String dUrl)
{
return new JavaClient(sName,aUrl,dUrl);
}
}
Then Have created ApplicationContextAware to create Beans.
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Iterator;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getContext() {
return applicationContext;
}
#PostConstruct
public void init()
{
try {
String fileName = "Url.txt";
Resource resource = new ClassPathResource(fileName);
File file = resource.getFile();
List<String> lines = Files.readAllLines(file.toPath());
for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) {
String line = (String) iterator.next();
String[] s = line.split(",");
applicationContext.getBean(JavaClient.class,s[0], s[1], s[2]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

Resources