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

org.jfaster.mango.jdbc.JdbcTemplate Maven / Gradle / Ivy

/*
 * Copyright 2014 mango.jfaster.org
 *
 * The Mango Project licenses this file to you 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 org.jfaster.mango.jdbc;

import org.jfaster.mango.binding.BoundSql;
import org.jfaster.mango.jdbc.exception.DataAccessException;
import org.jfaster.mango.jdbc.exception.DataRetrievalFailureException;
import org.jfaster.mango.mapper.RowMapper;
import org.jfaster.mango.transaction.DataSourceUtils;
import org.jfaster.mango.type.TypeHandler;
import org.jfaster.mango.util.local.CacheLoader;
import org.jfaster.mango.util.local.DoubleCheckCache;
import org.jfaster.mango.util.local.LoadingCache;
import org.jfaster.mango.util.logging.InternalLogger;
import org.jfaster.mango.util.logging.InternalLoggerFactory;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;


/**
 * @author ash
 */
public class JdbcTemplate implements JdbcOperations {

  private final static InternalLogger logger = InternalLoggerFactory.getInstance(JdbcTemplate.class);

  @Override
  public  T queryForObject(DataSource dataSource, BoundSql boundSql, RowMapper rowMapper)
      throws DataAccessException {

    return executeQuery(dataSource, boundSql, new ObjectResultSetExtractor(rowMapper));
  }

  @Override
  public  List queryForList(DataSource dataSource, BoundSql boundSql,
                                  ListSupplier listSupplier, RowMapper rowMapper)
      throws DataAccessException {

    return executeQuery(dataSource, boundSql, new ListResultSetExtractor(listSupplier, rowMapper));
  }

  @Override
  public  Set queryForSet(DataSource dataSource, BoundSql boundSql,
                                SetSupplier setSupplier, RowMapper rowMapper)
      throws DataAccessException {

    return executeQuery(dataSource, boundSql, new SetResultSetExtractor(setSupplier, rowMapper));
  }

  @Override
  public  Object queryForArray(DataSource dataSource, BoundSql boundSql, RowMapper rowMapper)
      throws DataAccessException {

    return executeQuery(dataSource, boundSql, new ArrayResultSetExtractor(rowMapper));
  }

  @Override
  public int update(DataSource dataSource, BoundSql boundSql)
      throws DataAccessException {

    return update(dataSource, boundSql, null);
  }

  @Override
  public int update(DataSource dataSource, BoundSql boundSql, GeneratedKeyHolder holder)
      throws DataAccessException {

    Connection conn = DataSourceUtils.getConnection(dataSource);
    PreparedStatement ps = null;
    ResultSet rs = null;
    String sql = boundSql.getSql();
    try {
      boolean needGenerateKey = holder != null;
      ps = needGenerateKey ?
          conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : // 生成自增key
          conn.prepareStatement(sql); // 不生成自增key
      setValues(ps, boundSql);

      if (logger.isDebugEnabled()) {
        logger.debug("Executing \"{}\" {}", sql, boundSql.getArgs());
      }

      int r = ps.executeUpdate();
      if (needGenerateKey) { // 生成自增key
        rs = ps.getGeneratedKeys();
        if (!rs.next()) {
          throw new DataRetrievalFailureException("Unable to retrieve the generated key. " +
              "Check that the table has an identity column enabled.");
        }
        Number key = holder.getTypeHandler().getResult(rs, 1);
        holder.setKey(key);
      }
      return r;
    } catch (SQLException e) {
      closeResultSet(rs);
      rs = null;
      closeStatement(ps);
      ps = null;
      DataSourceUtils.releaseConnection(conn, dataSource);
      conn = null;

      throw getExceptionTranslator(dataSource).translate(sql, e);
    } finally {
      closeResultSet(rs);
      closeStatement(ps);
      DataSourceUtils.releaseConnection(conn, dataSource);
    }
  }

  @Override
  public int[] batchUpdate(DataSource dataSource, List boundSqls) throws DataAccessException {
    return isUniqueSql(boundSqls) ?
        batchUpdateForUniqueSql(dataSource, boundSqls) :
        batchUpdateForDifferentSql(dataSource, boundSqls);
  }

  private  T executeQuery(DataSource dataSource, BoundSql boundSql, ResultSetExtractor rse)
      throws DataAccessException {

    Connection conn = DataSourceUtils.getConnection(dataSource);
    PreparedStatement ps = null;
    ResultSet rs = null;
    String sql = boundSql.getSql();
    try {
      ps = conn.prepareStatement(sql);
      setValues(ps, boundSql);

      if (logger.isDebugEnabled()) {
        logger.debug("Executing \"{}\" {}", sql, boundSql.getArgs());
      }

      rs = ps.executeQuery();
      return rse.extractData(rs);
    } catch (SQLException e) {
      closeResultSet(rs);
      rs = null;
      closeStatement(ps);
      ps = null;
      DataSourceUtils.releaseConnection(conn, dataSource);
      conn = null;

      throw getExceptionTranslator(dataSource).translate(sql, e);
    } finally {
      closeResultSet(rs);
      closeStatement(ps);
      DataSourceUtils.releaseConnection(conn, dataSource);
    }
  }

