DATAJPA-269 - Add support for derived identifier of nested composite keys.

Extended JpaMetamodelEntityInformation to deal with entities with custom composite keys for which we have to derive the identifier. Introduced IdentifierDerivingDirectFieldAccessFallbackBeanWrapper that is capable of deriving identifier values. Added test case to verify the support for save / load of entities with custom composite keys.

Original pull request: #28.
This commit is contained in:
Thomas Darimont
2013-07-25 14:09:22 +02:00
committed by Oliver Gierke
parent 5e5f494ac5
commit 6824cc158e
11 changed files with 639 additions and 1 deletions

View File

@@ -28,6 +28,7 @@ import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type.PersistenceType;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
@@ -48,6 +49,7 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
private final IdMetadata<T> idMetadata;
private final SingularAttribute<? super T, ?> versionAttribute;
private final Metamodel metamodel;
/**
* Creates a new {@link JpaMetamodelEntityInformation} for the given domain class and {@link Metamodel}.
@@ -60,6 +62,8 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
super(domainClass);
Assert.notNull(metamodel);
this.metamodel = metamodel;
ManagedType<T> type = metamodel.managedType(domainClass);
if (type == null) {
@@ -106,7 +110,7 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
}
BeanWrapper idWrapper = new DirectFieldAccessFallbackBeanWrapper(idMetadata.getType());
BeanWrapper idWrapper = new IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(idMetadata.getType(), metamodel);
boolean partialIdValueFound = false;
for (SingularAttribute<? super T, ?> attribute : idMetadata) {
@@ -291,4 +295,67 @@ public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends J
}
}
}
/**
* Custom extension of {@link DirectFieldAccessFallbackBeanWrapper} that allows to derived the identifier if composite
* keys with complex key attribute types (e.g. types that are annotated with {@code @Entity} themselves) are used.
*
* @author Thomas Darimont
*/
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper extends
DirectFieldAccessFallbackBeanWrapper {
private final Metamodel metamodel;
public IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class<?> type, Metamodel metamodel) {
super(type);
this.metamodel = metamodel;
}
/**
* In addition to the functionality described in {@link BeanWrapperImpl} it is checked whether we have a nested
* entity that is part of the id key. If this is the case, we need to derive the identifier of the nested entity.
*
* @see org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.DirectFieldAccessFallbackBeanWrapper#setPropertyValue(java.lang.String,
* java.lang.Object)
*/
@Override
public void setPropertyValue(String propertyName, Object value) {
if (isIdentifierDerivationNecessary(value)) {
// Derive the identifer from the nested entity that is part of the composite key.
@SuppressWarnings({ "rawtypes", "unchecked" })
JpaMetamodelEntityInformation nestedEntityInformation = new JpaMetamodelEntityInformation(value.getClass(),
this.metamodel);
Object nestedIdPropertyValue = new DirectFieldAccessFallbackBeanWrapper(value)
.getPropertyValue(nestedEntityInformation.getIdAttribute().getName());
super.setPropertyValue(propertyName, nestedIdPropertyValue);
return;
}
super.setPropertyValue(propertyName, value);
}
/**
* @param value
* @return {@literal true} if the given value is not {@literal null} and a mapped persistable entity otherwise
* {@literal false}
*/
private boolean isIdentifierDerivationNecessary(Object value) {
if (value == null) {
return false;
}
try {
ManagedType<? extends Object> managedType = this.metamodel.managedType(value.getClass());
return managedType != null && managedType.getPersistenceType() == PersistenceType.ENTITY;
} catch (IllegalArgumentException iae) {
// no mapped type
return false;
}
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.domain.sample;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class EmbeddedIdExampleDepartment {
private String name;
@Id private Long departmentId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (departmentId ^ (departmentId >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EmbeddedIdExampleDepartment other = (EmbeddedIdExampleDepartment) obj;
if (departmentId != other.departmentId)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.domain.sample;
import javax.persistence.CascadeType;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
@Entity
public class EmbeddedIdExampleEmployee {
@EmbeddedId EmbeddedIdExampleEmployeePK employeePk;
@MapsId("departmentId") @ManyToOne(cascade = CascadeType.ALL)//
EmbeddedIdExampleDepartment department;
public EmbeddedIdExampleEmployeePK getEmployeePk() {
return employeePk;
}
public void setEmployeePk(EmbeddedIdExampleEmployeePK employeePk) {
this.employeePk = employeePk;
}
public EmbeddedIdExampleDepartment getDepartment() {
return department;
}
public void setDepartment(EmbeddedIdExampleDepartment department) {
this.department = department;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.domain.sample;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class EmbeddedIdExampleEmployeePK implements Serializable {
private static final long serialVersionUID = 1L;
@Column(nullable = false) private Long employeeId;
@Column(nullable = false) private Long departmentId;
public Long getEmployeeId() {
return this.employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public Long getDepartmentId() {
return this.departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (departmentId ^ (departmentId >>> 32));
result = prime * result + (int) (employeeId ^ (employeeId >>> 32));
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EmbeddedIdExampleEmployeePK other = (EmbeddedIdExampleEmployeePK) obj;
if (departmentId != other.departmentId)
return false;
if (employeeId != other.employeeId)
return false;
return true;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.domain.sample;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class IdClassExampleDepartment {
private String name;
@Id private long departmentId;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(long departmentId) {
this.departmentId = departmentId;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (departmentId ^ (departmentId >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IdClassExampleDepartment other = (IdClassExampleDepartment) obj;
if (departmentId != other.departmentId)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.domain.sample;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
@IdClass(IdClassExampleEmployeePK.class)
@Entity
public class IdClassExampleEmployee {
@Id long empId;
@Id @ManyToOne IdClassExampleDepartment department;
public long getEmpId() {
return empId;
}
public void setEmpId(long empId) {
this.empId = empId;
}
public IdClassExampleDepartment getDepartment() {
return department;
}
public void setDepartment(IdClassExampleDepartment department) {
this.department = department;
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.domain.sample;
import java.io.Serializable;
public class IdClassExampleEmployeePK implements Serializable {
private static final long serialVersionUID = 1L;
private long empId;
private long department;
public IdClassExampleEmployeePK() {}
public long getEmpId() {
return this.empId;
}
public void setEmpId(long empId) {
this.empId = empId;
}
public long getDepartment() {
return this.department;
}
public void setDepartment(long department) {
this.department = department;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (department ^ (department >>> 32));
result = prime * result + (int) (empId ^ (empId >>> 32));
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
IdClassExampleEmployeePK other = (IdClassExampleEmployeePK) obj;
if (department != other.department) {
return false;
}
if (empId != other.empId) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,111 @@
/*
* 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;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleDepartment;
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployee;
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployeePK;
import org.springframework.data.jpa.domain.sample.IdClassExampleDepartment;
import org.springframework.data.jpa.domain.sample.IdClassExampleEmployee;
import org.springframework.data.jpa.domain.sample.IdClassExampleEmployeePK;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.sample.EmployeeRepositoryWithEmbeddedId;
import org.springframework.data.jpa.repository.sample.EmployeeRepositoryWithIdClass;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Tests some usage variants of composite keys with spring data jpa.
*
* @see DATAJPA-269
* @author Thomas Darimont
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class DataJpa269RepositoryWithCompositeKeyTests {
@Configuration
@ImportResource("classpath:infrastructure.xml")
@EnableJpaRepositories
static interface Config {}
@Autowired EmployeeRepositoryWithIdClass employeeRepositoryWithIdClass;
@Autowired EmployeeRepositoryWithEmbeddedId employeeRepositoryWithEmbeddedId;
/**
* @see DATAJPA-269
* @see Final JPA 2.0 Specification 2.4.1.3 Derived Identities Example 2
*/
@Test
public void shouldSupportSavingEntitiesWithCompositeKeyClassesWithIdClassAndDerivedIdentities() {
IdClassExampleDepartment dep = new IdClassExampleDepartment();
dep.setName("TestDepartment");
dep.setDepartmentId(-1);
IdClassExampleEmployee emp = new IdClassExampleEmployee();
emp.setDepartment(dep);
employeeRepositoryWithIdClass.save(emp);
IdClassExampleEmployeePK key = new IdClassExampleEmployeePK();
key.setDepartment(dep.getDepartmentId());
key.setEmpId(emp.getEmpId());
IdClassExampleEmployee persistedEmp = employeeRepositoryWithIdClass.findOne(key);
assertThat(persistedEmp, is(notNullValue()));
assertThat(persistedEmp.getDepartment(), is(notNullValue()));
assertThat(persistedEmp.getDepartment().getName(), is(dep.getName()));
}
/**
* @see DATAJPA-269
* @see Final JPA 2.0 Specification 2.4.1.3 Derived Identities Example 3
*/
@Test
public void shouldSupportSavingEntitiesWithCompositeKeyClassesWithEmbeddedIdsAndDerivedIdentities() {
EmbeddedIdExampleDepartment dep = new EmbeddedIdExampleDepartment();
dep.setName("TestDepartment");
dep.setDepartmentId(-1L);
EmbeddedIdExampleEmployee emp = new EmbeddedIdExampleEmployee();
emp.setDepartment(dep);
emp.setEmployeePk(new EmbeddedIdExampleEmployeePK());
emp = employeeRepositoryWithEmbeddedId.save(emp);
EmbeddedIdExampleEmployeePK key = new EmbeddedIdExampleEmployeePK();
key.setDepartmentId(emp.getDepartment().getDepartmentId());
key.setEmployeeId(emp.getEmployeePk().getEmployeeId());
EmbeddedIdExampleEmployee persistedEmp = employeeRepositoryWithEmbeddedId.findOne(key);
assertThat(persistedEmp, is(notNullValue()));
assertThat(persistedEmp.getDepartment(), is(notNullValue()));
assertThat(persistedEmp.getDepartment().getName(), is(dep.getName()));
}
}

View File

@@ -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.sample;
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployee;
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployeePK;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Demonstrates the support for composite primary keys with {@code @EmbeddedId}.
*
* @author Thomas Darimont
*/
public interface EmployeeRepositoryWithEmbeddedId extends
JpaRepository<EmbeddedIdExampleEmployee, EmbeddedIdExampleEmployeePK> {}

View File

@@ -0,0 +1,27 @@
/*
* 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.sample;
import org.springframework.data.jpa.domain.sample.IdClassExampleEmployee;
import org.springframework.data.jpa.domain.sample.IdClassExampleEmployeePK;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Demonstrates the support for composite primary keys with {@code @IdClass}.
*
* @author Thomas Darimont
*/
public interface EmployeeRepositoryWithIdClass extends JpaRepository<IdClassExampleEmployee, IdClassExampleEmployeePK> {}

View File

@@ -18,6 +18,11 @@
<class>org.springframework.data.jpa.domain.sample.AbstractMappedType</class>
<class>org.springframework.data.jpa.domain.sample.ConcreteType1</class>
<class>org.springframework.data.jpa.domain.sample.ConcreteType2</class>
<class>org.springframework.data.jpa.domain.sample.IdClassExampleEmployee</class>
<class>org.springframework.data.jpa.domain.sample.IdClassExampleDepartment</class>
<class>org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployeePK</class>
<class>org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployee</class>
<class>org.springframework.data.jpa.domain.sample.EmbeddedIdExampleDepartment</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
</persistence-unit>
<persistence-unit name="querydsl">