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

cn.icuter.jsql.executor.DefaultJdbcExecutor Maven / Gradle / Ivy

The newest version!
package cn.icuter.jsql.executor;

import cn.icuter.jsql.builder.Builder;
import cn.icuter.jsql.builder.BuilderContext;
import cn.icuter.jsql.data.JSQLBlob;
import cn.icuter.jsql.data.JSQLClob;
import cn.icuter.jsql.data.JSQLNClob;
import cn.icuter.jsql.dialect.Dialect;
import cn.icuter.jsql.exception.ExecutionException;
import cn.icuter.jsql.exception.JSQLException;
import cn.icuter.jsql.log.JSQLLogger;
import cn.icuter.jsql.log.Logs;
import cn.icuter.jsql.orm.ORMapper;
import cn.icuter.jsql.util.ObjectUtil;

import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author edward
 * @since 2018-08-20
 */
public class DefaultJdbcExecutor implements JdbcExecutor {
    private static final JSQLLogger LOGGER = Logs.getLogger(DefaultJdbcExecutor.class);

    protected final Connection connection;

    public DefaultJdbcExecutor(Connection connection) {
        this.connection = connection;
    }

    @Override
    public int execUpdate(Builder builder) throws JSQLException {
        checkAndBuild(builder);
        LOGGER.info("executing sql: " + builder.getSql());
        LOGGER.debug("executing values: " + builder.getPreparedValues());
        PreparedStatement ps = null;
        try {
            try {
                ps = connection.prepareStatement(builder.getSql());
                setPreparedStatementValues(ps, builder);
                return ps.executeUpdate();
            } finally {
                if (ps != null) {
                    ps.close();
                }
            }
        } catch (SQLException e) {
            LOGGER.error("executing update error builder detail: " + builder, e);
            throw new ExecutionException("executing update error builder detail: " + builder, e);
        }
    }

    private void setPreparedStatementValues(PreparedStatement ps, Builder builder) throws SQLException {
        Dialect dialect = builder.getBuilderContext().getDialect();
        List preparedValues = builder.getPreparedValues();
        for (int i = 0, len = preparedValues.size(); i < len; i++) {
            Object value = preparedValues.get(i);
            int paramIndex = i + 1;
            if (JSQLNClob.class.isAssignableFrom(value.getClass())) {
                if (dialect.supportNClob()) {
                    ps.setNClob(paramIndex, ((JSQLNClob) value).copyTo(connection.createNClob()));
                } else {
                    // usually, if driver do not support NClob, would do not support NString operation as well
                    // NString operation like getNString or setNString
                    ps.setString(paramIndex, ((JSQLNClob) value).getNClobString());
                }
            } else if (JSQLClob.class.isAssignableFrom(value.getClass())) {
                if (dialect.supportClob()) {
                    ps.setClob(paramIndex, ((JSQLClob) value).copyTo(connection.createClob()));
                } else {
                    ps.setString(paramIndex, ((JSQLClob) value).getClobString());
                }
            } else if (JSQLBlob.class.isAssignableFrom(value.getClass())) {
                if (dialect.supportBlob()) {
                    ps.setBlob(paramIndex, ((JSQLBlob) value).copyTo(connection.createBlob()));
                } else {
                    ps.setBytes(paramIndex, ((JSQLBlob) value).getBlobBytes());
                }
            } else {
                ps.setObject(paramIndex, value);
            }
        }
    }

    @Override
    public  List execQuery(final Builder builder, final Class clazz) throws JSQLException {
        return doExecQuery(builder, new QueryExecutor>() {
            @Override
            public List doExec(ResultSet rs, ResultSetMetaData meta) throws Exception {
                Dialect dialect = builder.getBuilderContext().getDialect();
                int fetchSize = rs.getFetchSize();
                boolean hasLimit = !dialect.supportOffsetLimit() && fetchSize > 0;
                Map colIndexFieldMap = mapColumnFieldAndIndex(clazz, meta);
                List queriedResult = new LinkedList();
                while (rs.next()) {
                    T record = clazz.newInstance();
                    for (Map.Entry entry : colIndexFieldMap.entrySet()) {
                        Field field = entry.getKey();
                        int rsIndex = entry.getValue();
                        field.setAccessible(true);
                        field.set(record, getValueByType(field.getType(), rs, rsIndex));
                    }
                    queriedResult.add(record);
                    if (hasLimit && --fetchSize <= 0) {
                        break;
                    }
                }
                return queriedResult;
            }
        });
    }

