| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.persistence.embed.compositepk; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.persistence.EntityManager; |
| import javax.persistence.EntityTransaction; |
| import javax.persistence.TypedQuery; |
| import javax.persistence.criteria.CriteriaBuilder; |
| import javax.persistence.criteria.CriteriaQuery; |
| import javax.persistence.criteria.ParameterExpression; |
| import javax.persistence.criteria.Predicate; |
| import javax.persistence.criteria.Root; |
| |
| import junit.framework.Assert; |
| |
| import org.apache.openjpa.persistence.test.SingleEMFTestCase; |
| |
| public class TestCompositePrimaryKeys extends SingleEMFTestCase { |
| |
| // NOTE: There are 3 aspects to the fix to OPENJPA-2631, each being tested in some manner in the test |
| // methods below. The 3 aspects of the fix are: |
| // |
| // 1) Fix in ClassMapping which resolves the reported ClassCastEx. |
| // 2) After #1, things progressed further, but for some CriteriaBuilder tests incorrect SQL was created as follows: |
| // 2.1) An equals expression was created for only one of the columns in the composite PK. To |
| // resolve this a fix was made to class EqualExpression. |
| // 2.2) An extra parameter marker (?) was added to the SQL. To resolve this a fix was made to class Lit. |
| |
| protected EntityManager em; |
| private EntityTransaction tx; |
| |
| public void setUp() { |
| super.setUp(DROP_TABLES, Subject.class, SubjectKey.class, SubjectWithIdClass.class, Topic.class); |
| |
| em = emf.createEntityManager(); |
| tx = em.getTransaction(); |
| tx.begin(); |
| createData(); |
| } |
| |
| /* |
| * OpenJPA handles this test just fine with or without the fixes of OPENJPA-2631. |
| */ |
| public void testFindUsingFindOnSubjectKey() { |
| |
| Subject s = createSubject(); |
| |
| Subject s2 = em.find(Subject.class, s.getKey()); |
| |
| verifySubject(s, s2); |
| } |
| |
| /* |
| * OpenJPA handles this test just fine with or without the fixes of OPENJPA-2631. This works, |
| * compared to other tests, because a select is performed on the key class' fields. |
| */ |
| public void testFindUsingEqualsOnObjectJPQL() { |
| Subject s = createSubject(); |
| |
| TypedQuery<Subject> query = em.createQuery("select distinct s from Subject s where " + |
| "s.key.subjectNummer = :subjectNummer AND s.key.subjectTypeCode = " + |
| ":subjectTypeCode", Subject.class); |
| query.setParameter("subjectNummer", s.getKey().getSubjectNummer()); |
| query.setParameter("subjectTypeCode", s.getKey().getSubjectTypeCode()); |
| |
| Subject s2 = query.getSingleResult(); |
| |
| verifySubject(s, s2); |
| } |
| |
| /* |
| * Just like the previous test, OpenJPA handles this test just fine with or without the |
| * fixes of OPENJPA-2631. This works, compared to other tests, because a select is |
| * performed on the key class' fields. This slight difference in this test compared to the |
| * previous test is that it traverses from Topic to the SubjectKey fields. |
| */ |
| public void testFindUsingJPQLEqualsOnSubjectKeyAttributes() { |
| |
| Subject s = createSubject(); |
| |
| TypedQuery<Topic> query = em.createQuery("select distinct t from Topic t where t.subject.key.subjectNummer = " + |
| ":subjectNummer AND t.subject.key.subjectTypeCode = :subjectTypeCode", Topic.class); |
| query.setParameter("subjectNummer", s.getKey().getSubjectNummer()); |
| query.setParameter("subjectTypeCode", s.getKey().getSubjectTypeCode()); |
| Topic topic = query.getSingleResult(); |
| |
| verifyResults(topic, s); |
| } |
| |
| /* |
| * This test results in an EXPECTED exception: |
| * |
| * ArgumentException: An error occurred while parsing the query filter 'select distinct g from Topic g where |
| * t.subject.key = :subjectKey'. Error message: JPQL query does not support conditional expression over embeddable |
| * class. JPQL string: "key". |
| * |
| * The message in the exception tells it all. Per the spec, you can not do a compare on embeddables. |
| */ |
| public void testFindUsingJPQLEqualsOnSubjectKey() { |
| try { |
| em.createQuery("select distinct t from Topic t where t.subject.key = :subjectKey"); |
| } catch (Throwable t) { |
| // An exception is EXPECTED! |
| Assert.assertTrue(t.getMessage().contains("does not support conditional expression")); |
| } |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to |
| * [Ljava.lang.Object;] |
| * at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149) |
| * |
| * With fix #1, this test works fine. |
| */ |
| public void testFindSubjectUsingJPQLEqualsOnSubject() { |
| |
| Subject s = createSubject(); |
| |
| TypedQuery<Subject> query = em.createQuery("select s from Subject s where s = :subject", Subject.class); |
| query.setParameter("subject", s); |
| Subject s2 = query.getSingleResult(); |
| |
| verifySubject(s, s2); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to |
| * [Ljava.lang.Object;] |
| * at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149) |
| * |
| * With fix #1, this test works fine. |
| */ |
| public void testFindUsingNamedQuery() { |
| |
| Subject s = createSubject(); |
| |
| TypedQuery<Topic> q = em.createNamedQuery("bySubject", Topic.class); |
| |
| q.setParameter("subject", s); |
| |
| Topic topic = q.getSingleResult(); |
| |
| verifyResults(topic, s); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to |
| * [Ljava.lang.Object;] |
| * at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149) |
| * |
| * With fix #1, this test works fine. |
| */ |
| public void testFindUsingJPQLEqualsOnSubject() { |
| |
| Subject s = createSubject(); |
| |
| TypedQuery<Topic> query = |
| em.createQuery("select distinct t from Topic t where t.subject = :subject", Topic.class); |
| query.setParameter("subject", s); |
| Topic topic = query.getSingleResult(); |
| |
| verifyResults(topic, s); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey cannot be cast to |
| * [Ljava.lang.Object;] |
| * at org.apache.openjpa.jdbc.kernel.exps.Param.appendTo(Param.java:149) |
| * |
| * With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows: |
| * |
| * SELECT t0.SUBJECTNUMMER, t0.CODE_SUBJECTTYPE FROM SUBJECT t0 WHERE (t0.SUBJECTNUMMER = ?) |
| * optimize for 1 row [params=(int) 1] |
| * |
| * Notice that 't0.CODE_SUBJECTTYPE' is missing. With fix #2.1 this issue is resolved. |
| * |
| * The thing to note (which is different than the test 'findSubjectUsingCriteriaBuilderEquals' below) is that |
| * the Subject is treated as an OpenJPA 'Parameter' (see changes in EqualExpression). The test |
| * 'findSubjectUsingCriteriaBuilderEquals' below causes the Subject to be treated as a Lit. There is |
| * a bug in both cases, with an additional bug for the 'Lit' case. |
| */ |
| public void testFindSubjectUsingCriteriaBuilderEqualsAndParameter() { |
| |
| Subject s = createSubject(); |
| |
| CriteriaBuilder builder = em.getCriteriaBuilder(); |
| CriteriaQuery<Subject> cq = builder.createQuery(Subject.class); |
| |
| Root<Subject> subjectRoot = cq.from(Subject.class); |
| cq.select(subjectRoot); |
| |
| ParameterExpression<Subject> param1 = builder.parameter(Subject.class, "subject"); |
| Predicate subjectPredicate = builder.equal(subjectRoot, param1); |
| |
| cq.where(subjectPredicate); |
| |
| TypedQuery<Subject> query = em.createQuery(cq); |
| query.setParameter("subject", s); |
| |
| Subject s2 = query.getSingleResult(); |
| |
| verifySubject(s, s2); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey |
| * cannot be cast to [Ljava.lang.Object; |
| * at org.apache.openjpa.jdbc.kernel.exps.Lit.appendTo(Lit.java:120) |
| * |
| * Notice the exception this time is in 'Lit'. Previous CCEx for the other tests have been in Param. |
| * With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows: |
| * |
| * SELECT t0.SUBJECTNUMMER, t0.CODE_SUBJECTTYPE FROM SUBJECT t0 WHERE (t0.SUBJECTNUMMER = ??) |
| * optimize for 1 row [params=(int) 1, (String) Type] |
| * |
| * Notice that 't0.CODE_SUBJECTTYPE' is missing, and there are two parameter markers. With fix #2.1 and |
| * #2.2, this issue is resolved. |
| * |
| * The other thing to note (which is different than the test 'findSubjectUsingCriteriaBuilderEqualsAndParameter' |
| * above) is that the Subject is treated as an OpenJPA 'Lit' (see changes in EqualExpression). The test |
| * 'findSubjectUsingCriteriaBuilderEqualsAndParameter' above treats the Subject as a Parameter. There is a bug in |
| * both cases, with an additional bug for the 'Lit' case. |
| */ |
| public void testFindSubjectUsingCriteriaBuilderEquals() { |
| |
| Subject s = createSubject(); |
| |
| CriteriaBuilder builder = em.getCriteriaBuilder(); |
| CriteriaQuery<Subject> cq = builder.createQuery(Subject.class); |
| |
| Root<Subject> subjectRoot = cq.from(Subject.class); |
| cq.select(subjectRoot); |
| |
| Predicate subjectPredicate = builder.equal(subjectRoot, s); |
| |
| // Before the fix of JIRA OPENJPA-2631, the following was a way to fix/work around the issue, in |
| // other words, selecting the individual fields of the PK worked fine....I'll leave this here but |
| // commented out for history sake: |
| // Predicate subjectPredicate1 = builder.equal(subjectRoot.get(Subject_.key).get(SubjectKey_.subjectNummer), |
| // subject.getKey().getSubjectNummer()); |
| // Predicate subjectPredicate2 = builder.equal(subjectRoot.get(Subject_.key).get(SubjectKey_.subjectTypeCode), |
| // subject.getKey().getSubjectTypeCode()); |
| // Predicate subjectPredicate = builder.and(subjectPredicate1,subjectPredicate2); |
| |
| cq.where(subjectPredicate); |
| |
| TypedQuery<Subject> query = em.createQuery(cq); |
| |
| Subject s2 = query.getSingleResult(); |
| |
| verifySubject(s, s2); |
| } |
| |
| /* |
| * For comparison, this test does the same CriteriaBuilder code on Topic (an entity |
| * with a single PK) as was done in the previous test to make sure it works. |
| */ |
| public void testFindTopicUsingCriteriaBuilderEquals() { |
| |
| Topic t = new Topic(); |
| t.setId(5); |
| |
| CriteriaBuilder builder = em.getCriteriaBuilder(); |
| CriteriaQuery<Topic> cq = builder.createQuery(Topic.class); |
| |
| Root<Topic> topicRoot = cq.from(Topic.class); |
| cq.select(topicRoot); |
| |
| Predicate topicPredicate = builder.equal(topicRoot, t); |
| cq.where(topicPredicate); |
| |
| TypedQuery<Topic> query = em.createQuery(cq); |
| |
| Topic topic = query.getSingleResult(); |
| |
| verifyResults(topic, createSubject()); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey |
| * cannot be cast to [Ljava.lang.Object; |
| * at org.apache.openjpa.jdbc.kernel.exps.Lit.appendTo(Lit.java:120) |
| * |
| * Notice the exception this time is in 'Lit'. Previous CCEx for the other tests have been in Param. |
| * With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows: |
| * |
| * SELECT t0.ID, t1.SUBJECTNUMMER, t1.CODE_SUBJECTTYPE FROM TOPIC t0 LEFT OUTER JOIN SUBJECT t1 ON |
| * t0.SUBJECT_SUBJECTNUMMER = |
| * t1.SUBJECTNUMMER AND t0.SUBJECT_CODE_SUBJECTTYPE = t1.CODE_SUBJECTTYPE WHERE (t0.SUBJECT_SUBJECTNUMMER = ??) |
| * optimize for 1 row [params=(int) 1, (String) Type] |
| * |
| * Notice that 't0.CODE_SUBJECTTYPE' is missing, and there are two parameter markers. With fix #2.1 and |
| * #2.2, this issue is resolved. |
| */ |
| public void testFindUsingCriteriaBuilderEquals() { |
| |
| Subject s = createSubject(); |
| CriteriaBuilder builder = em.getCriteriaBuilder(); |
| CriteriaQuery<Topic> cq = builder.createQuery(Topic.class); |
| |
| Root<Topic> topic = cq.from(Topic.class); |
| cq.select(topic).distinct(true); |
| |
| Predicate topicPredicate = builder.equal(topic.get("subject"), s); |
| cq.where(topicPredicate); |
| |
| TypedQuery<Topic> query = em.createQuery(cq); |
| Topic t = query.getSingleResult(); |
| |
| verifyResults(t, s); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey |
| * cannot be cast to [Ljava.lang.Object; |
| * at org.apache.openjpa.jdbc.kernel.exps.InExpression.orContains(InExpression.java:178) |
| * |
| * Notice this time the CCEx occurs in InExpression. With fix #1 the issue is resolved. |
| */ |
| public void testFindUsingJPQLInClauseOnSubject() { |
| Subject s = createSubject(); |
| SubjectKey key = new SubjectKey(999, "Bla"); |
| Subject s2 = new Subject(); |
| s2.setKey(key); |
| |
| List<Subject> subjectList = new ArrayList<Subject>(); |
| subjectList.add(s); |
| subjectList.add(s2); |
| |
| TypedQuery<Topic> query = em.createQuery( |
| "select distinct t from Topic t where t.subject in :subjectList", Topic.class); |
| query.setParameter("subjectList", subjectList); |
| Topic t = query.getSingleResult(); |
| |
| verifyResults(t, s); |
| } |
| |
| /* |
| * Prior to the fix #1 (see notes above), this fails on OJ with: |
| * |
| * Caused by: java.lang.ClassCastException: org.apache.openjpa.persistence.embed.compositepk.SubjectKey |
| * cannot be cast to [Ljava.lang.Object; |
| * at org.apache.openjpa.jdbc.kernel.exps.Lit.appendTo(Lit.java:120) |
| * |
| * Notice the exception this time is in 'Lit'. Previous CCEx for the other tests have been in Param. |
| * |
| * With fix #1, the CCEx is avoided/resolved. However, we then got an incorrectly generated SQL as follows: |
| * |
| * SELECT t0.ID, t1.SUBJECTNUMMER, t1.CODE_SUBJECTTYPE FROM TOPIC t0 LEFT OUTER JOIN SUBJECT t1 ON |
| * t0.SUBJECT_SUBJECTNUMMER = |
| * t1.SUBJECTNUMMER AND t0.SUBJECT_CODE_SUBJECTTYPE = t1.CODE_SUBJECTTYPE WHERE (t0.SUBJECT_SUBJECTNUMMER = ??) |
| * optimize for 1 row [params=(int) 1, (String) Type] |
| * |
| * Notice that 't0.CODE_SUBJECTTYPE' is missing, and there are two parameter markers. With fix #2.1 and |
| * #2.2, this issue is resolved. |
| */ |
| public void testFindUsingCriteriaBuilderInClauseOnSubject() { |
| |
| Subject s = createSubject(); |
| SubjectKey key = new SubjectKey(999, "Bla"); |
| Subject s2 = new Subject(); |
| s2.setKey(key); |
| |
| List<Subject> subjectList = new ArrayList<Subject>(); |
| subjectList.add(s); |
| subjectList.add(s2); |
| |
| CriteriaBuilder builder = em.getCriteriaBuilder(); |
| CriteriaQuery<Topic> cq = builder.createQuery(Topic.class); |
| |
| Root<Topic> topic = cq.from(Topic.class); |
| cq.select(topic).distinct(true); |
| |
| Predicate subjectInSubjectList = topic.get(Topic_.subject).in(subjectList); |
| cq.where(subjectInSubjectList); |
| |
| TypedQuery<Topic> query = em.createQuery(cq); |
| Topic t = query.getSingleResult(); |
| |
| verifyResults(t, s); |
| } |
| |
| /* |
| * This test works fine with or without the fixes. This was added as a comparison to the case |
| * where an @EmbeddedId is used. In other words, this query selects a Subject which uses |
| * a @IdClass (still considered an embeddable in OpenJPA). |
| */ |
| public void testFindUsingJPQLEqualsOnSubjectWithIdClass() { |
| SubjectWithIdClass s = new SubjectWithIdClass(); |
| s.setSubjectNummer(1); |
| s.setSubjectTypeCode("Type"); |
| |
| TypedQuery<SubjectWithIdClass> query = |
| em.createQuery("select s from SubjectWithIdClass s where s = :subject", SubjectWithIdClass.class); |
| |
| query.setParameter("subject", s); |
| SubjectWithIdClass s2 = query.getSingleResult(); |
| |
| |
| Assert.assertNotNull(s2); |
| Assert.assertEquals(s.getSubjectNummer(), s2.getSubjectNummer()); |
| Assert.assertEquals(s.getSubjectTypeCode(), s2.getSubjectTypeCode()); |
| } |
| |
| /* |
| * For this test, the CCEx is actually never hit with or without the fixes. However, incorrect |
| * SQL was generated as follows: |
| * |
| * SELECT t0.SUBJECTNUMMER, t0.CODE_SUBJECTTYPE FROM SUBJECT2 t0 WHERE |
| * (t0.SUBJECTNUMMER = ??) optimize for 1 row [params=(int) 1, (String) Type]} |
| * |
| * Notice that 't0.CODE_SUBJECTTYPE' is missing, and there is an extra parameter marker. With |
| * fix #2.1 and #2.2 this issue is resolved. |
| */ |
| public void testFindUsingCriteriaBuilderOnSubjectWithIdClass() { |
| SubjectWithIdClass s = new SubjectWithIdClass(); |
| s.setSubjectNummer(1); |
| s.setSubjectTypeCode("Type"); |
| |
| CriteriaBuilder builder = em.getCriteriaBuilder(); |
| CriteriaQuery<SubjectWithIdClass> cq = builder.createQuery(SubjectWithIdClass.class); |
| |
| Root<SubjectWithIdClass> subjectRoot = cq.from(SubjectWithIdClass.class); |
| cq.select(subjectRoot); |
| |
| Predicate subjectPredicate = builder.equal(subjectRoot, s); |
| |
| cq.where(subjectPredicate); |
| |
| TypedQuery<SubjectWithIdClass> query = em.createQuery(cq); |
| |
| SubjectWithIdClass s2 = query.getSingleResult(); |
| |
| Assert.assertNotNull(s2); |
| Assert.assertEquals(s.getSubjectNummer(), s2.getSubjectNummer()); |
| Assert.assertEquals(s.getSubjectTypeCode(), s2.getSubjectTypeCode()); |
| } |
| |
| |
| private void createData(){ |
| Subject s = new Subject(); |
| SubjectKey sk = new SubjectKey(); |
| sk.setSubjectNummer(1); |
| sk.setSubjectType("Type2"); |
| s.setKey(sk); |
| em.persist(s); |
| |
| s = new Subject(); |
| sk = new SubjectKey(); |
| sk.setSubjectNummer(1); |
| sk.setSubjectType("Type"); |
| s.setKey(sk); |
| em.persist(s); |
| |
| Topic t = new Topic(); |
| t.setId(5); |
| t.setSubject(s); |
| em.persist(t); |
| |
| SubjectWithIdClass swic = new SubjectWithIdClass(); |
| swic.setSubjectNummer(1); |
| swic.setSubjectTypeCode("Type"); |
| em.persist(swic); |
| |
| swic = new SubjectWithIdClass(); |
| swic.setSubjectNummer(1); |
| swic.setSubjectTypeCode("Type2"); |
| em.persist(swic); |
| |
| em.flush(); |
| } |
| |
| private Subject createSubject() { |
| SubjectKey key = new SubjectKey(1, "Type"); |
| Subject result = new Subject(); |
| result.setKey(key); |
| |
| return result; |
| } |
| |
| public void verifyResults(Topic topic, Subject s) { |
| Assert.assertNotNull(topic); |
| Assert.assertEquals(new Integer(5), topic.getId()); |
| Subject s2 = topic.getSubject(); |
| verifySubject(s, s2); |
| } |
| |
| public void verifySubject(Subject expected, Subject actual) { |
| Assert.assertNotNull(expected); |
| Assert.assertEquals(expected.getKey().getSubjectNummer(), actual.getKey().getSubjectNummer()); |
| Assert.assertEquals(expected.getKey().getSubjectTypeCode(), actual.getKey().getSubjectTypeCode()); |
| } |
| |
| public void tearDown() { |
| if (tx != null && tx.isActive()) { |
| tx.rollback(); |
| tx = null; |
| } |
| |
| if (em != null && em.isOpen()) { |
| em.close(); |
| em = null; |
| } |
| } |
| } |