HHH-7387 - Integrate Draft 6 of the JPA 2.1 spec : REF_CURSOR param handling

This commit is contained in:
Steve Ebersole 2012-07-12 16:05:51 -05:00
parent cb13cea1ac
commit 219707df1d
38 changed files with 798 additions and 27 deletions

View File

@ -27,6 +27,7 @@ import javax.persistence.ParameterMode;
import javax.persistence.TemporalType;
import java.util.List;
import org.hibernate.internal.StoredProcedureCallImpl;
import org.hibernate.type.Type;
/**

View File

@ -1466,14 +1466,16 @@ public abstract class Dialect implements ConversionContext {
// callable statement support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Registers an OUT parameter which will be returning a
* {@link java.sql.ResultSet}. How this is accomplished varies greatly
* from DB to DB, hence its inclusion (along with {@link #getResultSet}) here.
* Registers a parameter (either OUT, or the new REF_CURSOR param type available in Java 8) capable of
* returning {@link java.sql.ResultSet} *by position*. Pre-Java 8, registering such ResultSet-returning
* parameters varied greatly across database and drivers; hence its inclusion as part of the Dialect contract.
*
* @param statement The callable statement.
* @param position The bind position at which to register the OUT param.
* @param position The bind position at which to register the output param.
*
* @return The number of (contiguous) bind positions used.
* @throws SQLException Indicates problems registering the OUT param.
*
* @throws SQLException Indicates problems registering the param.
*/
public int registerResultSetOutParameter(CallableStatement statement, int position) throws SQLException {
throw new UnsupportedOperationException(
@ -1482,6 +1484,25 @@ public abstract class Dialect implements ConversionContext {
);
}
/**
* Registers a parameter (either OUT, or the new REF_CURSOR param type available in Java 8) capable of
* returning {@link java.sql.ResultSet} *by name*. Pre-Java 8, registering such ResultSet-returning
* parameters varied greatly across database and drivers; hence its inclusion as part of the Dialect contract.
*
* @param statement The callable statement.
* @param name The parameter name (for drivers which support named parameters).
*
* @return The number of (contiguous) bind positions used.
*
* @throws SQLException Indicates problems registering the param.
*/
public int registerResultSetOutParameter(CallableStatement statement, String name) throws SQLException {
throw new UnsupportedOperationException(
getClass().getName() +
" does not support resultsets via stored procedures"
);
}
/**
* Given a callable statement previously processed by {@link #registerResultSetOutParameter},
* extract the {@link java.sql.ResultSet} from the OUT parameter.
@ -1497,6 +1518,42 @@ public abstract class Dialect implements ConversionContext {
);
}
/**
* Given a callable statement previously processed by {@link #registerResultSetOutParameter},
* extract the {@link java.sql.ResultSet}.
*
* @param statement The callable statement.
* @param position The bind position at which to register the output param.
*
* @return The extracted result set.
*
* @throws SQLException Indicates problems extracting the result set.
*/
public ResultSet getResultSet(CallableStatement statement, int position) throws SQLException {
throw new UnsupportedOperationException(
getClass().getName() +
" does not support resultsets via stored procedures"
);
}
/**
* Given a callable statement previously processed by {@link #registerResultSetOutParameter},
* extract the {@link java.sql.ResultSet} from the OUT parameter.
*
* @param statement The callable statement.
* @param name The parameter name (for drivers which support named parameters).
*
* @return The extracted result set.
*
* @throws SQLException Indicates problems extracting the result set.
*/
public ResultSet getResultSet(CallableStatement statement, String name) throws SQLException {
throw new UnsupportedOperationException(
getClass().getName() +
" does not support resultsets via stored procedures"
);
}
// current timestamp support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**

View File

@ -57,6 +57,7 @@ import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.service.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.jdbc.cursor.internal.StandardRefCursorSupport;
import org.hibernate.service.jdbc.dialect.spi.DialectFactory;
import org.hibernate.service.spi.Configurable;
import org.hibernate.service.spi.ServiceRegistryAwareService;
@ -92,6 +93,8 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
Dialect dialect = null;
LobCreatorBuilder lobCreatorBuilder = null;
boolean metaSupportsRefCursors = false;
boolean metaSupportsNamedParams = false;
boolean metaSupportsScrollable = false;
boolean metaSupportsGetGeneratedKeys = false;
boolean metaSupportsBatchUpdates = false;
@ -133,6 +136,8 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
LOG.debugf( "JDBC version : %s.%s", meta.getJDBCMajorVersion(), meta.getJDBCMinorVersion() );
}
metaSupportsRefCursors = StandardRefCursorSupport.supportsRefCursors( meta );
metaSupportsNamedParams = meta.supportsNamedParameters();
metaSupportsScrollable = meta.supportsResultSetType( ResultSet.TYPE_SCROLL_INSENSITIVE );
metaSupportsBatchUpdates = meta.supportsBatchUpdates();
metaReportsDDLCausesTxnCommit = meta.dataDefinitionCausesTransactionCommit();
@ -191,6 +196,8 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
this.sqlStatementLogger = new SqlStatementLogger( showSQL, formatSQL );
this.extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl(
metaSupportsRefCursors,
metaSupportsNamedParams,
metaSupportsScrollable,
metaSupportsGetGeneratedKeys,
metaSupportsBatchUpdates,
@ -316,6 +323,8 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
}
private static class ExtractedDatabaseMetaDataImpl implements ExtractedDatabaseMetaData {
private final boolean supportsRefCursors;
private final boolean supportsNamedParameters;
private final boolean supportsScrollableResults;
private final boolean supportsGetGeneratedKeys;
private final boolean supportsBatchUpdates;
@ -329,6 +338,8 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
private final LinkedHashSet<TypeInfo> typeInfoSet;
private ExtractedDatabaseMetaDataImpl(
boolean supportsRefCursors,
boolean supportsNamedParameters,
boolean supportsScrollableResults,
boolean supportsGetGeneratedKeys,
boolean supportsBatchUpdates,
@ -340,6 +351,8 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
String connectionSchemaName,
String connectionCatalogName,
LinkedHashSet<TypeInfo> typeInfoSet) {
this.supportsRefCursors = supportsRefCursors;
this.supportsNamedParameters = supportsNamedParameters;
this.supportsScrollableResults = supportsScrollableResults;
this.supportsGetGeneratedKeys = supportsGetGeneratedKeys;
this.supportsBatchUpdates = supportsBatchUpdates;
@ -353,6 +366,16 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
this.typeInfoSet = typeInfoSet;
}
@Override
public boolean supportsRefCursors() {
return supportsRefCursors;
}
@Override
public boolean supportsNamedParameters() {
return supportsNamedParameters;
}
@Override
public boolean supportsScrollableResults() {
return supportsScrollableResults;

View File

@ -44,6 +44,22 @@ public interface ExtractedDatabaseMetaData {
UNKOWN
}
/**
* Does the driver report supporting named parameters?
*
* @return {@code true} indicates the driver reported true; {@code false} indicates the driver reported false
* or that the driver could not be asked.
*/
public boolean supportsNamedParameters();
/**
* Does the driver report supporting REF_CURSORs?
*
* @return {@code true} indicates the driver reported true; {@code false} indicates the driver reported false
* or that the driver could not be asked.
*/
public boolean supportsRefCursors();
/**
* Did the driver report to supporting scrollable result sets?
*

View File

@ -47,12 +47,14 @@ import org.hibernate.StoredProcedureCall;
import org.hibernate.StoredProcedureOutputs;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.type.DateType;
import org.hibernate.type.ProcedureParameterExtractionAware;
import org.hibernate.type.Type;
@ -156,22 +158,42 @@ public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl impl
private void registerParameter(StoredProcedureParameterImplementor parameter) {
if ( StringHelper.isNotEmpty( parameter.getName() ) ) {
if ( typeOfParameters == TypeOfParameter.POSITIONAL ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
typeOfParameters = TypeOfParameter.NAMED;
registeredParameters.add( parameter );
prepareForNamedParameters();
}
else if ( parameter.getPosition() != null ) {
if ( typeOfParameters == TypeOfParameter.NAMED ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
typeOfParameters = TypeOfParameter.POSITIONAL;
registeredParameters.add( parameter.getPosition(), parameter );
prepareForPositionalParameters();
}
else {
throw new IllegalArgumentException( "Given parameter did not define name nor position [" + parameter + "]" );
}
registeredParameters.add( parameter );
}
private void prepareForPositionalParameters() {
if ( typeOfParameters == TypeOfParameter.NAMED ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
typeOfParameters = TypeOfParameter.POSITIONAL;
}
private void prepareForNamedParameters() {
if ( typeOfParameters == TypeOfParameter.POSITIONAL ) {
throw new QueryException( "Cannot mix named and positional parameters" );
}
if ( typeOfParameters == null ) {
// protect to only do this check once
final ExtractedDatabaseMetaData databaseMetaData = session().getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getJdbcServices()
.getExtractedMetaDataSupport();
if ( ! databaseMetaData.supportsNamedParameters() ) {
throw new QueryException(
"Named stored procedure parameters used, but JDBC driver does not support named parameters"
);
}
typeOfParameters = TypeOfParameter.NAMED;
}
}
@Override
@ -183,8 +205,8 @@ public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl impl
@Override
@SuppressWarnings("unchecked")
public List getRegisteredParameters() {
return registeredParameters;
public List<StoredProcedureParameter> getRegisteredParameters() {
return new ArrayList<StoredProcedureParameter>( registeredParameters );
}
@Override
@ -197,7 +219,8 @@ public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl impl
return parameter;
}
}
throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" ); }
throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" );
}
@Override
public StoredProcedureParameterImplementor getRegisteredParameter(int position) {
@ -320,6 +343,16 @@ public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl impl
return qp;
}
public StoredProcedureParameterImplementor[] collectRefCursorParameters() {
List<StoredProcedureParameterImplementor> refCursorParams = new ArrayList<StoredProcedureParameterImplementor>();
for ( StoredProcedureParameterImplementor param : registeredParameters ) {
if ( param.getMode() == ParameterMode.REF_CURSOR ) {
refCursorParams.add( param );
}
}
return refCursorParams.toArray( new StoredProcedureParameterImplementor[refCursorParams.size()] );
}
/**
* Ternary logic enum
*/
@ -442,6 +475,19 @@ public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl impl
}
}
}
else {
// we have a REF_CURSOR type param
if ( procedureCall.typeOfParameters == TypeOfParameter.NAMED ) {
session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, getName() );
}
else {
session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.registerRefCursorParameter( statement, getPosition() );
}
}
}
public int[] getSqlTypes() {
@ -476,6 +522,13 @@ public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl impl
@Override
@SuppressWarnings("unchecked")
public T extract(CallableStatement statement) {
if ( mode == ParameterMode.IN ) {
throw new QueryException( "IN parameter not valid for output extraction" );
}
else if ( mode == ParameterMode.REF_CURSOR ) {
throw new QueryException( "REF_CURSOR parameters should be accessed via results" );
}
try {
if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) {
return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( statement, startIndex, session() );

View File

@ -23,24 +23,29 @@
*/
package org.hibernate.internal;
import javax.persistence.ParameterMode;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.JDBCException;
import org.hibernate.StoredProcedureCall.StoredProcedureParameter;
import org.hibernate.StoredProcedureOutputs;
import org.hibernate.StoredProcedureResultSetReturn;
import org.hibernate.StoredProcedureReturn;
import org.hibernate.StoredProcedureUpdateCountReturn;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.StoredProcedureCallImpl.StoredProcedureParameterImplementor;
import org.hibernate.loader.custom.CustomLoader;
import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.loader.custom.Return;
import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor;
import org.hibernate.service.jdbc.cursor.spi.RefCursorSupport;
/**
* @author Steve Ebersole
@ -49,15 +54,19 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
private final StoredProcedureCallImpl procedureCall;
private final CallableStatement callableStatement;
private final StoredProcedureParameterImplementor[] refCursorParameters;
private final CustomLoaderExtension loader;
private CurrentReturnDescriptor currentReturnDescriptor;
private boolean executed = false;
private int refCursorParamIndex = 0;
StoredProcedureOutputsImpl(StoredProcedureCallImpl procedureCall, CallableStatement callableStatement) {
this.procedureCall = procedureCall;
this.callableStatement = callableStatement;
this.refCursorParameters = procedureCall.collectRefCursorParameters();
// For now...
this.loader = buildSpecializedCustomLoader( procedureCall );
}
@ -105,14 +114,16 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
}
}
currentReturnDescriptor = new CurrentReturnDescriptor( isResultSet, updateCount );
currentReturnDescriptor = new CurrentReturnDescriptor( isResultSet, updateCount, refCursorParamIndex );
}
return hasMoreResults( currentReturnDescriptor );
}
private boolean hasMoreResults(CurrentReturnDescriptor descriptor) {
return currentReturnDescriptor.isResultSet || currentReturnDescriptor.updateCount >= 0;
return descriptor.isResultSet
|| descriptor.updateCount >= 0
|| descriptor.refCursorParamIndex < refCursorParameters.length;
}
@Override
@ -141,22 +152,45 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
throw convert( e, "Error calling CallableStatement.getResultSet" );
}
}
else {
else if ( copyReturnDescriptor.updateCount >= 0 ) {
return new UpdateCountReturn( this, copyReturnDescriptor.updateCount );
}
else {
this.refCursorParamIndex++;
ResultSet resultSet;
int refCursorParamIndex = copyReturnDescriptor.refCursorParamIndex;
StoredProcedureParameterImplementor refCursorParam = refCursorParameters[refCursorParamIndex];
if ( refCursorParam.getName() != null ) {
resultSet = procedureCall.session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.getResultSet( callableStatement, refCursorParam.getName() );
}
else {
resultSet = procedureCall.session().getFactory().getServiceRegistry()
.getService( RefCursorSupport.class )
.getResultSet( callableStatement, refCursorParam.getPosition() );
}
return new ResultSetReturn( this, resultSet );
}
}
protected JDBCException convert(SQLException e, String message) {
return procedureCall.session().getFactory().getSQLExceptionHelper().convert( e, message, procedureCall.getProcedureName() );
return procedureCall.session().getFactory().getSQLExceptionHelper().convert(
e,
message,
procedureCall.getProcedureName()
);
}
private static class CurrentReturnDescriptor {
private final boolean isResultSet;
private final int updateCount;
private final int refCursorParamIndex;
private CurrentReturnDescriptor(boolean resultSet, int updateCount) {
isResultSet = resultSet;
private CurrentReturnDescriptor(boolean isResultSet, int updateCount, int refCursorParamIndex) {
this.isResultSet = isResultSet;
this.updateCount = updateCount;
this.refCursorParamIndex = refCursorParamIndex;
}
}

View File

@ -501,6 +501,12 @@ public class SimpleValue implements KeyValue {
throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, index, options ) );
}
@Override
@SuppressWarnings("unchecked")
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return (X) converter.convertToEntityAttribute( realExtractor.extract( statement, new String[] {name}, options ) );
}
};
}
}

View File

@ -38,6 +38,7 @@ import org.hibernate.service.config.internal.ConfigurationServiceInitiator;
import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator;
import org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator;
import org.hibernate.service.jdbc.connections.internal.MultiTenantConnectionProviderInitiator;
import org.hibernate.service.jdbc.cursor.internal.RefCursorSupportInitiator;
import org.hibernate.service.jdbc.dialect.internal.DialectFactoryInitiator;
import org.hibernate.service.jdbc.dialect.internal.DialectResolverInitiator;
import org.hibernate.service.jmx.internal.JmxServiceInitiator;
@ -72,6 +73,7 @@ public class StandardServiceInitiators {
serviceInitiators.add( DialectFactoryInitiator.INSTANCE );
serviceInitiators.add( BatchBuilderInitiator.INSTANCE );
serviceInitiators.add( JdbcServicesInitiator.INSTANCE );
serviceInitiators.add( RefCursorSupportInitiator.INSTANCE );
serviceInitiators.add( MutableIdentifierGeneratorFactoryInitiator.INSTANCE);

View File

@ -0,0 +1,47 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.jdbc.cursor.internal;
import java.util.Map;
import org.hibernate.service.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.service.spi.BasicServiceInitiator;
import org.hibernate.service.spi.ServiceRegistryImplementor;
/**
* @author Steve Ebersole
*/
public class RefCursorSupportInitiator implements BasicServiceInitiator<RefCursorSupport> {
public static final RefCursorSupportInitiator INSTANCE = new RefCursorSupportInitiator();
@Override
public RefCursorSupport initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
return new StandardRefCursorSupport();
}
@Override
public Class<RefCursorSupport> getServiceInitiated() {
return RefCursorSupport.class;
}
}

View File

@ -0,0 +1,233 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.jdbc.cursor.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.service.jdbc.cursor.spi.RefCursorSupport;
import org.hibernate.service.spi.InjectService;
/**
* @author Steve Ebersole
*/
public class StandardRefCursorSupport implements RefCursorSupport {
private static final Logger log = Logger.getLogger( StandardRefCursorSupport.class );
private JdbcServices jdbcServices;
@InjectService
@SuppressWarnings("UnusedDeclaration")
public void injectJdbcServices(JdbcServices jdbcServices) {
this.jdbcServices = jdbcServices;
}
@Override
public void registerRefCursorParameter(CallableStatement statement, int position) {
if ( jdbcServices.getExtractedMetaDataSupport().supportsRefCursors() ) {
try {
statement.registerOutParameter( position, refCursorTypeCode() );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert( e, "Error registering REF_CURSOR parameter [" + position + "]" );
}
}
else {
try {
jdbcServices.getDialect().registerResultSetOutParameter( statement, position );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert( e, "Error asking dialect to register ref cursor parameter [" + position + "]" );
}
}
}
@Override
public void registerRefCursorParameter(CallableStatement statement, String name) {
if ( jdbcServices.getExtractedMetaDataSupport().supportsRefCursors() ) {
try {
statement.registerOutParameter( name, refCursorTypeCode() );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert( e, "Error registering REF_CURSOR parameter [" + name + "]" );
}
}
else {
try {
jdbcServices.getDialect().registerResultSetOutParameter( statement, name );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert( e, "Error asking dialect to register ref cursor parameter [" + name + "]" );
}
}
}
@Override
public ResultSet getResultSet(CallableStatement statement, int position) {
if ( jdbcServices.getExtractedMetaDataSupport().supportsRefCursors() ) {
try {
return (ResultSet) getResultSetByPositionMethod().invoke( statement, position, ResultSet.class );
}
catch (InvocationTargetException e) {
if ( e.getTargetException() instanceof SQLException ) {
throw jdbcServices.getSqlExceptionHelper().convert(
(SQLException) e.getTargetException(),
"Error extracting REF_CURSOR parameter [" + position + "]"
);
}
else {
throw new HibernateException( "Unexpected error extracting REF_CURSOR parameter [" + position + "]", e.getTargetException() );
}
}
catch (Exception e) {
throw new HibernateException( "Unexpected error extracting REF_CURSOR parameter [" + position + "]", e );
}
}
else {
try {
return jdbcServices.getDialect().getResultSet( statement, position );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Error asking dialect to extract ResultSet from CallableStatement parameter [" + position + "]"
);
}
}
}
@Override
public ResultSet getResultSet(CallableStatement statement, String name) {
if ( jdbcServices.getExtractedMetaDataSupport().supportsRefCursors() ) {
try {
return (ResultSet) getResultSetByNameMethod().invoke( statement, name, ResultSet.class );
}
catch (InvocationTargetException e) {
if ( e.getTargetException() instanceof SQLException ) {
throw jdbcServices.getSqlExceptionHelper().convert(
(SQLException) e.getTargetException(),
"Error extracting REF_CURSOR parameter [" + name + "]"
);
}
else {
throw new HibernateException( "Unexpected error extracting REF_CURSOR parameter [" + name + "]", e.getTargetException() );
}
}
catch (Exception e) {
throw new HibernateException( "Unexpected error extracting REF_CURSOR parameter [" + name + "]", e );
}
}
else {
try {
return jdbcServices.getDialect().getResultSet( statement, name );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Error asking dialect to extract ResultSet from CallableStatement parameter [" + name + "]"
);
}
}
}
@SuppressWarnings("UnnecessaryUnboxing")
public static boolean supportsRefCursors(DatabaseMetaData meta) {
// Standard JDBC REF_CURSOR support was not added until Java 8, so we need to use reflection to attempt to
// access these fields/methods...
try {
return ( (Boolean) meta.getClass().getMethod( "supportsRefCursors" ).invoke( null ) ).booleanValue();
}
catch (NoSuchMethodException e) {
log.trace( "JDBC DatabaseMetaData class does not define supportsRefCursors method..." );
}
catch (Exception e) {
log.debug( "Unexpected error trying to gauge level of JDBC REF_CURSOR support : " + e.getMessage() );
}
return false;
}
private static Integer refCursorTypeCode;
@SuppressWarnings("UnnecessaryUnboxing")
private int refCursorTypeCode() {
if ( refCursorTypeCode == null ) {
try {
refCursorTypeCode = (Integer) Types.class.getField( "REF_CURSOR" ).get( null );
}
catch (NoSuchFieldException e) {
throw new HibernateException( "java.sql.Types class does not define REF_CURSOR field..." );
}
catch (IllegalAccessException e) {
throw new HibernateException( "Unexpected error trying to determine REF_CURSOR field value : " + e.getMessage() );
}
}
return refCursorTypeCode.intValue();
}
private static Method getResultSetByPositionMethod;
private Method getResultSetByPositionMethod() {
if ( getResultSetByPositionMethod == null ) {
try {
getResultSetByPositionMethod = CallableStatement.class.getMethod( "getObject", int.class, Class.class );
}
catch (NoSuchMethodException e) {
throw new HibernateException( "CallableStatement class does not define getObject(int,Class) method" );
}
catch (Exception e) {
throw new HibernateException( "Unexpected error trying to access CallableStatement#getObject(int,Class)" );
}
}
return getResultSetByPositionMethod;
}
private static Method getResultSetByNameMethod;
private Method getResultSetByNameMethod() {
if ( getResultSetByNameMethod == null ) {
try {
getResultSetByNameMethod = CallableStatement.class.getMethod( "getObject", String.class, Class.class );
}
catch (NoSuchMethodException e) {
throw new HibernateException( "CallableStatement class does not define getObject(String,Class) method" );
}
catch (Exception e) {
throw new HibernateException( "Unexpected error trying to access CallableStatement#getObject(String,Class)" );
}
}
return getResultSetByNameMethod;
}
}

