HHH-8493 - Implement ConstructorResults handling

This commit is contained in:
Steve Ebersole 2013-09-12 07:09:49 -05:00
parent 312283cb0e
commit 0bf29bc2fd
9 changed files with 422 additions and 14 deletions

View File

@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.EntityResult;
import javax.persistence.FieldResult;
import javax.persistence.SqlResultSetMapping;
@ -43,6 +44,7 @@ import org.hibernate.cfg.BinderHelper;
import org.hibernate.cfg.Mappings;
import org.hibernate.cfg.QuerySecondPass;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn;
import org.hibernate.internal.CoreMessageLogger;
@ -191,6 +193,21 @@ public class ResultsetMappingSecondPass implements QuerySecondPass {
);
}
for ( ConstructorResult constructorResult : ann.classes() ) {
List<NativeSQLQueryScalarReturn> columnReturns = new ArrayList<NativeSQLQueryScalarReturn>();
for ( ColumnResult columnResult : constructorResult.columns() ) {
columnReturns.add(
new NativeSQLQueryScalarReturn(
mappings.getObjectNameNormalizer().normalizeIdentifierQuoting( columnResult.name() ),
null
)
);
}
definition.addQueryReturn(
new NativeSQLQueryConstructorReturn( constructorResult.targetClass(), columnReturns )
);
}
if ( isDefault ) {
mappings.addDefaultResultSetMapping( definition );
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, 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.engine.query.spi.sql;
import java.util.List;
/**
* Describes a {@link javax.persistence.ConstructorResult}
*
* @author Steve Ebersole
*/
public class NativeSQLQueryConstructorReturn implements NativeSQLQueryReturn {
private final Class targetClass;
private final NativeSQLQueryScalarReturn[] columnReturns;
public NativeSQLQueryConstructorReturn(Class targetClass, List<NativeSQLQueryScalarReturn> columnReturns) {
this.targetClass = targetClass;
this.columnReturns = columnReturns.toArray( new NativeSQLQueryScalarReturn[ columnReturns.size() ] );
}
public Class getTargetClass() {
return targetClass;
}
public NativeSQLQueryScalarReturn[] getColumnReturns() {
return columnReturns;
}
}

View File

@ -42,6 +42,7 @@ import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.ParameterMetadata;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryJoinReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
@ -205,6 +206,10 @@ public class SQLQueryImpl extends AbstractQueryImpl implements SQLQuery {
break;
}
}
else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) {
autoDiscoverTypes = true;
break;
}
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, 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.loader.custom;
import java.lang.reflect.Constructor;
/**
* @author Steve Ebersole
*/
public class ConstructorReturn implements Return {
private final Class targetClass;
private final ScalarReturn[] scalars;
// private final Constructor constructor;
public ConstructorReturn(Class targetClass, ScalarReturn[] scalars) {
this.targetClass = targetClass;
this.scalars = scalars;
// constructor = resolveConstructor( targetClass, scalars );
}
private static Constructor resolveConstructor(Class targetClass, ScalarReturn[] scalars) {
for ( Constructor constructor : targetClass.getConstructors() ) {
final Class[] argumentTypes = constructor.getParameterTypes();
if ( argumentTypes.length != scalars.length ) {
continue;
}
boolean allMatched = true;
for ( int i = 0; i < argumentTypes.length; i++ ) {
if ( areAssignmentCompatible( argumentTypes[i], scalars[i].getType().getReturnedClass() ) ) {
allMatched = false;
break;
}
}
if ( !allMatched ) {
continue;
}
return constructor;
}
throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() );
}
@SuppressWarnings("unchecked")
private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) {
// todo : add handling for primitive/wrapper equivalents
return argumentType.isAssignableFrom( typeReturnedClass );
}
public Class getTargetClass() {
return targetClass;
}
public ScalarReturn[] getScalars() {
return scalars;
}
// public Constructor getConstructor() {
// return constructor;
// }
}

View File

