Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,11 @@ else if ( Map.class.equals( resultType ) ) {
}
else if ( isClass( resultType ) ) {
try {
return new RowTransformerConstructorImpl<>( resultType, tupleMetadata );
return new RowTransformerConstructorImpl<>(
resultType,
tupleMetadata,
sqm.nodeBuilder().getTypeConfiguration()
);
}
catch (InstantiationException ie) {
return new RowTransformerCheckingImpl<>( resultType );
Expand All @@ -310,7 +314,11 @@ else if ( Map.class.equals( resultType ) ) {
return (RowTransformer<T>) new RowTransformerMapImpl( tupleMetadata );
}
else if ( isClass( resultType ) ) {
return new RowTransformerConstructorImpl<>( resultType, tupleMetadata );
return new RowTransformerConstructorImpl<>(
resultType,
tupleMetadata,
sqm.nodeBuilder().getTypeConfiguration()
);
}
else {
throw new QueryTypeMismatchException( "Result type '" + resultType.getSimpleName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
import org.hibernate.sql.results.graph.InitializerParent;
import org.hibernate.sql.results.graph.instantiation.DynamicInstantiationResult;
import org.hibernate.type.descriptor.java.JavaType;

import org.hibernate.type.spi.TypeConfiguration;

import org.jboss.logging.Logger;

import static java.util.stream.Collectors.toList;
import static org.hibernate.sql.results.graph.instantiation.internal.InstantiationHelper.isConstructorCompatible;
import static org.hibernate.sql.results.graph.instantiation.internal.InstantiationHelper.findMatchingConstructor;

/**
* @author Steve Ebersole
Expand Down Expand Up @@ -164,13 +164,14 @@ private DomainResultAssembler<R> assembler(
.getMappingMetamodel()
.getTypeConfiguration();
// find a constructor matching argument types
for ( Constructor<?> constructor : javaType.getJavaTypeClass().getDeclaredConstructors() ) {
if ( isConstructorCompatible( constructor, argumentTypes, typeConfiguration ) ) {
constructor.setAccessible( true );
@SuppressWarnings("unchecked")
final Constructor<R> construct = (Constructor<R>) constructor;
return new DynamicInstantiationAssemblerConstructorImpl<>( construct, javaType, argumentReaders );
}
final Constructor<R> constructor = findMatchingConstructor(
javaType.getJavaTypeClass(),
argumentTypes,
typeConfiguration
);
if ( constructor != null ) {
constructor.setAccessible( true );
return new DynamicInstantiationAssemblerConstructorImpl<>( constructor, javaType, argumentReaders );
}

if ( log.isDebugEnabled() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,20 @@ private static boolean checkArgument(Class<?> targetJavaType, BeanInfo beanInfo,
}

public static boolean isConstructorCompatible(Class<?> javaClass, List<Class<?>> argTypes, TypeConfiguration typeConfiguration) {
for ( Constructor<?> constructor : javaClass.getDeclaredConstructors() ) {
if ( isConstructorCompatible( constructor, argTypes, typeConfiguration) ) {
return true;
return findMatchingConstructor( javaClass, argTypes, typeConfiguration ) != null;
}

public static <T> Constructor<T> findMatchingConstructor(
Class<T> type,
List<Class<?>> argumentTypes,
TypeConfiguration typeConfiguration) {
for ( final Constructor<?> constructor : type.getDeclaredConstructors() ) {
if ( isConstructorCompatible( constructor, argumentTypes, typeConfiguration ) ) {
//noinspection unchecked
return (Constructor<T>) constructor;
}
}
return false;
return null;
}

public static boolean isConstructorCompatible(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@

import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.tree.SqmExpressibleAccessor;
import org.hibernate.type.spi.TypeConfiguration;

import static org.hibernate.query.sqm.tree.expression.Compatibility.areAssignmentCompatible;
import static java.util.stream.Collectors.toList;
import static org.hibernate.sql.results.graph.instantiation.internal.InstantiationHelper.findMatchingConstructor;

/**
* {@link RowTransformer} instantiating an arbitrary class
Expand All @@ -25,25 +27,25 @@ public class RowTransformerConstructorImpl<T> implements RowTransformer<T> {
private final Class<T> type;
private final Constructor<T> constructor;

public RowTransformerConstructorImpl(Class<T> type, TupleMetadata tupleMetadata) {
public RowTransformerConstructorImpl(
Class<T> type,
TupleMetadata tupleMetadata,
TypeConfiguration typeConfiguration) {
this.type = type;
final List<TupleElement<?>> elements = tupleMetadata.getList();
final Class<?>[] sig = new Class[elements.size()];
for (int i = 0; i < elements.size(); i++) {
sig[i] = resolveElementJavaType( elements.get( i ) );
}
if ( sig.length == 1 && sig[0] == null ) {
final List<Class<?>> argumentTypes = elements.stream()
.map( RowTransformerConstructorImpl::resolveElementJavaType )
.collect( toList() );
if ( argumentTypes.size() == 1 && argumentTypes.get( 0 ) == null ) {
// Can not (properly) resolve constructor for single null element
throw new InstantiationException( "Cannot instantiate query result type, argument types are unknown ", type );
}
try {
constructor = findMatchingConstructor( type, sig );
constructor.setAccessible( true );
}
catch (Exception e) {
//TODO try again with primitive types
throw new InstantiationException( "Cannot instantiate query result type ", type, e );

constructor = findMatchingConstructor( type, argumentTypes, typeConfiguration );
if ( constructor == null ) {
throw new InstantiationException( "Cannot instantiate query result type, found no matching constructor", type );
}
constructor.setAccessible( true );
}

private static Class<?> resolveElementJavaType(TupleElement<?> element) {
Expand All @@ -57,36 +59,6 @@ private static Class<?> resolveElementJavaType(TupleElement<?> element) {
return element.getJavaType();
}

private Constructor<T> findMatchingConstructor(Class<T> type, Class<?>[] sig) throws Exception {
try {
return type.getDeclaredConstructor( sig );
}
catch (NoSuchMethodException | SecurityException e) {
constructor_loop:
for ( final Constructor<?> constructor : type.getDeclaredConstructors() ) {
final Class<?>[] parameterTypes = constructor.getParameterTypes();
if ( parameterTypes.length == sig.length ) {
for ( int i = 0; i < sig.length; i++ ) {
final Class<?> parameterType = parameterTypes[i];
final Class<?> argType = sig[i];
final boolean assignmentCompatible;
assignmentCompatible =
argType == null && !parameterType.isPrimitive()
|| areAssignmentCompatible(
parameterType,
argType
);
if ( !assignmentCompatible ) {
continue constructor_loop;
}
}
return (Constructor<T>) constructor;
}
}
throw e;
}
}

@Override
public T transformRow(Object[] row) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,52 @@

import static org.junit.jupiter.api.Assertions.assertEquals;

@DomainModel(
annotatedClasses = { MatchingConstructorTest.TestEntity.class }
)
@DomainModel( annotatedClasses = { DynamicInstantiationConstructorMatchingTest.TestEntity.class } )
@SessionFactory
@Jira("https://hibernate.atlassian.net/browse/HHH-18322")
public class MatchingConstructorTest {

@Jira( "https://hibernate.atlassian.net/browse/HHH-18322" )
@Jira( "https://hibernate.atlassian.net/browse/HHH-18664" )
public class DynamicInstantiationConstructorMatchingTest {
@BeforeAll
public void prepareData(final SessionFactoryScope scope) {
scope.inTransaction(
session -> session.persist( new TestEntity( 1, 42, "test", 13 ) )
);
scope.inTransaction( session -> session.persist( new TestEntity( 1, 42, "test", 13 ) ) );
}

@AfterAll
public void cleanUpData(final SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createQuery( "delete TestEntity" ).executeUpdate()
);
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
}

@Test
void testExplicitConstructor(final SessionFactoryScope scope) {
scope.inSession( session -> {
final var result = session.createQuery(
"select new ConstructorDto(num, str) from TestEntity",
ConstructorDto.class
).getSingleResult();
assertEquals( 42, result.getNum() );
assertEquals( "test", result.getStr() );
} );
}

@Test
void testImplicitConstructor(final SessionFactoryScope scope) {
scope.inSession( session -> {
final var result = session.createQuery( "select num, str from TestEntity", ConstructorDto.class )
.getSingleResult();
assertEquals( 42, result.getNum() );
assertEquals( "test", result.getStr() );
} );
}

@Test
void testExplicitConstructorWithPrimitive(final SessionFactoryScope scope) {
scope.inSession( session -> {
final var result = session.createQuery(
"select num, str from TestEntity",
ConstructorDto.class
"select new ConstructorWithPrimitiveDto(intValue, str) from TestEntity",
ConstructorWithPrimitiveDto.class
)
.setMaxResults( 1 ).getSingleResult();
assertEquals( 42, result.getNum() );
.getSingleResult();
assertEquals( 13, result.getIntValue() );
assertEquals( "test", result.getStr() );
} );
}
Expand All @@ -60,13 +76,13 @@ void testImplicitConstructorWithPrimitive(final SessionFactoryScope scope) {
"select intValue, str from TestEntity",
ConstructorWithPrimitiveDto.class
)
.setMaxResults( 1 ).getSingleResult();
.getSingleResult();
assertEquals( 13, result.getIntValue() );
assertEquals( "test", result.getStr() );
} );
}

@Entity(name = "TestEntity")
@Entity( name = "TestEntity" )
public static class TestEntity {
@Id
private Integer id;
Expand All @@ -86,38 +102,6 @@ public TestEntity(final Integer id, final Integer num, final String str, final i
this.str = str;
this.intValue = intValue;
}

public Integer getId() {
return id;
}

public void setId(final Integer id) {
this.id = id;
}

public Integer getNum() {
return num;
}

public void setNum(final Integer num) {
this.num = num;
}

public String getStr() {
return str;
}

public void setStr(final String str) {
this.str = str;
}

public int getIntValue() {
return intValue;
}

public void setIntValue(final int intValue) {
this.intValue = intValue;
}
}

@Imported
Expand Down
Loading