Skip to content

Commit

Permalink
DATAJPA-50 - Introduced support for usage of @IdClass.
Browse files Browse the repository at this point in the history
Introduced to support @IdClass to define entity ids. The JpeMetamodelEntityInformation builds instances of the annotated @IdClass in case any of the attributes of the entity declared in the @IdClass has a non-null value.
  • Loading branch information
odrotbohm committed Jan 31, 2012
1 parent 0795827 commit 3962f1a
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2011-2012 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.
Expand All @@ -17,14 +17,21 @@

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

import javax.persistence.IdClass;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.beans.NotReadablePropertyException;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
Expand All @@ -37,13 +44,13 @@
public class JpaMetamodelEntityInformation<T, ID extends Serializable> extends JpaEntityInformationSupport<T, ID>
implements JpaEntityInformation<T, ID> {

private final SingularAttribute<? super T, ?> attribute;
private final IdMetadata<T> idMetadata;

/**
* Creates a new {@link JpaMetamodelEntityInformation} for the given domain class and {@link Metamodel}.
*
* @param domainClass
* @param metamodel
* @param domainClass must not be {@literal null}.
* @param metamodel must not be {@@iteral null}.
*/
public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel) {

Expand All @@ -60,21 +67,36 @@ public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel)
throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
}

IdentifiableType<T> identifiableType = (IdentifiableType<T>) type;
this.attribute = identifiableType.getId(identifiableType.getIdType().getJavaType());
this.idMetadata = new IdMetadata<T>((IdentifiableType<T>) type);
}

/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.support.IdAware#getId(java.lang.Object
* )
* @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object)
*/
@SuppressWarnings("unchecked")
public ID getId(T entity) {

return (ID) getMemberValue(attribute.getJavaMember(), entity);
BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);

if (idMetadata.hasSimpleId()) {
return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
}

BeanWrapper idWrapper = new DirectFieldAccessFallbackBeanWrapper(idMetadata.getType());
boolean partialIdValueFound = false;

for (SingularAttribute<? super T, ?> attribute : idMetadata) {
Object propertyValue = entityWrapper.getPropertyValue(attribute.getName());

if (propertyValue != null) {
partialIdValueFound = true;
}

idWrapper.setPropertyValue(attribute.getName(), propertyValue);
}

return (ID) (partialIdValueFound ? idWrapper.getWrappedInstance() : null);
}

/*
Expand All @@ -85,40 +107,109 @@ public ID getId(T entity) {
*/
@SuppressWarnings("unchecked")
public Class<ID> getIdType() {
return (Class<ID>) idMetadata.getType();
}

return (Class<ID>) attribute.getJavaType();
/*
* (non-Javadoc)
*
* @see org.springframework.data.jpa.repository.support.JpaEntityMetadata#
* getIdAttribute()
*/
public SingularAttribute<? super T, ?> getIdAttribute() {
return idMetadata.getSimpleIdAttribute();
}