    private Object getValueByType(Class type, ResultSet rs, int rsIndex) throws SQLException {
        if (type.isPrimitive()) {
            if (type == Boolean.TYPE) {
                return rs.getBoolean(rsIndex);
            } else if (type == Byte.TYPE) {
                return rs.getByte(rsIndex);
            } else if (type == Short.TYPE) {
                return rs.getShort(rsIndex);
            } else if (type == Integer.TYPE) {
                return rs.getInt(rsIndex);
            } else if (type == Long.TYPE) {
                return rs.getLong(rsIndex);
            } else if (type == Float.TYPE) {
                return rs.getFloat(rsIndex);
            } else if (type == Double.TYPE) {
                return rs.getDouble(rsIndex);
            } else {
                return rs.getObject(rsIndex);
            }
        } else if (Blob.class.isAssignableFrom(type)) {
            return new JSQLBlob(rs.getBytes(rsIndex));
        } else if (NClob.class.isAssignableFrom(type)) {
            return new JSQLNClob(rs.getNString(rsIndex));
        } else if (Clob.class.isAssignableFrom(type)) {
            return new JSQLClob(rs.getString(rsIndex));
        } else if (ObjectUtil.isByteArray(type)) {
            return rs.getBytes(rsIndex);
        } else if (Boolean.class.isAssignableFrom(type)) {
            return rs.getBoolean(rsIndex);
        } else if (Byte.class.isAssignableFrom(type)) {
            return rs.getByte(rsIndex);
        } else if (Short.class.isAssignableFrom(type)) {
            return rs.getShort(rsIndex);
        } else if (Integer.class.isAssignableFrom(type)) {
            return rs.getInt(rsIndex);
        } else if (Long.class.isAssignableFrom(type)) {
            return rs.getLong(rsIndex);
        } else if (Float.class.isAssignableFrom(type)) {
            return rs.getFloat(rsIndex);
        } else if (Double.class.isAssignableFrom(type)) {
            return rs.getDouble(rsIndex);
        } else if (String.class.isAssignableFrom(type)) {
            return rs.getString(rsIndex);
        } else if (BigDecimal.class.isAssignableFrom(type)) {
            return rs.getBigDecimal(rsIndex);
        } else {
            return rs.getObject(rsIndex);
        }
    }

    @Override
    public List> execQuery(final Builder builder) throws JSQLException {
        return doExecQuery(builder, new QueryExecutor>>() {
            @Override
            public List> doExec(ResultSet rs, ResultSetMetaData meta) throws Exception {
                Dialect dialect = builder.getBuilderContext().getDialect();
                int fetchSize = rs.getFetchSize();
                boolean hasLimit = !dialect.supportOffsetLimit() && fetchSize > 0;
                List> result = new LinkedList>();
                while (rs.next()) {
                    Map record = new LinkedHashMap();
                    for (int i = 1; i <= meta.getColumnCount(); i++) {
                        String colName = meta.getColumnLabel(i).toLowerCase();
                        if (colName.startsWith("rownumber_")) {
                            continue;
                        }
                        record.put(colName, rs.getObject(colName));
                    }
                    result.add(record);
                    if (hasLimit && --fetchSize <= 0) {
                        break;
                    }
                }
                return result;
            }
        });
    }

