/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.commons.datasource;
import java.io.PrintWriter;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.LruCache;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.datasource.internal.SqlErrorCodes;
import org.xipki.commons.datasource.internal.SqlStateCodes;
import org.xipki.commons.datasource.springframework.dao.CannotAcquireLockException;
import org.xipki.commons.datasource.springframework.dao.CannotSerializeTransactionException;
import org.xipki.commons.datasource.springframework.dao.ConcurrencyFailureException;
import org.xipki.commons.datasource.springframework.dao.DataAccessException;
import org.xipki.commons.datasource.springframework.dao.DataAccessResourceFailureException;
import org.xipki.commons.datasource.springframework.dao.DataIntegrityViolationException;
import org.xipki.commons.datasource.springframework.dao.DeadlockLoserDataAccessException;
import org.xipki.commons.datasource.springframework.dao.PermissionDeniedDataAccessException;
import org.xipki.commons.datasource.springframework.dao.QueryTimeoutException;
import org.xipki.commons.datasource.springframework.dao.TransientDataAccessResourceException;
import org.xipki.commons.datasource.springframework.jdbc.BadSqlGrammarException;
import org.xipki.commons.datasource.springframework.jdbc.DuplicateKeyException;
import org.xipki.commons.datasource.springframework.jdbc.InvalidResultSetAccessException;
import org.xipki.commons.datasource.springframework.jdbc.UncategorizedSqlException;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public abstract class DataSourceWrapper {
// CHECKSTYLE:SKIP
private static class MySQL extends DataSourceWrapper {
MySQL(final String name, final HikariDataSource service) {
super(name, service, DatabaseType.MYSQL);
}
MySQL(final String name, final HikariDataSource service, final DatabaseType type) {
super(name, service, type);
}
@Override
public String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql) {
// 'SELECT ': 7
// ' LIMIT ': 7
// rows (till 9999): 4
int size = coreSql.length() + 18;
if (StringUtil.isNotBlank(orderBy)) {
// ' ORDER BY ': 10
size += 10;
size += orderBy.length();
}
StringBuilder sql = new StringBuilder(size);
sql.append("SELECT ").append(coreSql);
if (StringUtil.isNotBlank(orderBy)) {
sql.append(" ORDER BY ").append(orderBy);
}
return sql.append(" LIMIT ").append(rows).toString();
}
@Override
protected String buildCreateSequenceSql(final String sequenceName, final long startValue) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 75);
sql.append("INSERT INTO SEQ_TBL (SEQ_NAME,SEQ_VALUE) VALUES('");
return sql.append(sequenceName).append("', ").append(startValue).append(")").toString();
}
@Override
protected String buildDropSequenceSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 40);
sql.append("DELETE FROM SEQ_TBL WHERE SEQ_NAME='").append(sequenceName).append("'");
return sql.toString();
}
@Override
protected String buildNextSeqValueSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 75);
sql.append("UPDATE SEQ_TBL SET SEQ_VALUE=(@cur_value:=SEQ_VALUE)+1 WHERE SEQ_NAME='");
return sql.append(sequenceName).append("'").toString();
}
@Override
public long nextSeqValue(final Connection conn, final String sequenceName)
throws DataAccessException {
final String sqlUpdate = buildAndCacheNextSeqValueSql(sequenceName);
final String sqlSelect = "SELECT @cur_value";
String sql = null;
boolean newConn = (conn == null);
Connection tmpConn = (conn != null) ? conn : getConnection();
Statement stmt = null;
ResultSet rs = null;
long ret;
try {
stmt = tmpConn.createStatement();
sql = sqlUpdate;
stmt.executeUpdate(sql);
sql = sqlSelect;
rs = stmt.executeQuery(sql);
if (rs.next()) {
ret = rs.getLong(1);
} else {
throw new DataAccessException(
"could not increment the sequence " + sequenceName);
}
} catch (SQLException ex) {
throw translate(sqlUpdate, ex);
} finally {
if (newConn) {
releaseResources(stmt, rs);
} else {
super.releaseStatementAndResultSet(stmt, rs);
}
}
LOG.debug("datasource {} NEXVALUE({}): {}", name, sequenceName, ret);
return ret;
} // method nextSeqValue
@Override
protected String getSqlToDropForeignKeyConstraint(final String constraintName,
final String baseTable) throws DataAccessException {
StringBuilder sb = new StringBuilder(baseTable.length() + constraintName.length() + 30);
return sb.append("ALTER TABLE ").append(baseTable).append(" DROP FOREIGN KEY ")
.append(constraintName).toString();
}
@Override
protected String getSqlToDropIndex(final String table, final String indexName) {
StringBuilder sb = new StringBuilder(indexName.length() + table.length() + 15);
return sb.append("DROP INDEX ").append(indexName).append(" ON ").append(table)
.toString();
}
@Override
protected String getSqlToDropUniqueConstraint(final String constraintName,
final String table) {
StringBuilder sb = new StringBuilder(constraintName.length() + table.length() + 22);
return sb.append("ALTER TABLE ").append(table).append(" DROP KEY ")
.append(constraintName).toString();
}
} // class MySQL
// CHECKSTYLE:SKIP
private static class MariaDB extends MySQL {
MariaDB(String name, HikariDataSource service) {
super(name, service, DatabaseType.MARIADB);
}
}
// CHECKSTYLE:SKIP
private static class DB2 extends DataSourceWrapper {
DB2(final String name, final HikariDataSource service) {
super(name, service, DatabaseType.DB2);
}
@Override
public String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql) {
// 'SELECT ': 7
// ' FETCH FIRST ': 15
// ' ROWS ONLY' : 10
// rows (till 9999): 4
int size = coreSql.length() + 36;
if (StringUtil.isNotBlank(orderBy)) {
// ' ORDER BY ': 10
size += 10;
size += orderBy.length();
}
StringBuilder sql = new StringBuilder(size);
sql.append("SELECT ").append(coreSql);
if (StringUtil.isNotBlank(orderBy)) {
sql.append(" ORDER BY ").append(orderBy);
}
return sql.append(" FETCH FIRST ").append(rows).append(" ROWS ONLY").toString();
}
@Override
protected String buildCreateSequenceSql(final String sequenceName, final long startValue) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 80);
sql.append("CREATE SEQUENCE ").append(sequenceName).append(" AS BIGINT START WITH ");
return sql.append(startValue).append(" INCREMENT BY 1 NO CYCLE NO CACHE").toString();
}
@Override
protected String buildDropSequenceSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 14);
return sql.append("DROP SEQUENCE ").append(sequenceName).toString();
}
@Override
protected String buildNextSeqValueSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 44);
sql.append("SELECT NEXT VALUE FOR ").append(sequenceName)
.append(" FROM sysibm.sysdummy1");
return sql.toString();
}
} // class DB2
// CHECKSTYLE:SKIP
private static class PostgreSQL extends DataSourceWrapper {
PostgreSQL(final String name, final HikariDataSource service) {
super(name, service, DatabaseType.POSTGRES);
}
@Override
public String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql) {
// 'SELECT ': 7
// ' FETCH FIRST ': 13
// ' ROWS ONLY': 10
// rows (till 9999): 4
int size = coreSql.length() + 34;
if (StringUtil.isNotBlank(orderBy)) {
// ' ORDER BY ': 10
size += 10;
size += orderBy.length();
}
StringBuilder sql = new StringBuilder(size);
sql.append("SELECT ").append(coreSql);
if (StringUtil.isNotBlank(orderBy)) {
sql.append(" ORDER BY ").append(orderBy);
}
return sql.append(" FETCH FIRST ").append(rows).append(" ROWS ONLY").toString();
}
@Override
protected String buildCreateSequenceSql(final String sequenceName, final long startValue) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 70);
sql.append("CREATE SEQUENCE ").append(sequenceName).append(" START WITH ");
return sql.append(startValue).append(" INCREMENT BY 1 NO CYCLE").toString();
}
@Override
protected String buildDropSequenceSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 14);
return sql.append("DROP SEQUENCE ").append(sequenceName).toString();
}
@Override
protected String buildNextSeqValueSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 20);
return sql.append("SELECT NEXTVAL ('").append(sequenceName).append("')").toString();
}
@Override
protected boolean isUseSqlStateAsCode() {
return true;
}
@Override
protected String getSqlToDropPrimaryKey(final String primaryKeyName, final String table) {
StringBuilder sb = new StringBuilder(500);
sb.append("DO $$ DECLARE constraint_name varchar;\n");
sb.append("BEGIN\n");
sb.append(" SELECT tc.CONSTRAINT_NAME into strict constraint_name\n");
sb.append(" FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc\n");
sb.append(" WHERE CONSTRAINT_TYPE='PRIMARY KEY'\n");
sb.append(" AND TABLE_NAME='").append(table.toLowerCase())
.append("' AND TABLE_SCHEMA='public';\n");
sb.append(" EXECUTE 'alter table public.").append(table.toLowerCase())
.append(" drop constraint ' || constraint_name;\n");
sb.append("END $$;");
return sb.toString();
}
} // class PostgreSQL
private static class Oracle extends DataSourceWrapper {
Oracle(final String name, final HikariDataSource service) {
super(name, service, DatabaseType.ORACLE);
}
/*
* Oracle: http://www.oracle.com/technetwork/issue-archive/2006/06-sep/o56asktom-086197.html
*
*/
@Override
public String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql) {
int size = coreSql.length() + 18;
size += StringUtil.isBlank(orderBy) ? 14 : orderBy.length() + 40;
// ' ROWNUM < ': 10
// rows (till 9999): 4
size += 14;
StringBuilder sql = new StringBuilder(size);
if (StringUtil.isBlank(orderBy)) {
sql.append("SELECT ").append(coreSql);
if (coreSql.contains(" WHERE")) {
sql.append(" AND");
} else {
sql.append(" WHERE");
}
} else {
sql.append("SELECT * FROM (SELECT ");
sql.append(coreSql);
sql.append(" ORDER BY ").append(orderBy).append(" ) WHERE");
}
return sql.append(" ROWNUM<").append(rows + 1).toString();
}
@Override
protected String buildCreateSequenceSql(final String sequenceName, final long startValue) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 59);
sql.append("CREATE SEQUENCE ").append(sequenceName);
sql.append(" START WITH ").append(startValue);
return sql.append(" INCREMENT BY 1 NOCYCLE NOCACHE").toString();
}
@Override
protected String buildDropSequenceSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 14);
return sql.append("DROP SEQUENCE ").append(sequenceName).toString();
}
@Override
protected String buildNextSeqValueSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 21);
sql.append("SELECT ").append(sequenceName).append(".NEXTVAL FROM DUAL");
return sql.toString();
}
@Override
protected String getSqlToDropPrimaryKey(final String primaryKeyName, final String table) {
return getSqlToDropUniqueConstraint(primaryKeyName, table);
}
@Override
protected String getSqlToDropUniqueConstraint(final String contraintName,
final String table) {
StringBuilder sql = new StringBuilder(table.length() + contraintName.length() + 40);
return sql.append("ALTER TABLE ").append(table)
.append(" DROP CONSTRAINT ").append(contraintName)
.append(" DROP INDEX").toString();
}
@Override
protected String getSqlToAddForeignKeyConstraint(final String constraintName,
final String baseTable, final String baseColumn, final String referencedTable,
final String referencedColumn, final String onDeleteAction,
final String onUpdateAction) {
final StringBuilder sb = new StringBuilder(100);
sb.append("ALTER TABLE ").append(baseTable);
sb.append(" ADD CONSTRAINT ").append(constraintName);
sb.append(" FOREIGN KEY (").append(baseColumn).append(")");
sb.append(" REFERENCES ").append(referencedTable);
sb.append(" (").append(referencedColumn).append(")");
return sb.append(" ON DELETE ").append(onDeleteAction).toString();
}
@Override
protected String getSqlToAddPrimaryKey(final String primaryKeyName, final String table,
final String... columns) {
StringBuilder sb = new StringBuilder(100);
sb.append("ALTER TABLE ").append(table);
sb.append(" ADD CONSTRAINT ").append(primaryKeyName);
sb.append(" PRIMARY KEY(");
final int n = columns.length;
for (int i = 0; i < n; i++) {
if (i != 0) {
sb.append(",");
}
sb.append(columns[i]);
}
sb.append(")");
return sb.toString();
}
} // class Oracle
private static class H2 extends DataSourceWrapper {
H2(final String name, final HikariDataSource service) {
super(name, service, DatabaseType.H2);
}
@Override
public String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql) {
// 'SELECT ': 7
// ' LIMIT ': 7
// rows (till 9999): 4
int size = coreSql.length() + 18;
if (StringUtil.isNotBlank(orderBy)) {
// ' ORDER BY ': 10
size += 10;
size += orderBy.length();
}
StringBuilder sql = new StringBuilder(size);
sql.append("SELECT ").append(coreSql);
if (StringUtil.isNotBlank(orderBy)) {
sql.append(" ORDER BY ").append(orderBy);
}
sql.append(" LIMIT ").append(rows);
return sql.toString();
}
@Override
protected String buildCreateSequenceSql(final String sequenceName, final long startValue) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 80);
sql.append("CREATE SEQUENCE ").append(sequenceName);
sql.append(" START WITH ").append(startValue);
return sql.append(" INCREMENT BY 1 NO CYCLE NO CACHE").toString();
}
@Override
protected String buildDropSequenceSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 14);
return sql.append("DROP SEQUENCE ").append(sequenceName).toString();
}
@Override
protected String buildNextSeqValueSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 20);
return sql.append("SELECT NEXTVAL ('").append(sequenceName).append("')").toString();
}
} // class H2
// CHECKSTYLE:SKIP
private static class HSQL extends DataSourceWrapper {
HSQL(final String name, final HikariDataSource service) {
super(name, service, DatabaseType.HSQL);
}
@Override
public String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql) {
// 'SELECT ': 7
// ' LIMIT ': 7
// rows (till 9999): 4
int size = coreSql.length() + 18;
if (StringUtil.isNotBlank(orderBy)) {
// ' ORDER BY ': 10
size += 10;
size += orderBy.length();
}
StringBuilder sql = new StringBuilder(size);
sql.append("SELECT ").append(coreSql);
if (StringUtil.isNotBlank(orderBy)) {
sql.append(" ORDER BY ").append(orderBy);
}
return sql.append(" LIMIT ").append(rows).toString();
}
@Override
protected String buildCreateSequenceSql(final String sequenceName, final long startValue) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 70);
sql.append("CREATE SEQUENCE ").append(sequenceName);
sql.append(" AS BIGINT START WITH ").append(startValue);
return sql.append(" INCREMENT BY 1").toString();
}
@Override
protected String buildDropSequenceSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 14);
return sql.append("DROP SEQUENCE ").append(sequenceName).toString();
}
@Override
protected String buildNextSeqValueSql(final String sequenceName) {
StringBuilder sql = new StringBuilder(sequenceName.length() + 20);
return sql.append("SELECT NEXTVAL ('").append(sequenceName).append("')").toString();
}
} // class HSQL
private static final Logger LOG = LoggerFactory.getLogger(DataSourceWrapper.class);
/**
* References the real data source implementation this class acts as pure
* proxy for. Derived classes must set this field at construction time.
*/
protected final HikariDataSource service;
protected final String name;
private final Object lastUsedSeqValuesLock = new Object();
private final ConcurrentHashMap<String, Long> lastUsedSeqValues
= new ConcurrentHashMap<String, Long>();
private final SqlErrorCodes sqlErrorCodes;
private final SqlStateCodes sqlStateCodes;
private final DatabaseType databaseType;
private final LruCache<String, String> cacheSeqNameSqls;
private DataSourceWrapper(final String name, final HikariDataSource service,
final DatabaseType dbType) {
this.service = ParamUtil.requireNonNull("service", service);
this.databaseType = ParamUtil.requireNonNull("dbType", dbType);
this.name = name;
this.sqlErrorCodes = SqlErrorCodes.newInstance(dbType);
this.sqlStateCodes = SqlStateCodes.newInstance(dbType);
this.cacheSeqNameSqls = new LruCache<>(100);
}
public final String getDatasourceName() {
return name;
}
public final DatabaseType getDatabaseType() {
return this.databaseType;
}
public final int getMaximumPoolSize() {
return service.getMaximumPoolSize();
}
public final Connection getConnection() throws DataAccessException {
try {
return service.getConnection();
} catch (Exception ex) {
Throwable cause = ex.getCause();
if (cause instanceof SQLException) {
ex = (SQLException) cause;
}
LogUtil.error(LOG, ex, "could not create connection to database");
if (ex instanceof SQLException) {
throw translate(null, (SQLException) ex);
} else {
throw new DataAccessException("error occured while getting Connection: "
+ ex.getMessage(), ex);
}
}
}
public void returnConnection(final Connection conn) {
if (conn == null) {
return;
}
try {
conn.close();
} catch (Exception ex) {
Throwable cause = ex.getCause();
if (cause instanceof SQLException) {
ex = (SQLException) cause;
}
LogUtil.error(LOG, ex, "could not close connection to database {}");
}
}
public void shutdown() {
try {
service.close();
} catch (Exception ex) {
LOG.warn("could not shutdown datasource: {}", ex.getMessage());
LOG.debug("could not close datasource", ex);
}
}
public final PrintWriter getLogWriter() throws SQLException {
return service.getLogWriter();
}
public Statement createStatement(final Connection conn) throws DataAccessException {
ParamUtil.requireNonNull("conn", conn);
try {
return conn.createStatement();
} catch (SQLException ex) {
throw translate(null, ex);
}
}
public PreparedStatement prepareStatement(final Connection conn, final String sqlQuery)
throws DataAccessException {
ParamUtil.requireNonNull("conn", conn);
try {
return conn.prepareStatement(sqlQuery);
} catch (SQLException ex) {
throw translate(sqlQuery, ex);
}
}
public void releaseResources(final Statement ps, final ResultSet rs) {
releaseResources(ps, rs, true);
}
public void releaseResources(final Statement ps, final ResultSet rs, boolean returnConnection) {
if (rs != null) {
try {
rs.close();
} catch (Throwable th) {
LOG.warn("could not close ResultSet", th);
}
}
if (ps != null) {
Connection conn = null;
try {
conn = ps.getConnection();
} catch (SQLException ex) {
LOG.error("could not get connection from statement: {}", ex.getMessage());
}
try {
ps.close();
} catch (Throwable th) {
LOG.warn("could not close statement", th);
} finally {
if (returnConnection && conn != null) {
returnConnection(conn);
}
}
}
}
private void releaseStatementAndResultSet(final Statement ps, final ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Throwable th) {
LOG.warn("could not close ResultSet", th);
}
}
if (ps != null) {
try {
ps.close();
} catch (Throwable th) {
LOG.warn("could not close statement", th);
}
}
}
public String buildSelectFirstSql(final int rows, final String coreSql) {
return buildSelectFirstSql(rows, null, coreSql);
}
public abstract String buildSelectFirstSql(final int rows, final String orderBy,
final String coreSql);
public long getMin(final Connection conn, final String table, final String column)
throws DataAccessException {
return getMin(conn, table, column, null);
}
public long getMin(final Connection conn, final String table, final String column,
final String condition) throws DataAccessException {
ParamUtil.requireNonBlank("table", table);
ParamUtil.requireNonBlank("column", column);
int size = column.length() + table.length() + 20;
if (StringUtil.isNotBlank(condition)) {
size += 7 + condition.length();
}
StringBuilder sqlBuilder = new StringBuilder(size);
sqlBuilder.append("SELECT MIN(").append(column).append(") FROM ").append(table);
if (StringUtil.isNotBlank(condition)) {
sqlBuilder.append(" WHERE ").append(condition);
}
final String sql = sqlBuilder.toString();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = (conn != null) ? conn.createStatement() : getConnection().createStatement();
rs = stmt.executeQuery(sql);
rs.next();
return rs.getLong(1);
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
if (conn == null) {
releaseResources(stmt, rs);
} else {
releaseStatementAndResultSet(stmt, rs);
}
}
}
public int getCount(final Connection conn, final String table) throws DataAccessException {
ParamUtil.requireNonBlank("table", table);
StringBuilder sqlBuilder = new StringBuilder(table.length() + 21);
sqlBuilder.append("SELECT COUNT(*) FROM ").append(table);
final String sql = sqlBuilder.toString();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = (conn != null) ? conn.createStatement() : getConnection().createStatement();
rs = stmt.executeQuery(sql);
rs.next();
return rs.getInt(1);
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
if (conn == null) {
releaseResources(stmt, rs);
} else {
releaseStatementAndResultSet(stmt, rs);
}
}
}
public long getMax(final Connection conn, final String table, final String column)
throws DataAccessException {
return getMax(conn, table, column, null);
}
public long getMax(final Connection conn, final String table, final String column,
final String condition) throws DataAccessException {
ParamUtil.requireNonBlank("table", table);
ParamUtil.requireNonBlank("column", column);
int size = column.length() + table.length() + 20;
if (StringUtil.isNotBlank(condition)) {
size += 7 + condition.length();
}
StringBuilder sqlBuilder = new StringBuilder(size);
sqlBuilder.append("SELECT MAX(").append(column).append(") FROM ").append(table);
if (StringUtil.isNotBlank(condition)) {
sqlBuilder.append(" WHERE ").append(condition);
}
final String sql = sqlBuilder.toString();
Statement stmt = null;
ResultSet rs = null;
try {
stmt = (conn != null) ? conn.createStatement() : getConnection().createStatement();
rs = stmt.executeQuery(sql);
rs.next();
return rs.getLong(1);
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
if (conn == null) {
releaseResources(stmt, rs);
} else {
releaseStatementAndResultSet(stmt, rs);
}
}
}
public boolean deleteFromTable(final Connection conn, final String table, final String idColumn,
final long id) {
ParamUtil.requireNonBlank("table", table);
ParamUtil.requireNonBlank("idColumn", idColumn);
final StringBuilder sb = new StringBuilder(table.length() + idColumn.length() + 35);
sb.append("DELETE FROM ").append(table).append(" WHERE ")
.append(idColumn).append("=").append(id);
final String sql = sb.toString();
Connection tmpConn;
if (conn != null) {
tmpConn = conn;
} else {
try {
tmpConn = getConnection();
} catch (Throwable th) {
if (LOG.isWarnEnabled()) {
LOG.warn("datasource {} could not get connection: {}", name, th.getMessage());
}
return false;
}
}
Statement stmt = null;
try {
stmt = tmpConn.createStatement();
stmt.execute(sql);
} catch (Throwable th) {
if (LOG.isWarnEnabled()) {
LOG.warn("datasource {} could not deletefrom table {}: {}", name, table,
th.getMessage());
}
return false;
} finally {
if (conn == null) {
releaseResources(stmt, null);
} else {
releaseStatementAndResultSet(stmt, null);
}
}
return true;
}
public boolean columnExists(final Connection conn, final String table, final String column,
final Object value) throws DataAccessException {
ParamUtil.requireNonBlank("table", table);
ParamUtil.requireNonBlank("column", column);
ParamUtil.requireNonNull("value", value);
StringBuilder sb = new StringBuilder(2 * column.length() + 15);
sb.append(column).append(" FROM ").append(table);
sb.append(" WHERE ").append(column).append("=?");
String sql = buildSelectFirstSql(1, sb.toString());
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = (conn != null) ? conn.prepareStatement(sql)
: getConnection().prepareStatement(sql);
if (value instanceof Integer) {
stmt.setInt(1, (Integer) value);
} else if (value instanceof Long) {
stmt.setLong(1, (Long) value);
} else if (value instanceof String) {
stmt.setString(1, (String) value);
} else {
stmt.setString(1, value.toString());
}
rs = stmt.executeQuery();
return rs.next();
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
if (conn == null) {
releaseResources(stmt, rs);
} else {
releaseStatementAndResultSet(stmt, rs);
}
}
} // method columnExists
public boolean tableHasColumn(final Connection conn, final String table, final String column)
throws DataAccessException {
ParamUtil.requireNonBlank("table", table);
ParamUtil.requireNonBlank("column", column);
Statement stmt;
try {
stmt = (conn != null) ? conn.createStatement() : getConnection().createStatement();
} catch (SQLException ex) {
throw translate(null, ex);
}
StringBuilder sqlBuilder = new StringBuilder(column.length() + table.length() + 20);
sqlBuilder.append(column).append(" FROM ").append(table);
final String sql = buildSelectFirstSql(1, sqlBuilder.toString());
try {
stmt.execute(sql);
return true;
} catch (SQLException ex) {
return false;
} finally {
if (conn == null) {
releaseResources(stmt, null);
} else {
releaseStatementAndResultSet(stmt, null);
}
}
}
public boolean tableExists(final Connection conn, final String table)
throws DataAccessException {
ParamUtil.requireNonBlank("table", table);
Statement stmt;
try {
stmt = (conn != null) ? conn.createStatement() : getConnection().createStatement();
} catch (SQLException ex) {
throw translate(null, ex);
}
StringBuilder sqlBuilder = new StringBuilder(table.length() + 10);
sqlBuilder.append("1 FROM ").append(table);
final String sql = buildSelectFirstSql(1, sqlBuilder.toString());
try {
stmt.execute(sql);
return true;
} catch (SQLException ex) {
return false;
} finally {
if (conn == null) {
releaseResources(stmt, null);
} else {
releaseStatementAndResultSet(stmt, null);
}
}
}
protected abstract String buildCreateSequenceSql(String sequenceName, long startValue);
protected abstract String buildDropSequenceSql(String sequenceName);
protected abstract String buildNextSeqValueSql(String sequenceName);
protected final String buildAndCacheNextSeqValueSql(String sequenceName) {
String sql = cacheSeqNameSqls.get(sequenceName);
if (sql == null) {
sql = buildNextSeqValueSql(sequenceName);
cacheSeqNameSqls.put(sequenceName, sql);
}
return sql;
}
protected boolean isUseSqlStateAsCode() {
return false;
}
public void dropAndCreateSequence(final String sequenceName, final long startValue)
throws DataAccessException {
try {
dropSequence(sequenceName);
} catch (DataAccessException ex) {
LOG.error("could not drop sequence {}: {}", sequenceName, ex.getMessage());
}
createSequence(sequenceName, startValue);
}
public void createSequence(final String sequenceName, final long startValue)
throws DataAccessException {
ParamUtil.requireNonBlank("sequenceName", sequenceName);
final String sql = buildCreateSequenceSql(sequenceName, startValue);
Connection conn = getConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
stmt.execute(sql);
LOG.info("datasource {} CREATESEQ {} START {}", name, sequenceName, startValue);
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
releaseResources(stmt, null);
}
}
public void dropSequence(final String sequenceName) throws DataAccessException {
ParamUtil.requireNonBlank("sequenceName", sequenceName);
final String sql = buildDropSequenceSql(sequenceName);
Connection conn = getConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
stmt.execute(sql);
LOG.info("datasource {} DROPSEQ {}", name, sequenceName);
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
releaseResources(stmt, null);
}
}
public void setLastUsedSeqValue(final String sequenceName, final long sequenceValue) {
ParamUtil.requireNonBlank("sequenceName", sequenceName);
lastUsedSeqValues.put(sequenceName, sequenceValue);
}
public long nextSeqValue(final Connection conn, final String sequenceName)
throws DataAccessException {
ParamUtil.requireNonBlank("sequenceName", sequenceName);
final String sql = buildAndCacheNextSeqValueSql(sequenceName);
boolean newConn = (conn == null);
Connection tmpConn = (conn != null) ? conn : getConnection();
Statement stmt = null;
long next;
try {
stmt = tmpConn.createStatement();
while (true) {
ResultSet rs = stmt.executeQuery(sql);
try {
if (rs.next()) {
next = rs.getLong(1);
synchronized (lastUsedSeqValuesLock) {
Long lastValue = lastUsedSeqValues.get(sequenceName);
if (lastValue == null || next > lastValue) {
lastUsedSeqValues.put(sequenceName, next);
break;
}
}
} else {
throw new DataAccessException(
"could not increment the sequence " + sequenceName);
}
} finally {
releaseStatementAndResultSet(null, rs);
}
}
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
if (newConn) {
releaseResources(stmt, null);
} else {
releaseStatementAndResultSet(stmt, null);
}
}
LOG.debug("datasource {} NEXVALUE({}): {}", name, sequenceName, next);
return next;
} // method nextSeqValue
protected String getSqlToDropPrimaryKey(final String primaryKeyName, final String table) {
ParamUtil.requireNonBlank("primaryKeyName", primaryKeyName);
ParamUtil.requireNonBlank("table", table);
StringBuilder sql = new StringBuilder(table.length() + 30);
return sql.append("ALTER TABLE ").append(table).append(" DROP PRIMARY KEY ").toString();
}
public void dropPrimaryKey(final Connection conn, final String primaryKeyName,
final String table) throws DataAccessException {
executeUpdate(conn, getSqlToDropPrimaryKey(primaryKeyName, table));
}
protected String getSqlToAddPrimaryKey(final String primaryKeyName, final String table,
final String... columns) {
ParamUtil.requireNonBlank("primaryKeyName", primaryKeyName);
ParamUtil.requireNonBlank("table", table);
final StringBuilder sb = new StringBuilder(100);
sb.append("ALTER TABLE ").append(table);
sb.append(" ADD CONSTRAINT ").append(primaryKeyName);
sb.append(" PRIMARY KEY (");
final int n = columns.length;
for (int i = 0; i < n; i++) {
if (i != 0) {
sb.append(",");
}
sb.append(columns[i]);
}
sb.append(")");
return sb.toString();
}
public void addPrimaryKey(final Connection conn, final String primaryKeyName,
final String table, final String... columns) throws DataAccessException {
executeUpdate(conn, getSqlToAddPrimaryKey(primaryKeyName, table, columns));
}
protected String getSqlToDropForeignKeyConstraint(final String constraintName,
final String baseTable) throws DataAccessException {
ParamUtil.requireNonBlank("constraintName", constraintName);
ParamUtil.requireNonBlank("baseTable", baseTable);
StringBuilder sb = new StringBuilder(baseTable.length() + constraintName.length() + 30);
return sb.append("ALTER TABLE ").append(baseTable).append(" DROP CONSTRAINT ")
.append(constraintName).toString();
}
public void dropForeignKeyConstraint(final Connection conn, final String constraintName,
final String baseTable) throws DataAccessException {
executeUpdate(conn, getSqlToDropForeignKeyConstraint(constraintName, baseTable));
}
protected String getSqlToAddForeignKeyConstraint(final String constraintName,
final String baseTable, final String baseColumn, final String referencedTable,
final String referencedColumn, final String onDeleteAction,
final String onUpdateAction) {
ParamUtil.requireNonBlank("constraintName", constraintName);
ParamUtil.requireNonBlank("baseTable", baseTable);
ParamUtil.requireNonBlank("baseColumn", baseColumn);
ParamUtil.requireNonBlank("referencedTable", referencedTable);
ParamUtil.requireNonBlank("referencedColumn", referencedColumn);
ParamUtil.requireNonBlank("onDeleteAction", onDeleteAction);
ParamUtil.requireNonBlank("onUpdateAction", onUpdateAction);
final StringBuilder sb = new StringBuilder(100);
sb.append("ALTER TABLE ").append(baseTable);
sb.append(" ADD CONSTRAINT ").append(constraintName);
sb.append(" FOREIGN KEY (").append(baseColumn).append(")");
sb.append(" REFERENCES ").append(referencedTable);
sb.append(" (").append(referencedColumn).append(")");
sb.append(" ON DELETE ").append(onDeleteAction);
sb.append(" ON UPDATE ").append(onUpdateAction);
return sb.toString();
}
public void addForeignKeyConstraint(final Connection conn, final String constraintName,
final String baseTable, final String baseColumn, final String referencedTable,
final String referencedColumn, final String onDeleteAction, final String onUpdateAction)
throws DataAccessException {
final String sql = getSqlToAddForeignKeyConstraint(constraintName, baseTable, baseColumn,
referencedTable, referencedColumn, onDeleteAction, onUpdateAction);
executeUpdate(conn, sql);
}
protected String getSqlToDropIndex(final String table, final String indexName) {
ParamUtil.requireNonBlank("indexName", indexName);
return "DROP INDEX " + indexName;
}
public void dropIndex(final Connection conn, final String table, final String indexName)
throws DataAccessException {
executeUpdate(conn, getSqlToDropIndex(table, indexName));
}
protected String getSqlToCreateIndex(final String indexName, final String table,
final String... columns) {
ParamUtil.requireNonBlank("indexName", indexName);
ParamUtil.requireNonBlank("table", table);
if (columns == null || columns.length == 0) {
throw new IllegalArgumentException("columns must not be null and empty");
}
final StringBuilder sb = new StringBuilder(200);
sb.append("CREATE INDEX ").append(indexName);
sb.append(" ON ").append(table).append("(");
for (String column : columns) {
ParamUtil.requireNonBlank("column", column);
sb.append(column).append(',');
}
sb.deleteCharAt(sb.length() - 1); // delete the last ","
sb.append(")");
return sb.toString();
}
public void createIndex(final Connection conn, final String indexName, final String table,
final String... columns) throws DataAccessException {
executeUpdate(conn, getSqlToCreateIndex(indexName, table, columns));
}
protected String getSqlToDropUniqueConstraint(final String constraintName, final String table) {
ParamUtil.requireNonBlank("table", table);
ParamUtil.requireNonBlank("constraintName", constraintName);
StringBuilder sb = new StringBuilder(table.length() + constraintName.length() + 30);
return sb.append("ALTER TABLE ").append(table).append(" DROP CONSTRAINT ")
.append(constraintName).toString();
}
public void dropUniqueConstrain(final Connection conn, final String constraintName,
final String table) throws DataAccessException {
executeUpdate(conn, getSqlToDropUniqueConstraint(constraintName, table));
}
protected String getSqlToAddUniqueConstrain(final String constraintName, final String table,
final String... columns) {
ParamUtil.requireNonBlank("constraintName", constraintName);
ParamUtil.requireNonBlank("table", table);
final StringBuilder sb = new StringBuilder(100);
sb.append("ALTER TABLE ").append(table);
sb.append(" ADD CONSTRAINT ").append(constraintName);
sb.append(" UNIQUE (");
final int n = columns.length;
for (int i = 0; i < n; i++) {
if (i != 0) {
sb.append(",");
}
sb.append(columns[i]);
}
sb.append(")");
return sb.toString();
}
public void addUniqueConstrain(final Connection conn, final String constraintName,
final String table, final String... columns) throws DataAccessException {
executeUpdate(conn, getSqlToAddUniqueConstrain(constraintName, table, columns));
}
public DataAccessException translate(final String sql, final SQLException ex) {
ParamUtil.requireNonNull("ex", ex);
String tmpSql = sql;
if (tmpSql == null) {
tmpSql = "";
}
SQLException sqlEx = ex;
if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {
SQLException nestedSqlEx = sqlEx.getNextException();
if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {
LOG.debug("Using nested SQLException from the BatchUpdateException");
sqlEx = nestedSqlEx;
}
}
// Check SQLErrorCodes with corresponding error code, if available.
String errorCode;
String sqlState;
if (sqlErrorCodes.isUseSqlStateForTranslation()) {
errorCode = sqlEx.getSQLState();
sqlState = null;
} else {
// Try to find SQLException with actual error code, looping through the causes.
// E.g. applicable to java.sql.DataTruncation as of JDK 1.6.
SQLException current = sqlEx;
while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) {
current = (SQLException) current.getCause();
}
errorCode = Integer.toString(current.getErrorCode());
sqlState = current.getSQLState();
}
if (errorCode != null) {
// look for grouped error codes.
if (sqlErrorCodes.getBadSqlGrammarCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new BadSqlGrammarException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getInvalidResultSetAccessCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new InvalidResultSetAccessException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getDuplicateKeyCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new DuplicateKeyException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getDataIntegrityViolationCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new DataIntegrityViolationException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getPermissionDeniedCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new PermissionDeniedDataAccessException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getDataAccessResourceFailureCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new DataAccessResourceFailureException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getTransientDataAccessResourceCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new TransientDataAccessResourceException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getCannotAcquireLockCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new CannotAcquireLockException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getDeadlockLoserCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new DeadlockLoserDataAccessException(buildMessage(tmpSql, sqlEx), sqlEx);
} else if (sqlErrorCodes.getCannotSerializeTransactionCodes().contains(errorCode)) {
logTranslation(tmpSql, sqlEx);
return new CannotSerializeTransactionException(buildMessage(tmpSql, sqlEx), sqlEx);
}
} // end if (errorCode)
// try SQLState
if (sqlState != null && sqlState.length() >= 2) {
String classCode = sqlState.substring(0, 2);
if (sqlStateCodes.getBadSqlGrammarCodes().contains(classCode)) {
return new BadSqlGrammarException(buildMessage(tmpSql, sqlEx), ex);
} else if (sqlStateCodes.getDataIntegrityViolationCodes().contains(classCode)) {
return new DataIntegrityViolationException(buildMessage(tmpSql, ex), ex);
} else if (sqlStateCodes.getDataAccessResourceFailureCodes().contains(classCode)) {
return new DataAccessResourceFailureException(buildMessage(tmpSql, ex), ex);
} else if (sqlStateCodes.getTransientDataAccessResourceCodes().contains(classCode)) {
return new TransientDataAccessResourceException(buildMessage(tmpSql, ex), ex);
} else if (sqlStateCodes.getConcurrencyFailureCodes().contains(classCode)) {
return new ConcurrencyFailureException(buildMessage(tmpSql, ex), ex);
}
}
// For MySQL: exception class name indicating a timeout?
// (since MySQL doesn't throw the JDBC 4 SQLTimeoutException)
if (ex.getClass().getName().contains("Timeout")) {
return new QueryTimeoutException(buildMessage(tmpSql, ex), ex);
}
// We couldn't identify it more precisely
if (LOG.isDebugEnabled()) {
String codes;
if (sqlErrorCodes.isUseSqlStateForTranslation()) {
codes = new StringBuilder(60).append("SQL state '").append(sqlEx.getSQLState())
.append("', error code '").append(sqlEx.getErrorCode()).toString();
} else {
codes = "Error code '" + sqlEx.getErrorCode() + "'";
}
LOG.debug("Unable to translate SQLException with " + codes);
}
return new UncategorizedSqlException(buildMessage(tmpSql, sqlEx), sqlEx);
} // method translate
private void logTranslation(final String sql, final SQLException sqlEx) {
if (!LOG.isDebugEnabled()) {
return;
}
LOG.debug(
"Translating SQLException: SQL state '{}', error code '{}', message [{}]; SQL was [{}]",
sqlEx.getSQLState(), sqlEx.getErrorCode(), sqlEx.getMessage(), sql);
}
private String buildMessage(final String sql, final SQLException ex) {
String msg = ex.getMessage();
StringBuilder sb = new StringBuilder(msg.length() + sql.length() + 8);
return sb.append("SQL [").append(sql).append("]; ").append(ex.getMessage()).toString();
}
private void executeUpdate(Connection conn, String sql) throws DataAccessException {
Statement stmt = null;
try {
stmt = (conn != null) ? conn.createStatement() : getConnection().createStatement();
stmt.executeUpdate(sql);
} catch (SQLException ex) {
throw translate(sql, ex);
} finally {
if (conn == null) {
releaseResources(stmt, null);
} else {
releaseStatementAndResultSet(stmt, null);
}
}
}
static DataSourceWrapper createDataSource(final String name, final Properties props,
final DatabaseType databaseType) {
ParamUtil.requireNonNull("props", props);
ParamUtil.requireNonNull("databaseType", databaseType);
// The DB2 schema name is case-sensitive, and must be specified in uppercase characters
String datasourceClassName = props.getProperty("dataSourceClassName");
if (datasourceClassName != null) {
if (datasourceClassName.contains(".db2.")) {
String propName = "dataSource.currentSchema";
String schema = props.getProperty(propName);
if (schema != null) {
String upperCaseSchema = schema.toUpperCase();
if (!schema.equals(upperCaseSchema)) {
props.setProperty(propName, upperCaseSchema);
}
}
}
} else {
String propName = "jdbcUrl";
final String url = props.getProperty(propName);
if (StringUtil.startsWithIgnoreCase(url, "jdbc:db2:")) {
String sep = ":currentSchema=";
int idx = url.indexOf(sep);
if (idx != 1) {
String schema = url.substring(idx + sep.length());
if (schema.endsWith(";")) {
schema = schema.substring(0, schema.length() - 1);
}
String upperCaseSchema = schema.toUpperCase();
if (!schema.equals(upperCaseSchema)) {
String newUrl = url.replace(sep + schema, sep + upperCaseSchema);
props.setProperty(propName, newUrl);
}
}
}
} // end if
if (databaseType == DatabaseType.DB2 || databaseType == DatabaseType.H2
|| databaseType == DatabaseType.HSQL || databaseType == DatabaseType.MYSQL
|| databaseType == DatabaseType.MARIADB || databaseType == DatabaseType.ORACLE
|| databaseType == DatabaseType.POSTGRES) {
HikariConfig conf = new HikariConfig(props);
HikariDataSource service = new HikariDataSource(conf);
switch (databaseType) {
case DB2:
return new DB2(name, service);
case H2:
return new H2(name, service);
case HSQL:
return new HSQL(name, service);
case MYSQL:
return new MySQL(name, service);
case MARIADB:
return new MariaDB(name, service);
case ORACLE:
return new Oracle(name, service);
default: // POSTGRESQL:
return new PostgreSQL(name, service);
}
} else {
throw new IllegalArgumentException("unknown datasource type " + databaseType);
}
} // method createDataSource
}