/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.engine.jdbc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.service.ServiceRegistry;

import static org.hibernate.internal.CoreLogging.messageLogger;

A proxy for a ResultSet delegate, responsible for locally caching the columnName-to-columnIndex resolution that has been found to be inefficient in a few vendor's drivers (i.e., Oracle and Postgres).
Author:Steve Ebersole, Gail Badner
/** * A proxy for a ResultSet delegate, responsible for locally caching the columnName-to-columnIndex resolution that * has been found to be inefficient in a few vendor's drivers (i.e., Oracle and Postgres). * * @author Steve Ebersole * @author Gail Badner */
public class ResultSetWrapperProxy implements InvocationHandler { private static final CoreMessageLogger LOG = messageLogger( ResultSetWrapperProxy.class ); private static final SqlExceptionHelper SQL_EXCEPTION_HELPER = new SqlExceptionHelper( false ); private final ResultSet rs; private final ColumnNameCache columnNameCache; private ResultSetWrapperProxy(ResultSet rs, ColumnNameCache columnNameCache) { this.rs = rs; this.columnNameCache = columnNameCache; }
Generates a proxy wrapping the ResultSet.
Params:
  • resultSet – The resultSet to wrap.
  • columnNameCache – The cache storing data for converting column names to column indexes.
  • serviceRegistry – Access to any needed services
Returns:The generated proxy.
/** * Generates a proxy wrapping the ResultSet. * * @param resultSet The resultSet to wrap. * @param columnNameCache The cache storing data for converting column names to column indexes. * @param serviceRegistry Access to any needed services * * @return The generated proxy. */
public static ResultSet generateProxy( ResultSet resultSet, ColumnNameCache columnNameCache, ServiceRegistry serviceRegistry) { return serviceRegistry.getService( ClassLoaderService.class ).generateProxy( new ResultSetWrapperProxy( resultSet, columnNameCache ), ResultSet.class ); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ( "findColumn".equals( method.getName() ) ) { return findColumn( (String) args[0] ); } if ( isFirstArgColumnLabel( method, args ) ) { try { final Integer columnIndex = findColumn( (String) args[0] ); return invokeMethod( locateCorrespondingColumnIndexMethod( method ), buildColumnIndexMethodArgs( args, columnIndex ) ); } catch ( SQLException ex ) { final String msg = "Exception getting column index for column: [" + args[0] + "].\nReverting to using: [" + args[0] + "] as first argument for method: [" + method + "]"; SQL_EXCEPTION_HELPER.logExceptions( ex, msg ); } catch ( NoSuchMethodException ex ) { LOG.unableToSwitchToMethodUsingColumnIndex( method ); } } return invokeMethod( method, args ); }
Locate the column index corresponding to the given column name via the cache.
Params:
  • columnName – The column name to resolve into an index.
Throws:
  • SQLException – if the ResultSet object does not contain columnName or a database access error occurs
Returns:The column index corresponding to the given column name.
/** * Locate the column index corresponding to the given column name via the cache. * * @param columnName The column name to resolve into an index. * @return The column index corresponding to the given column name. * @throws SQLException if the ResultSet object does not contain columnName or a database access error occurs */
private Integer findColumn(String columnName) throws SQLException { return columnNameCache.getIndexForColumnName( columnName, rs ); } private boolean isFirstArgColumnLabel(Method method, Object[] args) { // method name should start with either get or update if ( ! ( method.getName().startsWith( "get" ) || method.getName().startsWith( "update" ) ) ) { return false; } // method should have arguments, and have same number as incoming arguments if ( ! ( method.getParameterCount() > 0 && args.length == method.getParameterCount() ) ) { return false; } // The first argument should be a String (the column name) //noinspection RedundantIfStatement if ( ! ( String.class.isInstance( args[0] ) && method.getParameterTypes()[0].equals( String.class ) ) ) { return false; } return true; }
For a given ResultSet method passed a column name, locate the corresponding method passed the same parameters but the column index.
Params:
  • columnNameMethod – The method passed the column name
Throws:
Returns:The corresponding method passed the column index.
/** * For a given {@link ResultSet} method passed a column name, locate the corresponding method passed the same * parameters but the column index. * * @param columnNameMethod The method passed the column name * @return The corresponding method passed the column index. * @throws NoSuchMethodException Should never happen, but... */
private Method locateCorrespondingColumnIndexMethod(Method columnNameMethod) throws NoSuchMethodException { final Class[] actualParameterTypes = new Class[columnNameMethod.getParameterCount()]; actualParameterTypes[0] = int.class; System.arraycopy( columnNameMethod.getParameterTypes(), 1, actualParameterTypes, 1, columnNameMethod.getParameterCount() - 1 ); return columnNameMethod.getDeclaringClass().getMethod( columnNameMethod.getName(), actualParameterTypes ); } private Object[] buildColumnIndexMethodArgs(Object[] incomingArgs, Integer columnIndex) { final Object[] actualArgs = new Object[incomingArgs.length]; actualArgs[0] = columnIndex; System.arraycopy( incomingArgs, 1, actualArgs, 1, incomingArgs.length - 1 ); return actualArgs; } private Object invokeMethod(Method method, Object[] args) throws Throwable { try { return method.invoke( rs, args ); } catch ( InvocationTargetException e ) { throw e.getTargetException(); } } }