View File

@ -0,0 +1,73 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.service.jdbc.cursor.spi;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import org.hibernate.service.Service;
/**
* @author Steve Ebersole
*/
public interface RefCursorSupport extends Service {
/**
* Register a parameter capable of returning a {@link java.sql.ResultSet} *by position*.
*
* @param statement The callable statement.
* @param position The bind position at which to register the output param.
*/
public void registerRefCursorParameter(CallableStatement statement, int position);
/**
* Register a parameter capable of returning a {@link java.sql.ResultSet} *by name*.
*
* @param statement The callable statement.
* @param name The parameter name (for drivers which support named parameters).
*/
public void registerRefCursorParameter(CallableStatement statement, String name);
/**
* Given a callable statement previously processed by {@link #registerRefCursorParameter(java.sql.CallableStatement, int)},
* extract the {@link java.sql.ResultSet}.
*
*
* @param statement The callable statement.
* @param position The bind position at which to register the output param.
*
* @return The extracted result set.
*/
public ResultSet getResultSet(CallableStatement statement, int position);
/**
* Given a callable statement previously processed by {@link #registerRefCursorParameter(java.sql.CallableStatement, String)},
* extract the {@link java.sql.ResultSet}.
*
* @param statement The callable statement.
* @param name The parameter name (for drivers which support named parameters).
*
* @return The extracted result set.
*/
public ResultSet getResultSet(CallableStatement statement, String name);
}

