Mapstruct 1.4.2.Final : NullValuePropertyMappingStrategy.SET_TO_DEFAULT not working as expected - spring-boot

I have recently updated my spring boot version from 2.3.0.RELEASE to 2.4.4 and also updated the Map Struct version to the latest from 1.3.1.Final to is 1.4.2.Final. lombok version used - 1.18.18
But one of the mapper which was working fine with 1.3.1 is not working anymore with 1.4.2 and the scenario is as below.
POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
......
<properties>
<!-- used for configuration meta data processor, please keep in sync with
spring boot parent version -->
<spring-boot.version>2.4.4</spring-boot.version>
<java.version>11</java.version>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
.....
</properties>
<dependencies>
........
........
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
........
........
</dependencies>
<build>
<finalName>dras_mt</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
........
........
</annotationProcessorPaths>
</configuration>
</plugin>
......
......
</plugins>
<pluginManagement>
.....
</pluginManagement>
</build>
</project>
Entity:
#Entity
#Data
public class TestEntity {
#Id
private Long id;
#NotNull
private String plntId;
#NotNull
private Boolean approvalNeeded = false;
}
Domain:
#Value
#Builder(toBuilder = true)
public class TestDomain {
private PlantDomain plantDomain;
#NonFinal
#Setter
private SomeInfo someInfo;
#NonFinal
#Setter
#Default
private Boolean approvalNeeded = false;
}
Mapper:
#Mapper(componentModel = "spring", uses = { ActaulPlantMapper.class })
public interface TestEntityMapper {
#Mapping(target = "someInfo", ignore = true)
#Mapping(source = "plntId", target = "plantDomain")
#Mapping(target = "approvalNeeded", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
TestDomain fromEntity(TestEntity entity);
}
Generated Code with MapStruct 1.3.1
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-03-24T11:33:38+0530",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 11.0.9.1 (AdoptOpenJDK)"
)
#Component
public class TestEntityMapperImpl implements TestEntityMapper {
#Autowired
private ActualPlantMapper actualPlantMapper;
#Override
public TestDomain fromEntity(WareHouseOptionEntity entity) {
if ( entity == null ) {
return null;
}
TestDomainBuilder testDomain = TestDomain.builder();
testDomain.plantDomain( actualPlantMapper.fromSapId( entity.getPlntId() ) );
if ( entity.getApprovalNeeded() != null ) {
testDomain.approvalNeeded( entity.getApprovalNeeded() );
}
else {
testDomain.approvalNeeded( false );
}
return testDomain.build();
}
}
Generated Code with MapStruct 1.4.2
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-03-24T11:33:38+0530",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 11.0.9.1 (AdoptOpenJDK)"
)
#Component
public class TestEntityMapperImpl implements TestEntityMapper {
#Autowired
private ActualPlantMapper actualPlantMapper;
#Override
public TestDomain fromEntity(WareHouseOptionEntity entity) {
if ( entity == null ) {
return null;
}
TestDomainBuilder testDomain = TestDomain.builder();
testDomain.plantDomain( actualPlantMapper.fromSapId( entity.getPlntId() ) );
testDomain.approvalNeeded( entity.getApprovalNeeded() );
return testDomain.build();
}
}
As you see with 1.4.2 version the NullValuePropertyMappingStrategy.SET_TO_DEFAULT is not applied.
I have tried with other options like putting the NullValuePropertyMappingStrategy on class level which didn't help.
Any futher inputs is highly appreaciated.

The fact that NullValuePropertyMappingStrategy was working on 1.3 is a coincidence. The strategy should only be applicable to update methods i.e. methods with #MappingTarget.
If you want to keep defaults as defined in your source class I would suggest using NullValueCheckStrategy#ALWAYS

Related

mvn spring-boot:run "No primary or single public constructor found...TemplateLineItemInput" but only from commandline?

