View Javadoc
1   /*
2    *    Copyright 2009-2023 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    *       https://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  
18  import java.sql.Connection;
19  import java.sql.PreparedStatement;
20  import java.sql.ResultSet;
21  import java.sql.ResultSetMetaData;
22  import java.sql.SQLException;
23  import java.sql.Statement;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  
31  import org.apache.ibatis.io.Resources;
32  import org.apache.ibatis.type.TypeHandler;
33  import org.apache.ibatis.type.TypeHandlerRegistry;
34  
35  /**
36   * @author Clinton Begin
37   */
38  public class SqlRunner {
39  
40    public static final int NO_GENERATED_KEY = Integer.MIN_VALUE + 1001;
41  
42    private final Connection connection;
43    private final TypeHandlerRegistry typeHandlerRegistry;
44    private boolean useGeneratedKeySupport;
45  
46    public SqlRunner(Connection connection) {
47      this.connection = connection;
48      this.typeHandlerRegistry = new TypeHandlerRegistry();
49    }
50  
51    public void setUseGeneratedKeySupport(boolean useGeneratedKeySupport) {
52      this.useGeneratedKeySupport = useGeneratedKeySupport;
53    }
54  
55    /**
56     * Executes a SELECT statement that returns one row.
57     *
58     * @param sql
59     *          The SQL
60     * @param args
61     *          The arguments to be set on the statement.
62     *
63     * @return The row expected.
64     *
65     * @throws SQLException
66     *           If less or more than one row is returned
67     */
68    public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
69      List<Map<String, Object>> results = selectAll(sql, args);
70      if (results.size() != 1) {
71        throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
72      }
73      return results.get(0);
74    }
75  
76    /**
77     * Executes a SELECT statement that returns multiple rows.
78     *
79     * @param sql
80     *          The SQL
81     * @param args
82     *          The arguments to be set on the statement.
83     *
84     * @return The list of rows expected.
85     *
86     * @throws SQLException
87     *           If statement preparation or execution fails
88     */
89    public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
90      try (PreparedStatement ps = connection.prepareStatement(sql)) {
91        setParameters(ps, args);
92        try (ResultSet rs = ps.executeQuery()) {
93          return getResults(rs);
94        }
95      }
96    }
97  
98    /**
99     * Executes an INSERT statement.
100    *
101    * @param sql
102    *          The SQL
103    * @param args
104    *          The arguments to be set on the statement.
105    *
106    * @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
107    *
108    * @throws SQLException
109    *           If statement preparation or execution fails
110    */
111   public int insert(String sql, Object... args) throws SQLException {
112     PreparedStatement ps;
113     if (useGeneratedKeySupport) {
114       ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
115     } else {
116       ps = connection.prepareStatement(sql);
117     }
118 
119     try {
120       setParameters(ps, args);
121       ps.executeUpdate();
122       if (useGeneratedKeySupport) {
123         try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
124           List<Map<String, Object>> keys = getResults(generatedKeys);
125           if (keys.size() == 1) {
126             Map<String, Object> key = keys.get(0);
127             Iterator<Object> i = key.values().iterator();
128             if (i.hasNext()) {
129               Object genkey = i.next();
130               if (genkey != null) {
131                 try {
132                   return Integer.parseInt(genkey.toString());
133                 } catch (NumberFormatException e) {
134                   // ignore, no numeric key support
135                 }
136               }
137             }
138           }
139         }
140       }
141       return NO_GENERATED_KEY;
142     } finally {
143       try {
144         ps.close();
145       } catch (SQLException e) {
146         // ignore
147       }
148     }
149   }
150 
151   /**
152    * Executes an UPDATE statement.
153    *
154    * @param sql
155    *          The SQL
156    * @param args
157    *          The arguments to be set on the statement.
158    *
159    * @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
160    *
161    * @throws SQLException
162    *           If statement preparation or execution fails
163    */
164   public int update(String sql, Object... args) throws SQLException {
165     try (PreparedStatement ps = connection.prepareStatement(sql)) {
166       setParameters(ps, args);
167       return ps.executeUpdate();
168     }
169   }
170 
171   /**
172    * Executes a DELETE statement.
173    *
174    * @param sql
175    *          The SQL
176    * @param args
177    *          The arguments to be set on the statement.
178    *
179    * @return The number of rows impacted or BATCHED_RESULTS if the statements are being batched.
180    *
181    * @throws SQLException
182    *           If statement preparation or execution fails
183    */
184   public int delete(String sql, Object... args) throws SQLException {
185     return update(sql, args);
186   }
187 
188   /**
189    * Executes any string as a JDBC Statement. Good for DDL
190    *
191    * @param sql
192    *          The SQL
193    *
194    * @throws SQLException
195    *           If statement preparation or execution fails
196    */
197   public void run(String sql) throws SQLException {
198     try (Statement stmt = connection.createStatement()) {
199       stmt.execute(sql);
200     }
201   }
202 
203   /**
204    * @deprecated Since 3.5.4, this method is deprecated. Please close the {@link Connection} outside of this class.
205    */
206   @Deprecated
207   public void closeConnection() {
208     try {
209       connection.close();
210     } catch (SQLException e) {
211       // ignore
212     }
213   }
214 
215   private void setParameters(PreparedStatement ps, Object... args) throws SQLException {
216     for (int i = 0, n = args.length; i < n; i++) {
217       if (args[i] == null) {
218         throw new SQLException(
219             "SqlRunner requires an instance of Null to represent typed null values for JDBC compatibility");
220       }
221       if (args[i] instanceof Null) {
222         ((Null) args[i]).getTypeHandler().setParameter(ps, i + 1, null, ((Null) args[i]).getJdbcType());
223       } else {
224         TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(args[i].getClass());
225         if (typeHandler == null) {
226           throw new SQLException("SqlRunner could not find a TypeHandler instance for " + args[i].getClass());
227         } else {
228           typeHandler.setParameter(ps, i + 1, args[i], null);
229         }
230       }
231     }
232   }
233 
234   private List<Map<String, Object>> getResults(ResultSet rs) throws SQLException {
235     List<Map<String, Object>> list = new ArrayList<>();
236     List<String> columns = new ArrayList<>();
237     List<TypeHandler<?>> typeHandlers = new ArrayList<>();
238     ResultSetMetaData rsmd = rs.getMetaData();
239     for (int i = 0, n = rsmd.getColumnCount(); i < n; i++) {
240       columns.add(rsmd.getColumnLabel(i + 1));
241       try {
242         Class<?> type = Resources.classForName(rsmd.getColumnClassName(i + 1));
243         TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(type);
244         if (typeHandler == null) {
245           typeHandler = typeHandlerRegistry.getTypeHandler(Object.class);
246         }
247         typeHandlers.add(typeHandler);
248       } catch (Exception e) {
249         typeHandlers.add(typeHandlerRegistry.getTypeHandler(Object.class));
250       }
251     }
252     while (rs.next()) {
253       Map<String, Object> row = new HashMap<>();
254       for (int i = 0, n = columns.size(); i < n; i++) {
255         String name = columns.get(i);
256         TypeHandler<?> handler = typeHandlers.get(i);
257         row.put(name.toUpperCase(Locale.ENGLISH), handler.getResult(rs, name));
258       }
259       list.add(row);
260     }
261     return list;
262   }
263 
264 }