#Cacheable and initialization during startup - spring

I would like to initialize all entries in cache during startup of my spring boot application (loading stuff from DB). Ideally, this is done before the application is already ready. So I implemented all loading in #PostConstruct. I remarked, that the cache is not already setup in #PostContruct and I followed some tips to do such initializations in the ApplicationReadyEvent. However, this still does not work as expected:
Even though I already call a #Cacheable Method in ApplicationReadyEvent, the second invocation re-enters the method instead of using the cache.
My Service:
#Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {
#Cacheable("entry")
public List<String> getEntry() {
System.out.println("getEntry called!");
return Arrays.asList("aaa", "bbb");
}
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("*** onApplicationEvent");
getEntry();
}
}
My Caffeine CacheManager Config:
#Configuration
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
List<CaffeineCache> caffeineCaches = chacheList(Arrays.asList(
"entry"
));
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(caffeineCaches);
System.out.println("*** #Bean CacheManager");
return simpleCacheManager;
}
private List<CaffeineCache> chacheList(List<String> cacheNames) {
return cacheNames.stream().map(s -> new CaffeineCache(s, Caffeine.newBuilder().build()))
.collect(Collectors.toList());
}
}
A simple REST endpoint using the service:
#RestController
public class MyController {
#Autowired
MyService myService;
#GetMapping("/test")
public void test()
{
System.out.println("*** GET /test");
myService.getEntry();
}
}
If I start the application and perform two GET /test, I get the following output:
INFO 20120 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 907 ms
*** #Bean CacheManager
INFO 20120 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
INFO 20120 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
INFO 20120 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.639 seconds (JVM running for 2.473)
*** onApplicationEvent
*** getEntry called!
INFO 20120 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 20120 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO 20120 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
*** GET /test
*** getEntry called!
*** GET /test
So why does the second invocation of MyService.getEntry (i.e. the first invocation after "Startup") enters the code again?
At the end, I need a solution, which performs the first loading before the application finished to startup - i.e. I will try ContextRefreshedEvent or again #PostConstruct (and #Autowire CacheManager to have it configured before executing #PostConstruct). But the first step would be to get this example here behave as expected.

Ok, stupid error: in my service, the call to getEntry() must be done over proxy object rather than directly:
#Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {
#Autowired
MyService self;
#Cacheable("entry")
public List<String> getEntry() {
System.out.println("getEntry called!");
return Arrays.asList("aaa", "bbb");
}
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("*** onApplicationEvent");
self.getEntry();
}
}

Related

JOOQ and Spring Batch transaction management

