From 562dc19b8cdfa1662f8eeba1e32bbfd4a7c2bbb3 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Sat, 26 Jan 2013 02:47:03 +0100 Subject: [PATCH] =?UTF-8?q?DATAJPA-266=20-=20Fixed=20SimpleJpaRepository.e?= =?UTF-8?q?xists(=E2=80=A6)=20for=20composite=20ids.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now build a correct count query for entities that use a compound key mapped using @IdClass. Added integration tests for EclipseLink and OpenJPA to run the basic tests of SimpleJpaRepository against different persistence providers. Ignored failing test against OpenJPA. --- .../data/jpa/repository/query/QueryUtils.java | 29 +++++++++++++-- .../support/JpaEntityInformation.java | 27 +++++++++++++- .../JpaMetamodelEntityInformation.java | 35 ++++++++++++++++++ .../support/SimpleJpaRepository.java | 16 ++++++-- .../EclipseLinkJpaRepositoryTests.java | 28 ++++++++++++++ .../JpaEntityInformationSupportUnitTests.java | 13 +++++++ .../support/JpaRepositoryTests.java | 18 ++++++++- .../support/OpenJpaJpaRepositoryTests.java | 37 +++++++++++++++++++ 8 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java create mode 100644 src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index f6121cac9..e5f6595f6 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -58,10 +58,8 @@ import org.springframework.util.StringUtils; public abstract class QueryUtils { public static final String COUNT_QUERY_STRING = "select count(%s) from %s x"; - public static final String EXISTS_QUERY_STRING = "select count(%s) from %s x where x.%s = :id"; - public static final String DELETE_ALL_QUERY_STRING = "delete from %s x"; - public static final String READ_ALL_QUERY = "select x from %s x"; + private static final String DEFAULT_ALIAS = "x"; private static final String COUNT_REPLACEMENT = "select count($3$5) $4$5$6"; @@ -74,6 +72,8 @@ public abstract class QueryUtils { private static final String LEFT_JOIN = "left (outer )?join " + IDENTIFIER + " (as )?" + IDENTIFIER_GROUP; private static final Pattern LEFT_JOIN_PATTERN = Pattern.compile(LEFT_JOIN, Pattern.CASE_INSENSITIVE); + private static final String EQUALS_CONDITION_STRING = "%s.%s = :%s"; + private static final Set ASSOCIATION_TYPES; static { @@ -113,6 +113,29 @@ public abstract class QueryUtils { } + /** + * Returns the query string to execute an exists query for the given id attributes. + * + * @param entityName the name of the entity to create the query for, must not be {@literal null}. + * @param countQueryPlaceHolder the placeholder for the count clause, must not be {@literal null}. + * @param idAttributes the id attributes for the entity, must not be {@literal null}. + * @return + */ + public static String getExistsQueryString(String entityName, String countQueryPlaceHolder, + Iterable idAttributes) { + + StringBuilder sb = new StringBuilder(String.format(COUNT_QUERY_STRING, countQueryPlaceHolder, entityName)); + sb.append(" WHERE "); + + for (String idAttribute : idAttributes) { + sb.append(String.format(EQUALS_CONDITION_STRING, "x", idAttribute, idAttribute)); + sb.append(" AND "); + } + + sb.append("1 = 1"); + return sb.toString(); + } + /** * Returns the query string for the given class name. * diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java index 801884467..be13dc1fa 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.data.repository.core.EntityInformation; * Extension of {@link EntityInformation} to capture aditional JPA specific information about entities. * * @author Oliver Gierke + * @author Thomas Darimont */ public interface JpaEntityInformation extends EntityInformation { @@ -35,6 +36,30 @@ public interface JpaEntityInformation extends Entity */ SingularAttribute getIdAttribute(); + /** + * Returns {@literal true} if the entity has a composite id + * + * @return + */ + boolean hasCompositeId(); + + /** + * Returns the attribute names of the id attributes. If the entity has a composite id, then all id attribute names are + * returned. If the entity has a single id attribute then this single attribute name is returned. + * + * @return + */ + Iterable getIdAttributeNames(); + + /** + * Extracts the value for the given id attribute from a composite id + * + * @param id + * @param idAttribute + * @return + */ + Object getCompositeIdAttributeValue(Serializable id, String idAttribute); + /** * Returns the JPA entity name. * diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java index 9861510e8..3fb649031 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -17,8 +17,10 @@ package org.springframework.data.jpa.repository.support; import java.io.Serializable; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Set; import javax.persistence.IdClass; @@ -39,6 +41,7 @@ import org.springframework.util.ReflectionUtils; * to find the domain class' id field. * * @author Oliver Gierke + * @author Thomas Darimont */ public class JpaMetamodelEntityInformation extends JpaEntityInformationSupport { @@ -118,6 +121,38 @@ public class JpaMetamodelEntityInformation extends J return idMetadata.getSimpleIdAttribute(); } + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#hasCompositeId() + */ + public boolean hasCompositeId() { + return !idMetadata.hasSimpleId(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttributeNames() + */ + public Iterable getIdAttributeNames() { + + List attributeNames = new ArrayList(idMetadata.attributes.size()); + + for (SingularAttribute attribute : idMetadata.attributes) { + attributeNames.add(attribute.getName()); + } + + return attributeNames; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getCompositeIdAttributeValue(java.io.Serializable, java.lang.String) + */ + public Object getCompositeIdAttributeValue(Serializable id, String idAttribute) { + Assert.isTrue(hasCompositeId()); + return new DirectFieldAccessFallbackBeanWrapper(id).getPropertyValue(idAttribute); + } + /** * Simple value object to encapsulate id specific metadata. * diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 73e62ed98..c1a66ec37 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -50,6 +50,7 @@ import org.springframework.util.Assert; * * @author Oliver Gierke * @author Eberhard Wolff + * @author Thomas Darimont * @param the type of the entity to handle * @param the type of the entity's identifier */ @@ -219,13 +220,20 @@ public class SimpleJpaRepository implements JpaRepos String placeholder = provider.getCountQueryPlaceholder(); String entityName = entityInformation.getEntityName(); - String idAttributeName = entityInformation.getIdAttribute().getName(); - String existsQuery = String.format(EXISTS_QUERY_STRING, placeholder, entityName, idAttributeName); + Iterable idAttributeNames = entityInformation.getIdAttributeNames(); + String existsQuery = QueryUtils.getExistsQueryString(entityName, placeholder, idAttributeNames); TypedQuery query = em.createQuery(existsQuery, Long.class); - query.setParameter("id", id); - return query.getSingleResult() == 1; + if (entityInformation.hasCompositeId()) { + for (String idAttributeName : idAttributeNames) { + query.setParameter(idAttributeName, entityInformation.getCompositeIdAttributeValue(id, idAttributeName)); + } + } else { + query.setParameter(idAttributeNames.iterator().next(), id); + } + + return query.getSingleResult() == 1L; } else { return findOne(id) != null; } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java new file mode 100644 index 000000000..50ce6fb7d --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed 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.springframework.data.jpa.repository.support; + +import org.springframework.test.context.ContextConfiguration; + +/** + * Integration tests to execute {@link JpaRepositoryTests} against EclipseLink. + * + * @author Oliver Gierke + */ +@ContextConfiguration("classpath:eclipselink.xml") +public class EclipseLinkJpaRepositoryTests extends JpaRepositoryTests { + +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java index 0b2bb84fd..c16866a92 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.Serializable; +import java.util.Collections; import javax.persistence.Entity; import javax.persistence.EntityManager; @@ -93,5 +94,17 @@ public class JpaEntityInformationSupportUnitTests { return null; } + + public Iterable getIdAttributeNames() { + return Collections.emptySet(); + } + + public boolean hasCompositeId() { + return false; + } + + public Object getCompositeIdAttributeValue(Serializable id, String idAttribute) { + return null; + } } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java index fdc5dd0e2..c5eafef1e 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2012 the original author or authors. + * Copyright 2008-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ public class JpaRepositoryTests { } /** - * DATAJPA-50 + * @see DATAJPA-50 */ @Test public void executesCrudOperationsForEntityWithIdClass() { @@ -90,6 +90,20 @@ public class JpaRepositoryTests { assertThat(idClassRepository.findOne(id), is(entity)); } + /** + * @see DATAJPA-266 + */ + @Test + public void testExistsForDomainObjectsWithCompositeKeys() throws Exception { + + SampleWithIdClass s1 = idClassRepository.save(new SampleWithIdClass(1L, 1L)); + SampleWithIdClass s2 = idClassRepository.save(new SampleWithIdClass(2L, 2L)); + + assertThat(idClassRepository.exists(s1.getId()), is(true)); + assertThat(idClassRepository.exists(s2.getId()), is(true)); + assertThat(idClassRepository.exists(new SampleWithIdClassPK(1L, 2L)), is(false)); + } + private static interface SampleEntityRepository extends JpaRepository { } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java new file mode 100644 index 000000000..23bb45156 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed 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.springframework.data.jpa.repository.support; + +import org.junit.Ignore; +import org.springframework.test.context.ContextConfiguration; + +/** + * Integration tests to execute {@link JpaRepositoryTests} against OpenJpa. + * + * @author Oliver Gierke + */ +@ContextConfiguration("classpath:openjpa.xml") +public class OpenJpaJpaRepositoryTests extends JpaRepositoryTests { + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.JpaRepositoryTests#testCrudOperationsForCompoundKeyEntity() + */ + @Override + @Ignore + public void testCrudOperationsForCompoundKeyEntity() throws Exception { + } +}