I am downloading file(s) from S3 bucket and then trying to read it using MultiResourceItemReader as there could be more then one csv file in the bucket.
I am able to download the files and place it on my local but I am not able to read the files, the files are not in the classpath. I am passing full filepath to read the files, but something is not working as expected. I am getting : No resources to read. Set strict=true if this should be an error condition.
I may not be passing the file path correctly.
Custom Log :
2021-04-21 00:32:09.690 INFO 106084 --- [ restartedMain] c.c.S.I.TaskletS3DownloadFiles : Started Downloading files from S3 bucket..
2021-04-21 00:32:10.086 INFO 106084 --- [ restartedMain] c.c.S.Config.S3BucketConfig : The input file : MI4275/input/MI4275sample.csv, has been successfully copied to : C:\Users\Desktop\data\in\MI4275sample.csv, and the file size is : 239505 bytes
2021-04-21 00:32:11.369 INFO 106084 --- [ restartedMain] o.s.batch.core.step.AbstractStep : Step: [step1MI4275S3ListCopyFiles] executed in 4s697ms
2021-04-21 00:32:16.475 INFO 106084 --- [ restartedMain] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1ReadLoadMI4275CSV]
2021-04-21 00:32:17.477 WARN 106084 --- [ restartedMain] o.s.b.item.file.MultiResourceItemReader : No resources to read. Set strict=true if this should be an error condition.
2021-04-21 00:32:20.686 INFO 106084 --- [ restartedMain] o.s.batch.core.step.AbstractStep : Step: [step1ReadLoadMI4275CSV] executed in 4s210ms
2021-04-21 00:32:23.960 INFO 106084 --- [ restartedMain] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=jobCSProvMI4275]] completed with the following parameters: [{JobID=1618990319394}] and the following status: [COMPLETED] in 20s416ms
Code :
#Configuration
#EnableBatchProcessing
public class JobStepBuilderConfig {
#Autowired
JobBuilderFactory jobBuilderFactory;
#Autowired
StepBuilderFactory stepBuilderFactory;
#Autowired
DataSource datasource;
#Autowired
TaskletS3DownloadFiles taskletS3DownloadFiles;
#Value("${local.input.file.path.pattern}")
private Resource[] resources;
String currentDate ;
#Bean
#StepScope
// MultiResourceItemReader to read multiple files sequentially
public MultiResourceItemReader<Provider> providerMultiResourceItemReader() {
MultiResourceItemReader<Provider> multiResourceItemReader = new MultiResourceItemReader<>();
multiResourceItemReader.setResources(resources);
multiResourceItemReader.setDelegate(providerItemReader());
return multiResourceItemReader;
}
#Bean
#StepScope
// FlatfileItemReader to define file properties
public FlatFileItemReader<Provider> providerItemReader(){
// create FlatFileItemReader
FlatFileItemReader<Provider> reader = new FlatFileItemReader<>();
// skip header
reader.setLinesToSkip(1);
DefaultLineMapper<Provider> customerLineMapper = new DefaultLineMapper<>();
// tokenizer for delimited file
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
customerLineMapper.setLineTokenizer(tokenizer);
customerLineMapper.setFieldSetMapper(new ProviderFieldSetMapper());
customerLineMapper.afterPropertiesSet();
reader.setLineMapper(customerLineMapper);
return reader ;
}
#Bean
// JdbcBatchItemWriter to write records into database
public JdbcBatchItemWriter<Provider> providerJdbcBatchItemWriter(){
JdbcBatchItemWriter<Provider> jdbcBatchItemWriter = new JdbcBatchItemWriter<>();
jdbcBatchItemWriter.setDataSource(this.datasource);
jdbcBatchItemWriter.setSql(insertQuery);
jdbcBatchItemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
jdbcBatchItemWriter.afterPropertiesSet();
return jdbcBatchItemWriter;
}
// Steps
#Bean
public Step step1(){
// step 1 : Read records from custom table and call stored procedure to update facets table
return stepBuilderFactory.get("step1S3ListCopyFiles")
.tasklet(taskletS3DownloadFiles)
.build();
}
#Bean
public Step step2(){
// step 2 : Read csv files and dump it into a custom table
return stepBuilderFactory.get("step2ReadLoadCSV")
.<Provider, Provider>chunk(1000)
.reader(providerMultiResourceItemReader())
.writer(providerJdbcBatchItemWriter())
.build();
}
// Job
#Bean
public Job job(){
return jobBuilderFactory.get("jobCSProvMI")
.start(step1())
.next(step2())
.incrementer(new RunIdIncrementer())
.build();
}
}
application.properties
local.input.file.path.pattern =file:C:/Users/Desktop/data/in/*.csv
You are declaring a bean definition method (the method providerMultiResourceItemReader() annotated with #Bean) in a class annotated with #Component. This is not correct. You should be declaring your beans in a class annotated with #Configuration instead.
I see no need for your #Component public class FileItemReader { .. } class, you could define the reader in the same class where you define your step, something like:
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
#Configuration
#EnableBatchProcessing
public class MyJobConfiguration {
#Bean
public ItemReader<Integer> itemReader() {
return new ListItemReader<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
}
#Bean
public ItemWriter<Integer> itemWriter() {
return items -> items.forEach(System.out::println);
}
#Bean
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) {
return jobs.get("job")
.start(steps.get("step")
.<Integer, Integer>chunk(5)
.reader(itemReader())
.writer(itemWriter())
.build())
.build();
}
}
If you really want to extract the reader's configuration in a separate class, then you need to annotate it with #Configuration:
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
#Configuration
public class ItemReaderConfiguration {
#Bean
public ItemReader<Integer> itemReader() {
return new ListItemReader<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
}
}
Then import that class in your main job configuration:
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
#Configuration
#Import(ItemReaderConfiguration.class)
#EnableBatchProcessing
public class MyJobConfiguration {
#Bean
public ItemWriter<Integer> itemWriter() {
return items -> items.forEach(System.out::println);
}
#Bean
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps, ItemReader<Integer> itemReader) {
return jobs.get("job")
.start(steps.get("step")
.<Integer, Integer>chunk(5)
.reader(itemReader)
.writer(itemWriter())
.build())
.build();
}
}
Related
Situation :
There is a batch steps named A, B, C, D, E.
A is always executed first.
Depending on A's exitStatus, if A gives "SOMECONDITION", B is executed. On "OTHERCONDITION" C is executed. Job instances are not allowed to execute both B and C.
Regardless of executed Step is B or C, they should go to D.
After executing D, by using next(), E is executed.
Problem : Ugly code structure.
By redundant use of on(), to(), I managed to figure out the flow that I need.
As directly observable, there is really ugly code next() on each on-to, to direct them into common flow.
Adding more ugliness, I have to use from()-on("*") to direct each of on-to-next to some common flow.
Question :
At least this works, but i wonder, is this the only code that satisfies above condition?
Problem : Ugly code structure.
Adding more ugliness, I have to [...]
We, the Spring team, are always open to constructive feedback. So if you have ideas on how to improve the DSL of defining job flows, you are welcome to open a ticket on our issue tracker and we will do our best to improve things if needed.
Now to answer your question, the next(...) statements after each on("..").to("..") are not necessary. This is not what the documentation says, see example here: Conditional Flow.
According to your description, the flow to implement is the following:
This is actually a conditional flow (A -> decider -> B or C) followed by a sequential flow (D -> E).
The way I would define such flow is something like:
#Bean
public Job job() {
Flow conditionalFlow = new FlowBuilder<SimpleFlow>("conditionalFlow")
.start(stepA())
.next(decider()).on("SOMECONDITION").to(stepB())
.from(decider()).on("OTHERCONDITION").to(stepC())
.end();
return new JobBuilder("myJob", jobRepository)
.start(conditionalFlow)
.next(stepD())
.next(stepE())
.end()
.build();
}
This way, the flows are clearly defined. Here is a complete sample that you can run and check the results (make sure to use Spring Batch 5 and to have HSQLDB in the classpath):
package org.springframework.batch.sample;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.job.flow.support.SimpleFlow;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
#Configuration
#EnableBatchProcessing
public class MyJobConfiguration {
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
public MyJobConfiguration(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
this.jobRepository = jobRepository;
this.transactionManager = transactionManager;
}
private Step createStep(String stepName) {
return new StepBuilder("step" + stepName, jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("running step " + stepName);
return RepeatStatus.FINISHED;
}, transactionManager)
.build();
}
#Bean
public Step stepA() {
return createStep("A");
}
#Bean
public Step stepB() {
return createStep("B");
}
#Bean
public Step stepC() {
return createStep("C");
}
#Bean
public Step stepD() {
return createStep("D");
}
#Bean
public Step stepE() {
return createStep("E");
}
#Bean
public JobExecutionDecider decider() {
return (jobExecution, stepExecution) -> {
return new FlowExecutionStatus("SOMECONDITION"); // SOMECONDITION, OTHERCONDITION
};
}
#Bean
public Job job() {
Flow conditionalFlow = new FlowBuilder<SimpleFlow>("conditionalFlow")
.start(stepA())
.next(decider()).on("SOMECONDITION").to(stepB())
.from(decider()).on("OTHERCONDITION").to(stepC())
.end();
return new JobBuilder("myJob", jobRepository)
.start(conditionalFlow)
.next(stepD())
.next(stepE())
.end()
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
jobLauncher.run(job, new JobParameters());
}
// infrastructure beans
static class DataSourceConfiguration {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
.build();
}
#Bean
public JdbcTransactionManager transactionManager(DataSource dataSource) {
return new JdbcTransactionManager(dataSource);
}
}
}
This sample prints:
running step A
running step B
running step D
running step E
If you make the decider return "OTHERCONDITION", the sample prints:
running step A
running step C
running step D
running step E
which is what is required from your description.
I am learning Spring AOP and tried to call the #BeforeAdvise for two Different methods in different classes. But the log added in the first call is getting printed only. Please check the code below and let me know what is not correct here.
MainClass
package com.example.aspects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.aspects.AopClasses.DemoConfig;
#SpringBootApplication
public class AspectsApplication {
public static void main(String[] args) {
SpringApplication.run(AspectsApplication.class, args);
}
}
AspectClass
package com.example.aspects.Acpects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyLoggingDemo {
private static final Logger logger = LogManager.getLogger(MyLoggingDemo.class);
#Before("execution(public String test())")
public void beforeCheck() {
logger.info("Before Check !!!!!!!!!!!! "+System.currentTimeMillis() );
}
}
ConfigClass
package com.example.aspects.AopClasses;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy
#ComponentScan("com.example.aspects")
public class DemoConfig {
}
TestClass- Where the first call is made to the function
package com.example.aspects.AopClasses;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/")
public class tesTClass {
private static final Logger logger = LogManager.getLogger(tesTClass.class);
MemberShipDAO dao = new MemberShipDAO();
#RequestMapping(value = "/")
public String test() {
logger.info("Hey I am here");
return "HEy There I am Working !!";
}
#RequestMapping(value ="/test")
public String testing() {
logger.info("MemberShipDAO Call");
return dao.test(); // method with same name called here again
}
}
MemberShipDAO - Declared the test method again here
package com.example.aspects.AopClasses;
import org.springframework.stereotype.Component;
#Component
public class MemberShipDAO {
public String test() {
return "HEy!!";
}
}
Console
2022-01-18 11:37:52.794 INFO 8032 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-01-18 11:37:52.796 INFO 8032 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-01-18 11:37:52.802 INFO 8032 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
2022-01-18 11:37:52.811 INFO 8032 --- [nio-8080-exec-1] c.example.aspects.Acpects.MyLoggingDemo : Before Check !!!!!!!!!!!! 1642486072811
2022-01-18 11:37:52.854 INFO 8032 --- [nio-8080-exec-1] c.example.aspects.AopClasses.tesTClass : Hey I am here
2022-01-18 11:37:57.383 INFO 8032 --- [nio-8080-exec-3] c.example.aspects.AopClasses.tesTClass : MemberShipDAO Call
Change this
MemberShipDAO dao = new MemberShipDAO();
to this
#Autowired MemberShipDAO dao;
Spring's magic for the AOP happens only when you use the instance created by Spring. Behind the scene, Spring instantiated your dao and wrapped it with a proxy that makes a call to your aspect.
I'm wasting a lot of time right now with AOP logging setup.
I don't know why AOP isn't working in my project.
I think I've done all the settings I can.
Please let me know if you guys have a solutions.
Thank you.
application.java
#EnableAspectJAutoProxy
#SpringBootApplication
#ComponentScan(basePackages = "com.demo.apiservice")
#MapperScan("com.demo.apiservice.mapper")
public class ApiServiceApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ApiServiceApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ApiServiceApplication.class, args);
}
#Bean
public ModelMapper modelMapper() {
return new CustmizedModelMapper();
}
#Bean
public AopLoggingConfig loggingAspect(){
return new AopLoggingConfig();
}
}
build.gradle
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
dependencies {
//implementation 'org.springframework.boot:spring-boot-starter-security:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-log4j2:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-mail:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-aop:2.5.5'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.3'
implementation 'org.springframework.boot:spring-boot-starter-data-rest:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.projectlombok:lombok:1.18.8'
implementation 'org.modelmapper:modelmapper:2.3.8'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.5'
implementation 'org.springframework.boot:spring-boot-starter-jdbc:2.5.5'
implementation 'com.h2database:h2:1.4.200'
implementation 'org.springframework.boot:spring-boot-configuration-processor:2.5.5'
implementation 'org.springframework.security:spring-security-core:5.4.2'
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.3'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.3'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
implementation 'com.nimbusds:nimbus-jose-jwt:9.7'
implementation 'io.springfox:springfox-swagger2:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:2.9.2'
implementation 'joda-time:joda-time:2.10.10'
implementation 'io.jsonwebtoken:jjwt-api:0.11.1'
implementation 'javax.inject:javax.inject:1'
implementation 'com.googlecode.json-simple:json-simple:1.1'
implementation 'de.mkammerer:argon2-jvm:2.7'
implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
implementation 'org.apache.maven.plugins:maven-surefire-plugin:2.22.0'
implementation 'javax.validation:validation-api:2.0.1.Final'
implementation 'org.postgresql:postgresql:42.1.4'
implementation 'org.hibernate:hibernate-gradle-plugin:5.6.1.Final'
implementation 'com.jayway.jsonpath:json-path:2.6.0'
compileOnly 'org.projectlombok:lombok:1.18.8'
testCompileOnly 'org.projectlombok:lombok:1.18.8'
runtimeOnly 'org.springframework.boot:spring-boot-devtools:2.5.5'
annotationProcessor 'org.projectlombok:lombok:1.18.8'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.8'
testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.5'
testImplementation 'org.springframework.security:spring-security-test:5.5.2'
}
AopLoggingComponent.java
package com.demo.apiservice.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class AopLoggingConfig {
Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
static String name = "";
static String type = "";
/**
* AspectJ is applied only to a specific class/method in package.
*/
#Around("execution(* com.demo.apiservice.customer.*Controller.*(..))")
public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
type = joinPoint.getSignature().getDeclaringTypeName();
if (type.indexOf("Controller") -1) {
name = "Controller \t: ";
}
else if (type.indexOf("Service") -1) {
name = "ServiceImpl \t: ";
}
else if (type.indexOf("DAO") -1) {
name = "DAO \t\t: ";
}
logger.debug(name + type + ".######################### " + joinPoint.getSignature().getName() + "()");
return joinPoint.proceed();
}
}
controller.java
package com.demo.apiservice.customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.demo.apiservice.constant.Constants;
import com.demo.apiservice.customer.service.CustomerService;
import com.demo.apiservice.customer.service.impl.CustomerServiceImpl;
import com.demo.apiservice.request.CustomerRequest;
import com.demo.apiservice.request.LoginRequest;
import com.demo.apiservice.response.GetCustomerResponse;
import com.demo.apiservice.response.SuccessResponse;
import com.demo.apiservice.utils.ResponseHandler;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import javax.validation.Valid;
#Slf4j
#RestController
#Api(tags = "Customers APIs")
#RequestMapping("/apiservice/v1/customers")
public class CustomerController {
#Autowired
private CustomerService customerService;
#GetMapping
#ApiOperation(value = "List of Customers API")
#ApiResponses(value = {
#ApiResponse(code = 400, message = Constants.BAD_REQUEST),
#ApiResponse(code = 403, message = Constants.ACCESS_DENIED)})
public ResponseEntity<Object retrieveAll() {
log.info("Start of CustomerController::retrieveAll method");
return customerService.retrieveAll();
}
}
application.yml
logging:
level:
org:
springframework.web: DEBUG
hibernat: DEBUG
com:
atoz_develop:
mybatissample:
repository: TRACE
mybatis:
mapper-locations: classpath:/mappers/*.xml
type-aliases-package: com.demo.apiservice.entity
configuration:
map-underscore-to-camel-case: 'true'
debug: 'true'
spring:
datasource:
driver-class-name: org.postgresql.Driver
username: postgres
url: jdbc:postgresql://localhost:5432/postgres
platform: postgres
password: postgres
jpa:
generate-ddl: 'false'
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQL10Dialect
format_sql: 'true'
hibernate:
ddl-auto: update
show-sql: 'true'
stack trace log
2021-11-17 16:05:19.992 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.DispatcherServlet : GET /apiservice/v1/customers, parameters={} 2021-11-17 16:05:19.992 DEBUG 23300 --- [nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.demo.apiservice.customer.CustomerController#retrieveAll()
2021-11-17 16:05:19.993 INFO 23300 --- [nio-8080-exec-8] c.l.a.c.CustomerController : Start of CustomerController::retrieveAll method
2021-11-17 16:05:19.993 INFO 23300 --- [nio-8080-exec-8] c.l.a.c.s.i.CustomerServiceImpl : Inside of the CustomerServiceImpl :: retrieveAll method
2021-11-17 16:05:19.996 INFO 23300 --- [nio-8080-exec-8] c.l.a.c.s.i.CustomerServiceImpl : End of the CustomerServiceImpl :: retrieveAll method
2021-11-17 16:05:19.996 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using application/xhtml+xml, given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [application/json, application/*+json, application/json, application/*+json, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8, application/xml;charset=UTF-8, text/xml;charset=UTF-8, application/*+xml;charset=UTF-8]
2021-11-17 16:05:19.996 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{data=[Customer(email=test1#test.com, password=null, customerId=null, storeName=eos, firstname=test1 (truncated)...]
2021-11-17 16:05:19.998 DEBUG 23300 --- [nio-8080-exec-8] o.s.w.s.DispatcherServlet : Completed 200 OK
Your aspect is triggered. I added an explicit controller method call in order to check:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ApiServiceApplication.class, args);
context.getBean(CustomerController.class).retrieveAll();
}
Then I fixed some typos in your code like the one mentioned in my previous comment.
Probably your problem was that you simply did not see the log output because you forgot the log configuration for your application package com.demo.apiservice:
logging:
level:
org:
springframework.web: DEBUG
hibernate: DEBUG
com:
atoz_develop:
mybatissample:
repository: TRACE
demo.apiservice: DEBUG
BTW, I also corrected your typo hibernat to hibernate, but that is unrelated to the problem at hand.
Then I see this in the log:
[ restartedMain] o.s.b.w.e.t.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
[ restartedMain] c.d.a.ApiServiceApplication : Started ApiServiceApplication in 5.101 seconds (JVM running for 6.117)
[ restartedMain] c.d.a.c.AopLoggingConfig : Controller : com.demo.apiservice.customer.CustomerController.######################### retrieveAll()
[ restartedMain] c.d.a.c.AopLoggingConfig : Controller : com.demo.apiservice.customer.CustomerController.######################### retrieveAll()
[ restartedMain] c.d.a.c.CustomerController : Start of CustomerController::retrieveAll method
Do you see the problem? You get duplicate logging, because the aspect is picked up once by component scan and instantiated one more time as a bean in your application configuration. So you need to remove this part from ApiServiceApplication:
#Bean
public AopLoggingConfig loggingAspect() {
return new AopLoggingConfig();
}
Now the duplicate logging is gone.
Next, maybe you want to simplify your aspect a bit and simply log joinPoint or joinPoint.getSignature(). You also want to make name and type local variables, because the static fields are not thread-safe. Instead, probably you want a static logger in the aspect.
#Component
#Aspect
public class AopLoggingConfig {
private static Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
#Around("execution(* com.demo.apiservice.customer.*Controller.*(..))")
public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
String type = joinPoint.getSignature().getDeclaringTypeName();
String name = "";
if (type.contains("Controller")) {
name = "Controller \t: ";
}
else if (type.contains("Service")) {
name = "ServiceImpl \t: ";
}
else if (type.contains("DAO")) {
name = "DAO \t\t: ";
}
logger.debug(name + joinPoint.getSignature());
return joinPoint.proceed();
}
}
The log line becomes:
Controller : ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll()
But actually, both the package name and the class name indicate that we are dealing with a controller, DAO or service. So why bother with the if-else stuff in the first place? Why make a simple matter complicated and the aspect slower? Besides, if you only want to log something and not influence the control flow, the method parameters or the return value, a simple #Before advice would do, the expensive #Around is unnecessary.
#Component
#Aspect
public class AopLoggingConfig {
private static Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
#Before("execution(* com.demo.apiservice.customer.*Controller.*(..))")
public void logPrint(JoinPoint joinPoint) {
logger.debug(joinPoint.getSignature().toString());
}
}
The log line becomes:
ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll()
Isn't that enough?
Or even simpler:
logger.debug(joinPoint.toString());
Log:
execution(ResponseEntity com.demo.apiservice.customer.CustomerController.retrieveAll())
Keep it simple!
The following should work:
package com.demo.apiservice.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class AopLoggingConfig {
Logger logger = LoggerFactory.getLogger(AopLoggingConfig.class);
static String name = "";
static String type = "";
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void controllerClassMethods() {}
#Around("controllerClassMethods()")
public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
type = joinPoint.getSignature().getDeclaringTypeName();
if (type.indexOf("Controller") -1) {
name = "Controller \t: ";
}
else if (type.indexOf("Service") -1) {
name = "ServiceImpl \t: ";
}
else if (type.indexOf("DAO") -1) {
name = "DAO \t\t: ";
}
logger.debug(name + type + ".######################### " + joinPoint.getSignature().getName() + "()");
return joinPoint.proceed();
}
}
This will match all the methods in all classes annotated with #RestController.
I'm trying upgrade a JHipster project, however I found the following issue:
Description:
Parameter 0 of constructor in com.cervaki.config.AsyncConfiguration required a single bean, but 2 were found:
- jhipster-io.github.jhipster.config.JHipsterProperties: defined in null
- io.github.jhipster.config.JHipsterProperties: defined in null
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
What I understood is that spring can't inject the correct bean because there are two candidates, but I only have the io.github.jhipster.config.JHipsterProperties implementation:
package com.cervaki.config;
import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
import io.github.jhipster.config.JHipsterProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
#Configuration
#EnableAsync
#EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final JHipsterProperties jHipsterProperties;
public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
#Override
#Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
executor.setThreadNamePrefix("cervaki-Executor-");
return new ExceptionHandlingAsyncTaskExecutor(executor);
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
You can download the pom.xml here.
I did a search in the entire code and libs to find the jhipster-io.github.jhipster.config.JHipsterProperties file, however I didn't find anything.
What can I do to solve this problem?
I also faced this issue after generating new JhipsterApp,
And as you - I don't find the "jhipster-io" dependencies in project
How I solve this:
in src/main/java/your/package/config create a "AppConfiguration.java"
with content:
import io.github.jhipster.config.JHipsterProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
#Configuration
public class AppConfiguration {
#Bean
#Primary
public JHipsterProperties jHipsterProperties() {
return new JHipsterProperties();
}
}
even without #Primary - I haven't got this error
I tried to extend the Managing Transactions example in the spring boot guides to two datasources, but the #Transaction annotation seems to work for only one of the datasources.
In "Application.java" I added the beans for the two datasources and their JdbcTemplates. In "BookingService.java" I used the JdbcTemplate belonging to the second datasource.
Here is my "Application.java":
package hello;
import javax.sql.DataSource;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
#SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
#Bean
BookingService bookingService() {
return new BookingService();
}
#Primary
#Bean(name="datasource1")
#ConfigurationProperties(prefix="datasource1")
DataSource datasource1() {
return DataSourceBuilder.create().build();
}
#Bean(name="jdbcTemplate1")
#Autowired
JdbcTemplate jdbcTemplate1(#Qualifier ("datasource1") DataSource datasource) {
return new JdbcTemplate(datasource);
}
#Bean(name="datasource2")
#ConfigurationProperties(prefix="datasource2")
DataSource datasource2() {
return DataSourceBuilder.create().build();
}
#Bean(name="jdbcTemplate2")
#Autowired
JdbcTemplate jdbcTemplate2(#Qualifier ("datasource2") DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
log.info("Creating tables");
jdbcTemplate.execute("drop table BOOKINGS if exists");
jdbcTemplate.execute("create table BOOKINGS("
+ "ID serial, FIRST_NAME varchar(5) NOT NULL)");
return jdbcTemplate;
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
BookingService bookingService = ctx.getBean(BookingService.class);
bookingService.book("Alice", "Bob", "Carol");
Assert.assertEquals("First booking should work with no problem", 3,
bookingService.findAllBookings().size());
try {
bookingService.book("Chris", "Samuel");
}
catch (RuntimeException e) {
log.info("v--- The following exception is expect because 'Samuel' is too big for the DB ---v");
log.error(e.getMessage());
}
for (String person : bookingService.findAllBookings()) {
log.info("So far, " + person + " is booked.");
}
log.info("You shouldn't see Chris or Samuel. Samuel violated DB constraints, and Chris was rolled back in the same TX");
Assert.assertEquals("'Samuel' should have triggered a rollback", 3,
bookingService.findAllBookings().size());
try {
bookingService.book("Buddy", null);
}
catch (RuntimeException e) {
log.info("v--- The following exception is expect because null is not valid for the DB ---v");
log.error(e.getMessage());
}
for (String person : bookingService.findAllBookings()) {
log.info("So far, " + person + " is booked.");
}
log.info("You shouldn't see Buddy or null. null violated DB constraints, and Buddy was rolled back in the same TX");
Assert.assertEquals("'null' should have triggered a rollback", 3, bookingService
.findAllBookings().size());
}
}
And here is "BookingService.java":
package hello;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;
public class BookingService {
private final static Logger log = LoggerFactory.getLogger(BookingService.class);
#Autowired
#Qualifier("jdbcTemplate2")
JdbcTemplate jdbcTemplate;
#Transactional
public void book(String... persons) {
for (String person : persons) {
log.info("Booking " + person + " in a seat...");
jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
}
};
public List<String> findAllBookings() {
return jdbcTemplate.query("select FIRST_NAME from BOOKINGS", new RowMapper<String>() {
#Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString("FIRST_NAME");
}
});
}
}
These are the apllication properties in "application.yml":
datasource1:
url: "jdbc:h2:~/h2/ds1;DB_CLOSE_ON_EXIT=FALSE"
username: "sa"
datasource2:
url: "jdbc:h2:~/h2/ds2;DB_CLOSE_ON_EXIT=FALSE"
username: "sa"
The "pom.xml" here is the same as in Managing Transactions.
When the #Primary annotation is on the datasource2 bean, everything works as expected.
When the #Primary annotation is on the datasource1 bean, the write in datasource2 is not transactional and one gets the following output:
...
2016-05-27 16:01:23.775 INFO 884 --- [ main] hello.Application : So far, Alice is booked.
2016-05-27 16:01:23.775 INFO 884 --- [ main] hello.Application : So far, Bob is booked.
2016-05-27 16:01:23.775 INFO 884 --- [ main] hello.Application : So far, Carol is booked.
2016-05-27 16:01:23.775 INFO 884 --- [ main] hello.Application : So far, Chris is booked.
2016-05-27 16:01:23.775 INFO 884 --- [ main] hello.Application : You shouldn't see Chris or Samuel. Samuel violated DB constraints, and Chris was rolled back in the same TX
Exception in thread "main" 2016-05-27 16:01:23.776 INFO 884 --- [ Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext#3901d134: startup date [Fri May 27 16:01:22 CEST 2016]; root of context hierarchy
java.lang.AssertionError: 'Samuel' should have triggered a rollback expected:<3> but was:<4>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
at org.junit.Assert.assertEquals(Assert.java:645)
at hello.Application.main(Application.java:84)
2016-05-27 16:01:23.778 INFO 884 --- [ Thread-2] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
So "Chris" wasn't rolled back.
I guess it has something to do with properly initializing both databases. Is this a bug, or am I missing something here?
Thanks!
I added two beans in "Application.java":
#Bean(name="tm1")
#Autowired
DataSourceTransactionManager tm1(#Qualifier ("datasource1") DataSource datasource) {
DataSourceTransactionManager txm = new DataSourceTransactionManager(datasource);
return txm;
}
#Bean(name="tm2")
#Autowired
DataSourceTransactionManager tm2(#Qualifier ("datasource2") DataSource datasource) {
DataSourceTransactionManager txm = new DataSourceTransactionManager(datasource);
return txm;
}
and changed the #Transactional in "BookingService.java" to:
#Transactional("tm2")
So now we have two resource-local transaction managers, one for each datasource, and it works as expected.
Many thanks to M.Deinum!