Skip to content

Commit 9388abb

Browse files
breglerjfmbenhassine
authored andcommitted
Integrate SAP Hana as supported Spring Batch database
This commit adds SAP HANA as a supported Spring Batch database, enabling developers to seamlessly move their existing Spring Batch projects to SAP HANA or easily starting new Spring Batch projects on SAP HANA. This commit contains the following changes: - Add SAP HANA to the DatabaseType enum - Add HanaPagingQueryProvider and tests - Add properties files for SAP HANA Issue #2515
1 parent 44dd4f7 commit 9388abb

File tree

23 files changed

+761
-3
lines changed

23 files changed

+761
-3
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<h2.version>2.1.210</h2.version>
9393
<sqlite.version>3.36.0.3</sqlite.version>
9494
<derby.version>10.14.2.0</derby.version>
95+
<hana.version>2.9.12</hana.version>
9596
<artemis.version>2.20.0</artemis.version>
9697
<jaxb-core.version>3.0.2</jaxb-core.version>
9798
<log4j.version>2.17.2</log4j.version>

spring-batch-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@
172172
<version>${derby.version}</version>
173173
<scope>test</scope>
174174
</dependency>
175+
<dependency>
176+
<groupId>com.sap.cloud.db.jdbc</groupId>
177+
<artifactId>ngdbc</artifactId>
178+
<version>${hana.version}</version>
179+
<scope>test</scope>
180+
</dependency>
175181
<dependency>
176182
<groupId>commons-io</groupId>
177183
<artifactId>commons-io</artifactId>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Placeholders batch.*
2+
# for SAP HANA:
3+
batch.jdbc.driver=com.sap.db.jdbc.Driver
4+
batch.jdbc.url=jdbc:sap://localhost:39015/
5+
batch.jdbc.user=SPRING_TEST
6+
batch.jdbc.password=Spr1ng_test
7+
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.HanaSequenceMaxValueIncrementer
8+
batch.schema.script=classpath:/org/springframework/batch/core/schema-hana.sql
9+
batch.drop.script=classpath:/org/springframework/batch/core/schema-drop-hana.sql
10+
batch.jdbc.testWhileIdle=true
11+
batch.jdbc.validationQuery=
12+
13+
14+
# Non-platform dependent settings that you might like to change
15+
batch.data.source.init=true
16+
batch.table.prefix=BATCH_
17+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Autogenerated: do not edit this file
2+
DROP TABLE BATCH_STEP_EXECUTION_CONTEXT ;
3+
DROP TABLE BATCH_JOB_EXECUTION_CONTEXT ;
4+
DROP TABLE BATCH_JOB_EXECUTION_PARAMS ;
5+
DROP TABLE BATCH_STEP_EXECUTION ;
6+
DROP TABLE BATCH_JOB_EXECUTION ;
7+
DROP TABLE BATCH_JOB_INSTANCE ;
8+
9+
DROP SEQUENCE BATCH_STEP_EXECUTION_SEQ ;
10+
DROP SEQUENCE BATCH_JOB_EXECUTION_SEQ ;
11+
DROP SEQUENCE BATCH_JOB_SEQ ;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
-- Autogenerated: do not edit this file
2+
3+
CREATE TABLE BATCH_JOB_INSTANCE (
4+
JOB_INSTANCE_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
5+
VERSION BIGINT ,
6+
JOB_NAME VARCHAR(100) NOT NULL,
7+
JOB_KEY VARCHAR(32) NOT NULL,
8+
constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY)
9+
) ;
10+
11+
CREATE TABLE BATCH_JOB_EXECUTION (
12+
JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
13+
VERSION BIGINT ,
14+
JOB_INSTANCE_ID BIGINT NOT NULL,
15+
CREATE_TIME TIMESTAMP NOT NULL,
16+
START_TIME TIMESTAMP DEFAULT NULL ,
17+
END_TIME TIMESTAMP DEFAULT NULL ,
18+
STATUS VARCHAR(10) ,
19+
EXIT_CODE VARCHAR(2500) ,
20+
EXIT_MESSAGE VARCHAR(2500) ,
21+
LAST_UPDATED TIMESTAMP,
22+
JOB_CONFIGURATION_LOCATION VARCHAR(2500) ,
23+
constraint JOB_INST_EXEC_FK foreign key (JOB_INSTANCE_ID)
24+
references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
25+
) ;
26+
27+
CREATE TABLE BATCH_JOB_EXECUTION_PARAMS (
28+
JOB_EXECUTION_ID BIGINT NOT NULL ,
29+
TYPE_CD VARCHAR(6) NOT NULL ,
30+
KEY_NAME VARCHAR(100) NOT NULL ,
31+
STRING_VAL VARCHAR(250) ,
32+
DATE_VAL TIMESTAMP DEFAULT NULL ,
33+
LONG_VAL BIGINT ,
34+
DOUBLE_VAL DOUBLE ,
35+
IDENTIFYING VARCHAR(1) NOT NULL ,
36+
constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
37+
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
38+
) ;
39+
40+
CREATE TABLE BATCH_STEP_EXECUTION (
41+
STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
42+
VERSION BIGINT NOT NULL,
43+
STEP_NAME VARCHAR(100) NOT NULL,
44+
JOB_EXECUTION_ID BIGINT NOT NULL,
45+
START_TIME TIMESTAMP NOT NULL ,
46+
END_TIME TIMESTAMP DEFAULT NULL ,
47+
STATUS VARCHAR(10) ,
48+
COMMIT_COUNT BIGINT ,
49+
READ_COUNT BIGINT ,
50+
FILTER_COUNT BIGINT ,
51+
WRITE_COUNT BIGINT ,
52+
READ_SKIP_COUNT BIGINT ,
53+
WRITE_SKIP_COUNT BIGINT ,
54+
PROCESS_SKIP_COUNT BIGINT ,
55+
ROLLBACK_COUNT BIGINT ,
56+
EXIT_CODE VARCHAR(2500) ,
57+
EXIT_MESSAGE VARCHAR(2500) ,
58+
LAST_UPDATED TIMESTAMP,
59+
constraint JOB_EXEC_STEP_FK foreign key (JOB_EXECUTION_ID)
60+
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
61+
) ;
62+
63+
CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT (
64+
STEP_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
65+
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
66+
SERIALIZED_CONTEXT CLOB ,
67+
constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
68+
references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
69+
) ;
70+
71+
CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT (
72+
JOB_EXECUTION_ID BIGINT NOT NULL PRIMARY KEY,
73+
SHORT_CONTEXT VARCHAR(2500) NOT NULL,
74+
SERIALIZED_CONTEXT CLOB ,
75+
constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
76+
references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
77+
) ;
78+
79+
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ START WITH 0 MINVALUE 0 NO CYCLE;
80+
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ START WITH 0 MINVALUE 0 NO CYCLE;
81+
CREATE SEQUENCE BATCH_JOB_SEQ START WITH 0 MINVALUE 0 NO CYCLE;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
platform=hana
2+
# SQL language oddities
3+
BIGINT = BIGINT
4+
IDENTITY =
5+
GENERATED = GENERATED BY DEFAULT AS IDENTITY
6+
IFEXISTSBEFORE =
7+
DOUBLE = DOUBLE
8+
BLOB = BLOB
9+
CLOB = CLOB
10+
TIMESTAMP = TIMESTAMP
11+
VARCHAR = VARCHAR
12+
CHAR = VARCHAR
13+
# for generating drop statements...
14+
SEQUENCE = SEQUENCE
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#macro (sequence $name $value)CREATE SEQUENCE ${name} START WITH ${value} MINVALUE 0 NO CYCLE;
2+
#end
3+
#macro (notnull $name $type)ALTER (${name} ${type} NOT NULL)#end
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2020-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.test.repository;
17+
18+
import java.time.Duration;
19+
import java.time.temporal.ChronoUnit;
20+
import java.util.Arrays;
21+
import java.util.HashMap;
22+
import java.util.HashSet;
23+
import java.util.Map;
24+
import java.util.Set;
25+
26+
import org.testcontainers.containers.JdbcDatabaseContainer;
27+
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
28+
import org.testcontainers.utility.DockerImageName;
29+
import org.testcontainers.utility.LicenseAcceptance;
30+
31+
import com.github.dockerjava.api.model.Ulimit;
32+
33+
/**
34+
* @author Jonathan Bregler
35+
*/
36+
public class HANAContainer<SELF extends HANAContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
37+
38+
private static final Integer PORT = 39041;
39+
40+
private static final String SYSTEM_USER = "SYSTEM";
41+
private static final String SYSTEM_USER_PASSWORD = "HXEHana1";
42+
43+
public HANAContainer(DockerImageName image) {
44+
45+
super( image );
46+
47+
addExposedPorts( 39013, 39017, 39041, 39042, 39043, 39044, 39045, 1128, 1129, 59013, 59014 );
48+
49+
// create ulimits
50+
Ulimit[] ulimits = new Ulimit[]{ new Ulimit( "nofile", 1048576L, 1048576L ) };
51+
52+
// create sysctls Map.
53+
Map<String, String> sysctls = new HashMap<String, String>();
54+
55+
sysctls.put( "kernel.shmmax", "1073741824" );
56+
sysctls.put( "net.ipv4.ip_local_port_range", "40000 60999" );
57+
58+
// Apply mounts, ulimits and sysctls.
59+
this.withCreateContainerCmdModifier( it -> it.getHostConfig().withUlimits( ulimits ).withSysctls( sysctls ) );
60+
61+
// Arguments for Image.
62+
this.withCommand( "--master-password " + SYSTEM_USER_PASSWORD + " --agree-to-sap-license" );
63+
64+
// Determine if container is ready.
65+
this.waitStrategy = new LogMessageWaitStrategy().withRegEx( ".*Startup finished!*\\s" ).withTimes( 1 )
66+
.withStartupTimeout( Duration.of( 600, ChronoUnit.SECONDS ) );
67+
}
68+
69+
@Override
70+
protected void configure() {
71+
/*
72+
* Enforce that the license is accepted - do not remove. License available at:
73+
* https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and- exhibit.pdf
74+
*/
75+
76+
// If license was not accepted programmatically, check if it was accepted via
77+
// resource file
78+
if ( !getEnvMap().containsKey( "AGREE_TO_SAP_LICENSE" ) ) {
79+
LicenseAcceptance.assertLicenseAccepted( this.getDockerImageName() );
80+
acceptLicense();
81+
}
82+
}
83+
84+
/**
85+
* Accepts the license for the SAP HANA Express container by setting the AGREE_TO_SAP_LICENSE=Y Calling this method
86+
* will automatically accept the license at:
87+
* https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf
88+
*
89+
* @return The container itself with an environment variable accepting the SAP HANA Express license
90+
*/
91+
public SELF acceptLicense() {
92+
addEnv( "AGREE_TO_SAP_LICENSE", "Y" );
93+
return self();
94+
}
95+
96+
@Override
97+
protected Set<Integer> getLivenessCheckPorts() {
98+
return new HashSet<>( Arrays.asList( new Integer[]{ getMappedPort( PORT ) } ) );
99+
}
100+
101+
@Override
102+
protected void waitUntilContainerStarted() {
103+
getWaitStrategy().waitUntilReady( this );
104+
}
105+
106+
@Override
107+
public String getDriverClassName() {
108+
return "com.sap.db.jdbc.Driver";
109+
}
110+
111+
@Override
112+
public String getUsername() {
113+
return SYSTEM_USER;
114+
}
115+
116+
@Override
117+
public String getPassword() {
118+
return SYSTEM_USER_PASSWORD;
119+
}
120+
121+
@Override
122+
public String getTestQueryString() {
123+
return "SELECT 1 FROM SYS.DUMMY";
124+
}
125+
126+
@Override
127+
public String getJdbcUrl() {
128+
return "jdbc:sap://" + getContainerIpAddress() + ":" + getMappedPort( PORT ) + "/";
129+
}
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2020-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.batch.core.test.repository;
17+
18+
import javax.sql.DataSource;
19+
20+
import org.junit.Assert;
21+
import org.junit.Before;
22+
import org.junit.ClassRule;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.springframework.batch.core.ExitStatus;
26+
import org.springframework.batch.core.Job;
27+
import org.springframework.batch.core.JobExecution;
28+
import org.springframework.batch.core.JobParameters;
29+
import org.springframework.batch.core.JobParametersBuilder;
30+
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
31+
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
32+
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
33+
import org.springframework.batch.core.launch.JobLauncher;
34+
import org.springframework.batch.repeat.RepeatStatus;
35+
import org.springframework.beans.factory.annotation.Autowired;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.core.io.ClassPathResource;
39+
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
40+
import org.springframework.test.context.ContextConfiguration;
41+
import org.springframework.test.context.junit4.SpringRunner;
42+
import org.testcontainers.utility.DockerImageName;
43+
44+
import com.sap.db.jdbcext.HanaDataSource;
45+
46+
/**
47+
* @author Jonathan Bregler
48+
*/
49+
@RunWith(SpringRunner.class)
50+
@ContextConfiguration
51+
public class HANAJobRepositoryIntegrationTests {
52+
53+
private static final DockerImageName HANA_IMAGE = DockerImageName.parse( "store/saplabs/hanaexpress:2.00.054.00.20210603.1" );
54+
55+
@ClassRule
56+
public static HANAContainer<?> hana = new HANAContainer<>( HANA_IMAGE ).acceptLicense();
57+
58+
@Autowired
59+
private DataSource dataSource;
60+
@Autowired
61+
private JobLauncher jobLauncher;
62+
@Autowired
63+
private Job job;
64+
65+
@Before
66+
public void setUp() {
67+
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
68+
databasePopulator.addScript( new ClassPathResource( "/org/springframework/batch/core/schema-hana.sql" ) );
69+
databasePopulator.execute( this.dataSource );
70+
}
71+
72+
@Test
73+
public void testJobExecution() throws Exception {
74+
// given
75+
JobParameters jobParameters = new JobParametersBuilder().toJobParameters();
76+
77+
// when
78+
JobExecution jobExecution = this.jobLauncher.run( this.job, jobParameters );
79+
80+
// then
81+
Assert.assertNotNull( jobExecution );
82+
Assert.assertEquals( ExitStatus.COMPLETED, jobExecution.getExitStatus() );
83+
}
84+
85+
@Configuration
86+
@EnableBatchProcessing
87+
static class TestConfiguration {
88+
89+
@Bean
90+
public DataSource dataSource() throws Exception {
91+
HanaDataSource dataSource = new HanaDataSource();
92+
dataSource.setUser( hana.getUsername() );
93+
dataSource.setPassword( hana.getPassword() );
94+
dataSource.setUrl( hana.getJdbcUrl() );
95+
return dataSource;
96+
}
97+
98+
@Bean
99+
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) {
100+
return jobs.get( "job" )
101+
.start( steps.get( "step" ).tasklet( (contribution, chunkContext) -> RepeatStatus.FINISHED ).build() )
102+
.build();
103+
}
104+
105+
}
106+
}

0 commit comments

Comments
 (0)