All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.speedment.runtime.core.internal.db.AbstractDbmsOperationHandler Maven / Gradle / Ivy

/**
 *
 * 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.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.StreamSupplierComponent;
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.component.sql.SqlStreamSupplierComponentImpl;
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.Objects;
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 Logger LOGGER = LoggerManager.getLogger(AbstractDbmsOperationHandler.class);
    protected static final Logger LOGGER_PERSIST = LoggerManager.getLogger(LogType.PERSIST.getLoggerName());
    protected static final Logger LOGGER_UPDATE = LoggerManager.getLogger(LogType.UPDATE.getLoggerName());
    protected static final Logger LOGGER_REMOVE = LoggerManager.getLogger(LogType.REMOVE.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 @Inject ConnectionPoolComponent connectionPoolComponent;
    private @Inject DbmsHandlerComponent dbmsHandlerComponent;
    private @Inject TransactionComponent transactionComponent;

    protected AbstractDbmsOperationHandler() {
        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 {
            try {
                connectionInfo.close();
            } catch (SQLException sqle) {
                throw new SpeedmentException(sqle);
            }
        }
    }

    @Override
    public  AsynchronousQueryResult executeQueryAsync(
        final Dbms dbms,
        final String sql,
        final List values,
        final SqlFunction rsMapper,
        final ParallelStrategy parallelStrategy
    ) {
        assertNotClosed();
        return new AsynchronousQueryResultImpl<>(
            Objects.requireNonNull(sql),
            Objects.requireNonNull(values),
            Objects.requireNonNull(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));
    }

    protected 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,
        Connection conn,
        final List sqlStatementList
    ) throws SQLException {
        requireNonNull(dbms);
        requireNonNull(conn);
        requireNonNull(sqlStatementList);

        assertNotClosed();
        int retryCount = 5;
        boolean transactionCompleted = false;

        do {
            final AtomicReference lastSqlStatement = new AtomicReference<>();
            try {
                conn.setAutoCommit(false);
                executeSqlStatementList(sqlStatementList, lastSqlStatement, dbms, conn);
                conn.commit();
                conn.close();
                transactionCompleted = true;
                conn = null;
            } catch (SQLException sqlEx) {
                LOGGER.error("SqlStatementList: " + sqlStatementList);
                LOGGER.error("SQL: " + lastSqlStatement.get());
                LOGGER.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, and conn is not null, the
                        // transaction should be rolled back, as not
                        // all work has been done
                        try {
                            conn.rollback();
                        } finally {
                            conn.close();
                        }

                    } catch (SQLException sqlEx) {
                        //
                        // If we got an exception here, something
                        // pretty serious is going on, so we better
                        // pass it up the stack, rather than just
                        // logging it. . .
                        LOGGER.error(sqlEx, "Rollback error! connection:" + sqlEx.getMessage());
                        throw sqlEx;
                    }
                }
            }
        } 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<>();
        try {
            executeSqlStatementList(sqlStatementList, lastSqlStatement, dbms, conn);
            postSuccessfulTransaction(sqlStatementList);
        } catch (SQLException sqlEx) {
            LOGGER.error("SqlStatementList: " + sqlStatementList);
            LOGGER.error("SQL: " + lastSqlStatement);
            LOGGER.error(sqlEx, sqlEx.getMessage());
            throw sqlEx;
        }
    }

    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."
            );
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy