add batch insert (#4)

* add batch insert
This commit is contained in:
vitaxa 2019-06-26 15:15:22 +03:00 committed by GitHub
parent ceff55f491
commit 0174048cf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 216 additions and 2 deletions

14
pom.xml
View File

@ -13,7 +13,7 @@
<packaging>jar</packaging>
<artifactId>db-common-lib</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -50,6 +50,18 @@
<version>${geck.common.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.28.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -66,4 +66,15 @@ public interface GenericDao {
int execute(String namedSql, SqlParameterSource parameterSource, int expectedRowsAffected, NamedParameterJdbcTemplate namedParameterJdbcTemplate, KeyHolder keyHolder) throws DaoException;
long batchExecute(List<Query> queries) throws DaoException;
long batchExecute(List<Query> queries, int expectedRowsPerQueryAffected) throws DaoException;
long batchExecute(List<Query> queries, int expectedRowsPerQueryAffected, NamedParameterJdbcTemplate namedParameterJdbcTemplate) throws DaoException;
long batchExecute(String namedSql, List<SqlParameterSource> parameterSources) throws DaoException;
long batchExecute(String namedSql, List<SqlParameterSource> parameterSources, int expectedRowsPerQueryAffected) throws DaoException;
long batchExecute(String namedSql, List<SqlParameterSource> parameterSources, int expectedRowsPerQueryAffected, NamedParameterJdbcTemplate namedParameterJdbcTemplate) throws DaoException;
}

View File

@ -17,9 +17,12 @@ import org.springframework.jdbc.support.KeyHolder;
import javax.sql.DataSource;
import java.sql.Types;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public abstract class AbstractGenericDao extends NamedParameterJdbcDaoSupport implements GenericDao {
@ -217,6 +220,76 @@ public abstract class AbstractGenericDao extends NamedParameterJdbcDaoSupport im
}
}
@Override
public long batchExecute(List<Query> queries) throws DaoException {
return batchExecute(queries, -1);
}
@Override
public long batchExecute(List<Query> queries, int expectedRowsAffected) throws DaoException {
return batchExecute(queries, expectedRowsAffected, getNamedParameterJdbcTemplate());
}
@Override
public long batchExecute(List<Query> queries, int expectedRowsAffected, NamedParameterJdbcTemplate namedParameterJdbcTemplate) throws DaoException {
AtomicLong affectedRowCounter = new AtomicLong();
queries.stream()
.collect(
Collectors.groupingBy(
query -> query.getSQL(ParamType.NAMED),
LinkedHashMap::new,
Collectors.mapping(query -> toSqlParameterSource(query.getParams()), Collectors.toList())
)
)
.forEach(
(namedSql, parameterSources) -> {
long affectedRowCount = batchExecute(
namedSql,
parameterSources,
expectedRowsAffected,
namedParameterJdbcTemplate
);
affectedRowCounter.getAndAccumulate(affectedRowCount, Long::sum);
}
);
return affectedRowCounter.get();
}
@Override
public long batchExecute(String namedSql, List<SqlParameterSource> parameterSources) throws DaoException {
return batchExecute(namedSql, parameterSources, -1);
}
@Override
public long batchExecute(String namedSql, List<SqlParameterSource> parameterSources, int expectedRowsAffected) throws DaoException {
return batchExecute(namedSql, parameterSources, expectedRowsAffected, getNamedParameterJdbcTemplate());
}
@Override
public long batchExecute(String namedSql, List<SqlParameterSource> parameterSources, int expectedRowsAffected, NamedParameterJdbcTemplate namedParameterJdbcTemplate) throws DaoException {
try {
int[] rowsPerBatchAffected = namedParameterJdbcTemplate.batchUpdate(namedSql, parameterSources.toArray(new SqlParameterSource[0]));
if (rowsPerBatchAffected.length != parameterSources.size()) {
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(namedSql, parameterSources.size(), rowsPerBatchAffected.length);
}
int count = 0;
for (int i : rowsPerBatchAffected) {
count += i;
}
if (expectedRowsAffected != -1) {
if (count != expectedRowsAffected) {
throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(namedSql, expectedRowsAffected, count);
}
}
return count;
} catch (NestedRuntimeException ex) {
throw new DaoException(ex);
}
}
protected Condition appendDateTimeRangeConditions(Condition condition,
Field<LocalDateTime> field,
Optional<LocalDateTime> fromTime,
@ -235,7 +308,13 @@ public abstract class AbstractGenericDao extends NamedParameterJdbcDaoSupport im
MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource();
for (Map.Entry<String, Param<?>> entry : params.entrySet()) {
Param<?> param = entry.getValue();
if (param.getValue() instanceof LocalDateTime || param.getValue() instanceof EnumType) {
Class<?> type = param.getDataType().getType();
if (String.class.isAssignableFrom(type)) {
String value = Optional.ofNullable(param.getValue())
.map(stringValue -> ((String) stringValue).replace("\u0000", "\\u0000"))
.orElse(null);
sqlParameterSource.addValue(entry.getKey(), value);
} else if (LocalDateTime.class.isAssignableFrom(type) || EnumType.class.isAssignableFrom(type)) {
sqlParameterSource.addValue(entry.getKey(), param.getValue(), Types.OTHER);
} else {
sqlParameterSource.addValue(entry.getKey(), param.getValue());

View File

@ -0,0 +1,112 @@
package com.rbkmoney.dao.impl;
import com.rbkmoney.dao.DaoException;
import org.jooq.DataType;
import org.jooq.Param;
import org.jooq.Query;
import org.jooq.conf.ParamType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import javax.sql.DataSource;
import java.sql.Types;
import java.time.LocalDateTime;
import java.util.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class AbstractGenericDaoTest {
private TestDao testDaoSpy;
@Before
public void setUp() throws Exception {
testDaoSpy = spy(new TestDao(mock(DataSource.class)));
}
@Test
public void batchExecuteTest() {
final NamedParameterJdbcTemplate namedParameterJdbcTemplateMock = mock(NamedParameterJdbcTemplate.class);
int[] rowPerBatchAffected = { 1, 1 };
when(namedParameterJdbcTemplateMock.batchUpdate(anyString(), any(SqlParameterSource[].class))).thenReturn(rowPerBatchAffected);
when(testDaoSpy.getNamedParameterJdbcTemplate()).thenReturn(namedParameterJdbcTemplateMock);
final Map<String, Param<?>> firstParamMap = paramMapMock("testString", "testValue", String.class);
firstParamMap.putAll(paramMapMock("testDate", LocalDateTime.now(), LocalDateTime.class));
final Query firstQueryMock = mock(Query.class);
when(firstQueryMock.getSQL(ParamType.NAMED)).thenReturn("test sql");
when(firstQueryMock.getParams()).thenReturn(firstParamMap);
final Map<String, Param<?>> secondParamMap = paramMapMock("testInteger", 12345, Integer.class);
secondParamMap.putAll(paramMapMock("testLong", 12345L, Long.class));
final Query secondQueryMock = mock(Query.class);
when(secondQueryMock.getSQL(ParamType.NAMED)).thenReturn("test sql");
when(secondQueryMock.getParams()).thenReturn(secondParamMap);
final List<Query> queryList = Arrays.asList(firstQueryMock, secondQueryMock);
final long rowsAffected = testDaoSpy.batchExecute(queryList, 2);
Assert.assertEquals(2, rowsAffected);
}
@Test(expected = DaoException.class)
public void batchExceptionTest() {
final NamedParameterJdbcTemplate namedParameterJdbcTemplateMock = mock(NamedParameterJdbcTemplate.class);
int[] rowPerBatchAffected = { 1 };
when(namedParameterJdbcTemplateMock.batchUpdate(anyString(), any(SqlParameterSource[].class))).thenReturn(rowPerBatchAffected);
when(testDaoSpy.getNamedParameterJdbcTemplate()).thenReturn(namedParameterJdbcTemplateMock);
final Map<String, Param<?>> firstParamMap = paramMapMock("testString", "testValue", String.class);
firstParamMap.putAll(paramMapMock("testDate", LocalDateTime.now(), LocalDateTime.class));
final Query firstQueryMock = mock(Query.class);
when(firstQueryMock.getSQL(ParamType.NAMED)).thenReturn("test sql");
when(firstQueryMock.getParams()).thenReturn(firstParamMap);
final List<Query> queryList = Collections.singletonList(firstQueryMock);
final long rowsAffected = testDaoSpy.batchExecute(queryList, 2);
}
@Test
public void toSqlParameterSourceNullByteTest() {
final Map<String, Param<?>> paramMap = paramMapMock("test", "\u0000", String.class);
final SqlParameterSource sqlParameterSource = testDaoSpy.toSqlParameterSource(paramMap);
Assert.assertEquals("\\u0000", sqlParameterSource.getValue("test"));
}
@Test
public void toSqlParameterSourceDateTest() {
final LocalDateTime dateTime = LocalDateTime.now();
final Map<String, Param<?>> paramMap = paramMapMock("testDate", dateTime, LocalDateTime.class);
final SqlParameterSource sqlParameterSource = testDaoSpy.toSqlParameterSource(paramMap);
Assert.assertEquals(dateTime, sqlParameterSource.getValue("testDate"));
Assert.assertEquals(Types.OTHER, sqlParameterSource.getSqlType("testDate"));
}
private Map<String, Param<?>> paramMapMock(String key, Object value, Class<?> dataType) {
final Param paramMock = mock(Param.class);
final DataType dataTypeMock = mock(DataType.class);
when(dataTypeMock.getType()).thenReturn(dataType);
when(paramMock.getDataType()).thenReturn(dataTypeMock);
when(paramMock.getValue()).thenReturn(value);
final Map<String, Param<?>> paramMap = new HashMap<>();
paramMap.put(key, paramMock);
return paramMap;
}
private class TestDao extends AbstractGenericDao {
TestDao(DataSource dataSource) {
super(dataSource);
}
}
}