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

com.landawn.abacus.util.SQLExecutor Maven / Gradle / Ivy

/*
 * Copyright (C) 2015 HaiYang Li
 *
 * 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.landawn.abacus.util;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import com.landawn.abacus.DataSet;
import com.landawn.abacus.DataSource;
import com.landawn.abacus.DataSourceManager;
import com.landawn.abacus.DataSourceSelector;
import com.landawn.abacus.DirtyMarker;
import com.landawn.abacus.IsolationLevel;
import com.landawn.abacus.annotation.Beta;
import com.landawn.abacus.annotation.Id;
import com.landawn.abacus.annotation.ReadOnlyId;
import com.landawn.abacus.condition.And;
import com.landawn.abacus.condition.Condition;
import com.landawn.abacus.condition.ConditionFactory.L;
import com.landawn.abacus.condition.Equal;
import com.landawn.abacus.dataSource.SQLDataSource;
import com.landawn.abacus.exception.AbacusException;
import com.landawn.abacus.exception.DuplicatedResultException;
import com.landawn.abacus.exception.UncheckedSQLException;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.Fn.FN;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.JdbcUtil.BiRecordGetter;
import com.landawn.abacus.util.JdbcUtil.PreparedQuery;
import com.landawn.abacus.util.SQLBuilder.NE;
import com.landawn.abacus.util.SQLBuilder.NE2;
import com.landawn.abacus.util.SQLBuilder.NE3;
import com.landawn.abacus.util.SQLBuilder.SP;
import com.landawn.abacus.util.StringUtil.Strings;
import com.landawn.abacus.util.u.Nullable;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.u.OptionalBoolean;
import com.landawn.abacus.util.u.OptionalByte;
import com.landawn.abacus.util.u.OptionalChar;
import com.landawn.abacus.util.u.OptionalDouble;
import com.landawn.abacus.util.u.OptionalFloat;
import com.landawn.abacus.util.u.OptionalInt;
import com.landawn.abacus.util.u.OptionalLong;
import com.landawn.abacus.util.u.OptionalShort;
import com.landawn.abacus.util.function.BiConsumer;
import com.landawn.abacus.util.function.Function;
import com.landawn.abacus.util.function.Supplier;
import com.landawn.abacus.util.stream.Collector;
import com.landawn.abacus.util.stream.ObjIteratorEx;
import com.landawn.abacus.util.stream.Stream;

/**
 * SQLExecutor is a simple sql/jdbc utility class. SQL is supported with different format: 
* *
 * 
 * 
  • INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (?, ?, ?, ?, ?)
  • *
  • INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (#{firstName}, #{lastName}, #{gui}, #{lastUpdateTime}, #{createTime})
  • *
  • INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (:firstName, :lastName, :gui, :lastUpdateTime, :createTime)
  • * * All these kinds of SQLs can be generated by SQLBuilder conveniently. Parameters with format of Array/List parameters are supported for parameterized sql with '?'. * Parameters with format of Array/List/Map/Entity are supported for parameterized SQL with named parameters. *
    * * Here is sample of CRUD(create/read/update/delete): *
    ======================================================================== *
     * 
            static final DataSource dataSource = JdbcUtil.createDataSource(...);
            static final SQLExecutor sqlExecutor = new SQLExecutor(dataSource);
            ...
            Account account = createAccount();
    
            // create
            String sql_insert = NE.insert(GUI, FIRST_NAME, LAST_NAME, LAST_UPDATE_TIME, CREATE_TIME).into(Account.class).sql();
            N.println(sql_insert);
            sqlExecutor.insert(sql_insert, account);
    
            // read
            String sql_selectByGUI = NE.selectFrom(Account.class, N.asSet(DEVICES)).where(L.eq(GUI, L.QME)).sql();
            N.println(sql_selectByGUI);
            Account dbAccount = sqlExecutor.findFirst(Account.class, sql_selectByGUI, account);
            assertEquals(account.getFirstName(), dbAccount.getFirstName());
    
            // update
            String sql_updateByLastName = NE.update(Account.class).set(FIRST_NAME).where(L.eq(LAST_NAME, L.QME)).sql();
            N.println(sql_updateByLastName);
            dbAccount.setFirstName("newFirstName");
            sqlExecutor.update(sql_updateByLastName, dbAccount);
    
            // delete
            String sql_deleteByFirstName = NE.deleteFrom(Account.class).where(L.eq(FIRST_NAME, L.QME)).sql();
            N.println(sql_deleteByFirstName);
            sqlExecutor.update(sql_deleteByFirstName, dbAccount);
    
            dbAccount = sqlExecutor.findFirst(Account.class, sql_selectByGUI, account);
            assertNull(dbAccount);
     * 
     * 
    * ======================================================================== *
    *
    * If {@code conn} argument is null or not specified, {@code SQLExecutor} is responsible to get the connection from the * internal {@code DataSource}, start and commit/roll back transaction for batch operations if needed, and close the * connection finally. otherwise it's user's responsibility to do such jobs if {@code conn} is specified and not null.
    *
    * * The general programming way with SQLExeucte is to execute sql scripts(generated by SQLBuilder) with array/list/map/entity by calling (batch)insert/update/delete/query/... methods. * if Transaction is required. it can be started: *
     * 
     *      final SQLTransaction tran = sqlExecutor.beginTransaction(IsolationLevel.READ_COMMITTED);
            boolean noException = false;
            try {
                // sqlExecutor.insert(...);
                // sqlExecutor.update(...);
                // sqlExecutor.query(...);
    
                noException = true;
            } finally {
                // The connection will be automatically closed after the transaction is committed or rolled back.            
                if (noException) {
                    tran.commit();
                } else {
                    tran.rollback();
                }
            }
     * 
     * 
    * * SQLExecutor is tread-safe.

    * * @since 0.8 * * @author Haiyang Li * * @see JdbcUtil * * @see {@link com.landawn.abacus.annotation.ReadOnly} * @see {@link com.landawn.abacus.annotation.ReadOnlyId} * @see {@link com.landawn.abacus.annotation.NonUpdatable} * @see {@link com.landawn.abacus.annotation.Transient} * @see {@link com.landawn.abacus.annotation.Table} * @see {@link com.landawn.abacus.annotation.Column} * * @see http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html * @see http://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html * @see http://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html * @see http://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html */ public class SQLExecutor implements Closeable { private static final Logger logger = LoggerFactory.getLogger(SQLExecutor.class); static final String ID = "id"; static final String QUERY_WITH_DATA_SOURCE = "queryWithDataSource"; private static final ResultExtractor EXISTS_RESULT_SET_EXTRACTOR = new ResultExtractor() { @Override public Boolean extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); return rs.next(); } }; private static final ResultExtractor COUNT_RESULT_SET_EXTRACTOR = new ResultExtractor() { @Override public Integer extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); int cnt = 0; while (rs.next()) { cnt++; } return cnt; } }; private static final ResultExtractor SINGLE_BOOLEAN_EXTRACTOR = new ResultExtractor() { @Override public OptionalBoolean extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalBoolean.of(rs.getBoolean(1)); } return OptionalBoolean.empty(); } }; private static final ResultExtractor SINGLE_CHAR_EXTRACTOR = new ResultExtractor() { @Override public OptionalChar extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { final String str = rs.getString(1); return OptionalChar.of(str == null || str.length() == 0 ? N.CHAR_0 : str.charAt(0)); } return OptionalChar.empty(); } }; private static final ResultExtractor SINGLE_BYTE_EXTRACTOR = new ResultExtractor() { @Override public OptionalByte extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalByte.of(rs.getByte(1)); } return OptionalByte.empty(); } }; private static final ResultExtractor SINGLE_SHORT_EXTRACTOR = new ResultExtractor() { @Override public OptionalShort extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalShort.of(rs.getShort(1)); } return OptionalShort.empty(); } }; private static final ResultExtractor SINGLE_INT_EXTRACTOR = new ResultExtractor() { @Override public OptionalInt extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalInt.of(rs.getInt(1)); } return OptionalInt.empty(); } }; private static final ResultExtractor SINGLE_LONG_EXTRACTOR = new ResultExtractor() { @Override public OptionalLong extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalLong.of(rs.getLong(1)); } return OptionalLong.empty(); } }; private static final ResultExtractor SINGLE_FLOAT_EXTRACTOR = new ResultExtractor() { @Override public OptionalFloat extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalFloat.of(rs.getFloat(1)); } return OptionalFloat.empty(); } }; private static final ResultExtractor SINGLE_DOUBLE_EXTRACTOR = new ResultExtractor() { @Override public OptionalDouble extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return OptionalDouble.of(rs.getDouble(1)); } return OptionalDouble.empty(); } }; private static final ResultExtractor> SINGLE_BIG_DECIMAL_EXTRACTOR = new ResultExtractor>() { @Override public Nullable extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return Nullable.of(rs.getBigDecimal(1)); } return Nullable.empty(); } }; private static final ResultExtractor> SINGLE_STRING_EXTRACTOR = new ResultExtractor>() { @Override public Nullable extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return Nullable.of(rs.getString(1)); } return Nullable.empty(); } }; private static final ResultExtractor> SINGLE_DATE_EXTRACTOR = new ResultExtractor>() { @Override public Nullable extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException { JdbcUtil.skip(rs, jdbcSettings.getOffset()); if (rs.next()) { return Nullable.of(rs.getDate(1)); } return Nullable.empty(); } }; private static final ResultExtractor> SINGLE_TIME_EXTRACTOR = new ResultExtractor>() { @Override public Nullable