从 plusone-commons 中独立出来。

dev
ZhouXY108 2023-07-19 22:19:57 +08:00
commit 19906f2bd7
11 changed files with 606 additions and 0 deletions

38
.gitignore vendored 100644
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

3
.idea/.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

14
.idea/misc.xml 100644
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml 100644
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

88
pom.xml 100644
View File

@ -0,0 +1,88 @@
<?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>
<groupId>xyz.zhouxy.jdbc</groupId>
<artifactId>simple-jdbc</artifactId>
<version>0.1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-commons</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.8</version>
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>aliyun</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-plugin</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -0,0 +1,75 @@
/*
* Copyright 2022-2023 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
*
* https://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 xyz.zhouxy.jdbc;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import xyz.zhouxy.plusone.commons.collection.AbstractMapWrapper;
import xyz.zhouxy.plusone.commons.util.OptionalUtil;
import java.util.*;
@Beta
public class DbRecord extends AbstractMapWrapper<String, Object, DbRecord> {
public DbRecord() {
super(new HashMap<>(), k -> Preconditions.checkArgument(StringUtils.isNotBlank(k), "Key must has text."), null);
}
public DbRecord(Map<String, Object> map) {
super(map, k -> Preconditions.checkArgument(StringUtils.isNotBlank(k), "Key must has text."), null);
}
public Optional<String> getValueAsString(String key) {
return this.getAndConvert(key);
}
public <T> List<T> getValueAsList(String key) {
return this.<Collection<T>>getAndConvert(key)
.map(l -> (l instanceof List) ? (List<T>) l : new ArrayList<>(l))
.orElse(Collections.emptyList());
}
public <T> Set<T> getValueAsSet(String key) {
return this.<Collection<T>>getAndConvert(key)
.map(l -> (l instanceof Set) ? (Set<T>) l : new HashSet<>(l))
.orElse(Collections.emptySet());
}
public OptionalInt getValueAsInt(String key) {
return OptionalUtil.toOptionalInt(this.getAndConvert(key));
}
public OptionalLong getValueAsLong(String key) {
return OptionalUtil.toOptionalLong(this.getAndConvert(key));
}
public OptionalDouble getValueAsDouble(String key) {
return OptionalUtil.toOptionalDouble(this.getAndConvert(key));
}
@Override
protected DbRecord getSelf() {
return this;
}
@Override
public String toString() {
return "xyz.zhouxy.plusone.commons.jdbc.DbRecord@" + super.toString();
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2022-2023 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
*
* https://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 xyz.zhouxy.jdbc;
import com.google.common.annotations.Beta;
import java.sql.ResultSet;
import java.sql.SQLException;
@Beta
@FunctionalInterface
public interface ResultMap<T> {
T map(ResultSet rs, int rowNumber) throws SQLException;
}

View File

@ -0,0 +1,256 @@
/*
* Copyright 2022-2023 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
*
* https://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 xyz.zhouxy.jdbc;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ArrayUtils;
import xyz.zhouxy.plusone.commons.util.MoreArrays;
import xyz.zhouxy.plusone.commons.util.MoreCollections;
import xyz.zhouxy.plusone.commons.util.OptionalUtil;
import java.math.BigDecimal;
import java.sql.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Beta
public class SimpleJdbcTemplate {
public static JdbcExecutor connect(final Connection conn) {
return new JdbcExecutor(conn);
}
public static String paramsToString(Object[] params) {
return Arrays.toString(params);
}
public static String paramsToString(final Collection<Object[]> params) {
if (params == null) {
return "null";
}
if (params.isEmpty()) {
return "[]";
}
int iMax = params.size() - 1;
StringBuilder b = new StringBuilder();
b.append('[');
int i = 0;
for (Object[] p : params) {
b.append(Arrays.toString(p));
if (i == iMax) {
return b.append(']').toString();
}
b.append(',');
i++;
}
return b.append(']').toString();
}
private SimpleJdbcTemplate() {
throw new IllegalStateException("Utility class");
}
public static class JdbcExecutor {
private final Connection conn;
public JdbcExecutor(Connection conn) {
this.conn = conn;
}
public <T> List<T> query(String sql, Object[] params, ResultMap<T> resultMap) throws SQLException {
try (PreparedStatement stmt = this.conn.prepareStatement(sql)) {
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}
}
try (ResultSet rs = stmt.executeQuery()) {
List<T> result = new ArrayList<>();
int rowNumber = 0;
while (rs.next()) {
T e = resultMap.map(rs, rowNumber++);
result.add(e);
}
return result;
}
}
}
public <T> Optional<T> queryFirst(String sql, Object[] params, ResultMap<T> resultMap) throws SQLException {
List<T> list = query(sql, params, resultMap);
return (list.isEmpty()) ? Optional.empty() : Optional.ofNullable(list.get(0));
}
public static final ResultMap<Map<String, Object>> mapResultMap = (rs, rowNumber) -> {
Map<String, Object> result = new HashMap<>();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String colName = metaData.getColumnName(i);
result.put(colName, rs.getObject(colName));
}
return result;
};
public List<Map<String, Object>> query(String sql, Object... params) throws SQLException {
return query(sql, params, mapResultMap);
}
public Optional<Map<String, Object>> queryFirst(String sql, Object... params) throws SQLException {
return queryFirst(sql, params, mapResultMap);
}
public static final ResultMap<DbRecord> recordResultMap = (rs, rowNumber) -> {
DbRecord result = new DbRecord();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String colName = metaData.getColumnName(i);
result.put(colName, rs.getObject(colName));
}
return result;
};
public List<DbRecord> queryToRecordList(String sql, Object... params) throws SQLException {
return query(sql, params, recordResultMap);
}
public Optional<DbRecord> queryFirstRecord(String sql, Object... params) throws SQLException {
return queryFirst(sql, params, recordResultMap);
}
public Optional<String> queryToString(String sql, Object... params) throws SQLException {
return queryFirst(sql, params, (ResultSet rs, int rowNumber) -> rs.getString(1));
}
public OptionalInt queryToInt(String sql, Object... params) throws SQLException {
Optional<Integer> result = queryFirst(sql, params, (ResultSet rs, int rowNumber) -> rs.getInt(1));
return OptionalUtil.toOptionalInt(result);
}
public OptionalLong queryToLong(String sql, Object... params) throws SQLException {
Optional<Long> result = queryFirst(sql, params, (ResultSet rs, int rowNumber) -> rs.getLong(1));
return OptionalUtil.toOptionalLong(result);
}
public OptionalDouble queryToDouble(String sql, Object... params) throws SQLException {
Optional<Double> result = queryFirst(sql, params, (ResultSet rs, int rowNumber) -> rs.getDouble(1));
return OptionalUtil.toOptionalDouble(result);
}
public Optional<BigDecimal> queryToBigDecimal(String sql, Object... params) throws SQLException {
return queryFirst(sql, params, (ResultSet rs, int rowNumber) -> rs.getBigDecimal(1));
}
public int update(String sql, Object... params) throws SQLException {
try (PreparedStatement stmt = this.conn.prepareStatement(sql)) {
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}
}
return stmt.executeUpdate();
}
}
public int[] batchUpdate(String sql, Collection<Object[]> params, int batchSize) throws SQLException {
int executeCount = params.size() / batchSize;
executeCount = (params.size() % batchSize == 0) ? executeCount : (executeCount + 1);
List<int[]> result = Lists.newArrayListWithCapacity(executeCount);
try (PreparedStatement stmt = this.conn.prepareStatement(sql)) {
int i = 0;
for (Object[] ps : params) {
i++;
for (int j = 0; j < ps.length; j++) {
stmt.setObject(j + 1, ps[j]);
}
stmt.addBatch();
if (i % batchSize == 0 || i >= params.size()) {
int[] n = stmt.executeBatch();
result.add(n);
stmt.clearBatch();
}
}
return MoreArrays.concatIntArray(result);
}
}
public <T extends Exception> void tx(final IAtom<T> atom) throws SQLException, T {
Preconditions.checkNotNull(atom, "Atom can not be null.");
try {
this.conn.setAutoCommit(false);
atom.execute();
conn.commit();
conn.setAutoCommit(true);
} catch (Exception e) {
conn.rollback();
conn.setAutoCommit(true);
throw e;
}
}
@FunctionalInterface
public interface IAtom<T extends Exception> {
@SuppressWarnings("all")
void execute() throws SQLException, T;
}
}
public static class ParamBuilder {
public static Object[] buildParams(final Object... params) {
if (ArrayUtils.isEmpty(params)) {
return ArrayUtils.EMPTY_OBJECT_ARRAY;
}
return Arrays.stream(params)
.map(param -> {
if (param instanceof Optional) {
return OptionalUtil.orElseNull((Optional<?>) param);
}
if (param instanceof OptionalInt) {
return OptionalUtil.toInteger(((OptionalInt) param));
}
if (param instanceof OptionalLong) {
return OptionalUtil.toLong(((OptionalLong) param));
}
if (param instanceof OptionalDouble) {
return OptionalUtil.toDouble(((OptionalDouble) param));
}
return param;
})
.toArray();
}
public static <T> List<Object[]> buildBatchParams(final Collection<T> c, final Function<T, Object[]> function) {
Preconditions.checkNotNull(c, "The collection can not be null.");
Preconditions.checkNotNull(function, "The function can not be null.");
if (MoreCollections.isEmpty(c)) {
return Collections.emptyList();
}
return c.stream().map(function).collect(Collectors.toList());
}
private ParamBuilder() {
throw new IllegalStateException("Utility class");
}
}
}

View File

@ -0,0 +1,85 @@
package xyz.zhouxy.jdbc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static xyz.zhouxy.jdbc.SimpleJdbcTemplate.ParamBuilder.*;
import static xyz.zhouxy.plusone.commons.sql.JdbcSql.IN;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import xyz.zhouxy.plusone.commons.sql.SQL;
class SimpleJdbcTemplateTests {
private static final Logger log = LoggerFactory.getLogger(SimpleJdbcTemplateTests.class);
final DataSource dataSource;
String[] cStruct = {
"id",
"created_by",
"create_time",
"updated_by",
"update_time",
"status"
};
SimpleJdbcTemplateTests() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/plusone");
config.setUsername("postgres");
config.setPassword("zhouxy108");
this.dataSource = new HikariDataSource(config);
}
@Test
void testQuery() throws SQLException {
try (Connection conn = this.dataSource.getConnection()) {
Object[] params = buildParams("501533", "501554", "544599");
String sql = SQL.newJdbcSql()
.SELECT("*")
.FROM("test_table")
.WHERE(IN("id", params))
.toString();
log.info(sql);
List<DbRecord> rs = SimpleJdbcTemplate.connect(conn)
.queryToRecordList(sql, params);
assertNotNull(rs);
for (DbRecord baseEntity : rs) {
// log.info("id: {}", baseEntity.getValueAsString("id"));
log.info(baseEntity.toString());
assertEquals(Optional.empty(), baseEntity.getValueAsString("updated_by"));
}
}
}
@Test
void testTransaction() {
try (Connection conn = dataSource.getConnection()) {
SimpleJdbcTemplate.connect(conn)
.tx(() -> {
SimpleJdbcTemplate.connect(conn)
.update("INSERT INTO base_table (created_by, create_time, status) VALUES (?, now(), 0)", 585757);
Optional<Map<String,Object>> first = SimpleJdbcTemplate.connect(conn)
.queryFirst("SELECT * FROM base_table WHERE created_by = ?", 585757);
log.info("first: {}", first);
throw new NullPointerException();
});
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}