View File

@ -413,6 +413,33 @@ public abstract class AbstractStandardBasicType<T>
}
};
return remapSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract( statement, startIndex, options );
return remapSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract(
statement,
startIndex,
options
);
}
@Override
public T extract(CallableStatement statement, String[] paramNames, final SessionImplementor session) throws SQLException {
// todo : have SessionImplementor extend WrapperOptions
final WrapperOptions options = new WrapperOptions() {
public boolean useStreamForLobBinding() {
return Environment.useStreamsForBinary();
}
public LobCreator getLobCreator() {
return Hibernate.getLobCreator( session );
}
public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) {
final SqlTypeDescriptor remapped = sqlTypeDescriptor.canBeRemapped()
? session.getFactory().getDialect().remapSqlTypeDescriptor( sqlTypeDescriptor )
: sqlTypeDescriptor;
return remapped == null ? sqlTypeDescriptor : remapped;
}
};
return remapSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract( statement, paramNames, options );
}
}

View File

@ -772,4 +772,34 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
return resolve( values, session, null );
}
@Override
public Object extract(CallableStatement statement, String[] paramNames, SessionImplementor session) throws SQLException {
// for this form to work all sub-property spans must be one (1)...
Object[] values = new Object[propertySpan];
int indx = 0;
boolean notNull = false;
for ( String paramName : paramNames ) {
// we know this cast is safe from canDoExtraction
final ProcedureParameterExtractionAware propertyType = (ProcedureParameterExtractionAware) propertyTypes[indx];
final Object value = propertyType.extract( statement, new String[] { paramName }, session );
if ( value == null ) {
if ( isKey ) {
return null; //different nullability rules for pk/fk
}
}
else {
notNull = true;
}
values[indx] = value;
}
if ( ! notNull ) {
values = null;
}
return resolve( values, session, null );
}
}

View File

@ -90,6 +90,11 @@ public class PostgresUUIDType extends AbstractSingleColumnStandardBasicType<UUID
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getObject( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getObject( name ), options );
}
};
}
}

View File

@ -55,4 +55,18 @@ public interface ProcedureParameterExtractionAware<T> extends Type {
* @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction}
*/
public T extract(CallableStatement statement, int startIndex, SessionImplementor session) throws SQLException;
/**
* Perform the extraction
*
* @param statement The CallableStatement from which to extract the parameter value(s).
* @param paramNames The parameter names.
* @param session The originating session
*
* @return The extracted value.
*
* @throws SQLException Indicates an issue calling into the CallableStatement
* @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction}
*/
public T extract(CallableStatement statement, String[] paramNames, SessionImplementor session) throws SQLException;
}

View File

@ -47,4 +47,6 @@ public interface ValueExtractor<X> {
public X extract(ResultSet rs, String name, WrapperOptions options) throws SQLException;
public X extract(CallableStatement rs, int index, WrapperOptions options) throws SQLException;
public X extract(CallableStatement statement, String[] paramNames, WrapperOptions options) throws SQLException;
}

View File

@ -120,4 +120,39 @@ public abstract class BasicExtractor<J> implements ValueExtractor<J> {
* @throws SQLException Indicates a problem accessing the parameter value
*/
protected abstract J doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException;
@Override
public J extract(CallableStatement statement, String[] paramNames, WrapperOptions options) throws SQLException {
if ( paramNames.length > 1 ) {
throw new IllegalArgumentException( "Basic value extraction cannot handle multiple output parameters" );
}
final String paramName = paramNames[0];
final J value = doExtract( statement, paramName, options );
if ( value == null || statement.wasNull() ) {
LOG.tracev( "Found [null] as procedure output parameter [{0}]", paramName );
return null;
}
else {
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Found [{0}] as procedure output parameter [{1}]", getJavaDescriptor().extractLoggableRepresentation( value ), paramName );
}
return value;
}
}
/**
* Perform the extraction.
* <p/>
* Called from {@link #extract}. Null checking of the value (as well as consulting {@link ResultSet#wasNull}) is
* done there.
*
* @param statement The callable statement containing the output parameter
* @param name The output parameter name
* @param options The binding options
*
* @return The extracted value.
*
* @throws SQLException Indicates a problem accessing the parameter value
*/
protected abstract J doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException;
}

View File

@ -78,6 +78,11 @@ public class BigIntTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getLong( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getLong( name ), options );
}
};
}
}

View File

@ -80,6 +80,11 @@ public class BitTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBoolean( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBoolean( name ), options );
}
};
}
}

View File

@ -68,6 +68,11 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBlob( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBlob( name ), options );
}
};
}

View File

@ -75,6 +75,11 @@ public class BooleanTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBoolean( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBoolean( name ), options );
}
};
}
}

View File

@ -65,6 +65,11 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getClob( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getClob( name ), options );
}
};
}

View File

@ -79,6 +79,11 @@ public class DateTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getDate( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getDate( name ), options );
}
};
}
}

View File

@ -79,6 +79,11 @@ public class DecimalTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBigDecimal( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBigDecimal( name ), options );
}
};
}
}

View File

@ -78,6 +78,11 @@ public class DoubleTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getDouble( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getDouble( name ), options );
}
};
}
}

View File

@ -78,6 +78,11 @@ public class IntegerTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getInt( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getInt( name ), options );
}
};
}
}

View File

@ -65,6 +65,11 @@ public abstract class NClobTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getNClob( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getNClob( name ), options );
}
};
}

View File

@ -78,6 +78,11 @@ public class NVarcharTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getNString( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getNString( name ), options );
}
};
}
}