@ -24,6 +24,8 @@
package org.hibernate.loader.custom;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
@ -139,6 +141,25 @@ public class CustomLoader extends Loader {
includeInResultRowList.add( true );
hasScalars = true;
}
else if ( ConstructorReturn.class.isInstance( rtn ) ) {
final ConstructorReturn constructorReturn = (ConstructorReturn) rtn;
resultTypes.add( null ); // this bit makes me nervous
includeInResultRowList.add( true );
hasScalars = true;
ScalarResultColumnProcessor[] scalarProcessors = new ScalarResultColumnProcessor[ constructorReturn.getScalars().length ];
int i = 0;
for ( ScalarReturn scalarReturn : constructorReturn.getScalars() ) {
scalarProcessors[i++] = new ScalarResultColumnProcessor(
StringHelper.unquote( scalarReturn.getColumnAlias(), factory.getDialect() ),
scalarReturn.getType()
);
}
resultColumnProcessors.add(
new ConstructorResultColumnProcessor( constructorReturn.getTargetClass(), scalarProcessors )
);
}
else if ( rtn instanceof RootReturn ) {
RootReturn rootRtn = ( RootReturn ) rtn;
Queryable persister = ( Queryable ) factory.getEntityPersister( rootRtn.getEntityName() );
@ -601,6 +622,90 @@ public class CustomLoader extends Loader {
}
}
public class ConstructorResultColumnProcessor implements ResultColumnProcessor {
private final Class targetClass;
private final ScalarResultColumnProcessor[] scalarProcessors;
private Constructor constructor;
public ConstructorResultColumnProcessor(Class targetClass, ScalarResultColumnProcessor[] scalarProcessors) {
this.targetClass = targetClass;
this.scalarProcessors = scalarProcessors;
}
@Override
public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session)
throws SQLException, HibernateException {
if ( constructor == null ) {
throw new IllegalStateException( "Constructor to call was null" );
}
final Object[] args = new Object[ scalarProcessors.length ];
for ( int i = 0; i < scalarProcessors.length; i++ ) {
args[i] = scalarProcessors[i].extract( data, resultSet, session );
}
try {
return constructor.newInstance( args );
}
catch (InvocationTargetException e) {
throw new HibernateException(
String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ),
e
);
}
catch (Exception e) {
throw new HibernateException(
String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ),
e
);
}
}
@Override
public void performDiscovery(Metadata metadata, List<Type> types, List<String> aliases) throws SQLException {
final List<Type> localTypes = new ArrayList<Type>();
for ( ScalarResultColumnProcessor scalar : scalarProcessors ) {
scalar.performDiscovery( metadata, localTypes, aliases );
}
types.addAll( localTypes );
constructor = resolveConstructor( targetClass, localTypes );
}
}
private static Constructor resolveConstructor(Class targetClass, List<Type> types) {
for ( Constructor constructor : targetClass.getConstructors() ) {
final Class[] argumentTypes = constructor.getParameterTypes();
if ( argumentTypes.length != types.size() ) {
continue;
}
boolean allMatched = true;
for ( int i = 0; i < argumentTypes.length; i++ ) {
if ( ! areAssignmentCompatible( argumentTypes[i], types.get( i ).getReturnedClass() ) ) {
allMatched = false;
break;
}
}
if ( !allMatched ) {
continue;
}
return constructor;
}
throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() );
}
@SuppressWarnings("unchecked")
private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) {
// todo : add handling for primitive/wrapper equivalents
return argumentType.isAssignableFrom( typeReturnedClass );
}
@Override
protected void autoDiscoverTypes(ResultSet rs) {
try {

View File

@ -37,6 +37,7 @@ import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryCollectionReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryJoinReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryNonScalarReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
@ -53,6 +54,7 @@ import org.hibernate.loader.GeneratedCollectionAliases;
import org.hibernate.loader.custom.CollectionFetchReturn;
import org.hibernate.loader.custom.CollectionReturn;
import org.hibernate.loader.custom.ColumnCollectionAliases;
import org.hibernate.loader.custom.ConstructorReturn;
import org.hibernate.loader.custom.EntityFetchReturn;
import org.hibernate.loader.custom.FetchReturn;
import org.hibernate.loader.custom.NonScalarReturn;
@ -353,6 +355,20 @@ public class SQLQueryReturnProcessor {
customReturns.add( customReturn );
customReturnsByAlias.put( alias, customReturn );
}
else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) {
final NativeSQLQueryConstructorReturn constructorReturn = (NativeSQLQueryConstructorReturn) queryReturn;
final ScalarReturn[] scalars = new ScalarReturn[ constructorReturn.getColumnReturns().length ];
int i = 0;
for ( NativeSQLQueryScalarReturn scalarReturn : constructorReturn.getColumnReturns() ) {
scalars[i++] = new ScalarReturn( scalarReturn.getType(), scalarReturn.getColumnAlias() );
}
customReturns.add( new ConstructorReturn( constructorReturn.getTargetClass(), scalars ) );
}
else {
throw new IllegalStateException(
"Unrecognized NativeSQLQueryReturn concrete type : " + queryReturn
);
}
}
return customReturns;
}
@ -381,11 +397,23 @@ public class SQLQueryReturnProcessor {
processRootReturn( ( NativeSQLQueryRootReturn ) rtn );
}
else if ( rtn instanceof NativeSQLQueryCollectionReturn ) {
processCollectionReturn( ( NativeSQLQueryCollectionReturn ) rtn );
processCollectionReturn( (NativeSQLQueryCollectionReturn) rtn );
}
else {
else if ( NativeSQLQueryJoinReturn.class.isInstance( rtn ) ) {
processJoinReturn( ( NativeSQLQueryJoinReturn ) rtn );
}
else if ( NativeSQLQueryConstructorReturn.class.isInstance( rtn ) ) {
processConstructorReturn( (NativeSQLQueryConstructorReturn) rtn );
}
else {
throw new IllegalStateException(
"Unrecognized NativeSQLQueryReturn concrete type encountered : " + rtn
);
}
}
private void processConstructorReturn(NativeSQLQueryConstructorReturn rtn) {
//To change body of created methods use File | Settings | File Templates.
}
private void processScalarReturn(NativeSQLQueryScalarReturn typeReturn) {

View File

@ -52,6 +52,7 @@ import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.hibernate.testing.junit4.ExtraAssertions;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
/**
@ -87,7 +88,7 @@ public class StoredProcedureResultSetMappingTest extends BaseUnitTestCase {
public Employee() {
}
public Employee(int id, String firstName, String lastName) {
public Employee(Integer id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
@ -142,8 +143,9 @@ public class StoredProcedureResultSetMappingTest extends BaseUnitTestCase {
ProcedureCall call = session.createStoredProcedureCall( "allEmployeeNames", "id-fname-lname" );
ProcedureOutputs outputs = call.getOutputs();
ResultSetOutput output = ExtraAssertions.assertTyping( ResultSetOutput.class, outputs.getCurrent() );
ResultSetOutput output = assertTyping( ResultSetOutput.class, outputs.getCurrent() );
assertEquals( 3, output.getResultList().size() );
assertTyping( Employee.class, output.getResultList().get( 0 ) );
session.getTransaction().commit();
session.close();

View File

@ -40,22 +40,13 @@ import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.jdbc.Work;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.mapping.AuxiliaryDatabaseObject;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.procedure.ProcedureOutputs;
import org.hibernate.result.ResultSetOutput;
import org.junit.Test;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.ExtraAssertions;
import static org.junit.Assert.assertEquals;
@ -92,7 +83,7 @@ public class StoredProcedureResultSetMappingTest extends BaseEntityManagerFuncti
public Employee() {
}
public Employee(int id, String firstName, String lastName) {
public Employee(Integer id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;

View File

@ -0,0 +1,126 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, 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.jpa.test.query;
import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;
import java.util.List;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
/**
* @author Steve Ebersole
*/
public class ConstructorResultNativeQueryTest extends BaseEntityManagerFunctionalTestCase {
@Entity( name = "Person" )
@SqlResultSetMapping(
name = "person-id-and-name",
classes = {
@ConstructorResult(
targetClass = Person.class,
columns = {
@ColumnResult( name = "id" ),
@ColumnResult( name = "p_name" )
}
)
}
)
@NamedNativeQuery(
name = "person-id-and-name",
query = "select p.id, p.p_name from person p order by p.p_name",
resultSetMapping = "person-id-and-name"
)
public static class Person {
@Id
private Integer id;
@Column( name = "p_name" )
private String name;
@Temporal( TemporalType.TIMESTAMP )
private Date birthDate;
public Person() {
}
public Person(Integer id, String name, Date birthDate) {
this.id = id;
this.name = name;
this.birthDate = birthDate;
}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Person.class };
}
@Test
public void testConstructorResultNativeQuery() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
em.persist( new Person( 1, "John", new Date() ) );
em.getTransaction().commit();
em.close();
em = getOrCreateEntityManager();
em.getTransaction().begin();
List results = em.createNativeQuery(
"select p.id, p.p_name from person p order by p.p_name",
"person-id-and-name"
).getResultList();
assertEquals( 1, results.size() );
assertTyping( Person.class, results.get( 0 ) );
em.getTransaction().commit();
em.close();
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.createQuery( "delete from Person" ).executeUpdate();
em.getTransaction().commit();
em.close();
}
}