com.speedment.runtime.core.internal.db.AbstractDbmsOperationHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tool-deploy Show documentation
Show all versions of tool-deploy Show documentation
A Speedment bundle that shades all dependencies into one jar. This is
useful when deploying an application on a server.
The newest version!
/*
*
* Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
*
* 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 com.speedment.runtime.core.internal.db;
import com.speedment.common.injector.State;
import com.speedment.common.injector.annotation.ExecuteBefore;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.common.injector.annotation.InjectOrNull;
import com.speedment.common.logger.Logger;
import com.speedment.common.logger.LoggerManager;
import com.speedment.runtime.config.Dbms;
import com.speedment.runtime.core.ApplicationBuilder.LogType;
import com.speedment.runtime.core.component.DbmsHandlerComponent;
import com.speedment.runtime.core.component.connectionpool.ConnectionPoolComponent;
import com.speedment.runtime.core.component.transaction.TransactionComponent;
import com.speedment.runtime.core.db.AsynchronousQueryResult;
import com.speedment.runtime.core.db.DbmsOperationHandler;
import com.speedment.runtime.core.db.SqlFunction;
import com.speedment.runtime.core.exception.SpeedmentException;
import com.speedment.runtime.core.internal.manager.sql.SqlDeleteStatement;
import com.speedment.runtime.core.internal.manager.sql.SqlInsertStatement;
import com.speedment.runtime.core.internal.manager.sql.SqlUpdateStatement;
import com.speedment.runtime.core.manager.sql.SqlStatement;
import com.speedment.runtime.core.stream.parallel.ParallelStrategy;
import com.speedment.runtime.field.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
import java.util.stream.Stream;
import static com.speedment.common.invariant.NullUtil.requireNonNulls;
import static com.speedment.runtime.core.util.DatabaseUtil.dbmsTypeOf;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
/**
* Abstract base class for {@link DbmsOperationHandler}-implementations.
*
* @author Per Minborg
* @author Emil Forslund
*/
public abstract class AbstractDbmsOperationHandler implements DbmsOperationHandler {
private static final int INITIAL_RETRY_COUNT = 5;
private static final Logger LOGGER = LoggerManager.getLogger(AbstractDbmsOperationHandler.class);
private static final Logger LOGGER_PERSIST = LoggerManager.getLogger(LogType.PERSIST.getLoggerName());
private static final Logger LOGGER_UPDATE = LoggerManager.getLogger(LogType.UPDATE.getLoggerName());
private static final Logger LOGGER_REMOVE = LoggerManager.getLogger(LogType.REMOVE.getLoggerName());
private static final Logger LOGGER_SQL_RETRY = LoggerManager.getLogger(LogType.SQL_RETRY.getLoggerName());
public static final boolean SHOW_METADATA = false; // Warning: Enabling SHOW_METADATA will make some dbmses fail on metadata (notably Oracle) because all the columns must be read in order...
private final AtomicBoolean closed;
private final ConnectionPoolComponent connectionPoolComponent;
private final DbmsHandlerComponent dbmsHandlerComponent;
private final TransactionComponent transactionComponent;
// @Inject private ConnectionPoolComponent connectionPoolComponent;
// @Inject private DbmsHandlerComponent dbmsHandlerComponent;
// @InjectOrNull private TransactionComponent transactionComponent;
protected AbstractDbmsOperationHandler(
final ConnectionPoolComponent connectionPoolComponent,
final DbmsHandlerComponent dbmsHandlerComponent,
final TransactionComponent transactionComponent
) {
this.connectionPoolComponent = requireNonNull(connectionPoolComponent);
this.dbmsHandlerComponent = requireNonNull(dbmsHandlerComponent);
this.transactionComponent = requireNonNull(transactionComponent);
closed = new AtomicBoolean();
}
@Override
public Stream executeQuery(Dbms dbms, String sql, List values, SqlFunction rsMapper) {
requireNonNulls(sql, values, rsMapper);
assertNotClosed();
final ConnectionInfo connectionInfo = new ConnectionInfo(dbms, connectionPoolComponent, transactionComponent);
try (
final PreparedStatement ps = connectionInfo.connection().prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY)) {
configureSelect(ps);
connectionInfo.ifNotInTransaction(c -> c.setAutoCommit(false));
try {
int i = 1;
for (final Object o : values) {
ps.setObject(i++, o);
}
try (final ResultSet rs = ps.executeQuery()) {
configureSelect(rs);
// Todo: Make a transparent stream with closeHandler added.
final Stream.Builder streamBuilder = Stream.builder();
while (rs.next()) {
streamBuilder.add(rsMapper.apply(rs));
}
return streamBuilder.build();
}
} finally {
connectionInfo.ifNotInTransaction(Connection::commit);
}
} catch (final SQLException sqle) {
LOGGER.error(sqle, "Error querying " + sql);
throw new SpeedmentException(sqle);
} finally {
closeQuietly(connectionInfo::close);
}
}
@Override
public AsynchronousQueryResult executeQueryAsync(
final Dbms dbms,
final String sql,
final List values,
final SqlFunction rsMapper,
final ParallelStrategy parallelStrategy
) {
assertNotClosed();
return new AsynchronousQueryResultImpl<>(
sql,
values,
rsMapper,
() -> new ConnectionInfo(dbms, connectionPoolComponent, transactionComponent),
parallelStrategy,
this::configureSelect,
this::configureSelect
);
}
@Override
public void executeInsert(Dbms dbms, String sql, List values, Collection> generatedKeyFields, Consumer> generatedKeyConsumer) throws SQLException {
logOperation(LOGGER_PERSIST, sql, values);
final SqlInsertStatement sqlUpdateStatement = new SqlInsertStatement(sql, values, new ArrayList<>(generatedKeyFields), generatedKeyConsumer);
execute(dbms, singletonList(sqlUpdateStatement));
}
@Override
public void executeUpdate(Dbms dbms, String sql, List values) throws SQLException {
logOperation(LOGGER_UPDATE, sql, values);
final SqlUpdateStatement sqlUpdateStatement = new SqlUpdateStatement(sql, values);
execute(dbms, singletonList(sqlUpdateStatement));
}
@Override
public void executeDelete(Dbms dbms, String sql, List values) throws SQLException {
logOperation(LOGGER_REMOVE, sql, values);
final SqlDeleteStatement sqlDeleteStatement = new SqlDeleteStatement(sql, values);
execute(dbms, singletonList(sqlDeleteStatement));
}
private void logOperation(Logger logger, final String sql, final List values) {
logger.debug("%s, values:%s", sql, values);
}
protected void execute(Dbms dbms, List sqlStatementList) throws SQLException {
final ConnectionInfo connectionInfo = new ConnectionInfo(dbms, connectionPoolComponent, transactionComponent);
if (connectionInfo.isInTransaction()) {
executeInTransaction(dbms, connectionInfo.connection(), sqlStatementList);
} else {
executeNotInTransaction(dbms, connectionInfo.connection(), sqlStatementList);
}
}
// Todo: Rewrite the method below.
protected void executeNotInTransaction(
final Dbms dbms,
final Connection conn,
final List sqlStatementList
) throws SQLException {
requireNonNull(dbms);
requireNonNull(conn);
requireNonNull(sqlStatementList);
assertNotClosed();
int retryCount = INITIAL_RETRY_COUNT;
boolean transactionCompleted = false;
do {
final AtomicReference lastSqlStatement = new AtomicReference<>();
try {
conn.setAutoCommit(false);
executeSqlStatementList(sqlStatementList, lastSqlStatement, dbms, conn);
conn.commit();
conn.close();
transactionCompleted = true;
} catch (SQLException sqlEx) {
if (retryCount < INITIAL_RETRY_COUNT) {
LOGGER_SQL_RETRY.error("SqlStatementList: " + sqlStatementList);
LOGGER_SQL_RETRY.error("SQL: " + lastSqlStatement.get());
LOGGER_SQL_RETRY.error(sqlEx, sqlEx.getMessage());
}
final String sqlState = sqlEx.getSQLState();
if ("08S01".equals(sqlState) || "40001".equals(sqlState)) {
retryCount--;
} else {
throw sqlEx; // Finally will be executed...
}
} finally {
if (!transactionCompleted) {
try {
// If we got here the transaction should be rolled back, as not
// all work has been done
conn.rollback();
} catch (SQLException sqlEx) {
// If we got an exception here, something
// pretty serious is going on
LOGGER.error(sqlEx, "Rollback error! connection:" + sqlEx.getMessage());
retryCount = 0;
} finally {
conn.close();
}
}
}
} while (!transactionCompleted && (retryCount > 0));
if (transactionCompleted) {
postSuccessfulTransaction(sqlStatementList);
}
}
protected void executeInTransaction(
final Dbms dbms,
final Connection conn,
final List sqlStatementList
) throws SQLException {
requireNonNull(dbms);
requireNonNull(conn);
requireNonNull(sqlStatementList);
assertNotClosed();
final AtomicReference lastSqlStatement = new AtomicReference<>();
executeSqlStatementList(sqlStatementList, lastSqlStatement, dbms, conn);
postSuccessfulTransaction(sqlStatementList);
}
private void executeSqlStatementList(List sqlStatementList, AtomicReference lastSqlStatement, Dbms dbms, Connection conn) throws SQLException {
assertNotClosed();
for (final SqlStatement sqlStatement : sqlStatementList) {
lastSqlStatement.set(sqlStatement);
switch (sqlStatement.getType()) {
case INSERT: {
final SqlInsertStatement s = (SqlInsertStatement) sqlStatement;
handleSqlStatement(dbms, conn, s);
break;
}
case UPDATE: {
final SqlUpdateStatement s = (SqlUpdateStatement) sqlStatement;
handleSqlStatement(dbms, conn, s);
break;
}
case DELETE: {
final SqlDeleteStatement s = (SqlDeleteStatement) sqlStatement;
handleSqlStatement(dbms, conn, s);
break;
}
}
}
}
protected void handleSqlStatement(Dbms dbms, Connection conn, SqlInsertStatement sqlStatement) throws SQLException {
assertNotClosed();
try (final PreparedStatement ps = conn.prepareStatement(sqlStatement.getSql(), Statement.RETURN_GENERATED_KEYS)) {
int i = 1;
for (Object o : sqlStatement.getValues()) {
ps.setObject(i++, o);
}
ps.executeUpdate();
handleGeneratedKeys(ps, sqlStatement::addGeneratedKey);
}
}
@Override
public void handleGeneratedKeys(PreparedStatement ps, LongConsumer longConsumer) throws SQLException {
try (final ResultSet generatedKeys = ps.getGeneratedKeys()) {
while (generatedKeys.next()) {
longConsumer.accept(generatedKeys.getLong(1));
//sqlStatement.addGeneratedKey(generatedKeys.getLong(1));
}
}
}
protected void handleSqlStatement(Dbms dbms, Connection conn, SqlUpdateStatement sqlStatement) throws SQLException {
handleSqlStatementHelper(conn, sqlStatement);
}
protected void handleSqlStatement(Dbms dbms, Connection conn, SqlDeleteStatement sqlStatement) throws SQLException {
handleSqlStatementHelper(conn, sqlStatement);
}
private void handleSqlStatementHelper(Connection conn, SqlStatement sqlStatement) throws SQLException {
assertNotClosed();
try (final PreparedStatement ps = conn.prepareStatement(sqlStatement.getSql(), Statement.NO_GENERATED_KEYS)) {
int i = 1;
for (Object o : sqlStatement.getValues()) {
ps.setObject(i++, o);
}
ps.executeUpdate();
}
}
protected void postSuccessfulTransaction(List sqlStatementList) {
sqlStatementList.stream()
.filter(SqlInsertStatement.class::isInstance)
.map(SqlInsertStatement.class::cast)
.forEach(SqlInsertStatement::notifyGeneratedKeyListener);
}
@FunctionalInterface
protected interface TableChildMutator {
void mutate(T t, U u) throws SQLException;
}
protected String encloseField(Dbms dbms, String fieldName) {
return dbmsTypeOf(dbmsHandlerComponent, dbms).getDatabaseNamingConvention().encloseField(fieldName);
}
@Override
public Clob createClob(Dbms dbms) throws SQLException {
return applyOnConnection(dbms, Connection::createClob);
}
@Override
public Blob createBlob(Dbms dbms) throws SQLException {
return applyOnConnection(dbms, Connection::createBlob);
}
@Override
public NClob createNClob(Dbms dbms) throws SQLException {
return applyOnConnection(dbms, Connection::createNClob);
}
@Override
public SQLXML createSQLXML(Dbms dbms) throws SQLException {
return applyOnConnection(dbms, Connection::createSQLXML);
}
@Override
public Array createArray(Dbms dbms, String typeName, Object[] elements) throws SQLException {
assertNotClosed();
try (final Connection connection = connectionPoolComponent.getConnection(dbms)) {
return connection.createArrayOf(typeName, elements);
}
}
@Override
public Struct createStruct(Dbms dbms, String typeName, Object[] attributes) throws SQLException {
assertNotClosed();
try (final Connection connection = connectionPoolComponent.getConnection(dbms)) {
return connection.createStruct(typeName, attributes);
}
}
private T applyOnConnection(Dbms dbms, SqlFunction mapper) throws SQLException {
assertNotClosed();
try (final Connection c = connectionPoolComponent.getConnection(dbms)) {
return mapper.apply(c);
}
}
@ExecuteBefore(State.STOPPED)
void close() {
closed.set(true);
}
private void assertNotClosed() {
if (closed.get()) {
throw new IllegalStateException(
"The " + DbmsOperationHandler.class.getSimpleName() + " " +
getClass().getSimpleName() + " has been closed."
);
}
}
protected interface ThrowingClosable {
void close() throws Exception;
}
protected void closeQuietly(ThrowingClosable closeable) {
try {
closeable.close();
} catch (Exception e) {
LOGGER.warn(e);
}
}
}