HHH-16710 constructor-based instantiation for native queries
This commit is contained in:
parent
280df7c98d
commit
acf9495af3
|
@ -45,6 +45,7 @@ import org.hibernate.id.uuid.StandardRandomStrategy;
|
|||
import org.hibernate.jdbc.ReturningWork;
|
||||
import org.hibernate.jdbc.Work;
|
||||
import org.hibernate.jdbc.WorkExecutorVisitable;
|
||||
import org.hibernate.jpa.spi.NativeQueryConstructorTransformer;
|
||||
import org.hibernate.jpa.spi.NativeQueryListTransformer;
|
||||
import org.hibernate.jpa.spi.NativeQueryMapTransformer;
|
||||
import org.hibernate.jpa.spi.NativeQueryTupleTransformer;
|
||||
|
@ -101,6 +102,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
|
|||
import jakarta.persistence.criteria.CriteriaUpdate;
|
||||
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static org.hibernate.internal.util.ReflectHelper.isClass;
|
||||
import static org.hibernate.internal.util.StringHelper.isEmpty;
|
||||
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
|
||||
import static org.hibernate.jpa.internal.util.FlushModeTypeHelper.getFlushModeType;
|
||||
|
@ -850,19 +852,26 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
|
||||
protected <T> void addResultType(Class<T> resultClass, NativeQueryImplementor<T> query) {
|
||||
if ( Tuple.class.equals( resultClass ) ) {
|
||||
query.setTupleTransformer( new NativeQueryTupleTransformer() );
|
||||
query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE );
|
||||
}
|
||||
else if ( Map.class.equals( resultClass ) ) {
|
||||
query.setTupleTransformer( new NativeQueryMapTransformer() );
|
||||
query.setTupleTransformer( NativeQueryMapTransformer.INSTANCE );
|
||||
}
|
||||
else if ( List.class.equals( resultClass ) ) {
|
||||
query.setTupleTransformer( new NativeQueryListTransformer() );
|
||||
query.setTupleTransformer( NativeQueryListTransformer.INSTANCE );
|
||||
}
|
||||
else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) {
|
||||
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
|
||||
}
|
||||
else if ( resultClass != Object.class && resultClass != Object[].class ) {
|
||||
query.addResultTypeClass( resultClass );
|
||||
if ( isClass( resultClass )
|
||||
&& getTypeConfiguration().getJavaTypeRegistry().findDescriptor( resultClass ) == null ) {
|
||||
// not a basic type
|
||||
query.setTupleTransformer( new NativeQueryConstructorTransformer<>( resultClass ) );
|
||||
}
|
||||
else {
|
||||
query.addResultTypeClass( resultClass );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -884,7 +893,13 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
@SuppressWarnings("unchecked")
|
||||
final NativeQueryImplementor<T> query = createNativeQuery( sqlString, resultSetMappingName );
|
||||
if ( Tuple.class.equals( resultClass ) ) {
|
||||
query.setTupleTransformer( new NativeQueryTupleTransformer() );
|
||||
query.setTupleTransformer( NativeQueryTupleTransformer.INSTANCE );
|
||||
}
|
||||
else if ( Map.class.equals( resultClass ) ) {
|
||||
query.setTupleTransformer( NativeQueryMapTransformer.INSTANCE );
|
||||
}
|
||||
else if ( List.class.equals( resultClass ) ) {
|
||||
query.setTupleTransformer( NativeQueryListTransformer.INSTANCE );
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
|
|
@ -898,4 +898,11 @@ public final class ReflectHelper {
|
|||
throw new AssertionFailure("member should have been a method or field");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isClass(Class<?> resultClass) {
|
||||
return !resultClass.isArray()
|
||||
&& !resultClass.isPrimitive()
|
||||
&& !resultClass.isEnum()
|
||||
&& !resultClass.isInterface();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.jpa.spi;
|
||||
|
||||
import org.hibernate.InstantiationException;
|
||||
import org.hibernate.query.TupleTransformer;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link TupleTransformer} for handling {@link List} results from native queries.
|
||||
*
|
||||
* @author Gavin King
|
||||
*/
|
||||
public class NativeQueryConstructorTransformer<T> implements TupleTransformer<T> {
|
||||
|
||||
private final Class<T> resultClass;
|
||||
private Constructor<T> constructor;
|
||||
|
||||
private Constructor<T> constructor(Object[] elements) {
|
||||
if ( constructor == null ) {
|
||||
try {
|
||||
// we cannot be sure of the "true" parameter types
|
||||
// of the constructor we're looking for, so we need
|
||||
// to do something a bit weird here: match on just
|
||||
// the number of parameters
|
||||
for ( final Constructor<?> candidate : resultClass.getDeclaredConstructors() ) {
|
||||
final Class<?>[] parameterTypes = candidate.getParameterTypes();
|
||||
if ( parameterTypes.length == elements.length ) {
|
||||
// found a candidate with the right number
|
||||
// of parameters
|
||||
if ( constructor == null ) {
|
||||
constructor = resultClass.getDeclaredConstructor( parameterTypes );
|
||||
constructor.setAccessible( true );
|
||||
}
|
||||
else {
|
||||
// ambiguous, more than one constructor
|
||||
// with the right number of parameters
|
||||
constructor = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new InstantiationException( "Cannot instantiate query result type ", resultClass, e );
|
||||
}
|
||||
if ( constructor == null ) {
|
||||
throw new InstantiationException( "Result class must have a single constructor with exactly "
|
||||
+ elements.length + " parameters", resultClass );
|
||||
}
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
public NativeQueryConstructorTransformer(Class<T> resultClass) {
|
||||
this.resultClass = resultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T transformTuple(Object[] tuple, String[] aliases) {
|
||||
try {
|
||||
return constructor( tuple ).newInstance( tuple );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new InstantiationException( "Cannot instantiate query result type", resultClass, e );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,9 @@ import java.util.List;
|
|||
* @author Gavin King
|
||||
*/
|
||||
public class NativeQueryListTransformer implements TupleTransformer<List<Object>> {
|
||||
|
||||
public static final NativeQueryListTransformer INSTANCE = new NativeQueryListTransformer();
|
||||
|
||||
@Override
|
||||
public List<Object> transformTuple(Object[] tuple, String[] aliases) {
|
||||
return List.of( tuple );
|
||||
|
|
|
@ -19,6 +19,9 @@ import static java.util.Locale.ROOT;
|
|||
* @author Gavin King
|
||||
*/
|
||||
public class NativeQueryMapTransformer implements TupleTransformer<Map<String,Object>> {
|
||||
|
||||
public static final NativeQueryMapTransformer INSTANCE = new NativeQueryMapTransformer();
|
||||
|
||||
@Override
|
||||
public Map<String,Object> transformTuple(Object[] tuple, String[] aliases) {
|
||||
Map<String,Object> map = new HashMap<>( aliases.length );
|
||||
|
|
|
@ -27,6 +27,8 @@ import static java.util.Locale.ROOT;
|
|||
*/
|
||||
public class NativeQueryTupleTransformer implements ResultTransformer<Tuple>, TypedTupleTransformer<Tuple> {
|
||||
|
||||
public static final NativeQueryTupleTransformer INSTANCE = new NativeQueryTupleTransformer();
|
||||
|
||||
@Override
|
||||
public Tuple transformTuple(Object[] tuple, String[] aliases) {
|
||||
return new NativeTupleImpl( tuple, aliases );
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Map;
|
|||
|
||||
import jakarta.persistence.Tuple;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.InstantiationException;
|
||||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
|
@ -60,6 +61,7 @@ import org.hibernate.sql.results.internal.TupleMetadata;
|
|||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
||||
import org.hibernate.sql.results.spi.RowTransformer;
|
||||
|
||||
import static org.hibernate.internal.util.ReflectHelper.isClass;
|
||||
import static org.hibernate.query.sqm.internal.QuerySqmImpl.CRITERIA_HQL_STRING;
|
||||
|
||||
/**
|
||||
|
@ -207,9 +209,12 @@ public class ConcreteSqmSelectQueryPlan<R> implements SelectQueryPlan<R> {
|
|||
else if ( Map.class.equals( resultType ) ) {
|
||||
return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
|
||||
}
|
||||
else {
|
||||
else if ( isClass( resultType ) ) {
|
||||
return new RowTransformerConstructorImpl<>( resultType, tupleMetadata );
|
||||
}
|
||||
else {
|
||||
throw new InstantiationException( "Query result type is not instantiable", resultType );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,21 @@ public class ImplicitInstantiationTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSqlRecordInstantiationWithoutAlias(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> {
|
||||
session.persist(new Thing(1L, "thing"));
|
||||
Record result = session.createNativeQuery("select id, upper(name) as name from thingy_table", Record.class)
|
||||
.addSynchronizedEntityClass(Thing.class)
|
||||
.getSingleResult();
|
||||
assertEquals( result.id(), 1L );
|
||||
assertEquals( result.name(), "THING" );
|
||||
session.getTransaction().setRollbackOnly();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTupleInstantiationWithAlias(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
|
@ -163,7 +178,7 @@ public class ImplicitInstantiationTest {
|
|||
scope.inTransaction(
|
||||
session -> {
|
||||
session.persist(new Thing(1L, "thing"));
|
||||
List result = session.createNativeQuery("select id as id, upper(name) as name from thingy_table", List.class)
|
||||
List result = session.createNativeQuery("select id, upper(name) as name from thingy_table", List.class)
|
||||
.addSynchronizedEntityClass(Thing.class)
|
||||
.getSingleResult();
|
||||
assertEquals( result.get(0), 1L );
|
||||
|
|
Loading…
Reference in New Issue