View File

@ -78,6 +78,11 @@ public class RealTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getFloat( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getFloat( name ), options );
}
};
}
}

View File

@ -78,6 +78,11 @@ public class SmallIntTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getShort( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getShort( name ), options );
}
};
}
}

View File

@ -142,10 +142,14 @@ public class SqlTypeDescriptorRegistry {
}
@Override
protected Object doExtract(CallableStatement statement, int index, WrapperOptions options)
throws SQLException {
protected Object doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return statement.getObject( index );
}
@Override
protected Object doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return statement.getObject( name );
}
};
}
}

View File

@ -79,6 +79,11 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTime( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTime( name ), options );
}
};
}
}

View File

@ -79,6 +79,11 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTimestamp( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getTimestamp( name ), options );
}
};
}
}

View File

@ -81,6 +81,11 @@ public class TinyIntTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getByte( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getByte( name ), options );
}
};
}
}

View File

@ -75,6 +75,11 @@ public class VarbinaryTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBytes( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getBytes( name ), options );
}
};
}
}

View File

@ -78,6 +78,11 @@ public class VarcharTypeDescriptor implements SqlTypeDescriptor {
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getString( index ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap( statement.getString( name ), options );
}
};
}
}

View File

@ -106,6 +106,16 @@ public class BasicTestingJdbcServiceImpl implements JdbcServices {
}
private static class MetaDataSupportImpl implements ExtractedDatabaseMetaData {
@Override
public boolean supportsRefCursors() {
return false;
}
@Override
public boolean supportsNamedParameters() {
return false;
}
public boolean supportsScrollableResults() {
return false;
}

View File

@ -84,6 +84,15 @@ public class StoredPrefixedStringType
}
return javaTypeDescriptor.wrap( stringValue.substring( PREFIX.length() ), options );
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
String stringValue = statement.getString( name );
if ( ! stringValue.startsWith( PREFIX ) ) {
throw new AssertionFailure( "Value read from procedure output param does not have prefix." );
}
return javaTypeDescriptor.wrap( stringValue.substring( PREFIX.length() ), options );
}
};
}
};