Source code: org/hibernate/jdbc/AbstractBatcher.java
1 //$Id: AbstractBatcher.java,v 1.20 2005/04/25 07:33:12 oneovthafew Exp $
2 package org.hibernate.jdbc;
3
4 import java.sql.CallableStatement;
5 import java.sql.Connection;
6 import java.sql.PreparedStatement;
7 import java.sql.ResultSet;
8 import java.sql.SQLException;
9 import java.util.HashSet;
10 import java.util.Iterator;
11
12 import org.apache.commons.logging.Log;
13 import org.apache.commons.logging.LogFactory;
14 import org.hibernate.AssertionFailure;
15 import org.hibernate.HibernateException;
16 import org.hibernate.ScrollMode;
17 import org.hibernate.dialect.Dialect;
18 import org.hibernate.engine.SessionFactoryImplementor;
19 import org.hibernate.exception.JDBCExceptionHelper;
20 import org.hibernate.util.GetGeneratedKeysHelper;
21 import org.hibernate.util.JDBCExceptionReporter;
22
23 /**
24 * Manages prepared statements and batching.
25 *
26 * @author Gavin King
27 */
28 public abstract class AbstractBatcher implements Batcher {
29
30 private static int globalOpenPreparedStatementCount;
31 private static int globalOpenResultSetCount;
32
33 private int openPreparedStatementCount;
34 private int openResultSetCount;
35
36 protected static final Log log = LogFactory.getLog(AbstractBatcher.class);
37 protected static final Log SQL_LOG = LogFactory.getLog("org.hibernate.SQL");
38
39 private final JDBCContext jdbcContext;
40 private final SessionFactoryImplementor factory;
41
42 private PreparedStatement batchUpdate;
43 private String batchUpdateSQL;
44
45 private HashSet statementsToClose = new HashSet();
46 private HashSet resultSetsToClose = new HashSet();
47 private PreparedStatement lastQuery;
48
49 public AbstractBatcher(JDBCContext jdbcContext) {
50 this.jdbcContext = jdbcContext;
51 this.factory = jdbcContext.getFactory();
52 }
53
54 protected PreparedStatement getStatement() {
55 return batchUpdate;
56 }
57
58 public CallableStatement prepareCallableStatement(String sql)
59 throws SQLException, HibernateException {
60 executeBatch();
61 logOpenPreparedStatement();
62 return getCallableStatement( jdbcContext.connection(), sql, false);
63 }
64
65 public PreparedStatement prepareStatement(String sql)
66 throws SQLException, HibernateException {
67 return prepareStatement(sql, false);
68 }
69
70 public PreparedStatement prepareStatement(String sql, boolean getGeneratedKeys)
71 throws SQLException, HibernateException {
72 executeBatch();
73 logOpenPreparedStatement();
74 return getPreparedStatement( jdbcContext.connection(), sql, false, getGeneratedKeys, null, false );
75 }
76
77 public PreparedStatement prepareSelectStatement(String sql)
78 throws SQLException, HibernateException {
79 logOpenPreparedStatement();
80 return getPreparedStatement( jdbcContext.connection(), sql, false, false, null, false );
81 }
82
83 public PreparedStatement prepareQueryStatement(String sql, boolean scrollable, ScrollMode scrollMode)
84 throws SQLException, HibernateException {
85 logOpenPreparedStatement();
86 PreparedStatement ps = getPreparedStatement( jdbcContext.connection(), sql, scrollable, scrollMode );
87 setStatementFetchSize(ps);
88 statementsToClose.add(ps);
89 lastQuery=ps;
90 return ps;
91 }
92
93 public CallableStatement prepareCallableQueryStatement(String sql, boolean scrollable, ScrollMode scrollMode)
94 throws SQLException, HibernateException {
95 logOpenPreparedStatement();
96 CallableStatement ps = (CallableStatement) getPreparedStatement(jdbcContext.connection(), sql, scrollable, false, scrollMode, true);
97 setStatementFetchSize(ps);
98 statementsToClose.add(ps);
99 lastQuery=ps;
100 return ps;
101 }
102
103 public void abortBatch(SQLException sqle) {
104 try {
105 if (batchUpdate!=null) closeStatement(batchUpdate);
106 }
107 catch (SQLException e) {
108 //noncritical, swallow and let the other propagate!
109 JDBCExceptionReporter.logExceptions(e);
110 }
111 finally {
112 batchUpdate=null;
113 batchUpdateSQL=null;
114 }
115 }
116
117 public ResultSet getResultSet(PreparedStatement ps) throws SQLException {
118 ResultSet rs = ps.executeQuery();
119 resultSetsToClose.add(rs);
120 logOpenResults();
121 return rs;
122 }
123
124 public ResultSet getResultSet(CallableStatement ps, Dialect dialect) throws SQLException {
125 // TODO: maybe controlled by dialect...this works under oracle.
126 ResultSet rs = dialect.getResultSet(ps);
127 logOpenResults();
128 return rs;
129
130 }
131 public void closeQueryStatement(PreparedStatement ps, ResultSet rs) throws SQLException {
132 statementsToClose.remove(ps);
133 if (rs!=null) resultSetsToClose.remove(rs);
134 try {
135 if (rs!=null) {
136 logCloseResults();
137 rs.close();
138 }
139 }
140 finally {
141 closeQueryStatement(ps);
142 }
143 }
144
145 public PreparedStatement prepareBatchStatement(String sql)
146 throws SQLException, HibernateException {
147 if ( !sql.equals(batchUpdateSQL) ) {
148 batchUpdate=prepareStatement(sql); // calls executeBatch()
149 batchUpdateSQL=sql;
150 }
151 else {
152 log.debug("reusing prepared statement");
153 log(sql);
154 }
155 return batchUpdate;
156 }
157
158 public CallableStatement prepareBatchCallableStatement(String sql)
159 throws SQLException, HibernateException {
160 if ( !sql.equals(batchUpdateSQL) ) { // TODO: what if batchUpdate is a callablestatement ?
161 batchUpdate=prepareCallableStatement(sql); // calls executeBatch()
162 batchUpdateSQL=sql;
163 }
164 return (CallableStatement)batchUpdate;
165 }
166
167
168 public void executeBatch() throws HibernateException {
169 if (batchUpdate!=null) {
170 try {
171 try {
172 doExecuteBatch(batchUpdate);
173 }
174 finally {
175 closeStatement(batchUpdate);
176 }
177 }
178 catch (SQLException sqle) {
179 throw JDBCExceptionHelper.convert(
180 factory.getSQLExceptionConverter(),
181 sqle,
182 "Could not execute JDBC batch update",
183 batchUpdateSQL
184 );
185 }
186 finally {
187 batchUpdate=null;
188 batchUpdateSQL=null;
189 }
190 }
191 }
192
193 public void closeStatement(PreparedStatement ps) throws SQLException {
194 logClosePreparedStatement();
195 closePreparedStatement(ps);
196 }
197
198 private void closeQueryStatement(PreparedStatement ps) throws SQLException {
199
200 try {
201 //work around a bug in all known connection pools....
202 if ( ps.getMaxRows()!=0 ) ps.setMaxRows(0);
203 if ( ps.getQueryTimeout()!=0 ) ps.setQueryTimeout(0);
204 }
205 catch (Exception e) {
206 log.warn("exception clearing maxRows/queryTimeout", e);
207 // ps.close(); //just close it; do NOT try to return it to the pool!
208 return; //NOTE: early exit!
209 }
210 finally {
211 closeStatement(ps);
212 }
213
214 if ( lastQuery==ps ) lastQuery = null;
215
216 }
217
218 public void closeStatements() {
219 try {
220 if (batchUpdate!=null) batchUpdate.close();
221 }
222 catch (SQLException sqle) {
223 //no big deal
224 log.warn("Could not close a JDBC prepared statement", sqle);
225 }
226 batchUpdate=null;
227 batchUpdateSQL=null;
228
229 Iterator iter = resultSetsToClose.iterator();
230 while ( iter.hasNext() ) {
231 try {
232 logCloseResults();
233 ( (ResultSet) iter.next() ).close();
234 }
235 catch (SQLException e) {
236 // no big deal
237 log.warn("Could not close a JDBC result set", e);
238 }
239 }
240 resultSetsToClose.clear();
241
242 iter = statementsToClose.iterator();
243 while ( iter.hasNext() ) {
244 try {
245 closeQueryStatement( (PreparedStatement) iter.next() );
246 }
247 catch (SQLException e) {
248 // no big deal
249 log.warn("Could not close a JDBC statement", e);
250 }
251 }
252 statementsToClose.clear();
253 }
254
255 protected abstract void doExecuteBatch(PreparedStatement ps) throws SQLException, HibernateException;
256
257 private String preparedStatementCountsToString() {
258 return
259 " (open PreparedStatements: " +
260 openPreparedStatementCount +
261 ", globally: " +
262 globalOpenPreparedStatementCount +
263 ")";
264 }
265
266 private String resultSetCountsToString() {
267 return
268 " (open ResultSets: " +
269 openResultSetCount +
270 ", globally: " +
271 globalOpenResultSetCount +
272 ")";
273 }
274
275 private void logOpenPreparedStatement() {
276 if ( log.isDebugEnabled() ) {
277 log.debug( "about to open PreparedStatement" + preparedStatementCountsToString() );
278 openPreparedStatementCount++;
279 globalOpenPreparedStatementCount++;
280 }
281 }
282
283 private void logClosePreparedStatement() {
284 if ( log.isDebugEnabled() ) {
285 log.debug( "about to close PreparedStatement" + preparedStatementCountsToString() );
286 openPreparedStatementCount--;
287 globalOpenPreparedStatementCount--;
288 }
289 }
290
291 private void logOpenResults() {
292 if ( log.isDebugEnabled() ) {
293 log.debug( "about to open ResultSet" + resultSetCountsToString() );
294 openResultSetCount++;
295 globalOpenResultSetCount++;
296 }
297 }
298 private void logCloseResults() {
299 if ( log.isDebugEnabled() ) {
300 log.debug( "about to close ResultSet" + resultSetCountsToString() );
301 openResultSetCount--;
302 globalOpenResultSetCount--;
303 }
304 }
305
306 protected SessionFactoryImplementor getFactory() {
307 return factory;
308 }
309
310 private void log(String sql) {
311 SQL_LOG.debug(sql);
312 if ( factory.getSettings().isShowSqlEnabled() ) System.out.println("Hibernate: " + sql);
313 }
314
315 private PreparedStatement getPreparedStatement(
316 final Connection conn,
317 final String sql,
318 final boolean scrollable,
319 final ScrollMode scrollMode)
320 throws SQLException {
321 return getPreparedStatement(conn, sql, scrollable, false, scrollMode, false);
322 }
323
324 private CallableStatement getCallableStatement( final Connection conn,
325 final String sql,
326 boolean scrollable)
327 throws SQLException {
328
329 if ( scrollable && !factory.getSettings().isScrollableResultSetsEnabled() ) {
330 throw new AssertionFailure("scrollable result sets are not enabled");
331 }
332
333 log(sql);
334
335 log.trace("preparing callable statement");
336 if (scrollable) {
337 return conn.prepareCall(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
338 }
339 else {
340 return conn.prepareCall(sql);
341 }
342
343 }
344
345 private PreparedStatement getPreparedStatement(
346 final Connection conn,
347 final String sql,
348 boolean scrollable,
349 final boolean useGetGeneratedKeys,
350 final ScrollMode scrollMode,
351 final boolean callable)
352 throws SQLException {
353
354 if ( scrollable && !factory.getSettings().isScrollableResultSetsEnabled() ) {
355 throw new AssertionFailure("scrollable result sets are not enabled");
356 }
357
358 if ( useGetGeneratedKeys && !factory.getSettings().isGetGeneratedKeysEnabled() ) {
359 throw new AssertionFailure("getGeneratedKeys() support is not enabled");
360 }
361
362 log(sql);
363
364 try {
365 log.trace("preparing statement");
366 PreparedStatement result;
367 if (scrollable) {
368 if (callable) {
369 result = conn.prepareCall( sql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY );
370 }
371 else {
372 result = conn.prepareStatement( sql, scrollMode.toResultSetType(), ResultSet.CONCUR_READ_ONLY );
373 }
374 }
375 else if (useGetGeneratedKeys) {
376 result = GetGeneratedKeysHelper.prepareStatement(conn, sql);
377 }
378 else {
379 if (callable) {
380 result = conn.prepareCall(sql);
381 }
382 else {
383 result = conn.prepareStatement(sql);
384 }
385 }
386
387 if ( factory.getStatistics().isStatisticsEnabled() ) {
388 factory.getStatisticsImplementor().prepareStatement();
389 }
390
391 return result;
392
393 }
394 catch (SQLException sqle) {
395 JDBCExceptionReporter.logExceptions(sqle);
396 throw sqle;
397 }
398
399 }
400
401 private void closePreparedStatement(PreparedStatement ps) throws SQLException {
402 try {
403 log.trace("closing statement");
404 ps.close();
405 if ( factory.getStatistics().isStatisticsEnabled() ) {
406 factory.getStatisticsImplementor().closeStatement();
407 }
408 }
409 finally {
410 if ( resultSetsToClose.size()==0 && statementsToClose.size()==0 ) {
411 jdbcContext.aggressiveRelease();
412 }
413 }
414 }
415
416 private void setStatementFetchSize(PreparedStatement statement) throws SQLException {
417 Integer statementFetchSize = factory.getSettings().getJdbcFetchSize();
418 if (statementFetchSize!=null) statement.setFetchSize( statementFetchSize.intValue() );
419 }
420
421 public Connection openConnection() throws HibernateException {
422 log.debug("opening JDBC connection");
423 try {
424 return factory.getConnectionProvider().getConnection();
425 }
426 catch (SQLException sqle) {
427 throw JDBCExceptionHelper.convert(
428 factory.getSQLExceptionConverter(),
429 sqle,
430 "Cannot open connection"
431 );
432 }
433 }
434
435 public void closeConnection(Connection conn) throws HibernateException {
436 if ( log.isDebugEnabled() ) {
437 log.debug(
438 "closing JDBC connection" +
439 preparedStatementCountsToString() +
440 resultSetCountsToString()
441 );
442 }
443
444 if ( conn == null ) {
445 return;
446 }
447
448 try {
449 if ( !conn.isClosed() ) {
450 JDBCExceptionReporter.logAndClearWarnings(conn);
451 }
452 factory.getConnectionProvider().closeConnection(conn);
453 }
454 catch (SQLException sqle) {
455 throw JDBCExceptionHelper.convert(
456 factory.getSQLExceptionConverter(),
457 sqle,
458 "Cannot close connection"
459 );
460 }
461 }
462
463 public void cancelLastQuery() throws HibernateException {
464 try {
465 if (lastQuery!=null) lastQuery.cancel();
466 }
467 catch (SQLException sqle) {
468 throw JDBCExceptionHelper.convert(
469 factory.getSQLExceptionConverter(),
470 sqle,
471 "Cannot cancel query"
472 );
473 }
474 }
475
476 }
477
478
479
480
481
482