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
This commit is contained in:
Jonathan Bregler
2018-04-12 13:40:20 +02:00
committed by Mahmoud Ben Hassine
parent 44dd4f7f56
commit 9388abb589
23 changed files with 761 additions and 3 deletions

View File

@@ -25,6 +25,7 @@ import org.springframework.batch.item.database.support.AbstractSqlPagingQueryPro
import org.springframework.batch.item.database.support.Db2PagingQueryProvider;
import org.springframework.batch.item.database.support.DerbyPagingQueryProvider;
import org.springframework.batch.item.database.support.H2PagingQueryProvider;
import org.springframework.batch.item.database.support.HanaPagingQueryProvider;
import org.springframework.batch.item.database.support.HsqlPagingQueryProvider;
import org.springframework.batch.item.database.support.MySqlPagingQueryProvider;
import org.springframework.batch.item.database.support.OraclePagingQueryProvider;
@@ -359,6 +360,7 @@ public class JdbcPagingItemReaderBuilder<T> {
case DB2ZOS:
case DB2AS400: provider = new Db2PagingQueryProvider(); break;
case H2: provider = new H2PagingQueryProvider(); break;
case HANA: provider = new HanaPagingQueryProvider(); break;
case HSQL: provider = new HsqlPagingQueryProvider(); break;
case SQLSERVER: provider = new SqlServerPagingQueryProvider(); break;
case MYSQL: provider = new MySqlPagingQueryProvider(); break;

View File

@@ -25,6 +25,7 @@ import org.springframework.jdbc.support.incrementer.Db2MainframeMaxValueIncremen
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.DerbyMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.H2SequenceMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.HanaSequenceMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.OracleSequenceMaxValueIncrementer;
@@ -37,6 +38,7 @@ import static org.springframework.batch.support.DatabaseType.DB2AS400;
import static org.springframework.batch.support.DatabaseType.DB2ZOS;
import static org.springframework.batch.support.DatabaseType.DERBY;
import static org.springframework.batch.support.DatabaseType.H2;
import static org.springframework.batch.support.DatabaseType.HANA;
import static org.springframework.batch.support.DatabaseType.HSQL;
import static org.springframework.batch.support.DatabaseType.MYSQL;
import static org.springframework.batch.support.DatabaseType.ORACLE;
@@ -99,6 +101,9 @@ public class DefaultDataFieldMaxValueIncrementerFactory implements DataFieldMaxV
else if (databaseType == H2) {
return new H2SequenceMaxValueIncrementer(dataSource, incrementerName);
}
else if (databaseType == HANA) {
return new HanaSequenceMaxValueIncrementer(dataSource, incrementerName);
}
else if (databaseType == MYSQL) {
MySQLMaxValueIncrementer mySQLMaxValueIncrementer = new MySQLMaxValueIncrementer(dataSource, incrementerName, incrementerColumnName);
mySQLMaxValueIncrementer.setUseNewConnection(true);

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2006-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.database.support;
import org.springframework.batch.item.database.PagingQueryProvider;
import org.springframework.util.StringUtils;
/**
* SAP HANA implementation of a {@link PagingQueryProvider} using database specific features.
*
* @author Jonathan Bregler
* @since 4.0
*/
public class HanaPagingQueryProvider extends AbstractSqlPagingQueryProvider {
@Override
public String generateFirstPageQuery(int pageSize) {
return SqlPagingQueryUtils.generateLimitSqlQuery(this, false, buildLimitClause(pageSize));
}
@Override
public String generateRemainingPagesQuery(int pageSize) {
if(StringUtils.hasText(getGroupClause())) {
return SqlPagingQueryUtils.generateLimitGroupedSqlQuery(this, true, buildLimitClause(pageSize));
}
else {
return SqlPagingQueryUtils.generateLimitSqlQuery(this, true, buildLimitClause(pageSize));
}
}
private String buildLimitClause(int pageSize) {
return new StringBuilder().append("LIMIT ").append(pageSize).toString();
}
@Override
public String generateJumpToItemQuery(int itemIndex, int pageSize) {
int page = itemIndex / pageSize;
int offset = (page * pageSize) - 1;
offset = offset<0 ? 0 : offset;
String limitClause = new StringBuilder().append("LIMIT 1 OFFSET ").append(offset).toString();
return SqlPagingQueryUtils.generateLimitJumpToQuery(this, limitClause);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2012 the original author or authors.
* Copyright 2006-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import static org.springframework.batch.support.DatabaseType.DB2ZOS;
import static org.springframework.batch.support.DatabaseType.DB2AS400;
import static org.springframework.batch.support.DatabaseType.DERBY;
import static org.springframework.batch.support.DatabaseType.H2;
import static org.springframework.batch.support.DatabaseType.HANA;
import static org.springframework.batch.support.DatabaseType.HSQL;
import static org.springframework.batch.support.DatabaseType.MYSQL;
import static org.springframework.batch.support.DatabaseType.ORACLE;
@@ -78,6 +79,7 @@ public class SqlPagingQueryProviderFactoryBean implements FactoryBean<PagingQuer
providers.put(DERBY,new DerbyPagingQueryProvider());
providers.put(HSQL,new HsqlPagingQueryProvider());
providers.put(H2,new H2PagingQueryProvider());
providers.put(HANA,new HanaPagingQueryProvider());
providers.put(MYSQL,new MySqlPagingQueryProvider());
providers.put(ORACLE,new OraclePagingQueryProvider());
providers.put(POSTGRES,new PostgresPagingQueryProvider());

View File

@@ -48,7 +48,8 @@ public enum DatabaseType {
POSTGRES("PostgreSQL"),
SYBASE("Sybase"),
H2("H2"),
SQLITE("SQLite");
SQLITE("SQLite"),
HANA("HDB");
private static final Map<String, DatabaseType> nameMap;

View File

@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
import org.springframework.jdbc.support.incrementer.Db2LuwMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.Db2MainframeMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.DerbyMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.HanaSequenceMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer;
import org.springframework.jdbc.support.incrementer.OracleSequenceMaxValueIncrementer;
@@ -63,6 +64,7 @@ public class DefaultDataFieldMaxValueIncrementerFactoryTests extends TestCase {
assertTrue(factory.isSupportedIncrementerType("sqlserver"));
assertTrue(factory.isSupportedIncrementerType("sybase"));
assertTrue(factory.isSupportedIncrementerType("sqlite"));
assertTrue(factory.isSupportedIncrementerType("hana"));
}
public void testUnsupportedDatabaseType(){
@@ -129,5 +131,9 @@ public class DefaultDataFieldMaxValueIncrementerFactoryTests extends TestCase {
public void testSqlite(){
assertTrue(factory.getIncrementer("sqlite", "NAME") instanceof SqliteMaxValueIncrementer);
}
public void testHana(){
assertTrue(factory.getIncrementer("hana", "NAME") instanceof HanaSequenceMaxValueIncrementer);
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2006-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.database.support;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.batch.item.database.Order;
import static org.junit.Assert.assertEquals;
/**
* @author Jonathan Bregler
* @since 4.0
*/
public class HanaPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests {
public HanaPagingQueryProviderTests() {
pagingQueryProvider = new HanaPagingQueryProvider();
}
@Test
@Override
public void testGenerateFirstPageQuery() {
String sql = "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY id ASC LIMIT 100";
String s = pagingQueryProvider.generateFirstPageQuery(pageSize);
assertEquals(sql, s);
}
@Test @Override
public void testGenerateRemainingPagesQuery() {
String sql = "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((id > ?)) ORDER BY id ASC LIMIT 100";
String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize);
assertEquals(sql, s);
}
@Test @Override
public void testGenerateJumpToItemQuery() {
String sql = "SELECT id FROM foo WHERE bar = 1 ORDER BY id ASC LIMIT 1 OFFSET 99";
String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize);
assertEquals(sql, s);
}
@Test @Override
public void testGenerateJumpToItemQueryForFirstPage() {
String sql = "SELECT id FROM foo WHERE bar = 1 ORDER BY id ASC LIMIT 1 OFFSET 0";
String s = pagingQueryProvider.generateJumpToItemQuery(45, pageSize);
assertEquals(sql, s);
}
@Override
@Test
public void testGenerateFirstPageQueryWithGroupBy() {
pagingQueryProvider.setGroupClause("dep");
String sql = "SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ORDER BY id ASC LIMIT 100";
String s = pagingQueryProvider.generateFirstPageQuery(pageSize);
assertEquals(sql, s);
}
@Override
@Test
public void testGenerateRemainingPagesQueryWithGroupBy() {
pagingQueryProvider.setGroupClause("dep");
String sql = "SELECT * FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC LIMIT 100";
String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize);
assertEquals(sql, s);
}
@Override
@Test
public void testGenerateJumpToItemQueryWithGroupBy() {
pagingQueryProvider.setGroupClause("dep");
String sql = "SELECT id FROM foo WHERE bar = 1 GROUP BY dep ORDER BY id ASC LIMIT 1 OFFSET 99";
String s = pagingQueryProvider.generateJumpToItemQuery(145, pageSize);
assertEquals(sql, s);
}
@Override
@Test
public void testGenerateJumpToItemQueryForFirstPageWithGroupBy() {
pagingQueryProvider.setGroupClause("dep");
String sql = "SELECT id FROM foo WHERE bar = 1 GROUP BY dep ORDER BY id ASC LIMIT 1 OFFSET 0";
String s = pagingQueryProvider.generateJumpToItemQuery(45, pageSize);
assertEquals(sql, s);
}
@Test
public void testFirstPageSqlWithAliases() {
Map<String, Order> sorts = new HashMap<>();
sorts.put("owner.id", Order.ASCENDING);
this.pagingQueryProvider = new HanaPagingQueryProvider();
this.pagingQueryProvider.setSelectClause("SELECT owner.id as ownerid, first_name, last_name, dog_name ");
this.pagingQueryProvider.setFromClause("FROM dog_owner owner INNER JOIN dog ON owner.id = dog.id ");
this.pagingQueryProvider.setSortKeys(sorts);
String firstPage = this.pagingQueryProvider.generateFirstPageQuery(5);
String jumpToItemQuery = this.pagingQueryProvider.generateJumpToItemQuery(7, 5);
String remainingPagesQuery = this.pagingQueryProvider.generateRemainingPagesQuery(5);
assertEquals("SELECT owner.id as ownerid, first_name, last_name, dog_name FROM dog_owner owner INNER JOIN dog ON owner.id = dog.id ORDER BY owner.id ASC LIMIT 5", firstPage);
assertEquals("SELECT owner.id FROM dog_owner owner INNER JOIN dog ON owner.id = dog.id ORDER BY owner.id ASC LIMIT 1 OFFSET 4", jumpToItemQuery);
assertEquals("SELECT owner.id as ownerid, first_name, last_name, dog_name FROM dog_owner owner INNER JOIN dog ON owner.id = dog.id WHERE ((owner.id > ?)) ORDER BY owner.id ASC LIMIT 5", remainingPagesQuery);
}
@Override
public String getFirstPageSqlWithMultipleSortKeys() {
return "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC LIMIT 100";
}
@Override
public String getRemainingSqlWithMultipleSortKeys() {
return "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC LIMIT 100";
}
@Override
public String getJumpToItemQueryWithMultipleSortKeys() {
return "SELECT name, id FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC LIMIT 1 OFFSET 99";
}
@Override
public String getJumpToItemQueryForFirstPageWithMultipleSortKeys() {
return "SELECT name, id FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC LIMIT 1 OFFSET 0";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2014 the original author or authors.
* Copyright 2008-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ import static org.springframework.batch.support.DatabaseType.POSTGRES;
import static org.springframework.batch.support.DatabaseType.SQLITE;
import static org.springframework.batch.support.DatabaseType.SQLSERVER;
import static org.springframework.batch.support.DatabaseType.SYBASE;
import static org.springframework.batch.support.DatabaseType.HANA;
import static org.springframework.batch.support.DatabaseType.fromProductName;
/**
@@ -57,6 +58,7 @@ public class DatabaseTypeTests {
assertEquals(POSTGRES, fromProductName("PostgreSQL"));
assertEquals(SYBASE, fromProductName("Sybase"));
assertEquals(SQLITE, fromProductName("SQLite"));
assertEquals(HANA, fromProductName("HDB"));
}
@Test(expected = IllegalArgumentException.class)
@@ -142,6 +144,12 @@ public class DatabaseTypeTests {
DataSource ds = DatabaseTypeTestUtils.getMockDataSource("Adaptive Server Enterprise");
assertEquals(SYBASE, DatabaseType.fromMetaData(ds));
}
@Test
public void testFromMetaDataForHana() throws Exception {
DataSource ds = DatabaseTypeTestUtils.getMockDataSource("HDB");
assertEquals(HANA, DatabaseType.fromMetaData(ds));
}
@Test(expected=MetaDataAccessException.class)
public void testBadMetaData() throws Exception {

View File

@@ -0,0 +1,14 @@
# Placeholders batch.*
# for SAP HANA:
batch.jdbc.driver=com.sap.db.jdbc.Driver
batch.jdbc.url=jdbc:sap://localhost:39015/
batch.jdbc.user=SPRING_TEST
batch.jdbc.password=Spr1ng_test
batch.jdbc.testWhileIdle=false
batch.jdbc.validationQuery=
batch.schema.script=classpath:org/springframework/batch/item/database/init-foo-schema-hana.sql
batch.business.schema.script=classpath:/org/springframework/batch/jms/init.sql
batch.data.source.init=true
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.HanaSequenceMaxValueIncrementer
batch.database.incrementer.parent=sequenceIncrementerParent
batch.verify.cursor.position=true

View File

@@ -0,0 +1,25 @@
DROP TABLE T_FOOS;
DROP TABLE T_WRITE_FOOS;
CREATE TABLE T_FOOS (
ID BIGINT NOT NULL,
NAME VARCHAR(45),
CODE VARCHAR(10),
VALUE BIGINT
);
ALTER TABLE T_FOOS ADD PRIMARY KEY (ID);
INSERT INTO t_foos (id, name, value) VALUES (1, 'bar2', 2);
INSERT INTO t_foos (id, name, value) VALUES (2, 'bar4', 4);
INSERT INTO t_foos (id, name, value) VALUES (3, 'bar1', 1);
INSERT INTO t_foos (id, name, value) VALUES (4, 'bar5', 5);
INSERT INTO t_foos (id, name, value) VALUES (5, 'bar3', 3);
CREATE TABLE T_WRITE_FOOS (
ID BIGINT NOT NULL,
NAME VARCHAR(45),
VALUE BIGINT
);
ALTER TABLE T_WRITE_FOOS ADD PRIMARY KEY (ID);