/**
* Returns the value of the given {@link Member} of the given {@link Object} .
* Simple value object to encapsulate id specific metadata.
*
* @param member
* @param source
* @return
* @author Oliver Gierke
*/
private static Object getMemberValue(Member member, Object source) {

if (member instanceof Field) {
Field field = (Field) member;
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, source);
} else if (member instanceof Method) {
Method method = (Method) member;
ReflectionUtils.makeAccessible(method);
return ReflectionUtils.invokeMethod(method, source);
private static class IdMetadata<T> implements Iterable<SingularAttribute<? super T, ?>> {

private final IdentifiableType<T> type;
private final Set<SingularAttribute<? super T, ?>> attributes;

@SuppressWarnings("unchecked")
public IdMetadata(IdentifiableType<T> source) {

this.type = source;
this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute() ? Collections
.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
}

throw new IllegalArgumentException("Given member is neither Field nor Method!");
public boolean hasSimpleId() {
return attributes.size() == 1;
}

public Class<?> getType() {

try {
return type.getIdType().getJavaType();
} catch (IllegalStateException e) {
// see https://hibernate.onjira.com/browse/HHH-6951
IdClass annotation = type.getJavaType().getAnnotation(IdClass.class);
return annotation == null ? null : annotation.value();
}
}

public SingularAttribute<? super T, ?> getSimpleIdAttribute() {
return attributes.iterator().next();
}

/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator<SingularAttribute<? super T, ?>> iterator() {
return attributes.iterator();
}
}

/*
* (non-Javadoc)
/**
* Custom extension of {@link BeanWrapperImpl} that falls back to direct field access in case the object or type being
* wrapped does not use accessor methods.
*
* @see org.springframework.data.jpa.repository.support.JpaEntityMetadata#
* getIdAttribute()
* @author Oliver Gierke
*/
public SingularAttribute<? super T, ?> getIdAttribute() {
private static class DirectFieldAccessFallbackBeanWrapper extends BeanWrapperImpl {

public DirectFieldAccessFallbackBeanWrapper(Object entity) {
super(entity);
}

return attribute;
public DirectFieldAccessFallbackBeanWrapper(Class<?> type) {
super(type);
}

/*
* (non-Javadoc)
* @see org.springframework.beans.BeanWrapperImpl#getPropertyValue(java.lang.String)
*/
@Override
public Object getPropertyValue(String propertyName) throws BeansException {
try {
return super.getPropertyValue(propertyName);
} catch (NotReadablePropertyException e) {
Field field = ReflectionUtils.findField(getWrappedClass(), propertyName);
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, getWrappedInstance());
}
}

/*
* (non-Javadoc)
* @see org.springframework.beans.BeanWrapperImpl#setPropertyValue(java.lang.String, java.lang.Object)
*/
@Override
public void setPropertyValue(String propertyName, Object value) throws BeansException {
try {
super.setPropertyValue(propertyName, value);
} catch (NotWritablePropertyException e) {
Field field = ReflectionUtils.findField(getWrappedClass(), propertyName);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, getWrappedInstance(), value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2008-2011 the original author or authors.
* Copyright 2008-2012 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.
Expand Down Expand Up @@ -195,16 +195,20 @@ public boolean exists(ID id) {

Assert.notNull(id, "The given id must not be null!");

String placeholder = provider.getCountQueryPlaceholder();
String entityName = entityInformation.getEntityName();
String idAttributeName = entityInformation.getIdAttribute().getName();
if (entityInformation.getIdAttribute() != null) {

String existsQuery = String.format(EXISTS_QUERY_STRING, placeholder, entityName, idAttributeName);
String placeholder = provider.getCountQueryPlaceholder();
String entityName = entityInformation.getEntityName();
String idAttributeName = entityInformation.getIdAttribute().getName();
String existsQuery = String.format(EXISTS_QUERY_STRING, placeholder, entityName, idAttributeName);

TypedQuery<Long> query = em.createQuery(existsQuery, Long.class);
query.setParameter("id", id);
TypedQuery<Long> query = em.createQuery(existsQuery, Long.class);
query.setParameter("id", id);

return query.getSingleResult() == 1;
return query.getSingleResult() == 1;
} else {
return findOne(id) != null;
}
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2012 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 org.springframework.data.domain.Persistable;

/**
* Sample entity using {@link IdClass} annotation to demarcate ids.
*
* @author Oliver Gierke
*/
@Entity
@IdClass(SampleWithIdClassPK.class)
public class SampleWithIdClass implements Persistable<SampleWithIdClassPK> {

private static final long serialVersionUID = 1L;

@Id
Long first;

@Id
Long second;

private boolean isNew;

protected SampleWithIdClass() {
this.isNew = true;
}

public SampleWithIdClass(Long first, Long second) {
this.first = first;
this.second = second;
this.isNew = true;
}

/**
* @return the first
*/
public Long getFirst() {
return first;
}

/**
* @return the second
*/
public Long getSecond() {
return second;
}

/* (non-Javadoc)
* @see org.springframework.data.domain.Persistable#getId()
*/
public SampleWithIdClassPK getId() {
return new SampleWithIdClassPK(first, second);
}

/* (non-Javadoc)
* @see org.springframework.data.domain.Persistable#isNew()
*/
public boolean isNew() {
return this.isNew;
}

public void setNotNew() {
this.isNew = false;
}
}
Loading

1 comment on commit 3962f1a

@cpaschou
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am developing a project that uses spring data jpa for persist data. I had faced the problem that CrudRepository class could not be applied for a class that had more than one primary keys, defined using @IdClass. Does the above commit add this fuctionality in spring data jpa? The current release of spring-data- jpa 1.0.3 RELEASE doesn't include the above changes. Do you have any idea in which version this feature will be introduced and when it is estimated this release to become live?

Many thanks!

Please sign in to comment.