HHH-11176: Add support for Tuple results for native queries

This commit is contained in:
Arnold Galovics 2017-07-17 23:00:14 +02:00 committed by Vlad Mihalcea
parent 1347ee6250
commit 68a40425b1
4 changed files with 540 additions and 2 deletions

View File

@ -61,6 +61,7 @@ import org.hibernate.engine.transaction.internal.TransactionImpl;
import org.hibernate.engine.transaction.spi.TransactionImplementor;
import org.hibernate.id.uuid.StandardRandomStrategy;
import org.hibernate.jpa.internal.util.FlushModeTypeHelper;
import org.hibernate.jpa.spi.NativeQueryTupleTransformer;
import org.hibernate.jpa.spi.TupleBuilderTransformer;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.procedure.ProcedureCall;
@ -781,7 +782,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
@SuppressWarnings({"WeakerAccess", "unchecked"})
protected <T> NativeQueryImplementor createNativeQuery(NamedSQLQueryDefinition queryDefinition, Class<T> resultType) {
if ( resultType != null ) {
if ( resultType != null && !Tuple.class.equals(resultType)) {
resultClassChecking( resultType, queryDefinition );
}
@ -790,6 +791,9 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
this,
factory.getQueryPlanCache().getSQLParameterMetadata( queryDefinition.getQueryString(), false )
);
if (Tuple.class.equals(resultType)) {
query.setResultTransformer(new NativeQueryTupleTransformer());
}
query.setHibernateFlushMode( queryDefinition.getFlushMode() );
query.setComment( queryDefinition.getComment() != null ? queryDefinition.getComment() : queryDefinition.getName() );
if ( queryDefinition.getLockOptions() != null ) {
@ -876,7 +880,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
try {
NativeQueryImplementor query = createNativeQuery( sqlString );
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
handleNativeQueryResult(query, resultClass);
return query;
}
catch ( RuntimeException he ) {
@ -884,6 +888,14 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
}
}
private void handleNativeQueryResult(NativeQueryImplementor query, Class resultClass) {
if (Tuple.class.equals(resultClass)) {
query.setResultTransformer(new NativeQueryTupleTransformer());
} else {
query.addEntity( "alias1", resultClass.getName(), LockMode.READ );
}
}
@Override
public NativeQueryImplementor createNativeQuery(String sqlString, String resultSetMapping) {
checkOpen();

View File

@ -140,6 +140,7 @@ public class CriteriaQueryTupleTransformer extends BasicTransformerAdapter {
}
public Object[] toArray() {
// todo : make a copy?
return tuples;
}

View File

@ -0,0 +1,119 @@
package org.hibernate.jpa.spi;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Tuple;
import javax.persistence.TupleElement;
import org.hibernate.HibernateException;
import org.hibernate.transform.BasicTransformerAdapter;
/**
* ResultTransformer adapter for handling Tuple results from Native queries
*
* @author Arnold Galovics
*/
public class NativeQueryTupleTransformer extends BasicTransformerAdapter {
@Override
public Object transformTuple(Object[] tuple, String[] aliases) {
return new NativeTupleImpl( tuple, aliases );
}
private static class NativeTupleElementImpl<X> implements TupleElement<X> {
private final Class<? extends X> javaType;
private final String alias;
public NativeTupleElementImpl(Class<? extends X> javaType, String alias) {
this.javaType = javaType;
this.alias = alias;
}
@Override
public Class<? extends X> getJavaType() {
return javaType;
}
@Override
public String getAlias() {
return alias;
}
}
private static class NativeTupleImpl implements Tuple {
private Object[] tuple;
private Map<String, Object> aliasToValue = new LinkedHashMap<>();
public NativeTupleImpl(Object[] tuple, String[] aliases) {
if ( tuple == null || aliases == null || tuple.length != aliases.length ) {
throw new HibernateException( "Got different size of tuples and aliases" );
}
this.tuple = tuple;
for ( int i = 0; i < tuple.length; i++ ) {
aliasToValue.put( aliases[i].toLowerCase(), tuple[i] );
}
}
@Override
public <X> X get(String alias, Class<X> type) {
final Object untyped = get( alias );
return ( untyped != null ) ? type.cast( untyped ) : null;
}
@Override
public Object get(String alias) {
Object tupleElement = aliasToValue.get( alias.toLowerCase() );
if ( tupleElement == null ) {
throw new IllegalArgumentException( "Unknown alias [" + alias + "]" );
}
return tupleElement;
}
@Override
public <X> X get(int i, Class<X> type) {
final Object untyped = get( i );
return ( untyped != null ) ? type.cast( untyped ) : null;
}
@Override
public Object get(int i) {
if ( i < 0 ) {
throw new IllegalArgumentException( "requested tuple index must be greater than zero" );
}
if ( i >= aliasToValue.size() ) {
throw new IllegalArgumentException( "requested tuple index exceeds actual tuple size" );
}
return tuple[i];
}
@Override
public Object[] toArray() {
// todo : make a copy?
return tuple;
}
@Override
public List<TupleElement<?>> getElements() {
List<TupleElement<?>> elements = new ArrayList<>( aliasToValue.size() );
for ( Map.Entry<String, Object> entry : aliasToValue.entrySet() ) {
elements.add( new NativeTupleElementImpl<>( entry.getValue().getClass(), entry.getKey() ) );
}
return elements;
}
@Override
public <X> X get(TupleElement<X> tupleElement) {
return get( tupleElement.getAlias(), tupleElement.getJavaType() );
}
}
}

View File

@ -0,0 +1,406 @@
package org.hibernate.jpa.test.query;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaDelete;
import java.math.BigInteger;
import java.util.List;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@RequiresDialect(H2Dialect.class)
public class TupleNativeQueryTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[]{User.class};
}
@Before
public void setUp() {
doInJPA(this::entityManagerFactory, entityManager -> {
User user = new User("Arnold");
entityManager.persist(user);
});
}
@After
public void tearDown() {
doInJPA(this::entityManagerFactory, entityManager -> {
CriteriaDelete<User> delete = entityManager.getCriteriaBuilder().createCriteriaDelete(User.class);
delete.from(User.class);
entityManager.createQuery(delete).executeUpdate();
});
}
@Test
public void testPositionalGetterShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get(0));
assertEquals("Arnold", tuple.get(1));
});
}
@Test
public void testPositionalGetterWithClassShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get(0, BigInteger.class));
assertEquals("Arnold", tuple.get(1, String.class));
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterShouldThrowExceptionWhenLessThanZeroGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get(-1);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithClassShouldThrowExceptionWhenLessThanZeroGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get(-1);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterShouldThrowExceptionWhenTupleSizePositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get(2);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithClassShouldThrowExceptionWhenTupleSizePositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get(2);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterShouldThrowExceptionWhenExceedingPositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get(3);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithClassShouldThrowExceptionWhenExceedingPositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get(3);
});
}
@Test
public void testAliasGetterWithoutExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ID"));
assertEquals("Arnold", tuple.get("FIRSTNAME"));
});
}
public void testAliasGetterShouldWorkWithoutExplicitAliasWhenLowerCaseAliasGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get("id");
});
}
@Test(expected = IllegalArgumentException.class)
public void testAliasGetterShouldThrowExceptionWithoutExplicitAliasWhenWrongAliasGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
tuple.get("e");
});
}
@Test
public void testAliasGetterWithClassWithoutExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleResult(entityManager);
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ID", BigInteger.class));
assertEquals("Arnold", tuple.get("FIRSTNAME", String.class));
});
}
@Test
public void testAliasGetterWithExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleAliasedResult(entityManager);
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ALIAS1"));
assertEquals("Arnold", tuple.get("ALIAS2"));
});
}
@Test
public void testAliasGetterWithClassWithExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = getTupleAliasedResult(entityManager);
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ALIAS1", BigInteger.class));
assertEquals("Arnold", tuple.get("ALIAS2", String.class));
});
}
@Test
public void testToArrayShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> tuples = getTupleResult(entityManager);
Object[] result = tuples.get(0).toArray();
assertArrayEquals(new Object[]{BigInteger.ONE, "Arnold"}, result);
});
}
@Test
public void testGetElementsShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> tuples = getTupleResult(entityManager);
List<TupleElement<?>> result = tuples.get(0).getElements();
assertEquals(2, result.size());
assertEquals(BigInteger.class, result.get(0).getJavaType());
assertEquals("id", result.get(0).getAlias());
assertEquals(String.class, result.get(1).getJavaType());
assertEquals("firstname", result.get(1).getAlias());
});
}
@Test
public void testPositionalGetterWithNamedNativeQueryShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get(0));
assertEquals("Arnold", tuple.get(1));
});
}
@Test
public void testPositionalGetterWithNamedNativeQueryWithClassShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get(0, BigInteger.class));
assertEquals("Arnold", tuple.get(1, String.class));
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenLessThanZeroGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get(-1);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenLessThanZeroGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get(-1);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenTupleSizePositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get(2);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenTupleSizePositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get(2);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithNamedNativeQueryShouldThrowExceptionWhenExceedingPositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get(3);
});
}
@Test(expected = IllegalArgumentException.class)
public void testPositionalGetterWithNamedNativeQueryWithClassShouldThrowExceptionWhenExceedingPositionGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get(3);
});
}
@Test
public void testAliasGetterWithNamedNativeQueryWithoutExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ID"));
assertEquals("Arnold", tuple.get("FIRSTNAME"));
});
}
public void testAliasGetterWithNamedNativeQueryShouldWorkWithoutExplicitAliasWhenLowerCaseAliasGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get("id");
});
}
@Test(expected = IllegalArgumentException.class)
public void testAliasGetterWithNamedNativeQueryShouldThrowExceptionWithoutExplicitAliasWhenWrongAliasGiven() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
tuple.get("e");
});
}
@Test
public void testAliasGetterWithNamedNativeQueryWithClassWithoutExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ID", BigInteger.class));
assertEquals("Arnold", tuple.get("FIRSTNAME", String.class));
});
}
@Test
public void testAliasGetterWithNamedNativeQueryWithExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard_with_alias", Tuple.class).getResultList();
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ALIAS1"));
assertEquals("Arnold", tuple.get("ALIAS2"));
});
}
@Test
public void testAliasGetterWithNamedNativeQueryWithClassWithExplicitAliasShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> result = entityManager.createNamedQuery("standard_with_alias", Tuple.class).getResultList();
Tuple tuple = result.get(0);
assertEquals(BigInteger.ONE, tuple.get("ALIAS1", BigInteger.class));
assertEquals("Arnold", tuple.get("ALIAS2", String.class));
});
}
@Test
public void testToArrayShouldWithNamedNativeQueryWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> tuples = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
Object[] result = tuples.get(0).toArray();
assertArrayEquals(new Object[]{BigInteger.ONE, "Arnold"}, result);
});
}
@Test
public void testGetElementsWithNamedNativeQueryShouldWorkProperly() {
doInJPA(this::entityManagerFactory, entityManager -> {
List<Tuple> tuples = entityManager.createNamedQuery("standard", Tuple.class).getResultList();
List<TupleElement<?>> result = tuples.get(0).getElements();
assertEquals(2, result.size());
assertEquals(BigInteger.class, result.get(0).getJavaType());
assertEquals("id", result.get(0).getAlias());
assertEquals(String.class, result.get(1).getJavaType());
assertEquals("firstname", result.get(1).getAlias());
});
}
@SuppressWarnings("unchecked")
private List<Tuple> getTupleAliasedResult(EntityManager entityManager) {
Query query = entityManager.createNativeQuery("SELECT id AS alias1, firstname AS alias2 FROM users", Tuple.class);
return (List<Tuple>) query.getResultList();
}
@SuppressWarnings("unchecked")
private List<Tuple> getTupleResult(EntityManager entityManager) {
Query query = entityManager.createNativeQuery("SELECT id, firstname FROM users", Tuple.class);
return (List<Tuple>) query.getResultList();
}
@Entity
@Table(name = "users")
@NamedNativeQueries({
@NamedNativeQuery(
name = "standard",
query = "SELECT id, firstname FROM users"
),
@NamedNativeQuery(
name = "standard_with_alias",
query = "SELECT id AS alias1, firstname AS alias2 FROM users"
)
})
public static class User {
@Id
private long id;
private String firstName;
public User() {
}
public User(String firstName) {
this.id = 1L;
this.firstName = firstName;
}
}
}