001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.dbutils;
018    
019    import java.beans.IntrospectionException;
020    import java.beans.Introspector;
021    import java.beans.PropertyDescriptor;
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.sql.Connection;
025    import java.sql.ParameterMetaData;
026    import java.sql.PreparedStatement;
027    import java.sql.ResultSet;
028    import java.sql.SQLException;
029    import java.sql.Statement;
030    import java.sql.Types;
031    import java.util.Arrays;
032    
033    import javax.sql.DataSource;
034    
035    /**
036     * Executes SQL queries with pluggable strategies for handling 
037     * <code>ResultSet</code>s.  This class is thread safe.
038     * 
039     * @see ResultSetHandler
040     */
041    public class QueryRunner {
042    
043        /**
044         * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried it yet)?
045         */
046        private volatile boolean pmdKnownBroken = false;
047        
048        /**
049         * The DataSource to retrieve connections from.
050         */
051        protected final DataSource ds;
052    
053        /**
054         * Constructor for QueryRunner.
055         */
056        public QueryRunner() {
057            super();
058            ds = null;
059        }
060    
061        /**
062         * Constructor for QueryRunner, allows workaround for Oracle drivers
063         * @param pmdKnownBroken Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
064         * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
065         * and if it breaks, we'll remember not to use it again.
066         */
067        public QueryRunner(boolean pmdKnownBroken) {
068            super();
069            this.pmdKnownBroken = pmdKnownBroken; 
070            ds = null;
071        }
072        
073        /**
074         * Constructor for QueryRunner, allows workaround for Oracle drivers.  Methods that do not take a 
075         * <code>Connection</code> parameter will retrieve connections from this
076         * <code>DataSource</code>.
077         * 
078         * @param ds The <code>DataSource</code> to retrieve connections from.
079         */
080        public QueryRunner(DataSource ds) {
081            super();
082            this.ds = ds;
083        }
084        
085        /**
086         * Constructor for QueryRunner, allows workaround for Oracle drivers.  Methods that do not take a 
087         * <code>Connection</code> parameter will retrieve connections from this
088         * <code>DataSource</code>.
089         * 
090         * @param ds The <code>DataSource</code> to retrieve connections from.
091         * @param pmdKnownBroken Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
092         * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
093         * and if it breaks, we'll remember not to use it again.
094         */
095        public QueryRunner(DataSource ds, boolean pmdKnownBroken) {
096            super();
097            this.pmdKnownBroken = pmdKnownBroken;
098            this.ds = ds;
099        }
100        
101        /**
102         * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
103         * 
104         * @param conn The Connection to use to run the query.  The caller is
105         * responsible for closing this Connection.
106         * @param sql The SQL to execute.
107         * @param params An array of query replacement parameters.  Each row in
108         * this array is one set of batch replacement values. 
109         * @return The number of rows updated per statement.
110         * @throws SQLException if a database access error occurs
111         * @since DbUtils 1.1
112         */
113        public int[] batch(Connection conn, String sql, Object[][] params)
114            throws SQLException {
115    
116            PreparedStatement stmt = null;
117            int[] rows = null;
118            try {
119                stmt = this.prepareStatement(conn, sql);
120    
121                for (int i = 0; i < params.length; i++) {
122                    this.fillStatement(stmt, params[i]);
123                    stmt.addBatch();
124                }
125                rows = stmt.executeBatch();
126    
127            } catch (SQLException e) {
128                this.rethrow(e, sql, (Object[])params);
129            } finally {
130                close(stmt);
131            }
132    
133            return rows;
134        }
135    
136        /**
137         * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.  The 
138         * <code>Connection</code> is retrieved from the <code>DataSource</code> 
139         * set in the constructor.  This <code>Connection</code> must be in 
140         * auto-commit mode or the update will not be saved. 
141         * 
142         * @param sql The SQL to execute.
143         * @param params An array of query replacement parameters.  Each row in
144         * this array is one set of batch replacement values. 
145         * @return The number of rows updated per statement.
146         * @throws SQLException if a database access error occurs
147         * @since DbUtils 1.1
148         */
149        public int[] batch(String sql, Object[][] params) throws SQLException {
150            Connection conn = this.prepareConnection();
151    
152            try {
153                return this.batch(conn, sql, params);
154            } finally {
155                close(conn);
156            }
157        }
158    
159        /**
160         * Fill the <code>PreparedStatement</code> replacement parameters with 
161         * the given objects.
162         * @param stmt PreparedStatement to fill
163         * @param params Query replacement parameters; <code>null</code> is a valid
164         * value to pass in.
165         * @throws SQLException if a database access error occurs
166         */
167        public void fillStatement(PreparedStatement stmt, Object... params)
168            throws SQLException {
169    
170            if (params == null) {
171                return;
172            }
173            
174            ParameterMetaData pmd = null;
175            if (!pmdKnownBroken) {
176                pmd = stmt.getParameterMetaData();
177                if (pmd.getParameterCount() < params.length) {
178                    throw new SQLException("Too many parameters: expected "
179                            + pmd.getParameterCount() + ", was given " + params.length);
180                }
181            }
182            for (int i = 0; i < params.length; i++) {
183                if (params[i] != null) {
184                    stmt.setObject(i + 1, params[i]);
185                } else {
186                    // VARCHAR works with many drivers regardless
187                    // of the actual column type.  Oddly, NULL and 
188                    // OTHER don't work with Oracle's drivers.
189                    int sqlType = Types.VARCHAR;
190                    if (!pmdKnownBroken) {
191                        try {
192                            sqlType = pmd.getParameterType(i + 1);
193                        } catch (SQLException e) {
194                            pmdKnownBroken = true;
195                        }
196                    }
197                    stmt.setNull(i + 1, sqlType);
198                }
199            }
200        }
201    
202        /**
203         * Fill the <code>PreparedStatement</code> replacement parameters with the
204         * given object's bean property values.
205         * 
206         * @param stmt
207         *            PreparedStatement to fill
208         * @param bean
209         *            a JavaBean object
210         * @param properties
211         *            an ordered array of properties; this gives the order to insert
212         *            values in the statement
213         * @throws SQLException
214         *             if a database access error occurs
215         */
216        public void fillStatementWithBean(PreparedStatement stmt, Object bean,
217                PropertyDescriptor[] properties) throws SQLException {
218            Object[] params = new Object[properties.length];
219            for (int i = 0; i < properties.length; i++) {
220                PropertyDescriptor property = properties[i];
221                Object value = null;
222                Method method = property.getReadMethod();
223                if (method == null) {
224                    throw new RuntimeException("No read method for bean property "
225                            + bean.getClass() + " " + property.getName());
226                }
227                try {
228                    value = method.invoke(bean, new Object[0]);
229                } catch (InvocationTargetException e) {
230                    throw new RuntimeException("Couldn't invoke method: " + method, e);
231                } catch (IllegalArgumentException e) {
232                    throw new RuntimeException("Couldn't invoke method with 0 arguments: " + method, e);
233                } catch (IllegalAccessException e) {
234                    throw new RuntimeException("Couldn't invoke method: " + method, e);
235                } 
236                params[i] = value;
237            }
238            fillStatement(stmt, params);
239        }
240    
241        /**
242         * Fill the <code>PreparedStatement</code> replacement parameters with the
243         * given object's bean property values.
244         * 
245         * @param stmt
246         *            PreparedStatement to fill
247         * @param bean
248         *            a JavaBean object
249         * @param propertyNames
250         *            an ordered array of property names (these should match the
251         *            getters/setters); this gives the order to insert values in the
252         *            statement
253         * @throws SQLException
254         *             if a database access error occurs
255         */
256        public void fillStatementWithBean(PreparedStatement stmt, Object bean,
257                String... propertyNames) throws SQLException {
258            PropertyDescriptor[] descriptors;
259            try {
260                descriptors = Introspector.getBeanInfo(bean.getClass())
261                        .getPropertyDescriptors();
262            } catch (IntrospectionException e) {
263                throw new RuntimeException("Couldn't introspect bean " + bean.getClass().toString(), e);
264            }
265            PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
266            for (int i = 0; i < propertyNames.length; i++) {
267                String propertyName = propertyNames[i];
268                if (propertyName == null) {
269                    throw new NullPointerException("propertyName can't be null: " + i);
270                }
271                boolean found = false;
272                for (int j = 0; j < descriptors.length; j++) {
273                    PropertyDescriptor descriptor = descriptors[j];
274                    if (propertyName.equals(descriptor.getName())) {
275                        sorted[i] = descriptor;
276                        found = true;
277                        break;
278                    }
279                }
280                if (!found) {
281                    throw new RuntimeException("Couldn't find bean property: "
282                            + bean.getClass() + " " + propertyName);
283                }
284            }
285            fillStatementWithBean(stmt, bean, sorted);
286        }
287    
288        /**
289         * Returns the <code>DataSource</code> this runner is using.  
290         * <code>QueryRunner</code> methods always call this method to get the
291         * <code>DataSource</code> so subclasses can provide specialized
292         * behavior.
293         *
294         * @return DataSource the runner is using
295         */
296        public DataSource getDataSource() {
297            return this.ds;
298        }
299    
300        /**
301         * Factory method that creates and initializes a 
302         * <code>PreparedStatement</code> object for the given SQL.  
303         * <code>QueryRunner</code> methods always call this method to prepare 
304         * statements for them.  Subclasses can override this method to provide 
305         * special PreparedStatement configuration if needed.  This implementation
306         * simply calls <code>conn.prepareStatement(sql)</code>.
307         *  
308         * @param conn The <code>Connection</code> used to create the 
309         * <code>PreparedStatement</code>
310         * @param sql The SQL statement to prepare.
311         * @return An initialized <code>PreparedStatement</code>.
312         * @throws SQLException if a database access error occurs
313         */
314        protected PreparedStatement prepareStatement(Connection conn, String sql)
315            throws SQLException {
316                
317            return conn.prepareStatement(sql);
318        }
319        
320        /**
321         * Factory method that creates and initializes a 
322         * <code>Connection</code> object.  <code>QueryRunner</code> methods 
323         * always call this method to retrieve connections from its DataSource.  
324         * Subclasses can override this method to provide 
325         * special <code>Connection</code> configuration if needed.  This 
326         * implementation simply calls <code>ds.getConnection()</code>.
327         * 
328         * @return An initialized <code>Connection</code>.
329         * @throws SQLException if a database access error occurs
330         * @since DbUtils 1.1
331         */
332        protected Connection prepareConnection() throws SQLException {
333            if(this.getDataSource() == null) {
334                throw new SQLException("QueryRunner requires a DataSource to be " +
335                    "invoked in this way, or a Connection should be passed in");
336            }
337            return this.getDataSource().getConnection();
338        }
339    
340        /**
341         * Execute an SQL SELECT query with a single replacement parameter. The
342         * caller is responsible for closing the connection.
343         * @param <T> The type of object that the handler returns
344         * @param conn The connection to execute the query in.
345         * @param sql The query to execute.
346         * @param param The replacement parameter.
347         * @param rsh The handler that converts the results into an object.
348         * @return The object returned by the handler.
349         * @throws SQLException if a database access error occurs
350         * @deprecated Use {@link #query(Connection, String, ResultSetHandler, Object...)}
351         */
352        public <T> T query(Connection conn, String sql, Object param,
353                ResultSetHandler<T> rsh) throws SQLException {
354    
355            return this.query(conn, sql, rsh, new Object[] { param });
356        }
357    
358        /**
359         * Execute an SQL SELECT query with replacement parameters.  The
360         * caller is responsible for closing the connection.
361         * @param <T> The type of object that the handler returns
362         * @param conn The connection to execute the query in.
363         * @param sql The query to execute.
364         * @param params The replacement parameters.
365         * @param rsh The handler that converts the results into an object.
366         * @return The object returned by the handler.
367         * @throws SQLException if a database access error occurs
368         * @deprecated Use {@link #query(Connection,String,ResultSetHandler,Object...)} instead
369         */
370        public <T> T query(Connection conn, String sql, Object[] params,
371                ResultSetHandler<T> rsh) throws SQLException {
372                    return query(conn, sql, rsh, params);
373                }
374        /**
375         * Execute an SQL SELECT query with replacement parameters.  The
376         * caller is responsible for closing the connection.
377         * @param <T> The type of object that the handler returns
378         * @param conn The connection to execute the query in.
379         * @param sql The query to execute.
380         * @param rsh The handler that converts the results into an object.
381         * @param params The replacement parameters.
382         * @return The object returned by the handler.
383         * @throws SQLException if a database access error occurs
384         */
385        public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh,
386                Object... params) throws SQLException {
387    
388            PreparedStatement stmt = null;
389            ResultSet rs = null;
390            T result = null;
391    
392            try {
393                stmt = this.prepareStatement(conn, sql);
394                this.fillStatement(stmt, params);
395                rs = this.wrap(stmt.executeQuery());
396                result = rsh.handle(rs);
397    
398            } catch (SQLException e) {
399                this.rethrow(e, sql, params);
400    
401            } finally {
402                try {
403                    close(rs);
404                } finally {
405                    close(stmt);
406                }
407            }
408    
409            return result;
410        }
411    
412        /**
413         * Execute an SQL SELECT query without any replacement parameters.  The
414         * caller is responsible for closing the connection.
415         * @param <T> The type of object that the handler returns
416         * @param conn The connection to execute the query in.
417         * @param sql The query to execute.
418         * @param rsh The handler that converts the results into an object.
419         * @return The object returned by the handler.
420         * @throws SQLException if a database access error occurs
421         */
422        public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh)
423            throws SQLException {
424    
425            return this.query(conn, sql, rsh, (Object[]) null);
426        }
427    
428        /**
429         * Executes the given SELECT SQL with a single replacement parameter.
430         * The <code>Connection</code> is retrieved from the
431         * <code>DataSource</code> set in the constructor.
432         * @param <T> The type of object that the handler returns
433         * @param sql The SQL statement to execute.
434         * @param param The replacement parameter.
435         * @param rsh The handler used to create the result object from 
436         * the <code>ResultSet</code>.
437         * 
438         * @return An object generated by the handler.
439         * @throws SQLException if a database access error occurs
440         * @deprecated Use {@link #query(String, ResultSetHandler, Object...)}
441         */
442        public <T> T query(String sql, Object param, ResultSetHandler<T> rsh)
443            throws SQLException {
444    
445            return this.query(sql, rsh, new Object[] { param });
446        }
447    
448        /**
449         * Executes the given SELECT SQL query and returns a result object.
450         * The <code>Connection</code> is retrieved from the 
451         * <code>DataSource</code> set in the constructor.
452         * @param <T> The type of object that the handler returns
453         * @param sql The SQL statement to execute.
454         * @param params Initialize the PreparedStatement's IN parameters with 
455         * this array.
456         * 
457         * @param rsh The handler used to create the result object from 
458         * the <code>ResultSet</code>.
459         * 
460         * @return An object generated by the handler.
461         * @throws SQLException if a database access error occurs
462         * @deprecated Use {@link #query(String, ResultSetHandler, Object...)}
463         */
464        public <T> T query(String sql, Object[] params, ResultSetHandler<T> rsh)
465            throws SQLException {
466                return query(sql, rsh, params);
467            }
468    
469        /**
470         * Executes the given SELECT SQL query and returns a result object.
471         * The <code>Connection</code> is retrieved from the 
472         * <code>DataSource</code> set in the constructor.
473         * @param <T> The type of object that the handler returns
474         * @param sql The SQL statement to execute.
475         * @param rsh The handler used to create the result object from 
476         * the <code>ResultSet</code>.
477         * @param params Initialize the PreparedStatement's IN parameters with 
478         * this array.
479         * @return An object generated by the handler.
480         * @throws SQLException if a database access error occurs
481         */
482        public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params)
483            throws SQLException {
484    
485            Connection conn = this.prepareConnection();
486    
487            try {
488                return this.query(conn, sql, rsh, params);
489            } finally {
490                close(conn);
491            }
492        }
493    
494        /**
495         * Executes the given SELECT SQL without any replacement parameters.
496         * The <code>Connection</code> is retrieved from the
497         * <code>DataSource</code> set in the constructor.
498         * @param <T> The type of object that the handler returns
499         * @param sql The SQL statement to execute.
500         * @param rsh The handler used to create the result object from 
501         * the <code>ResultSet</code>.
502         * 
503         * @return An object generated by the handler.
504         * @throws SQLException if a database access error occurs
505         */
506        public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
507            return this.query(sql, rsh, (Object[]) null);
508        }
509    
510        /**
511         * Throws a new exception with a more informative error message.
512         * 
513         * @param cause The original exception that will be chained to the new 
514         * exception when it's rethrown. 
515         * 
516         * @param sql The query that was executing when the exception happened.
517         * 
518         * @param params The query replacement parameters; <code>null</code> is a 
519         * valid value to pass in.
520         * 
521         * @throws SQLException if a database access error occurs
522         */
523        protected void rethrow(SQLException cause, String sql, Object... params)
524            throws SQLException {
525    
526            String causeMessage = cause.getMessage();
527            if (causeMessage == null) {
528                causeMessage = "";
529            }
530            StringBuffer msg = new StringBuffer(causeMessage);
531    
532            msg.append(" Query: ");
533            msg.append(sql);
534            msg.append(" Parameters: ");
535    
536            if (params == null) {
537                msg.append("[]");
538            } else {
539                msg.append(Arrays.deepToString(params));
540            }
541    
542            SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
543                    cause.getErrorCode());
544            e.setNextException(cause);
545    
546            throw e;
547        }
548    
549        /**
550         * Execute an SQL INSERT, UPDATE, or DELETE query without replacement
551         * parameters.
552         * 
553         * @param conn The connection to use to run the query.
554         * @param sql The SQL to execute.
555         * @return The number of rows updated.
556         * @throws SQLException if a database access error occurs
557         */
558        public int update(Connection conn, String sql) throws SQLException {
559            return this.update(conn, sql, (Object[]) null);
560        }
561    
562        /**
563         * Execute an SQL INSERT, UPDATE, or DELETE query with a single replacement
564         * parameter.
565         * 
566         * @param conn The connection to use to run the query.
567         * @param sql The SQL to execute.
568         * @param param The replacement parameter.
569         * @return The number of rows updated.
570         * @throws SQLException if a database access error occurs
571         */
572        public int update(Connection conn, String sql, Object param)
573            throws SQLException {
574    
575            return this.update(conn, sql, new Object[] { param });
576        }
577    
578        /**
579         * Execute an SQL INSERT, UPDATE, or DELETE query.
580         * 
581         * @param conn The connection to use to run the query.
582         * @param sql The SQL to execute.
583         * @param params The query replacement parameters.
584         * @return The number of rows updated.
585         * @throws SQLException if a database access error occurs
586         */
587        public int update(Connection conn, String sql, Object... params)
588            throws SQLException {
589    
590            PreparedStatement stmt = null;
591            int rows = 0;
592    
593            try {
594                stmt = this.prepareStatement(conn, sql);
595                this.fillStatement(stmt, params);
596                rows = stmt.executeUpdate();
597    
598            } catch (SQLException e) {
599                this.rethrow(e, sql, params);
600    
601            } finally {
602                close(stmt);
603            }
604    
605            return rows;
606        }
607    
608        /**
609         * Executes the given INSERT, UPDATE, or DELETE SQL statement without
610         * any replacement parameters. The <code>Connection</code> is retrieved 
611         * from the <code>DataSource</code> set in the constructor.  This 
612         * <code>Connection</code> must be in auto-commit mode or the update will 
613         * not be saved. 
614         * 
615         * @param sql The SQL statement to execute.
616         * @throws SQLException if a database access error occurs
617         * @return The number of rows updated.
618         */
619        public int update(String sql) throws SQLException {
620            return this.update(sql, (Object[]) null);
621        }
622    
623        /**
624         * Executes the given INSERT, UPDATE, or DELETE SQL statement with
625         * a single replacement parameter.  The <code>Connection</code> is 
626         * retrieved from the <code>DataSource</code> set in the constructor.
627         * This <code>Connection</code> must be in auto-commit mode or the 
628         * update will not be saved. 
629         * 
630         * @param sql The SQL statement to execute.
631         * @param param The replacement parameter.
632         * @throws SQLException if a database access error occurs
633         * @return The number of rows updated.
634         */
635        public int update(String sql, Object param) throws SQLException {
636            return this.update(sql, new Object[] { param });
637        }
638    
639        /**
640         * Executes the given INSERT, UPDATE, or DELETE SQL statement.  The 
641         * <code>Connection</code> is retrieved from the <code>DataSource</code> 
642         * set in the constructor.  This <code>Connection</code> must be in 
643         * auto-commit mode or the update will not be saved. 
644         * 
645         * @param sql The SQL statement to execute.
646         * @param params Initializes the PreparedStatement's IN (i.e. '?') 
647         * parameters.
648         * @throws SQLException if a database access error occurs
649         * @return The number of rows updated.
650         */
651        public int update(String sql, Object... params) throws SQLException {
652            Connection conn = this.prepareConnection();
653    
654            try {
655                return this.update(conn, sql, params);
656            } finally {
657                close(conn);
658            }
659        }
660        
661        /**
662         * Wrap the <code>ResultSet</code> in a decorator before processing it.
663         * This implementation returns the <code>ResultSet</code> it is given
664         * without any decoration.
665         *
666         * <p>
667         * Often, the implementation of this method can be done in an anonymous 
668         * inner class like this:
669         * </p>
670         * <pre> 
671         * QueryRunner run = new QueryRunner() {
672         *     protected ResultSet wrap(ResultSet rs) {
673         *         return StringTrimmedResultSet.wrap(rs);
674         *     }
675         * };
676         * </pre>
677         * 
678         * @param rs The <code>ResultSet</code> to decorate; never 
679         * <code>null</code>.
680         * @return The <code>ResultSet</code> wrapped in some decorator. 
681         */
682        protected ResultSet wrap(ResultSet rs) {
683            return rs;
684        }
685        
686        /**
687         * Close a <code>Connection</code>.  This implementation avoids closing if 
688         * null and does <strong>not</strong> suppress any exceptions.  Subclasses
689         * can override to provide special handling like logging.
690         * @param conn Connection to close
691         * @throws SQLException if a database access error occurs
692         * @since DbUtils 1.1
693         */
694        protected void close(Connection conn) throws SQLException {
695            DbUtils.close(conn);
696        }
697        
698        /**
699         * Close a <code>Statement</code>.  This implementation avoids closing if 
700         * null and does <strong>not</strong> suppress any exceptions.  Subclasses
701         * can override to provide special handling like logging.
702         * @param stmt Statement to close
703         * @throws SQLException if a database access error occurs
704         * @since DbUtils 1.1
705         */
706        protected void close(Statement stmt) throws SQLException {
707            DbUtils.close(stmt);
708        }
709    
710        /**
711         * Close a <code>ResultSet</code>.  This implementation avoids closing if 
712         * null and does <strong>not</strong> suppress any exceptions.  Subclasses
713         * can override to provide special handling like logging.
714         * @param rs ResultSet to close
715         * @throws SQLException if a database access error occurs
716         * @since DbUtils 1.1
717         */
718        protected void close(ResultSet rs) throws SQLException {
719            DbUtils.close(rs);
720        }
721    
722    }