View Javadoc

1   /*
2    * Copyright 2004-2007 Brian McCallister
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  
17  package org.skife.jdbi.v2;
18  
19  import org.skife.jdbi.v2.exceptions.UnableToCreateStatementException;
20  import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;
21  import org.skife.jdbi.v2.tweak.RewrittenStatement;
22  import org.skife.jdbi.v2.tweak.SQLLog;
23  import org.skife.jdbi.v2.tweak.StatementBuilder;
24  import org.skife.jdbi.v2.tweak.StatementLocator;
25  import org.skife.jdbi.v2.tweak.StatementRewriter;
26  
27  import java.sql.Connection;
28  import java.sql.PreparedStatement;
29  import java.sql.SQLException;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.Map;
33  
34  /**
35   * Represents a prepared batch statement. That is, a sql statement compiled as a prepared
36   * statement, and then executed multiple times in a single batch. This is, generally,
37   * a very efficient way to execute large numbers of the same statement where
38   * the statement only varies by the arguments bound to it.
39   */
40  public class PreparedBatch
41  {
42      private List<PreparedBatchPart> parts = new ArrayList<PreparedBatchPart>();
43      private final StatementLocator locator;
44      private final StatementRewriter rewriter;
45      private final Connection connection;
46      private final StatementBuilder preparedStatementCache;
47      private final String sql;
48      private final SQLLog log;
49      private final TimingCollector timingCollector;
50      private final StatementContext context;
51  
52      PreparedBatch(StatementLocator locator,
53                    StatementRewriter rewriter,
54                    Connection connection,
55                    StatementBuilder preparedStatementCache,
56                    String sql,
57                    Map<String, Object> globalStatementAttributes,
58                    SQLLog log,
59                    TimingCollector timingCollector)
60      {
61          this.locator = locator;
62          this.rewriter = rewriter;
63          this.connection = connection;
64          this.preparedStatementCache = preparedStatementCache;
65          this.sql = sql;
66          this.log = log;
67          this.timingCollector = timingCollector;
68          this.context = new StatementContext(globalStatementAttributes);
69      }
70  
71      /**
72       * Specify a value on the statement context for this batch
73       *
74       * @return self
75       */
76      public PreparedBatch define(String key, Object value)
77      {
78          context.setAttribute(key, value);
79          return this;
80      }
81  
82      /**
83       * Execute the batch
84       *
85       * @return the number of rows modified or inserted per batch part.
86       */
87      public int[] execute()
88      {
89          // short circuit empty batch
90          if (parts.size() == 0) return new int[]{};
91  
92          PreparedBatchPart current = parts.get(0);
93          final String my_sql ;
94          try {
95              my_sql = locator.locate(sql, context);
96          }
97          catch (Exception e) {
98              throw new UnableToCreateStatementException(String.format("Exception while locating statement for [%s]",
99                                                                       sql), e, context);
100         }
101         final RewrittenStatement rewritten = rewriter.rewrite(my_sql, current.getParameters(), context);
102         PreparedStatement stmt = null;
103         try {
104             try {
105                 stmt = connection.prepareStatement(rewritten.getSql());
106             }
107             catch (SQLException e) {
108                 throw new UnableToCreateStatementException(e, context);
109             }
110 
111             try {
112                 for (PreparedBatchPart part : parts) {
113                     rewritten.bind(part.getParameters(), stmt);
114                     stmt.addBatch();
115                 }
116             }
117             catch (SQLException e) {
118                 throw new UnableToExecuteStatementException("Exception while binding parameters", e, context);
119             }
120 
121             try {
122                 final long start = System.nanoTime();
123                 final int[] rs =  stmt.executeBatch();
124                 final long elapsedTime = (System.nanoTime() - start);
125                 log.logPreparedBatch(elapsedTime / 1000000L,  rewritten.getSql(), parts.size());
126                 timingCollector.collect(elapsedTime, context);
127                 return rs;
128             }
129             catch (SQLException e) {
130                 throw new UnableToExecuteStatementException(e, context);
131             }
132         }
133         finally {
134             QueryPostMungeCleanup.CLOSE_RESOURCES_QUIETLY.cleanup(null, stmt, null);
135             this.parts.clear();
136         }
137     }
138 
139     /**
140      * Add a statement (part) to this batch. You'll need to bindBinaryStream any arguments to the
141      * part.
142      *
143      * @return A part which can be used to bindBinaryStream parts to the statement
144      */
145     public PreparedBatchPart add()
146     {
147         PreparedBatchPart part = new PreparedBatchPart(this, locator, rewriter, connection, preparedStatementCache, sql, context, log, timingCollector);
148         parts.add(part);
149         return part;
150     }
151 
152 	public PreparedBatch add(Object... args)
153 	{
154         PreparedBatchPart part = new PreparedBatchPart(this, locator, rewriter, connection, preparedStatementCache, sql, context, log, timingCollector);
155 
156 		for (int i = 0; i < args.length; ++i) {
157 			part.bind(i, args[i]);
158 		}
159 
160 		parts.add(part);
161 
162 		return this;
163 	}
164 
165 
166     /**
167      * Create a new batch part by binding values looked up in <code>args</code> to
168      * named parameters on the statement.
169      *
170      * @param args map to bind arguments from for named parameters on the statement
171      *
172      * @return the new batch part
173      */
174     public PreparedBatchPart add(Map<String, ? extends Object> args)
175     {
176         PreparedBatchPart part = new PreparedBatchPart(this, locator, rewriter, connection, preparedStatementCache, sql, context, log, timingCollector);
177         parts.add(part);
178         part.bindFromMap(args);
179         return part;
180     }
181 
182     /**
183      * The number of statements which are in this batch
184      */
185     public int getSize()
186     {
187         return parts.size();
188     }
189 
190     /**
191      * The number of statements which are in this batch
192      */
193     public int size()
194     {
195         return parts.size();
196     }
197 }