I've got an up-the-middle Spring Boot + Lombok project that works like a champ from the IDE but errors strangely when I run it from the command line through mvn spring-boot:run. Its a pretty recent version Spring Boot...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Lombok is unremarkable and came from the Spring Initializr thing (https://start.spring.io/).
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
The JavaBean its complaining about is equally boring...
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
#Data
#Builder(toBuilder = true)
public class TemplateLineItemInput {
private final String partMasterId;
private final String partMasterIdPath;
private final String actionType;
private final BigDecimal actionQuantity;
}
The API of this Boot project is GraphQL but when I execute the following mutation from a mvn spring-boot:run invocation it always comes back as an error (nothing on the console...the framework is kicking it out somehow).
Request...
mutation createTemplateLineItem($tbomId: ID!) {
createTemplateLineItem(
tbomId: $tbomId
input: {
partMasterId: "2"
partMasterIdPath: "808863036.1"
actionType: "ADD"
actionQuantity: "2"
}) {
...TBomFrag
}
}
...
{
"partMasterId": "5025489768",
"tbomId": "a4688d22-9d99-41a2-9777-6acc75b2aab9",
"lineItemId": "9e460199-34fb-432c-b971-8cd8321d3283"
}
Response...
{
"errors": [
{
"message": "No primary or single public constructor found for class aero.blue.ems.boa.domain.TemplateLineItemInput - and no default constructor found either",
"locations": [
{
"line": 20,
"column": 3
}
],
"path": [
"createTemplateLineItem"
],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": {
"createTemplateLineItem": null
}
}
My spring-boot-maven-plugin is configured like...
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.4</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>repackage</id>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
...also from Spring Initializr
When I run the Application from the IDE directly, no issue. The mutation works fine.
Is there something missing from my spring-boot:run config in the pom.xml or something? Did I have to clue the plugin into annotation processing? This is really confusing.
Please,
Check the following config on section plugins at you pom.xml project, as following here:
<project>
<modelVersion>4.0.0</modelVersion>
<artifactId>getting-started</artifactId>
<!-- ... -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
For more information about, check this link: https://docs.spring.io/spring-boot/docs/2.5.4/maven-plugin/reference/htmlsingle/
Ultimately this comes down to Lombok misconfiguration of my beans. The fix is to add the #AllArgsConstructor to the bean definition
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
public class TemplateLineItemInput {
private final String partMasterId;
private final String partMasterIdPath;
private final String actionType;
private final BigDecimal actionQuantity;
}
How we figured this out was to "Delombok" the Bean and look at the resulting code. This observation matched the error message; there was no public constructor.
...
TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
this.partMasterId = partMasterId;
this.partMasterIdPath = partMasterIdPath;
this.actionType = actionType;
this.actionQuantity = actionQuantity;
}
Somehow (I still don't fully get why), the #Builder(toBuilder=true) annotation had Lombok producing a package private constructor. Jackson needed something public.
Adding the #AllArgsConstructor annotation made that constructor public and all is well.
public TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
this.partMasterId = partMasterId;
this.partMasterIdPath = partMasterIdPath;
this.actionType = actionType;
this.actionQuantity = actionQuantity;
}
Delombok was the key.

Parse csv file by using Spring Batch and print it on console in spring Boot

