HHH-16710 allow Map and List instantiation for native SQL queries

This commit is contained in:
Gavin 2023-05-29 12:39:21 +02:00 committed by Gavin King
parent ed75e24d94
commit 1557a66e6e
5 changed files with 112 additions and 3 deletions

View File

@ -12,6 +12,7 @@ import java.io.ObjectOutputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
@ -44,6 +45,8 @@ import org.hibernate.id.uuid.StandardRandomStrategy;
import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.ReturningWork;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.jdbc.WorkExecutorVisitable;
import org.hibernate.jpa.spi.NativeQueryListTransformer;
import org.hibernate.jpa.spi.NativeQueryMapTransformer;
import org.hibernate.jpa.spi.NativeQueryTupleTransformer; import org.hibernate.jpa.spi.NativeQueryTupleTransformer;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.procedure.ProcedureCall; import org.hibernate.procedure.ProcedureCall;
@ -849,7 +852,12 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
if ( Tuple.class.equals( resultClass ) ) { if ( Tuple.class.equals( resultClass ) ) {
query.setTupleTransformer( new NativeQueryTupleTransformer() ); query.setTupleTransformer( new NativeQueryTupleTransformer() );
} }
// TODO: handle Map, List as well else if ( Map.class.equals( resultClass ) ) {
query.setTupleTransformer( new NativeQueryMapTransformer() );
}
else if ( List.class.equals( resultClass ) ) {
query.setTupleTransformer( new NativeQueryListTransformer() );
}
else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) { else if ( getFactory().getMappingMetamodel().isEntityClass( resultClass ) ) {
query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
} }

View File

@ -0,0 +1,23 @@
/*
* 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.query.TupleTransformer;
import java.util.List;
/**
* A {@link TupleTransformer} for handling {@link List} results from native queries.
*
* @author Gavin King
*/
public class NativeQueryListTransformer implements TupleTransformer<List<Object>> {
@Override
public List<Object> transformTuple(Object[] tuple, String[] aliases) {
return List.of( tuple );
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.query.TupleTransformer;
import java.util.HashMap;
import java.util.Map;
import static java.util.Locale.ROOT;
/**
* A {@link TupleTransformer} for handling {@link Map} results from native queries.
*
* @author Gavin King
*/
public class NativeQueryMapTransformer implements TupleTransformer<Map<String,Object>> {
@Override
public Map<String,Object> transformTuple(Object[] tuple, String[] aliases) {
Map<String,Object> map = new HashMap<>( aliases.length );
for ( int i = 0; i < aliases.length; i++ ) {
map.put( aliases[i].toLowerCase(ROOT), tuple[i] );
}
return map;
}
}

View File

@ -18,6 +18,8 @@ import org.hibernate.HibernateException;
import org.hibernate.query.TypedTupleTransformer; import org.hibernate.query.TypedTupleTransformer;
import org.hibernate.transform.ResultTransformer; import org.hibernate.transform.ResultTransformer;
import static java.util.Locale.ROOT;
/** /**
* A {@link ResultTransformer} for handling JPA {@link Tuple} results from native queries. * A {@link ResultTransformer} for handling JPA {@link Tuple} results from native queries.
* *
@ -81,7 +83,7 @@ public class NativeQueryTupleTransformer implements ResultTransformer<Tuple>, Ty
final String alias = aliases[i]; final String alias = aliases[i];
if ( alias != null ) { if ( alias != null ) {
aliasToValue.put( alias, tuple[i] ); aliasToValue.put( alias, tuple[i] );
aliasReferences.put( alias.toLowerCase(), alias ); aliasReferences.put( alias.toLowerCase(ROOT), alias );
} }
} }
size = tuple.length; size = tuple.length;
@ -96,7 +98,7 @@ public class NativeQueryTupleTransformer implements ResultTransformer<Tuple>, Ty
@Override @Override
public Object get(String alias) { public Object get(String alias) {
final String aliasReference = aliasReferences.get( alias.toLowerCase() ); final String aliasReference = aliasReferences.get( alias.toLowerCase(ROOT) );
if ( aliasReference != null && aliasToValue.containsKey( aliasReference ) ) { if ( aliasReference != null && aliasToValue.containsKey( aliasReference ) ) {
return aliasToValue.get( aliasReference ); return aliasToValue.get( aliasReference );
} }

View File

@ -128,6 +128,52 @@ public class ImplicitInstantiationTest {
); );
} }
@Test
public void testSqlTupleInstantiationWithAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
Tuple result = session.createNativeQuery("select id as id, upper(name) as name from thingy_table", Tuple.class)
.addSynchronizedEntityClass(Thing.class)
.getSingleResult();
assertEquals( result.get("id"), 1L );
assertEquals( result.get("name"), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testSqlMapInstantiationWithAlias(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist(new Thing(1L, "thing"));
Map result = session.createNativeQuery("select id as id, upper(name) as name from thingy_table", Map.class)
.addSynchronizedEntityClass(Thing.class)
.getSingleResult();
assertEquals( result.get("id"), 1L );
assertEquals( result.get("name"), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Test
public void testSqlListInstantiationWithoutAlias(SessionFactoryScope scope) {
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)
.addSynchronizedEntityClass(Thing.class)
.getSingleResult();
assertEquals( result.get(0), 1L );
assertEquals( result.get(1), "THING" );
session.getTransaction().setRollbackOnly();
}
);
}
@Entity(name = "Thing") @Entity(name = "Thing")
@Table(name = "thingy_table") @Table(name = "thingy_table")
public class Thing { public class Thing {