diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java index 807621c3ec..61b9d8e006 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/expression/MapEntryExpression.java @@ -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 extends ExpressionImpl> - implements Expression>, Serializable { + implements CompoundSelection>, Serializable { - private final PathImplementor origin; - private final MapAttribute attribute; + private final MapAttributeJoin original; public MapEntryExpression( CriteriaBuilderImpl criteriaBuilder, Class> javaType, - PathImplementor origin, - MapAttribute attribute) { - super( criteriaBuilder, javaType); - this.origin = origin; - this.attribute = attribute; - } - - public MapAttribute getAttribute() { - return attribute; + MapAttributeJoin original) { + super( criteriaBuilder, javaType ); + 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> getCompoundSelectionItems() { + return Arrays.asList( original.key(), original.value() ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/MapAttributeJoin.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/MapAttributeJoin.java index 2e9ac06eda..05108bfab2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/MapAttributeJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/path/MapAttributeJoin.java @@ -77,7 +77,7 @@ public class MapAttributeJoin @Override @SuppressWarnings({ "unchecked" }) public Expression> entry() { - return new MapEntryExpression( criteriaBuilder(), Map.Entry.class, this, getAttribute() ); + return new MapEntryExpression( criteriaBuilder(), Map.Entry.class, this ); } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/Customer.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/Customer.java new file mode 100644 index 0000000000..6213ec62bd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/Customer.java @@ -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 . + */ +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 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 getOrderMap() { + return orderMap; + } + + public void setOrderMap(Map 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 + + '}'; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/CustomerOrder.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/CustomerOrder.java new file mode 100644 index 0000000000..7b5a6e8e8a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/CustomerOrder.java @@ -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 . + */ +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 + + '}'; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/MapJoinEntryTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/MapJoinEntryTest.java new file mode 100644 index 0000000000..f4cb85cd00 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/mapjoin/MapJoinEntryTest.java @@ -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 . + */ +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 query = criteriaBuilder.createQuery( Map.Entry.class ); + Root customer = query.from( Customer.class ); + MapJoin orderMap = customer.join( Customer_.orderMap ); + query.select( orderMap.entry() ); + + TypedQuery typedQuery = em.createQuery( query ); + List 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 query = em.createQuery( "SELECT ENTRY(mp) FROM Customer c JOIN c.orderMap mp", + Map.Entry.class ); + List resultList = query.getResultList(); + + assertEquals( 1, resultList.size() ); + assertEquals( "online", resultList.get( 0 ).getKey() ); + assertEquals( "AA Glass Cleaner", ( (CustomerOrder) resultList.get( 0 ).getValue() ).getItem() ); + } ); + } +}