package core.framework.impl.db; import core.framework.api.db.UncheckedSQLException; import core.framework.api.util.Exceptions; import core.framework.api.util.Lists; import core.framework.impl.resource.Pool; import core.framework.impl.resource.PoolItem; import java.math.BigDecimal; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; /** * @author neo */ public class DatabaseOperation { public final TransactionManager transactionManager; final EnumDBMapper enumMapper = new EnumDBMapper(); int queryTimeoutInSeconds; public DatabaseOperation(Pool<Connection> pool) { transactionManager = new TransactionManager(pool); } // for the boilerplate code, it is mainly for performance and clear purpose, as framework code, it's more important than DRY // make a lot of lambda and template pattern will make it harder to read and trace, also impact the mem usage and GC int update(String sql, Object[] params) { PoolItem<Connection> connection = transactionManager.getConnection(); try (PreparedStatement statement = connection.resource.prepareStatement(sql)) { statement.setQueryTimeout(queryTimeoutInSeconds); setParams(statement, params); return statement.executeUpdate(); } catch (SQLException e) { Connections.checkConnectionStatus(connection, e); throw new UncheckedSQLException(e); } finally { transactionManager.releaseConnection(connection); } } int[] batchUpdate(String sql, List<Object[]> params) { PoolItem<Connection> connection = transactionManager.getConnection(); try (PreparedStatement statement = connection.resource.prepareStatement(sql)) { statement.setQueryTimeout(queryTimeoutInSeconds); for (Object[] batchParams : params) { setParams(statement, batchParams); statement.addBatch(); } return statement.executeBatch(); } catch (SQLException e) { Connections.checkConnectionStatus(connection, e); throw new UncheckedSQLException(e); } finally { transactionManager.releaseConnection(connection); } } <T> Optional<T> selectOne(String sql, RowMapper<T> mapper, Object[] params) { validateSelectSQL(sql); PoolItem<Connection> connection = transactionManager.getConnection(); try (PreparedStatement statement = connection.resource.prepareStatement(sql)) { statement.setQueryTimeout(queryTimeoutInSeconds); setParams(statement, params); return fetchOne(statement, mapper); } catch (SQLException e) { Connections.checkConnectionStatus(connection, e); throw new UncheckedSQLException(e); } finally { transactionManager.releaseConnection(connection); } } <T> List<T> select(String sql, RowMapper<T> mapper, Object[] params) { validateSelectSQL(sql); PoolItem<Connection> connection = transactionManager.getConnection(); try (PreparedStatement statement = connection.resource.prepareStatement(sql)) { statement.setQueryTimeout(queryTimeoutInSeconds); setParams(statement, params); return fetch(statement, mapper); } catch (SQLException e) { Connections.checkConnectionStatus(connection, e); throw new UncheckedSQLException(e); } finally { transactionManager.releaseConnection(connection); } } Optional<Long> insert(String sql, Object[] params) { PoolItem<Connection> connection = transactionManager.getConnection(); try (PreparedStatement statement = connection.resource.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { statement.setQueryTimeout(queryTimeoutInSeconds); setParams(statement, params); statement.executeUpdate(); return fetchGeneratedKey(statement); } catch (SQLException e) { Connections.checkConnectionStatus(connection, e); throw new UncheckedSQLException(e); } finally { transactionManager.releaseConnection(connection); } } private void validateSelectSQL(String sql) { if (sql.contains("*")) throw Exceptions.error("sql must not contain wildcard(*), please only select columns needed, sql={}", sql); } private <T> Optional<T> fetchOne(PreparedStatement statement, RowMapper<T> mapper) throws SQLException { try (ResultSet resultSet = statement.executeQuery()) { ResultSetWrapper wrapper = new ResultSetWrapper(resultSet); T result = null; if (resultSet.next()) { result = mapper.map(wrapper); if (resultSet.next()) throw new Error("more than one row returned"); } return Optional.ofNullable(result); } } private <T> List<T> fetch(PreparedStatement statement, RowMapper<T> mapper) throws SQLException { try (ResultSet resultSet = statement.executeQuery()) { ResultSetWrapper wrapper = new ResultSetWrapper(resultSet); List<T> results = Lists.newArrayList(); while (resultSet.next()) { T result = mapper.map(wrapper); results.add(result); } return results; } } // the LAST_INSERT_ID() function of mysql returns BIGINT, so here it uses Long // http://dev.mysql.com/doc/refman/5.7/en/information-functions.html private Optional<Long> fetchGeneratedKey(PreparedStatement statement) throws SQLException { try (ResultSet keys = statement.getGeneratedKeys()) { if (keys.next()) { return Optional.of(keys.getLong(1)); } } return Optional.empty(); } private void setParams(PreparedStatement statement, Object[] params) throws SQLException { int index = 1; if (params != null) { for (Object param : params) { setParam(statement, index, param); index++; } } } private void setParam(PreparedStatement statement, int index, Object param) throws SQLException { if (param instanceof String) { statement.setString(index, (String) param); } else if (param instanceof Integer) { statement.setInt(index, (Integer) param); } else if (param instanceof Enum) { statement.setString(index, enumMapper.getDBValue((Enum<?>) param)); } else if (param instanceof LocalDateTime) { statement.setTimestamp(index, Timestamp.valueOf((LocalDateTime) param)); } else if (param instanceof ZonedDateTime) { statement.setTimestamp(index, new Timestamp(((ZonedDateTime) param).toInstant().toEpochMilli())); } else if (param instanceof Boolean) { statement.setBoolean(index, (Boolean) param); } else if (param instanceof Long) { statement.setLong(index, (Long) param); } else if (param instanceof Double) { statement.setDouble(index, (Double) param); } else if (param instanceof BigDecimal) { statement.setBigDecimal(index, (BigDecimal) param); } else if (param instanceof LocalDate) { statement.setDate(index, Date.valueOf((LocalDate) param)); } else if (param == null) { statement.setObject(index, null); } else { throw Exceptions.error("unsupported param type, please contact arch team, type={}, value={}", param.getClass().getCanonicalName(), param); } } }