SqlRunner.java

  1. /*
  2.  *    Copyright 2009-2021 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.jdbc;

  17. import java.sql.Connection;
  18. import java.sql.PreparedStatement;
  19. import java.sql.ResultSet;
  20. import java.sql.ResultSetMetaData;
  21. import java.sql.SQLException;
  22. import java.sql.Statement;
  23. import java.util.ArrayList;
  24. import java.util.HashMap;
  25. import java.util.Iterator;
  26. import java.util.List;
  27. import java.util.Locale;
  28. import java.util.Map;

  29. import org.apache.ibatis.io.Resources;
  30. import org.apache.ibatis.type.TypeHandler;
  31. import org.apache.ibatis.type.TypeHandlerRegistry;

  32. /**
  33.  * @author Clinton Begin
  34.  */
  35. public class SqlRunner {

  36.   public static final int NO_GENERATED_KEY = Integer.MIN_VALUE + 1001;

  37.   private final Connection connection;
  38.   private final TypeHandlerRegistry typeHandlerRegistry;
  39.   private boolean useGeneratedKeySupport;

  40.   public SqlRunner(Connection connection) {
  41.     this.connection = connection;
  42.     this.typeHandlerRegistry = new TypeHandlerRegistry();
  43.   }

  44.   public void setUseGeneratedKeySupport(boolean useGeneratedKeySupport) {
  45.     this.useGeneratedKeySupport = useGeneratedKeySupport;
  46.   }

  47.   /**
  48.    * Executes a SELECT statement that returns one row.
  49.    *
  50.    * @param sql  The SQL
  51.    * @param args The arguments to be set on the statement.
  52.    * @return The row expected.
  53.    * @throws SQLException If less or more than one row is returned
  54.    */
  55.   public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
  56.     List<Map<String, Object>> results = selectAll(sql, args);
  57.     if (results.size() != 1) {
  58.       throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
  59.     }
  60.     return results.get(0);
  61.   }

  62.   /**
  63.    * Executes a SELECT statement that returns multiple rows.
  64.    *
  65.    * @param sql  The SQL
  66.    * @param args The arguments to be set on the statement.
  67.    * @return The list of rows expected.
  68.    * @throws SQLException If statement preparation or execution fails
  69.    */
  70.   public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
  71.     try (PreparedStatement ps = connection.prepareStatement(sql)) {
  72.       setParameters(ps, args);
  73.       try (ResultSet rs = ps.executeQuery()) {
  74.         return getResults(rs);
  75.       }
  76.     }
  77.   }

  78.   /**
  79.    * Executes an INSERT statement.
  80.    *
  81.    * @param sql  The SQL
  82.    * @param args The arguments to be set on the statement.
  83.    * @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
  84.    * @throws SQLException If statement preparation or execution fails
  85.    */
  86.   public int insert(String sql, Object... args) throws SQLException {
  87.     PreparedStatement ps;
  88.     if (useGeneratedKeySupport) {
  89.       ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
  90.     } else {
  91.       ps = connection.prepareStatement(sql);
  92.     }

  93.     try {
  94.       setParameters(ps, args);
  95.       ps.executeUpdate();
  96.       if (useGeneratedKeySupport) {
  97.         try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
  98.           List<Map<String, Object>> keys = getResults(generatedKeys);
  99.           if (keys.size() == 1) {
  100.             Map<String, Object> key = keys.get(0);
  101.             Iterator<Object> i = key.values().iterator();
  102.             if (i.hasNext()) {
  103.               Object genkey = i.next();
  104.               if (genkey != null) {
  105.                 try {
  106.                   return Integer.parseInt(genkey.toString());
  107.                 } catch (NumberFormatException e) {
  108.                   //ignore, no numeric key support
  109.                 }
  110.               }
  111.             }
  112.           }
  113.         }
  114.       }
  115.       return NO_GENERATED_KEY;
  116.     } finally {
  117.       try {
  118.         ps.close();
  119.       } catch (SQLException e) {
  120.         //ignore
  121.       }
  122.     }
  123.   }

  124.   /**
  125.    * Executes an UPDATE statement.
  126.    *
  127.    * @param sql  The SQL
  128.    * @param args The arguments to be set on the statement.
  129.    * @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
  130.    * @throws SQLException If statement preparation or execution fails
  131.    */
  132.   public int update(String sql, Object... args) throws SQLException {
  133.     try (PreparedStatement ps = connection.prepareStatement(sql)) {
  134.       setParameters(ps, args);
  135.       return ps.executeUpdate();
  136.     }
  137.   }

  138.   /**
  139.    * Executes a DELETE statement.
  140.    *
  141.    * @param sql  The SQL
  142.    * @param args The arguments to be set on the statement.
  143.    * @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
  144.    * @throws SQLException If statement preparation or execution fails
  145.    */
  146.   public int delete(String sql, Object... args) throws SQLException {
  147.     return update(sql, args);
  148.   }

  149.   /**
  150.    * Executes any string as a JDBC Statement.
  151.    * Good for DDL
  152.    *
  153.    * @param sql The SQL
  154.    * @throws SQLException If statement preparation or execution fails
  155.    */
  156.   public void run(String sql) throws SQLException {
  157.     try (Statement stmt = connection.createStatement()) {
  158.       stmt.execute(sql);
  159.     }
  160.   }

  161.   /**
  162.    * @deprecated Since 3.5.4, this method is deprecated. Please close the {@link Connection} outside of this class.
  163.    */
  164.   @Deprecated
  165.   public void closeConnection() {
  166.     try {
  167.       connection.close();
  168.     } catch (SQLException e) {
  169.       //ignore
  170.     }
  171.   }

  172.   private void setParameters(PreparedStatement ps, Object... args) throws SQLException {
  173.     for (int i = 0, n = args.length; i < n; i++) {
  174.       if (args[i] == null) {
  175.         throw new SQLException("SqlRunner requires an instance of Null to represent typed null values for JDBC compatibility");
  176.       } else if (args[i] instanceof Null) {
  177.         ((Null) args[i]).getTypeHandler().setParameter(ps, i + 1, null, ((Null) args[i]).getJdbcType());
  178.       } else {
  179.         TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(args[i].getClass());
  180.         if (typeHandler == null) {
  181.           throw new SQLException("SqlRunner could not find a TypeHandler instance for " + args[i].getClass());
  182.         } else {
  183.           typeHandler.setParameter(ps, i + 1, args[i], null);
  184.         }
  185.       }
  186.     }
  187.   }

  188.   private List<Map<String, Object>> getResults(ResultSet rs) throws SQLException {
  189.     List<Map<String, Object>> list = new ArrayList<>();
  190.     List<String> columns = new ArrayList<>();
  191.     List<TypeHandler<?>> typeHandlers = new ArrayList<>();
  192.     ResultSetMetaData rsmd = rs.getMetaData();
  193.     for (int i = 0, n = rsmd.getColumnCount(); i < n; i++) {
  194.       columns.add(rsmd.getColumnLabel(i + 1));
  195.       try {
  196.         Class<?> type = Resources.classForName(rsmd.getColumnClassName(i + 1));
  197.         TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(type);
  198.         if (typeHandler == null) {
  199.           typeHandler = typeHandlerRegistry.getTypeHandler(Object.class);
  200.         }
  201.         typeHandlers.add(typeHandler);
  202.       } catch (Exception e) {
  203.         typeHandlers.add(typeHandlerRegistry.getTypeHandler(Object.class));
  204.       }
  205.     }
  206.     while (rs.next()) {
  207.       Map<String, Object> row = new HashMap<>();
  208.       for (int i = 0, n = columns.size(); i < n; i++) {
  209.         String name = columns.get(i);
  210.         TypeHandler<?> handler = typeHandlers.get(i);
  211.         row.put(name.toUpperCase(Locale.ENGLISH), handler.getResult(rs, name));
  212.       }
  213.       list.add(row);
  214.     }
  215.     return list;
  216.   }

  217. }