I am currently working on Spring Batch (2.6.3) application and I am using JOOQ (3.14.15) to access MariaDB database (10.5.8).
So far I got working few jobs and got to the point where I am testing transactions.
I am trying to run tasklet which is to my understanding executed in transaction by default. I have made repository with simple insert which I annotated with #Transactional. I have added Thread.sleep and my "test case" is that I let Spring Batch execute my test tasklet and during sleep period I kill my application. I expected that there will be no record in T_FOO table since I expected rollback of transaction but there is record in my T_FOO table even though in BATCH_STEP_EXECUTION my test step (after application shutdown) has status of STARTED with END_TIME set as null - which I am guessing is representation that is expected when application gets shutdown while step is executing.
I am guessing that I did not setup correctly transaction management for Spring Batch and JOOQ or something along those lines? But it colud be wrong guess of course
Could someone please help me with this issue? I was trying to google how to properly setup JOOQ with Spring Batch but the only resource I could find was this one and I wrote jooq configuration inspired by that article and also I have tried copy paste configuration in article but it did not resolve my issue.
Thanks in advance for your time.
Tasklet test step
public Step testStep() {
return this.stepBuilderFactory.get(TEST_STEP)
.tasklet((stepContribution, chunkContext) -> {
final Integer batchId = JobUtils.getBatchId(chunkContext);
final LocalDateTime batchDate = JobUtils.getBatchDate(chunkContext);
importRepository.importBatch(batchId, batchDate);
/* Thread.sleep is here only for testing purposes - at this sleep I am shuting down application */
log.debug("Waiting start");
Thread.sleep(30000);
log.debug("Waiting end");
return RepeatStatus.FINISHED;
}).listener(loadingProcessingErrorListener)
.build();
}
Transactional(propagation = Propagation.REQUIRED)
public void importBatch(#NonNull Integer batchId, #NonNull LocalDateTime batchDate) {
/* simple insert select statement */
context.insertInto(T_FOO)
select(context.select(asterisk()).from(T_BAR))
execute();
}
Batch configuration
#Configuration
#RequiredArgsConstructor
public class BatchConfiguration extends DefaultBatchConfigurer {
private final JobRegistry jobRegistry;
private final DataSource dataSource;
#Override
#NonNull
#SneakyThrows
public JobLauncher getJobLauncher() {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(getJobRepository());
jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
#Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() {
JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
postProcessor.setJobRegistry(jobRegistry);
return postProcessor;
}
#Override
#NonNull
public PlatformTransactionManager getTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
JOOQ configuration
#Configuration
#RequiredArgsConstructor
public class JooqConfiguration {
private final DataSource dataSource;
#Bean
public DataSourceConnectionProvider connectionProvider() {
return new DataSourceConnectionProvider(new TransactionAwareDataSourceProxy(dataSource));
}
#Bean
public DefaultDSLContext context() {
return new DefaultDSLContext(configuration());
}
public DefaultConfiguration configuration() {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(connectionProvider());
jooqConfiguration.setDataSource(dataSource);
jooqConfiguration.setSQLDialect(SQLDialect.MARIADB);
jooqConfiguration.setSettings(new Settings().withExecuteWithOptimisticLocking(true));
return jooqConfiguration;
}
}
Console log
2022-05-28 21:26:18.571 DEBUG 15656 --- [cTaskExecutor-1] s.o.p.c.p.a.j.i.ImportLoadingSteps : Starting loading import for Batch ID [1]
2022-05-28 21:26:18.607 INFO 15656 --- [cTaskExecutor-1] o.s.batch.core.job.SimpleStepHandler : Executing step: [testStep]
2022-05-28 21:26:18.861 DEBUG 15656 --- [cTaskExecutor-1] org.jooq.tools.LoggerListener : Executing query : insert into `t_foo`...
2022-05-28 21:26:18.875 DEBUG 15656 --- [cTaskExecutor-1] org.jooq.tools.LoggerListener : Affected row(s) : 1
2022-05-28 21:26:18.876 DEBUG 15656 --- [cTaskExecutor-1] s.o.p.c.p.a.j.i.ImportLoadingSteps : Waiting start
2022-05-28 21:26:23.759 INFO 15656 --- [ionShutdownHook] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED paused.
2022-05-28 21:26:23.783 INFO 15656 --- [ionShutdownHook] o.s.s.quartz.SchedulerFactoryBean : Shutting down Quartz Scheduler
2022-05-28 21:26:23.783 INFO 15656 --- [ionShutdownHook] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED shutting down.
2022-05-28 21:26:23.783 INFO 15656 --- [ionShutdownHook] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED paused.
2022-05-28 21:26:23.784 INFO 15656 --- [ionShutdownHook] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED shutdown complete.
2022-05-28 21:26:23.790 INFO 15656 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
Process finished with exit code -1
You're doing this:
jooqConfiguration.set(connectionProvider());
But then, you're also needlessly doing this:
jooqConfiguration.setDataSource(dataSource);
The latter overrides the former, removing the TransactionAwareDataSourceProxy semantics, and has no purpose otherwise, either. Just remove that call.

#SpringBootTest constructor is running multiple times

I am trying to run below test case in Spring Boot.
:: Spring Boot :: (v2.3.1.RELEASE)
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = com.dineoutsafe.api.dosadmin.DOSAdminAPIApplication.class)
#ActiveProfiles("test")
public class POSTSessionTest {
public POSTSessionTest() {
System.out.println("Calling post construct");
}
#Test
public void testOne(){
assertThat(45,equalTo(30+15));
}
#Test
public void testTwo(){
assertThat(45,equalTo(30+15));
}
#Test
public void testThree(){
assertThat(45,equalTo(30+15));
}
#Test
public void testFour(){
assertThat(45,equalTo(30+15));
}
#Test
public void testFive(){
assertThat(45,equalTo(30+15));
}
}
And I noticed that the constructor is running multiple times. Actually it is running (no. of #Test -1) times.
In standard output
2020-06-21 16:00:26.668 INFO 93912 --- [ task-1] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-06-21 16:00:26.679 INFO 93912 --- [ task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-06-21 16:00:27.025 INFO 93912 --- [ Test worker] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-06-21 16:00:27.034 INFO 93912 --- [ Test worker] c.d.a.d.i.session.POSTSessionTest : Started POSTSessionTest in 5.511 seconds (JVM running for 6.414)
Calling post construct
Calling post construct
Calling post construct
Calling post construct
Same behaviour I noticed for #PostConstruct.
Is it normal for #SpringBootTest?
This is the default behavior of JUnit5, you can change it by annotating the per-class lifecycle on the class: https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/TestInstance.Lifecycle.html

running the application on controller

I'm still a beginner with spring boot, I'm using spring JPA to fetch the data from multiple tables in the same database and everything going fine, I used to run my application at the Main but here I have added a Controller class and running things there, then i used #Scheduled(fixedRate=7000) instead of creating an infinite loop to keep check the data from db and stay live, the application working fine but as far as at the running time application executed twice instead of once at the beginning before scheduling, a is there any idea about what happened here :
Mainclass :
#SpringBootApplication
#EnableScheduling
public class AccessingDataJpaApplication {
public static void main(String[] args) throws Exception{
SpringApplication.run(AccessingDataJpaApplication.class);
}
}
Controller class :
#Controller
#EnableScheduling
public class MainController {
private static final Logger logger = LoggerFactory.getLogger(MainController.class);
#Autowired
private CustomerRepository customerRepository;
#Autowired
private MessageRepository messageRepository;
private Set<String> camps = new HashSet<String>();
#Bean
#Scheduled(fixedRate=7000)
public void run(){
logger.info("Running");
if((customerRepository.findAllByStatusAndCampType(0, 1).size()) > 0 ){
for(Customer customer : customerRepository.findAll()){
System.out.println(customer.getCampCd());
camps.add(customer.getCampCd());
}
System.out.println("----------------------------------------");
for(MessageCampain messagecampain : messageRepository.findAllByCampCdIn(camps)) {
System.out.println(messagecampain.toString());
}
System.out.println("------------------------------------------");
for(String value : camps) {
System.out.println(value);
}
}
}
}
execution log :
[ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
[ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
[ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
[ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
[ main] c.e.accessingdatajpa.MainController : Running
[ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
[ main] c.e.a.AccessingDataJpaApplication : Started AccessingDataJpaApplication in 5.467 seconds (JVM running for 6.242)
[ scheduling-1] c.e.accessingdatajpa.MainController : Running
[ scheduling-1] c.e.accessingdatajpa.MainController : Running
you can notice that at Running word
It is because you annotate the run() in MainController as #Bean , which will creates a lite mode bean called run. (Spring represents this bean as the type of NullBean internally)
So , the 1st call of 'Running' in the main thread is due to spring instantiate this run bean. The remaining calls of 'Running' in the scheduling-1 thread are due to the the effect of #Scheduled. So please remove #Bean from the run() as it does not have any points to create a null bean ...
#Scheduled(fixedRate=7000)
public void run(){
}

Configuring Camel for Spring with MINA 2

I want to configure Camel for Spring with MINA 2.
I did the following configuration code:
#Configuration
public class SpringConfiguration {
public static final String THREADPOOL_ID = "poolId";
#Bean
CamelContextConfiguration contextConfiguration() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext context) {
context.addComponent("mina2", new Mina2Component());
}
#Override
public void afterApplicationStart(CamelContext arg0) {
}
};
}
}
But when I wrote the router code like below. But it is not working:
#Component
public class RouteConfiguration extends RouteBuilder {
#Value("${app.collectorStringInput}")
private String collectorStringInput;
#Value("${app.mapOutputQueue}")
private String mapOutputQueue;
private final SiemParserProcessor parserProcessor;
public RouteConfiguration(SiemParserProcessor parser) {
this.parserProcessor = parser;
}
#Override
public void configure() throws Exception {
from("mina2:udp://10.31.0.32:514?disconnectOnNoReply=false&sync=false").to("log:edu.accs.siem.collector?level=DEBUG");
}
}
However, I can see this lines in the log:
2018-06-30 11:37:14.270 INFO 480 --- [ restartedMain] o.a.camel.spring.SpringCamelContext : Route: route1 started and consuming from: mina2://udp://10.31.0.32:514?disconnectOnNoReply=false&sync=false
2018-06-30 11:37:14.270 INFO 480 --- [ restartedMain] o.a.camel.spring.SpringCamelContext : Total 1 routes, of which 1 are started
2018-06-30 11:37:14.271 INFO 480 --- [ restartedMain] o.a.camel.spring.SpringCamelContext : Apache Camel 2.21.1 (CamelContext: camel-1) started in 0.185 seconds
It is working without using Spring. So I guess there is some configuration issue.
Can anybody tell me what I am missing?
PS: I checked out netty, but it seems not working even when not using Spring.
I got it working.
Actually, this was processing,
But somehow the logging wasn't displaying.

What is order of evaluation for #Autowiring in SpringBoot project

I'm trying to write a simple SprintBoot REST Controller to run on Websphere Liberty, and having a problem with #Autowire. Here are the relevant (I think) pieces of code:
#CrossOrigin
#RestController
#RequestMapping(path = "userSetting")
public class RESTInterface {
#Autowired
private UserSettingDAO userSettingDAO;
#RequestMapping(path = "/getUserSetting", method = { RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String getUserSetting(
#RequestParam(value="user_id", required=true ) String user_id,
#RequestParam(value="app_id", required=true ) String app_id,
#RequestParam(value="dashboard_name", required=true ) String dashboard_name
) {
if ( userSettingDAO == null ) {
System.out.println( "userSettingDAO is null" );
...
////////////////////////////////////////////////////////////////////////////////
#Component
public class UserSettingDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
private DataSource dataSource;
public UserSettingDAO() {
System.out.println( "dataSource is " + dataSource );
System.out.println( "jdbcTemplate is " + jdbcTemplate );
When I deploy the application to Liberty, it appears to start up OK.
When I hit the appropriate endpoint, I see the following in console.log:
UserSettingDAO.jdbcTemplate = null
dataSource is null
jdbcTemplate is null
2018-04-24 16:54:19.887 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/getUserSetting],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String com.ui.usersetting.restinterface.RESTInterface.getUserSetting(java.lang.String,java.lang.String,java.lang.String)
2018-04-24 16:54:19.890 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/upsertUserSetting],methods=[POST],produces=[application/json;charset=UTF-8]}" onto public void com.ui.usersetting.restinterface.RESTInterface.upsertUserSetting(java.lang.String,java.lang.String,java.lang.String,java.lang.String)
2018-04-24 16:54:19.891 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/deleteUserSetting],methods=[DELETE],produces=[application/json;charset=UTF-8]}" onto public void com.ui.usersetting.restinterface.RESTInterface.deleteUserSetting(java.lang.String,java.lang.String,java.lang.String)
2018-04-24 16:54:19.893 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/userSetting/hello],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String com.ui.usersetting.restinterface.RESTInterface.hello()
2018-04-24 16:54:19.924 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2018-04-24 16:54:19.925 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-04-24 16:54:20.166 INFO 8807 --- [cutor-thread-11] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for #ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext#6a5997f7: startup date [Tue Apr 24 16:54:15 EDT 2018]; root of context hierarchy
2018-04-24 16:54:21.386 INFO 8807 --- [cutor-thread-11] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-04-24 16:54:21.389 INFO 8807 --- [cutor-thread-11] o.s.j.e.a.AnnotationMBeanExporter : Bean with name 'dataSource' has been autodetected for JMX exposure
2018-04-24 16:54:21.396 INFO 8807 --- [cutor-thread-11] o.s.j.e.a.AnnotationMBeanExporter : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
My question is: Why are the two println statements saying that their respective variables are null? Does it have something to do with the fact that the UserSettingDAO constructor appears in the log to be executed before the lines about dataSource appear in the log?
What should I do to get those variables properly initialized?
At the time a Spring component's constructor is called, any #Autowired objects will still be null. This is because the spring container needs to initialize an instance of the class before it can inject values into the #Autowired fields.
I don't know how Spring implements injection, but most Java injection works something like this:
Construct new instance of the bean class, normally by invoking default ctor (or initializing a dynamically generated sub-class proxy)
Obtain objects that need to be injected into the bean
Using reflection/proxy, set the fields on the bean
To validate this, I created a method on UserSettingsDAO that utilizes the #AutoWired fields:
#Component
public class UserSettingDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
private DataSource ds;
public UserSettingDAO() {
System.out.println("ctor/jdbcTemplate is " + jdbcTemplate );
System.out.println("ctor/datasource is: " + ds);
}
public void doStuff() {
System.out.println("doStuff/jdbcTemplate is " + jdbcTemplate );
System.out.println("doStuff/datasource is: " + ds);
}
}
If we inject this DAO into another class and use it, we will see that the fields are initialized after the UserSettingDAO is constructed:
#CrossOrigin
#RestController
public class RESTInterface {
#Autowired
private UserSettingDAO dao;
#RequestMapping(path = "/jdbc", method = { RequestMethod.GET }, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String jdbc() {
return dao.doStuff();
}
}
Checking logs, this will produce the following output:
ctor/jdbcTemplate is null
ctor/datasource is: null
doStuff/jdbcTemplate is org.springframework.jdbc.core.JdbcTemplate#4eb46bed
doStuff/datasource is: com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource#727d23c5

Resources