My goal of the Project :
I have to read csv file by using Spring Batch and extract the specific column information like (Column Name :"msdin") "msdin" can print it on console. But my application is showing failed to start the application.
Well it is asking me to configure the data Source.Why we need to configure the data source in case of spring batch if my requirement is to read the csv file and print it on console.
I tried to identify the issues but not able to resolve. Can anybody help me how to resolve this issues?
Domain Class
public class Customer implements Serializable {
private Long id_type;
private String id_number;
private String customer_name;
private String email_address;
private LocalDate birthday;
private String citizenship;
private String address;
private Long msisdn;
private LocalDate kyc_date;
private String kyc_level;
private String goalscore;
private String mobile_network;
public Long getId_type() {
return id_type;
}
public void setId_type(Long id_type) {
this.id_type = id_type;
}
public String getId_number() {
return id_number;
}
public void setId_number(String id_number) {
this.id_number = id_number;
}
public String getCustomer_name() {
return customer_name;
}
public void setCustomer_name(String customer_name) {
this.customer_name = customer_name;
}
public String getEmail_address() {
return email_address;
}
public void setEmail_address(String email_address) {
this.email_address = email_address;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
public String getCitizenship() {
return citizenship;
}
public void setCitizenship(String citizenship) {
this.citizenship = citizenship;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Long getMsisdn() {
return msisdn;
}
public void setMsisdn(Long msisdn) {
this.msisdn = msisdn;
}
public LocalDate getKyc_date() {
return kyc_date;
}
public void setKyc_date(LocalDate kyc_date) {
this.kyc_date = kyc_date;
}
public String getKyc_level() {
return kyc_level;
}
public void setKyc_level(String kyc_level) {
this.kyc_level = kyc_level;
}
public String getGoalscore() {
return goalscore;
}
public void setGoalscore(String goalscore) {
this.goalscore = goalscore;
}
public String getMobile_network() {
return mobile_network;
}
public void setMobile_network(String mobile_network) {
this.mobile_network = mobile_network;
}
}
BatchConfiguration class
#Configuration
#EnableBatchProcessing
public class BatchConfiguration {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Value("classPath:/data/gcash.csv")
private Resource inputResource;
public ItemReader<Customer> itemReader() {
FlatFileItemReader<Customer> customerItemReader = new FlatFileItemReader<>();
customerItemReader.setLineMapper(linemapper());
customerItemReader.setLinesToSkip(1);
customerItemReader.setResource(inputResource);
return customerItemReader;
}
#Bean
public LineMapper<Customer> linemapper() {
DefaultLineMapper<Customer> linemapper = new DefaultLineMapper<>();
final DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter(";");
tokenizer.setStrict(false);
tokenizer.setNames(new String[] { "id_type", "id_number", "customer_name", "email_address", "birthday",
"citizenship", "address", "msisdn", "kyc_date", "kyc_level", "goalscore", "mobile_network" });
linemapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<Customer>() {
{
setTargetType(Customer.class);
}
});
return linemapper;
}
}
Error Stack
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-04-27 11:05:46.235 ERROR 22368 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.gcash.milo</groupId>
<artifactId>GCash_Milo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>GCash_Milo</name>
<description>Developing Milo project for GCash banking application.
</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-sftp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
</project>
in your spring boot application class, add the following snippet to #SpringBootApplication annotation:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
#SpringBootApplcation annotation uses #EnableAutoConfiguration, which expects a datasource to be configured

How to make spring batch to catch exception and continue with work without complete job?

I want to catch an exception in the item processor, do something and continue to work with the job without completing the job. And if it is possible with this to have JdbcPagingItemReader to avoid possible thread issue when reading.
Configuration:
#Configuration
#EnableJpaRepositories
#Slf4j
public class AppConfig {
private final ProcessorVerificationSkipper processorVerificationSkipper;
private final WriterVerificationSkipper writerVerificationSkipper;
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final MyProcessor myProcessor;
private final MyWriter myWriter;
private final DataSource dataSource;
public AppConfig (
JobBuilderFactory jobBuilderFactory,
StepBuilderFactory stepBuilderFactory,
MyProcessor myProcessor,
MyWriter myWriter,
DataSource dataSource,
ProcessorVerificationSkipper processorVerificationSkipper,
WriterVerificationSkipper writerVerificationSkipper) {
this.processorVerificationSkipper = processorVerificationSkipper;
this.writerVerificationSkipper = writerVerificationSkipper;
this.jobBuilderFactory = jobBuilderFactory;
this.stepBuilderFactory = stepBuilderFactory;
this.myProcessor = myProcessor;
this.myWriter= myWriter;
this.dataSource = dataSource;
}
/* Without pagination
#Bean
public ItemReader<MyTable> itemReader() {
JdbcCursorItemReader contactJdbcCursorItemReader = new JdbcCursorItemReader<>();
contactJdbcCursorItemReader.setSql("SELECT * FROM MYTABLE");
contactJdbcCursorItemReader.setDataSource(dataSource);
contactJdbcCursorItemReader.setRowMapper(new BeanPropertyRowMapper<>(MyTable.class));
return contactJdbcCursorItemReader;
}*/
// with pagination
#Bean
public JdbcPagingItemReader<MyTable> itemReader() throws Exception {
return new JdbcPagingItemReaderBuilder<Object>()
.dataSource(dataSource)
.saveState(false)
.queryProvider(queryProvider())
.rowMapper(new BeanPropertyRowMapper<>(MyTable.class))
.pageSize(10)
.fetchSize(10)
.build();
}
#Bean
public PagingQueryProvider queryProvider() throws Exception {
SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();
provider.setDataSource(dataSource);
provider.setSelectClause("SELECT *");
provider.setFromClause("FROM MYTABLE");
provider.setSortKey("ID");
return (PagingQueryProvider)provider.getObject();
}
#Bean
TaskExecutor taskExecutorStepPush() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(2);
taskExecutor.setMaxPoolSize(20);
taskExecutor.setQueueCapacity(4);
taskExecutor.setAllowCoreThreadTimeOut(true);
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
return taskExecutor;
}
#Bean
public Step step() throws Exception {
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
attribute.setPropagationBehavior(Propagation.REQUIRED.value());
attribute.setIsolationLevel(Isolation.READ_COMMITTED.value());
// return
SimpleStepBuilder<MyTable, MyTable> builder =
(SimpleStepBuilder<MyTable, MyTable>)stepBuilderFactory.get("stepName")
.<MyTable, MyTable >chunk(10)
.reader(itemReader())
.processor(myProcessor)
.faultTolerant()
.skipLimit(Integer.parseInt(env.getProperty(CHUNK_SIZE)))
.skip(Exception.class)
.skipPolicy(processorVerificationSkipper)
.writer(myWriter)
.faultTolerant()
.skip(Exception.class)
.skipPolicy(writerVerificationSkipper)
.noRetry(Exception.class)
.noRollback(Exception.class)
.transactionAttribute(attribute);
return builder.build();
}
ProcessorVerificationSkipper.java
#Component
public class ProcessorVerificationSkipper implements SkipPolicy {
#Override
public boolean shouldSkip(Throwable throwable, int i) throws SkipLimitExceededException {
return true;
}
}
WriterVerificationSkipper.java
#Component
public class WriterVerificationSkipper implements SkipPolicy {
#Override
public boolean shouldSkip(Throwable throwable, int i) throws SkipLimitExceededException {
return true;
}
}
MyTable.java
#Getter
#Setter
#Entity(name = "MYTABLE")
public class MyTable{
#Id
#Column(name = "ID", nullable = false, precision = 0)
private Long sourceId;
#Basic
#Column(name = "A", nullable = true, length = 10)
private String a;
}
application.properties:
spring.batch.job.enabled=false
job.delay.fixed=120000
## Actuator When to show full health details.
management.endpoint.health.show-details=always
spring.main.allow-bean-definition-overriding=true
## Actuator - Endpoint IDs that should be included or '*' for all.
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=shutdown,flyway
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
spring.datasource.jndi-name=jdbc/MyDS
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.auto-commit=false
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
MyProcessor.java:
#Slf4j
#Component
public class MyProcessor implements ItemProcessor<MyTable, MyTable> {
#Override
public Object process(MyTable myTable) {
return doSomething(myTable);
}
private Object doSomething(MyTable myTable) {
try {
myTable.setA("a");
// do something
}
catch(Exception1 e) {
myTable.setA("e1");
}
catch(Exception e) {
myTable.setA("e2");
}
return object;
}
}
MyWriter.java
#Component
public class MyWriter implements ItemWriter<MyTable> {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public DocumentWriter(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
#Override
public void write(List<? extends MyTable> list) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(list.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate("UPDATE MYTABLE SET A = :a, batch);
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.myapp</groupId>
<artifactId>my-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<oracle-ojdbc8.version>12.2.0.1</oracle-ojdbc8.version>
<maven-jaxws-plugin.version>2.5</maven-jaxws-plugin.version>
<maven-jaxb2-plugin.version>2.4</maven-jaxb2-plugin.version>
<jupiter.version>5.5.0</jupiter.version>
<mockito.version>2.23.0</mockito.version>
<maven-surefire-plugin.version>2.22.1</maven-surefire-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<weblogic-wlclient.version>12.1.1</weblogic-wlclient.version>
<lombok.version>1.16.20</lombok.version>
<oracle-ojdbc.version>12.2.0.1</oracle-ojdbc.version>
<appache.http-client.version>4.5.9</appache.http-client.version>
<mockito.version>3.0.0</mockito.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${appache.http-client.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<profiles>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.3.RELEASE</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExcludes>ch.qos.logback:logback-classic</classpathDependencyExcludes>
</classpathDependencyExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<archive>
<index>true</index>
<manifest>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Implementation-Version>${project.version}</Implementation-Version>
<Implementation-Vendor-Id>${project.groupId}</Implementation-Vendor-Id>
<Implementation-URL>${project.url}</Implementation-URL>
<Build-Time>${maven.build.timestamp}</Build-Time>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
<configuration>
<additionalProperties>
<version>${project.version}(${build.number})</version>
<Implementation-Version>${project.version}(${build.number})</Implementation-Version>
<BuildNumber>${build.number}</BuildNumber>
<BuildURL>${build.url}</BuildURL>
</additionalProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
With this when an exception occurs job finished as COMPLETED which I don't want. I want to go for all selected records to the end, no matter of exceptions, just to set the value of the property depending on the type of exception.
Is this possible?
How to configure a job?

SpringBootTest does not roll back

(Although, there are plenty of similar questions I was not able to find the solution among them).
Why #Transactional annotation with #SpringBootTest works and rolls back nicely when I use DAO directly, but does not work when I test with TestRestTemplate?
tl;dr
Entity
#Entity
public class ToDoItem {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull #Column(unique = true)
private String title;
public ToDoItem() {}
public ToDoItem(String title) { this.title = title;}
// getters, setters, etc.
Repository
public interface ToDoRepository extends CrudRepository<ToDoItem, Long> {}
Application
#EnableTransactionManagement
#SpringBootApplication
#RestController
#RequestMapping("/todos")
public class ToDoApp {
public static void main(String[] args) {
SpringApplication.run(ToDoApp.class, args);
}
#Autowired
private ToDoRepository toDoRepository;
#GetMapping
ResponseEntity<?> fetchAll() { return ok(toDoRepository.findAll()); }
#PostMapping()
ResponseEntity<?> createNew(#RequestBody ToDoItem toDoItem) {
try {
toDoRepository.save(toDoItem);
return noContent().build();
} catch (Exception e) {
return badRequest().body(e.getMessage());
}
}
}
Now I create two tests which do basically the same: store some to-do items in in-memory database and check if size has increased:
Repository test
#ExtendWith(SpringExtension.class)
#SpringBootTest
#Transactional
class ToDoRepoTests {
#Autowired
private ToDoRepository toDoRepository;
#Test
void when_adding_one_item_then_success() {
toDoRepository.save(new ToDoItem("Run tests"));
assertThat(toDoRepository.findAll()).hasSize(1);
}
#Test
void when_adding_two_items_then_success() {
toDoRepository.saveAll(List.of(
new ToDoItem("Run tests"), new ToDoItem("Deploy to prod")));
assertThat(toDoRepository.findAll()).hasSize(2);
}
}
Then I create similar test that does exactly the same thing but via REST API (This one does not work and fails with Unique index or primary key violation):
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = ToDoApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#Transactional
class ToDoControllerTests {
#LocalServerPort
private int localServerPort;
private TestRestTemplate testRestTemplate;
#BeforeEach
void setUp() {
testRestTemplate = new TestRestTemplate(new RestTemplateBuilder()
.rootUri("http://localhost:" + localServerPort));
}
#Test
void when_adding_one_item_then_success() {
// when
createToDo(new ToDoItem("Walk the dog"));
var allItems = fetchAllTodos();
// then
assertThat(allItems).hasSize(1);
}
#Test
void when_adding_two_items_then_success() {
// when
createToDo(new ToDoItem("Walk the dog"));
createToDo(new ToDoItem("Clean the kitchen"));
var allItems = fetchAllTodos();
// then
assertThat(allItems).hasSize(2);
}
private void createToDo(ToDoItem entity) {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
var response = testRestTemplate.postForEntity("/todos", new HttpEntity<>(entity, headers), String.class);
assertThat(response.getStatusCode()).isEqualTo(NO_CONTENT);
}
private List<ToDoItem> fetchAllTodos() {
return Arrays.asList(testRestTemplate.getForObject("/todos", ToDoItem[].class));
}
}
And here is my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>foo.bar</groupId>
<artifactId>todo-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>To-Do App</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Please, help me to understand, what I am doing wrong.
When you call the API using the RESTTemplate it crosses the boundary of the local transaction management and becomes as distributed transaction. There is not mechanism available for spring to roll back that call to the rest API. Although in your example the target system is the same application, it need not be so. It could be an external application that is not aware of the transactional boundaries that you have set in your test.

#DeleteMapping not recognized

I am new with spring boot, is very simple my question but I canĀ“t find where is the problem.
I have a basic #GetMapping and #DeleteMapping
#RestController
public class MyController
{
private MyServiceImpl myService;
#Autowired
public MyController(MyServiceImpl myService)
{
this.myService = myService;
}
#GetMapping("/test")
public List<Scan> getTestData(#RequestParam(value = "test_id", required = true) String test_id) {
return myService.findByTestId( test_id );
}
#DeleteMapping("/test")
public int deleteData(#RequestParam(value = "test_id", required = true) String test_id){
return myService.deleteTest( test_id );
}
#DeleteMapping("/test2")
public String deleteArticle() {
return "Test";
}
}
I am testing with Postman, the Get request is working, but the delete request is not. Even when I debug the spring boot application. The GetMapping is called, whereas the delete is not "reacting" /called
Maybe is not enough information, but I even tested #DeleteMapping("/test2") is not doing anything, I mean, I get no response, and in Postman it stays "loading..."
Any suggestions?
My complete POM
http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
<parent>
<groupId>com.ttt.pdt</groupId>
<artifactId>pdt-base</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>pdt-service</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The parent (pdt-base) has
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
MyServiceImpl
#Service
public class MyServiceImpl
{
private ScnDao scnDao;
#Autowired
public ScanServiceImpl(ScnDao scnDao){
this.scnDao = scnDao;
}
public int deleteTest( String test_id )
{
return scnDao.deleteTest( test_id );
}
}
public interface ScanDao
{
int deleteTest( String test_id )
}
#Repository
public class ScnPostgres implements ScnDao
{
private JdbcTemplate jdbcTemplate;
#Autowired
public ScnPostgres( JdbcTemplate jdbcTemplate )
{
this.jdbcTemplate = jdbcTemplate;
}
public int deleteTest( String test_id )
{
String SQL = "DELETE FROM scn WHERE test_id = ?";
return jdbcTemplate.update( SQL, new String[] { test_id } );
}
}
As in SPR comments described SPR-16874:
it does not support DELETE currently. It would be a trivial change to
make. There is suggested a workaround:
#DeleteMapping(value = "/greeting")
public Greeting handle(#RequestBody MultiValueMap<String, String> params) {
return new Greeting(counter.incrementAndGet(),
String.format(template, params));
}
As you can see the issue has been opened in the spring boot tracker.

Resources