    private  T doExecQuery(Builder builder, QueryExecutor queryExecutor) throws JSQLException {

        checkAndBuild(builder);

        LOGGER.info("executing query sql: " + builder.getSql());
        LOGGER.debug("executing query values: " + builder.getPreparedValues());
        PreparedStatement ps = null;
        ResultSet rs = null;
        BuilderContext builderContext = builder.getBuilderContext();
        try {
            if (!builderContext.getDialect().supportOffsetLimit() && builderContext.getOffset() > 0) {
                ps = connection.prepareStatement(builder.getSql(),
                        ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
            } else {
                ps = connection.prepareStatement(builder.getSql());
            }
            List preparedValues = builder.getPreparedValues();
            for (int i = 0, len = preparedValues.size(); i < len; i++) {
                ps.setObject(i + 1, preparedValues.get(i));
            }
            rs = ps.executeQuery();
            ResultSetMetaData meta = rs.getMetaData();

            if (!builderContext.getDialect().supportOffsetLimit()) {
                // like sql paging, must set both offset and limit or limit only
                int offset = builderContext.getOffset();
                if (offset > 0 && builderContext.getLimit() > 0) {
                    rs.absolute(offset);
                }
                if (builderContext.getLimit() > 0) {
                    rs.setFetchSize(builderContext.getLimit());
                }
            }
            return queryExecutor.doExec(rs, meta);
        } catch (Exception e) {
            LOGGER.error("executing query error, builder detail: " + builder, e);
            throw new ExecutionException("executing query error, builder detail: " + builder, e);
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    LOGGER.error("closing PreparedStatement error, builder detail: " + builder, e);
                }
            }
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    LOGGER.error("closing ResultSet error, builder detail: " + builder, e);
                }
            }
        }
    }

    @Override
    public void execBatch(List builders) throws JSQLException {
        Map> builderGroup = new LinkedHashMap>();
        for (Builder builder : builders) {
            List builderList = builderGroup.get(builder.getSql());
            if (builderList == null) {
                builderList = new LinkedList();
                builderList.add(builder);
                builderGroup.put(builder.getSql(), builderList);
            } else {
                builderList.add(builder);
            }
        }
        final List sqlList = new LinkedList();
        for (Builder builder : builders) {
            sqlList.add(builder.getSql());
        }
        Map> builderOrderGroup = new TreeMap>(new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                int first = sqlList.indexOf(o1);
                int second = sqlList.indexOf(o2);
                return first - second;
            }
        });
        builderOrderGroup.putAll(builderGroup);

        for (Map.Entry> entry : builderOrderGroup.entrySet()) {
            execBatch(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public void close() throws IOException {
        // noop
    }

    private void execBatch(String sql, List builderList) throws JSQLException {
        LOGGER.info("executing batch sql: " + sql);
        PreparedStatement ps = null;
        try {
            try {
                ps = connection.prepareStatement(sql);
                for (Builder builder : builderList) {
                    checkAndBuild(builder);

                    LOGGER.debug("executing batch values: " + builder.getPreparedValues());

                    setPreparedStatementValues(ps, builder);
                    ps.addBatch();
                }
                ps.executeBatch();
            } finally {
                if (ps != null) {
                    ps.close();
                }
            }
        } catch (SQLException e) {
            List values = new LinkedList();
            for (Builder builder : builderList) {
                values.add(builder.getPreparedValues());
            }
            LOGGER.error("executing batch update error, batch sql: " + sql
                    + ", batch values list: \n" + values, e);
            throw new ExecutionException("executing batch update error, batch sql: " + sql
                    + ", batch values list: \n" + values, e);
        }
    }

    private Map mapColumnFieldAndIndex(Class clazz, ResultSetMetaData meta) throws SQLException {
        int colLen = meta.getColumnCount();
        final List returnColumnList = new ArrayList(colLen);
        for (int i = 0; i < colLen; i++) {
            returnColumnList.add(meta.getColumnLabel(i + 1));
        }
        final Map colFieldMap = new LinkedHashMap(returnColumnList.size());
        ORMapper.mapColumn(clazz, new ORMapper.DBColumnMapper() {
            @Override
            public void map(String col, Field field) {
                for (int i = 0; i < returnColumnList.size(); i++) {
                    String retColName = returnColumnList.get(i);
                    int colIdx = -1;
                    if (retColName.equalsIgnoreCase(col)) {
                        colIdx = i;
                    }
                    if (colIdx >= 0) {
                        colFieldMap.put(field, colIdx + 1);
                        break;
                    }
                }
            }
        });
        return colFieldMap;
    }

    private void checkAndBuild(Builder builder) {
        if (!builder.getBuilderContext().hasBuilt()) {
            builder.build();
        }
    }

    interface QueryExecutor {
        T doExec(ResultSet rs, ResultSetMetaData meta) throws Exception;
    }
}