001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.support.rowset;
018
019import java.math.BigDecimal;
020import java.sql.Date;
021import java.sql.ResultSet;
022import java.sql.ResultSetMetaData;
023import java.sql.SQLException;
024import java.sql.Time;
025import java.sql.Timestamp;
026import java.util.Calendar;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Map;
030
031import org.springframework.jdbc.InvalidResultSetAccessException;
032import org.springframework.lang.Nullable;
033
034/**
035 * The default implementation of Spring's {@link SqlRowSet} interface, wrapping a
036 * {@link java.sql.ResultSet}, catching any {@link SQLException SQLExceptions} and
037 * translating them to a corresponding Spring {@link InvalidResultSetAccessException}.
038 *
039 * <p>The passed-in ResultSet should already be disconnected if the SqlRowSet is supposed
040 * to be usable in a disconnected fashion. This means that you will usually pass in a
041 * {@code javax.sql.rowset.CachedRowSet}, which implements the ResultSet interface.
042 *
043 * <p>Note: Since JDBC 4.0, it has been clarified that any methods using a String to identify
044 * the column should be using the column label. The column label is assigned using the ALIAS
045 * keyword in the SQL query string. When the query doesn't use an ALIAS, the default label is
046 * the column name. Most JDBC ResultSet implementations follow this new pattern but there are
047 * exceptions such as the {@code com.sun.rowset.CachedRowSetImpl} class which only uses
048 * the column name, ignoring any column labels. As of Spring 3.0.5, ResultSetWrappingSqlRowSet
049 * will translate column labels to the correct column index to provide better support for the
050 * {@code com.sun.rowset.CachedRowSetImpl} which is the default implementation used by
051 * {@link org.springframework.jdbc.core.JdbcTemplate} when working with RowSets.
052 *
053 * <p>Note: This class implements the {@code java.io.Serializable} marker interface
054 * through the SqlRowSet interface, but is only actually serializable if the disconnected
055 * ResultSet/RowSet contained in it is serializable. Most CachedRowSet implementations
056 * are actually serializable, so this should usually work out.
057 *
058 * @author Thomas Risberg
059 * @author Juergen Hoeller
060 * @since 1.2
061 * @see java.sql.ResultSet
062 * @see javax.sql.rowset.CachedRowSet
063 * @see org.springframework.jdbc.core.JdbcTemplate#queryForRowSet
064 */
065public class ResultSetWrappingSqlRowSet implements SqlRowSet {
066
067        /** use serialVersionUID from Spring 1.2 for interoperability. */
068        private static final long serialVersionUID = -4688694393146734764L;
069
070
071        private final ResultSet resultSet;
072
073        private final SqlRowSetMetaData rowSetMetaData;
074
075        private final Map<String, Integer> columnLabelMap;
076
077
078        /**
079         * Create a new ResultSetWrappingSqlRowSet for the given ResultSet.
080         * @param resultSet a disconnected ResultSet to wrap
081         * (usually a {@code javax.sql.rowset.CachedRowSet})
082         * @throws InvalidResultSetAccessException if extracting
083         * the ResultSetMetaData failed
084         * @see javax.sql.rowset.CachedRowSet
085         * @see java.sql.ResultSet#getMetaData
086         * @see ResultSetWrappingSqlRowSetMetaData
087         */
088        public ResultSetWrappingSqlRowSet(ResultSet resultSet) throws InvalidResultSetAccessException {
089                this.resultSet = resultSet;
090                try {
091                        this.rowSetMetaData = new ResultSetWrappingSqlRowSetMetaData(resultSet.getMetaData());
092                }
093                catch (SQLException se) {
094                        throw new InvalidResultSetAccessException(se);
095                }
096                try {
097                        ResultSetMetaData rsmd = resultSet.getMetaData();
098                        if (rsmd != null) {
099                                int columnCount = rsmd.getColumnCount();
100                                this.columnLabelMap = new HashMap<>(columnCount);
101                                for (int i = 1; i <= columnCount; i++) {
102                                        String key = rsmd.getColumnLabel(i);
103                                        // Make sure to preserve first matching column for any given name,
104                                        // as defined in ResultSet's type-level javadoc (lines 81 to 83).
105                                        if (!this.columnLabelMap.containsKey(key)) {
106                                                this.columnLabelMap.put(key, i);
107                                        }
108                                }
109                        }
110                        else {
111                                this.columnLabelMap = Collections.emptyMap();
112                        }
113                }
114                catch (SQLException se) {
115                        throw new InvalidResultSetAccessException(se);
116                }
117
118        }
119
120
121        /**
122         * Return the underlying ResultSet
123         * (usually a {@code javax.sql.rowset.CachedRowSet}).
124         * @see javax.sql.rowset.CachedRowSet
125         */
126        public final ResultSet getResultSet() {
127                return this.resultSet;
128        }
129
130        /**
131         * @see java.sql.ResultSetMetaData#getCatalogName(int)
132         */
133        @Override
134        public final SqlRowSetMetaData getMetaData() {
135                return this.rowSetMetaData;
136        }
137
138        /**
139         * @see java.sql.ResultSet#findColumn(String)
140         */
141        @Override
142        public int findColumn(String columnLabel) throws InvalidResultSetAccessException {
143                Integer columnIndex = this.columnLabelMap.get(columnLabel);
144                if (columnIndex != null) {
145                        return columnIndex;
146                }
147                else {
148                        try {
149                                return this.resultSet.findColumn(columnLabel);
150                        }
151                        catch (SQLException se) {
152                                throw new InvalidResultSetAccessException(se);
153                        }
154                }
155        }
156
157
158        // RowSet methods for extracting data values
159
160        /**
161         * @see java.sql.ResultSet#getBigDecimal(int)
162         */
163        @Override
164        @Nullable
165        public BigDecimal getBigDecimal(int columnIndex) throws InvalidResultSetAccessException {
166                try {
167                        return this.resultSet.getBigDecimal(columnIndex);
168                }
169                catch (SQLException se) {
170                        throw new InvalidResultSetAccessException(se);
171                }
172        }
173
174        /**
175         * @see java.sql.ResultSet#getBigDecimal(String)
176         */
177        @Override
178        @Nullable
179        public BigDecimal getBigDecimal(String columnLabel) throws InvalidResultSetAccessException {
180                return getBigDecimal(findColumn(columnLabel));
181        }
182
183        /**
184         * @see java.sql.ResultSet#getBoolean(int)
185         */
186        @Override
187        public boolean getBoolean(int columnIndex) throws InvalidResultSetAccessException {
188                try {
189                        return this.resultSet.getBoolean(columnIndex);
190                }
191                catch (SQLException se) {
192                        throw new InvalidResultSetAccessException(se);
193                }
194        }
195
196        /**
197         * @see java.sql.ResultSet#getBoolean(String)
198         */
199        @Override
200        public boolean getBoolean(String columnLabel) throws InvalidResultSetAccessException {
201                return getBoolean(findColumn(columnLabel));
202        }
203
204        /**
205         * @see java.sql.ResultSet#getByte(int)
206         */
207        @Override
208        public byte getByte(int columnIndex) throws InvalidResultSetAccessException {
209                try {
210                        return this.resultSet.getByte(columnIndex);
211                }
212                catch (SQLException se) {
213                        throw new InvalidResultSetAccessException(se);
214                }
215        }
216
217        /**
218         * @see java.sql.ResultSet#getByte(String)
219         */
220        @Override
221        public byte getByte(String columnLabel) throws InvalidResultSetAccessException {
222                return getByte(findColumn(columnLabel));
223        }
224
225        /**
226         * @see java.sql.ResultSet#getDate(int)
227         */
228        @Override
229        @Nullable
230        public Date getDate(int columnIndex) throws InvalidResultSetAccessException {
231                try {
232                        return this.resultSet.getDate(columnIndex);
233                }
234                catch (SQLException se) {
235                        throw new InvalidResultSetAccessException(se);
236                }
237        }
238
239        /**
240         * @see java.sql.ResultSet#getDate(String)
241         */
242        @Override
243        @Nullable
244        public Date getDate(String columnLabel) throws InvalidResultSetAccessException {
245                return getDate(findColumn(columnLabel));
246        }
247
248        /**
249         * @see java.sql.ResultSet#getDate(int, Calendar)
250         */
251        @Override
252        @Nullable
253        public Date getDate(int columnIndex, Calendar cal) throws InvalidResultSetAccessException {
254                try {
255                        return this.resultSet.getDate(columnIndex, cal);
256                }
257                catch (SQLException se) {
258                        throw new InvalidResultSetAccessException(se);
259                }
260        }
261
262        /**
263         * @see java.sql.ResultSet#getDate(String, Calendar)
264         */
265        @Override
266        @Nullable
267        public Date getDate(String columnLabel, Calendar cal) throws InvalidResultSetAccessException {
268                return getDate(findColumn(columnLabel), cal);
269        }
270
271        /**
272         * @see java.sql.ResultSet#getDouble(int)
273         */
274        @Override
275        public double getDouble(int columnIndex) throws InvalidResultSetAccessException {
276                try {
277                        return this.resultSet.getDouble(columnIndex);
278                }
279                catch (SQLException se) {
280                        throw new InvalidResultSetAccessException(se);
281                }
282        }
283
284        /**
285         * @see java.sql.ResultSet#getDouble(String)
286         */
287        @Override
288        public double getDouble(String columnLabel) throws InvalidResultSetAccessException {
289                return getDouble(findColumn(columnLabel));
290        }
291
292        /**
293         * @see java.sql.ResultSet#getFloat(int)
294         */
295        @Override
296        public float getFloat(int columnIndex) throws InvalidResultSetAccessException {
297                try {
298                        return this.resultSet.getFloat(columnIndex);
299                }
300                catch (SQLException se) {
301                        throw new InvalidResultSetAccessException(se);
302                }
303        }
304
305        /**
306         * @see java.sql.ResultSet#getFloat(String)
307         */
308        @Override
309        public float getFloat(String columnLabel) throws InvalidResultSetAccessException {
310                return getFloat(findColumn(columnLabel));
311        }
312
313        /**
314         * @see java.sql.ResultSet#getInt(int)
315         */
316        @Override
317        public int getInt(int columnIndex) throws InvalidResultSetAccessException {
318                try {
319                        return this.resultSet.getInt(columnIndex);
320                }
321                catch (SQLException se) {
322                        throw new InvalidResultSetAccessException(se);
323                }
324        }
325
326        /**
327         * @see java.sql.ResultSet#getInt(String)
328         */
329        @Override
330        public int getInt(String columnLabel) throws InvalidResultSetAccessException {
331                return getInt(findColumn(columnLabel));
332        }
333
334        /**
335         * @see java.sql.ResultSet#getLong(int)
336         */
337        @Override
338        public long getLong(int columnIndex) throws InvalidResultSetAccessException {
339                try {
340                        return this.resultSet.getLong(columnIndex);
341                }
342                catch (SQLException se) {
343                        throw new InvalidResultSetAccessException(se);
344                }
345        }
346
347        /**
348         * @see java.sql.ResultSet#getLong(String)
349         */
350        @Override
351        public long getLong(String columnLabel) throws InvalidResultSetAccessException {
352                return getLong(findColumn(columnLabel));
353        }
354
355        /**
356         * @see java.sql.ResultSet#getNString(int)
357         */
358        @Override
359        @Nullable
360        public String getNString(int columnIndex) throws InvalidResultSetAccessException {
361                try {
362                        return this.resultSet.getNString(columnIndex);
363                }
364                catch (SQLException se) {
365                        throw new InvalidResultSetAccessException(se);
366                }
367        }
368
369        /**
370         * @see java.sql.ResultSet#getNString(String)
371         */
372        @Override
373        @Nullable
374        public String getNString(String columnLabel) throws InvalidResultSetAccessException {
375                return getNString(findColumn(columnLabel));
376        }
377
378        /**
379         * @see java.sql.ResultSet#getObject(int)
380         */
381        @Override
382        @Nullable
383        public Object getObject(int columnIndex) throws InvalidResultSetAccessException {
384                try {
385                        return this.resultSet.getObject(columnIndex);
386                }
387                catch (SQLException se) {
388                        throw new InvalidResultSetAccessException(se);
389                }
390        }
391
392        /**
393         * @see java.sql.ResultSet#getObject(String)
394         */
395        @Override
396        @Nullable
397        public Object getObject(String columnLabel) throws InvalidResultSetAccessException {
398                return getObject(findColumn(columnLabel));
399        }
400
401        /**
402         * @see java.sql.ResultSet#getObject(int, Map)
403         */
404        @Override
405        @Nullable
406        public Object getObject(int columnIndex, Map<String, Class<?>> map) throws InvalidResultSetAccessException {
407                try {
408                        return this.resultSet.getObject(columnIndex, map);
409                }
410                catch (SQLException se) {
411                        throw new InvalidResultSetAccessException(se);
412                }
413        }
414
415        /**
416         * @see java.sql.ResultSet#getObject(String, Map)
417         */
418        @Override
419        @Nullable
420        public Object getObject(String columnLabel, Map<String, Class<?>> map) throws InvalidResultSetAccessException {
421                return getObject(findColumn(columnLabel), map);
422        }
423
424        /**
425         * @see java.sql.ResultSet#getObject(int, Class)
426         */
427        @Override
428        @Nullable
429        public <T> T getObject(int columnIndex, Class<T> type) throws InvalidResultSetAccessException {
430                try {
431                        return this.resultSet.getObject(columnIndex, type);
432                }
433                catch (SQLException se) {
434                        throw new InvalidResultSetAccessException(se);
435                }
436        }
437
438        /**
439         * @see java.sql.ResultSet#getObject(String, Class)
440         */
441        @Override
442        @Nullable
443        public <T> T getObject(String columnLabel, Class<T> type) throws InvalidResultSetAccessException {
444                return getObject(findColumn(columnLabel), type);
445        }
446
447        /**
448         * @see java.sql.ResultSet#getShort(int)
449         */
450        @Override
451        public short getShort(int columnIndex) throws InvalidResultSetAccessException {
452                try {
453                        return this.resultSet.getShort(columnIndex);
454                }
455                catch (SQLException se) {
456                        throw new InvalidResultSetAccessException(se);
457                }
458        }
459
460        /**
461         * @see java.sql.ResultSet#getShort(String)
462         */
463        @Override
464        public short getShort(String columnLabel) throws InvalidResultSetAccessException {
465                return getShort(findColumn(columnLabel));
466        }
467
468        /**
469         * @see java.sql.ResultSet#getString(int)
470         */
471        @Override
472        @Nullable
473        public String getString(int columnIndex) throws InvalidResultSetAccessException {
474                try {
475                        return this.resultSet.getString(columnIndex);
476                }
477                catch (SQLException se) {
478                        throw new InvalidResultSetAccessException(se);
479                }
480        }
481
482        /**
483         * @see java.sql.ResultSet#getString(String)
484         */
485        @Override
486        @Nullable
487        public String getString(String columnLabel) throws InvalidResultSetAccessException {
488                return getString(findColumn(columnLabel));
489        }
490
491        /**
492         * @see java.sql.ResultSet#getTime(int)
493         */
494        @Override
495        @Nullable
496        public Time getTime(int columnIndex) throws InvalidResultSetAccessException {
497                try {
498                        return this.resultSet.getTime(columnIndex);
499                }
500                catch (SQLException se) {
501                        throw new InvalidResultSetAccessException(se);
502                }
503        }
504
505        /**
506         * @see java.sql.ResultSet#getTime(String)
507         */
508        @Override
509        @Nullable
510        public Time getTime(String columnLabel) throws InvalidResultSetAccessException {
511                return getTime(findColumn(columnLabel));
512        }
513
514        /**
515         * @see java.sql.ResultSet#getTime(int, Calendar)
516         */
517        @Override
518        @Nullable
519        public Time getTime(int columnIndex, Calendar cal) throws InvalidResultSetAccessException {
520                try {
521                        return this.resultSet.getTime(columnIndex, cal);
522                }
523                catch (SQLException se) {
524                        throw new InvalidResultSetAccessException(se);
525                }
526        }
527
528        /**
529         * @see java.sql.ResultSet#getTime(String, Calendar)
530         */
531        @Override
532        @Nullable
533        public Time getTime(String columnLabel, Calendar cal) throws InvalidResultSetAccessException {
534                return getTime(findColumn(columnLabel), cal);
535        }
536
537        /**
538         * @see java.sql.ResultSet#getTimestamp(int)
539         */
540        @Override
541        @Nullable
542        public Timestamp getTimestamp(int columnIndex) throws InvalidResultSetAccessException {
543                try {
544                        return this.resultSet.getTimestamp(columnIndex);
545                }
546                catch (SQLException se) {
547                        throw new InvalidResultSetAccessException(se);
548                }
549        }
550
551        /**
552         * @see java.sql.ResultSet#getTimestamp(String)
553         */
554        @Override
555        @Nullable
556        public Timestamp getTimestamp(String columnLabel) throws InvalidResultSetAccessException {
557                return getTimestamp(findColumn(columnLabel));
558        }
559
560        /**
561         * @see java.sql.ResultSet#getTimestamp(int, Calendar)
562         */
563        @Override
564        @Nullable
565        public Timestamp getTimestamp(int columnIndex, Calendar cal) throws InvalidResultSetAccessException {
566                try {
567                        return this.resultSet.getTimestamp(columnIndex, cal);
568                }
569                catch (SQLException se) {
570                        throw new InvalidResultSetAccessException(se);
571                }
572        }
573
574        /**
575         * @see java.sql.ResultSet#getTimestamp(String, Calendar)
576         */
577        @Override
578        @Nullable
579        public Timestamp getTimestamp(String columnLabel, Calendar cal) throws InvalidResultSetAccessException {
580                return getTimestamp(findColumn(columnLabel), cal);
581        }
582
583
584        // RowSet navigation methods
585
586        /**
587         * @see java.sql.ResultSet#absolute(int)
588         */
589        @Override
590        public boolean absolute(int row) throws InvalidResultSetAccessException {
591                try {
592                        return this.resultSet.absolute(row);
593                }
594                catch (SQLException se) {
595                        throw new InvalidResultSetAccessException(se);
596                }
597        }
598
599        /**
600         * @see java.sql.ResultSet#afterLast()
601         */
602        @Override
603        public void afterLast() throws InvalidResultSetAccessException {
604                try {
605                        this.resultSet.afterLast();
606                }
607                catch (SQLException se) {
608                        throw new InvalidResultSetAccessException(se);
609                }
610        }
611
612        /**
613         * @see java.sql.ResultSet#beforeFirst()
614         */
615        @Override
616        public void beforeFirst() throws InvalidResultSetAccessException {
617                try {
618                        this.resultSet.beforeFirst();
619                }
620                catch (SQLException se) {
621                        throw new InvalidResultSetAccessException(se);
622                }
623        }
624
625        /**
626         * @see java.sql.ResultSet#first()
627         */
628        @Override
629        public boolean first() throws InvalidResultSetAccessException {
630                try {
631                        return this.resultSet.first();
632                }
633                catch (SQLException se) {
634                        throw new InvalidResultSetAccessException(se);
635                }
636        }
637
638        /**
639         * @see java.sql.ResultSet#getRow()
640         */
641        @Override
642        public int getRow() throws InvalidResultSetAccessException {
643                try {
644                        return this.resultSet.getRow();
645                }
646                catch (SQLException se) {
647                        throw new InvalidResultSetAccessException(se);
648                }
649        }
650
651        /**
652         * @see java.sql.ResultSet#isAfterLast()
653         */
654        @Override
655        public boolean isAfterLast() throws InvalidResultSetAccessException {
656                try {
657                        return this.resultSet.isAfterLast();
658                }
659                catch (SQLException se) {
660                        throw new InvalidResultSetAccessException(se);
661                }
662        }
663
664        /**
665         * @see java.sql.ResultSet#isBeforeFirst()
666         */
667        @Override
668        public boolean isBeforeFirst() throws InvalidResultSetAccessException {
669                try {
670                        return this.resultSet.isBeforeFirst();
671                }
672                catch (SQLException se) {
673                        throw new InvalidResultSetAccessException(se);
674                }
675        }
676
677        /**
678         * @see java.sql.ResultSet#isFirst()
679         */
680        @Override
681        public boolean isFirst() throws InvalidResultSetAccessException {
682                try {
683                        return this.resultSet.isFirst();
684                }
685                catch (SQLException se) {
686                        throw new InvalidResultSetAccessException(se);
687                }
688        }
689
690        /**
691         * @see java.sql.ResultSet#isLast()
692         */
693        @Override
694        public boolean isLast() throws InvalidResultSetAccessException {
695                try {
696                        return this.resultSet.isLast();
697                }
698                catch (SQLException se) {
699                        throw new InvalidResultSetAccessException(se);
700                }
701        }
702
703        /**
704         * @see java.sql.ResultSet#last()
705         */
706        @Override
707        public boolean last() throws InvalidResultSetAccessException {
708                try {
709                        return this.resultSet.last();
710                }
711                catch (SQLException se) {
712                        throw new InvalidResultSetAccessException(se);
713                }
714        }
715
716        /**
717         * @see java.sql.ResultSet#next()
718         */
719        @Override
720        public boolean next() throws InvalidResultSetAccessException {
721                try {
722                        return this.resultSet.next();
723                }
724                catch (SQLException se) {
725                        throw new InvalidResultSetAccessException(se);
726                }
727        }
728
729        /**
730         * @see java.sql.ResultSet#previous()
731         */
732        @Override
733        public boolean previous() throws InvalidResultSetAccessException {
734                try {
735                        return this.resultSet.previous();
736                }
737                catch (SQLException se) {
738                        throw new InvalidResultSetAccessException(se);
739                }
740        }
741
742        /**
743         * @see java.sql.ResultSet#relative(int)
744         */
745        @Override
746        public boolean relative(int rows) throws InvalidResultSetAccessException {
747                try {
748                        return this.resultSet.relative(rows);
749                }
750                catch (SQLException se) {
751                        throw new InvalidResultSetAccessException(se);
752                }
753        }
754
755        /**
756         * @see java.sql.ResultSet#wasNull()
757         */
758        @Override
759        public boolean wasNull() throws InvalidResultSetAccessException {
760                try {
761                        return this.resultSet.wasNull();
762                }
763                catch (SQLException se) {
764                        throw new InvalidResultSetAccessException(se);
765                }
766        }
767
768}