Browse Source

DATAJPA-266 - Fixed SimpleJpaRepository.exists(…) for composite ids.

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.
pull/20/head
Thomas Darimont 13 years ago committed by Oliver Gierke
parent
commit
562dc19b8c
  1. 29
      src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java
  2. 27
      src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java
  3. 35
      src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java
  4. 16
      src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java
  5. 28
      src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java
  6. 13
      src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java
  7. 18
      src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java
  8. 37
      src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java

29
src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java

@ -58,10 +58,8 @@ import org.springframework.util.StringUtils; @@ -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 { @@ -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<PersistentAttributeType> ASSOCIATION_TYPES;
static {
@ -113,6 +113,29 @@ public abstract class QueryUtils { @@ -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<String> 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.
*

27
src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java

@ -1,5 +1,5 @@ @@ -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; @@ -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<T, ID extends Serializable> extends EntityInformation<T, ID> {
@ -35,6 +36,30 @@ public interface JpaEntityInformation<T, ID extends Serializable> extends Entity @@ -35,6 +36,30 @@ public interface JpaEntityInformation<T, ID extends Serializable> extends Entity
*/
SingularAttribute<? super T, ?> 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<String> 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.
*

35
src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java

@ -17,8 +17,10 @@ package org.springframework.data.jpa.repository.support; @@ -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; @@ -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<T, ID extends Serializable> extends JpaEntityInformationSupport<T, ID> {
@ -118,6 +121,38 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J @@ -118,6 +121,38 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> 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<String> getIdAttributeNames() {
List<String> attributeNames = new ArrayList<String>(idMetadata.attributes.size());
for (SingularAttribute<? super T, ?> 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.
*

16
src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

@ -50,6 +50,7 @@ import org.springframework.util.Assert; @@ -50,6 +50,7 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
* @author Eberhard Wolff
* @author Thomas Darimont
* @param <T> the type of the entity to handle
* @param <ID> the type of the entity's identifier
*/
@ -219,13 +220,20 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos @@ -219,13 +220,20 @@ public class SimpleJpaRepository<T, ID extends Serializable> 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<String> idAttributeNames = entityInformation.getIdAttributeNames();
String existsQuery = QueryUtils.getExistsQueryString(entityName, placeholder, idAttributeNames);
TypedQuery<Long> 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;
}

28
src/test/java/org/springframework/data/jpa/repository/support/EclipseLinkJpaRepositoryTests.java

@ -0,0 +1,28 @@ @@ -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 {
}

13
src/test/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupportUnitTests.java

@ -19,6 +19,7 @@ import static org.junit.Assert.*; @@ -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 { @@ -93,5 +94,17 @@ public class JpaEntityInformationSupportUnitTests {
return null;
}
public Iterable<String> getIdAttributeNames() {
return Collections.emptySet();
}
public boolean hasCompositeId() {
return false;
}
public Object getCompositeIdAttributeValue(Serializable id, String idAttribute) {
return null;
}
}
}

18
src/test/java/org/springframework/data/jpa/repository/support/JpaRepositoryTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -74,7 +74,7 @@ public class JpaRepositoryTests {
}
/**
* DATAJPA-50
* @see DATAJPA-50
*/
@Test
public void executesCrudOperationsForEntityWithIdClass() {
@ -90,6 +90,20 @@ public class JpaRepositoryTests { @@ -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<SampleEntity, SampleEntityPK> {
}

37
src/test/java/org/springframework/data/jpa/repository/support/OpenJpaJpaRepositoryTests.java

@ -0,0 +1,37 @@ @@ -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 {
}
}
Loading…
Cancel
Save