  private int[] batchUpdateForUniqueSql(DataSource dataSource, List boundSqls)
      throws DataAccessException {
    Connection conn = DataSourceUtils.getConnection(dataSource);
    PreparedStatement ps = null;
    String sql = boundSqls.get(0).getSql();
    try {
      ps = conn.prepareStatement(sql);
      setBatchValues(ps, boundSqls);

      if (logger.isDebugEnabled()) {
        List> debugBatchArgs = new ArrayList>(boundSqls.size());
        for (BoundSql boundSql : boundSqls) {
          debugBatchArgs.add(boundSql.getArgs());
        }
        logger.debug("Executing \"{}\" {}", sql, debugBatchArgs);
      }

      return ps.executeBatch();
    } catch (SQLException e) {
      closeStatement(ps);
      ps = null;
      DataSourceUtils.releaseConnection(conn, dataSource);
      conn = null;

      throw getExceptionTranslator(dataSource).translate(sql, e);
    } finally {
      closeStatement(ps);
      DataSourceUtils.releaseConnection(conn, dataSource);
    }
  }

  private int[] batchUpdateForDifferentSql(DataSource dataSource, List boundSqls)
      throws DataAccessException {
    int size = boundSqls.size();
    int[] r = new int[size];
    Connection conn = DataSourceUtils.getConnection(dataSource);
    try {
      for (int i = 0; i < size; i++) {
        BoundSql boundSql = boundSqls.get(i);
        String sql = boundSql.getSql();
        PreparedStatement ps = null;
        try {
          ps = conn.prepareStatement(sql);
          setValues(ps, boundSql);

          if (logger.isDebugEnabled()) {
            logger.debug("Executing \"{}\" {}", sql, boundSql.getArgs());
          }

          r[i] = ps.executeUpdate();
        } catch (SQLException e) {
          closeStatement(ps);
          ps = null;
          DataSourceUtils.releaseConnection(conn, dataSource);
          conn = null;

          throw getExceptionTranslator(dataSource).translate(sql, e);
        } finally {
          closeStatement(ps);
        }
      }
    } finally {
      DataSourceUtils.releaseConnection(conn, dataSource);
    }
    return r;
  }

  @SuppressWarnings("unchecked")
  private void setValues(PreparedStatement ps, BoundSql boundSql) throws SQLException {
    List args = boundSql.getArgs();
    List> typeHandlers = boundSql.getTypeHandlers();
    for (int i = 0; i < args.size(); i++) {
      TypeHandler typeHandler;
      typeHandler = typeHandlers.get(i);
      Object value = args.get(i);
      typeHandler.setParameter(ps, i + 1, value);
    }
  }

  private void setBatchValues(PreparedStatement ps, List boundSqls) throws SQLException {
    for (BoundSql boundSql : boundSqls) {
      setValues(ps, boundSql);
      ps.addBatch();
    }
  }

  boolean isUniqueSql(List boundSqls) {
    String sql = boundSqls.get(0).getSql();
    boolean r = true;
    for (int i = 1; i < boundSqls.size(); i++) {
      if (!sql.equals(boundSqls.get(i).getSql())) {
        r = false;
        break;
      }
    }
    return r;
  }

  private SQLExceptionTranslator getExceptionTranslator(DataSource dataSource) {
    return exceptionTranslatorCache.get(dataSource);
  }

  private final LoadingCache exceptionTranslatorCache
      = new DoubleCheckCache(
      new CacheLoader() {
        public SQLExceptionTranslator load(DataSource dataSource) {
          return new SQLErrorCodeSQLExceptionTranslator(dataSource);
        }
      });

  /**
   * 关闭语句
   *
   * @param stmt
   */
  private void closeStatement(Statement stmt) {
    if (stmt != null) {
      try {
        stmt.close();
      } catch (SQLException e) {
        logger.error("Could not close JDBC Statement", e);
      } catch (Throwable e) {
        logger.error("Unexpected exception on closing JDBC Statement", e);
      }
    }
  }

  /**
   * 关闭结果集
   *
   * @param rs
   */
  private void closeResultSet(ResultSet rs) {
    if (rs != null) {
      try {
        rs.close();
      } catch (SQLException e) {
        logger.error("Could not close JDBC ResultSet", e);
      } catch (Throwable e) {
        logger.error("Unexpected exception on closing JDBC ResultSet", e);
      }
    }
  }

}