HHH-12945 Properly support entry() criteria expression

With additional changes from Gail.
This commit is contained in:
Guillaume Smet 2018-10-17 17:16:32 +02:00
parent e4c964fb36
commit 2e0976d8b6
5 changed files with 217 additions and 22 deletions

View File

@ -7,15 +7,17 @@
package org.hibernate.query.criteria.internal.expression;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.persistence.criteria.Expression;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.criteria.CompoundSelection;
import javax.persistence.criteria.Selection;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.ParameterRegistry;
import org.hibernate.query.criteria.internal.PathImplementor;
import org.hibernate.query.criteria.internal.Renderable;
import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.criteria.internal.path.MapAttributeJoin;
import org.hibernate.sql.ast.Clause;
/**
@ -25,41 +27,41 @@ import org.hibernate.sql.ast.Clause;
*/
public class MapEntryExpression<K,V>
extends ExpressionImpl<Map.Entry<K,V>>
implements Expression<Map.Entry<K,V>>, Serializable {
implements CompoundSelection<Map.Entry<K,V>>, Serializable {
private final PathImplementor origin;
private final MapAttribute<?, K, V> attribute;
private final MapAttributeJoin<?, K, V> original;
public MapEntryExpression(
CriteriaBuilderImpl criteriaBuilder,
Class<Map.Entry<K, V>> javaType,
PathImplementor origin,
MapAttribute<?, K, V> attribute) {
MapAttributeJoin<?, K, V> original) {
super( criteriaBuilder, javaType );
this.origin = origin;
this.attribute = attribute;
}
public MapAttribute<?, K, V> getAttribute() {
return attribute;
this.original = original;
}
@Override
public void registerParameters(ParameterRegistry registry) {
// none to register
}
@Override
public String render(RenderingContext renderingContext) {
if ( renderingContext.getClauseStack().getCurrent() == Clause.SELECT ) {
return "entry(" + path( renderingContext ) + ")";
return "entry(" + original.render( renderingContext ) + ")";
}
// don't think this is valid outside of select clause...
throw new IllegalStateException( "illegal reference to map entry outside of select clause." );
}
private String path(RenderingContext renderingContext) {
return origin.getPathIdentifier()
+ '.'
+ ( (Renderable) getAttribute() ).render( renderingContext );
@Override
public boolean isCompoundSelection() {
return true;
}
@Override
public List<Selection<?>> getCompoundSelectionItems() {
return Arrays.asList( original.key(), original.value() );
}
}

View File

@ -77,7 +77,7 @@ public class MapAttributeJoin<O,K,V>
@Override
@SuppressWarnings({ "unchecked" })
public Expression<Map.Entry<K, V>> entry() {
return new MapEntryExpression( criteriaBuilder(), Map.Entry.class, this, getAttribute() );
return new MapEntryExpression( criteriaBuilder(), Map.Entry.class, this );
}
@Override

View File

@ -0,0 +1,70 @@
/*
* 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.test.criteria.mapjoin;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class Customer {
@Id
@GeneratedValue
private int id;
private String name;
@OneToMany(cascade = CascadeType.ALL)
private Map<String, CustomerOrder> orderMap;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, CustomerOrder> getOrderMap() {
return orderMap;
}
public void setOrderMap(Map<String, CustomerOrder> orderMap) {
this.orderMap = orderMap;
}
public void addOrder(String orderType, String itemName, int qty) {
if ( orderMap == null ) {
orderMap = new HashMap<>();
}
CustomerOrder order = new CustomerOrder();
order.setItem( itemName );
order.setQty( qty );
orderMap.put( orderType, order );
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", orderMap=" + orderMap +
'}';
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.test.criteria.mapjoin;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class CustomerOrder {
@Id
@GeneratedValue
private long id;
private String item;
private int qty;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public int getQty() {
return qty;
}
public void setQty(int qty) {
this.qty = qty;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", item='" + item + '\'' +
", qty=" + qty +
'}';
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.test.criteria.mapjoin;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Map;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.MapJoin;
import javax.persistence.criteria.Root;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;
public class MapJoinEntryTest extends BaseEntityManagerFunctionalTestCase {
@Override
public Class[] getAnnotatedClasses() {
return new Class[]{ Customer.class, CustomerOrder.class };
}
@Before
public void setup() {
doInJPA( this::entityManagerFactory, em -> {
Customer customer = new Customer();
customer.setName( "Morgan Philips" );
customer.addOrder( "online", "AA Glass Cleaner", 3 );
em.persist( customer );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12945")
public void testMapJoinEntryCriteria() {
doInJPA( this::entityManagerFactory, em -> {
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Map.Entry> query = criteriaBuilder.createQuery( Map.Entry.class );
Root<Customer> customer = query.from( Customer.class );
MapJoin<Customer, String, CustomerOrder> orderMap = customer.join( Customer_.orderMap );
query.select( orderMap.entry() );
TypedQuery<Map.Entry> typedQuery = em.createQuery( query );
List<Map.Entry> resultList = typedQuery.getResultList();
assertEquals( 1, resultList.size() );
assertEquals( "online", resultList.get( 0 ).getKey() );
assertEquals( "AA Glass Cleaner", ( (CustomerOrder) resultList.get( 0 ).getValue() ).getItem() );
} );
}
@Test
public void testMapJoinEntryJPQL() {
doInJPA( this::entityManagerFactory, em -> {
TypedQuery<Map.Entry> query = em.createQuery( "SELECT ENTRY(mp) FROM Customer c JOIN c.orderMap mp",
Map.Entry.class );
List<Map.Entry> resultList = query.getResultList();
assertEquals( 1, resultList.size() );
assertEquals( "online", resultList.get( 0 ).getKey() );
assertEquals( "AA Glass Cleaner", ( (CustomerOrder) resultList.get( 0 ).getValue() ).getItem() );
} );
}
}