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

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

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

There is a newer version: 3.1.18
Show newest version
/**
 *
 * Copyright (c) 2006-2017, 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.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.component.DbmsHandlerComponent;
import com.speedment.runtime.core.component.connectionpool.ConnectionPoolComponent;
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.SqlStatement;
import com.speedment.runtime.core.internal.manager.sql.SqlUpdateStatement;
import com.speedment.runtime.core.stream.parallel.ParallelStrategy;
import com.speedment.runtime.field.Field;

import java.sql.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;

import static com.speedment.common.invariant.NullUtil.requireNonNulls;
import com.speedment.runtime.core.ApplicationBuilder.LogType;
import static com.speedment.runtime.core.util.DatabaseUtil.dbmsTypeOf;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;

/**
 *
 * @author Per Minborg
 * @author Emil Forslund
 */
public abstract class AbstractDbmsOperationHandler implements DbmsOperationHandler {

    private static final Logger LOGGER = LoggerManager.getLogger(AbstractDbmsOperationHandler.class);
//    public static final String LOGGER_INSERT_NAME = "#INSERT";
//    public static final String LOGGER_UPDATE_NAME = "#UPDATE";
//    public static final String LOGGER_DELETE_NAME = "#DELETE";
    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 @Inject ConnectionPoolComponent connectionPoolComponent;
    private @Inject DbmsHandlerComponent dbmsHandlerComponent;

    protected AbstractDbmsOperationHandler() {}

    @Override
    public  Stream executeQuery(Dbms dbms, String sql, List values, SqlFunction rsMapper) {
        requireNonNulls(sql, values, rsMapper);

        try (
                final Connection connection = connectionPoolComponent.getConnection(dbms);
                final PreparedStatement ps = connection.prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY)
        ) {
            configureSelect(ps);
            connection.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 {
                connection.commit();
            }
        } catch (final SQLException sqle) {
            LOGGER.error(sqle, "Error querying " + sql);
            throw new SpeedmentException(sqle);
        }
    }

    @Override
    public  AsynchronousQueryResult executeQueryAsync(
            Dbms dbms, 
            String sql, 
            List values, 
            SqlFunction rsMapper,
            ParallelStrategy parallelStrategy) {

        return new AsynchronousQueryResultImpl<>(
            Objects.requireNonNull(sql),
            Objects.requireNonNull(values),
            Objects.requireNonNull(rsMapper),
            () -> connectionPoolComponent.getConnection(dbms),
            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, 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 {
        requireNonNull(sqlStatementList);
        int retryCount = 5;
        boolean transactionCompleted = false;

        do {
            SqlStatement lastSqlStatement = null;
            Connection conn = null;
            try {
                conn = connectionPoolComponent.getConnection(dbms);
                conn.setAutoCommit(false);
                for (final SqlStatement sqlStatement : sqlStatementList) {
                    lastSqlStatement = 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;
                        }
                    }

                }
                conn.commit();
                conn.close();
                transactionCompleted = true;
                conn = null;
            } catch (SQLException sqlEx) {
                LOGGER.error("SqlStatementList: " + sqlStatementList);
                LOGGER.error("SQL: " + lastSqlStatement);
                LOGGER.error(sqlEx, sqlEx.getMessage());
                final String sqlState = sqlEx.getSQLState();

                if ("08S01".equals(sqlState) || "40001".equals(sqlState)) {
                    retryCount--;
                } else {
                    retryCount = 0;
                    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
                        if (conn != null) {
                            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 handleSqlStatement(Dbms dbms, Connection conn, SqlInsertStatement sqlStatement) throws SQLException {
        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();

            try (final ResultSet generatedKeys = ps.getGeneratedKeys()) {
                while (generatedKeys.next()) {
                    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 {
        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::acceptGeneratedKeys);
    }

    @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 {
        try (final Connection connection = connectionPoolComponent.getConnection(dbms)) {
            return connection.createArrayOf(typeName, elements);
        }
    }

    @Override
    public Struct createStruct(Dbms dbms, String typeName, Object[] attributes) throws SQLException {
        try (final Connection connection = connectionPoolComponent.getConnection(dbms)) {
            return connection.createStruct(typeName, attributes);
        }
    }

    private  T applyOnConnection(Dbms dbms, SqlFunction mapper) throws SQLException {
        try (final Connection c = connectionPoolComponent.getConnection(dbms)) {
            return mapper.apply(c);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy