HHH-4782 supports @AssociationOverride.joinTable

HHH-4679 add tests for dot notations in @AssocuiationOverride

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18518 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Emmanuel Bernard 2010-01-12 18:48:42 +00:00
parent 3f0e54c5d2
commit b1f925458e
10 changed files with 178 additions and 42 deletions

View File

@ -33,6 +33,7 @@ import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.MappedSuperclass;
import org.hibernate.AssertionFailure;
@ -50,9 +51,12 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
private Map<String, Column[]> currentPropertyColumnOverride;
private Map<String, JoinColumn[]> holderJoinColumnOverride;
private Map<String, JoinColumn[]> currentPropertyJoinColumnOverride;
private Map<String, JoinTable> holderJoinTableOverride;
private Map<String, JoinTable> currentPropertyJoinTableOverride;
private String path;
private ExtendedMappings mappings;
public AbstractPropertyHolder(
String path, PropertyHolder parent, XClass clazzToProcess, ExtendedMappings mappings
) {
@ -77,6 +81,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
if ( property == null ) {
this.currentPropertyColumnOverride = null;
this.currentPropertyJoinColumnOverride = null;
this.currentPropertyJoinTableOverride = null;
}
else {
this.currentPropertyColumnOverride = buildColumnOverride(
@ -93,6 +98,13 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
if ( this.currentPropertyJoinColumnOverride.size() == 0 ) {
this.currentPropertyJoinColumnOverride = null;
}
this.currentPropertyJoinTableOverride = buildJoinTableOverride(
property,
getPath()
);
if ( this.currentPropertyJoinTableOverride.size() == 0 ) {
this.currentPropertyJoinTableOverride = null;
}
}
}
@ -111,6 +123,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
// WARNING: this can conflict with user's expectations if:
// - the property uses some restricted values
// - the user has overridden the column
// also change getOverriddenJoinColumn and getOverriddenJoinTable as well
// if ( propertyName.contains( ".key." ) ) {
// //support for legacy @AttributeOverride declarations
@ -149,7 +162,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
* Get column overriding, property first, then parent, then holder
* find the overridden rules from the exact property name.
*/
public Column[] getExactOverriddenColumn(String propertyName) {
private Column[] getExactOverriddenColumn(String propertyName) {
Column[] override = null;
if ( parent != null ) {
override = parent.getExactOverriddenColumn( propertyName );
@ -165,8 +178,25 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
/**
* Get column overriding, property first, then parent, then holder
* replace the placeholder 'collection&&element' with nothing
*
* These rules are here to support both JPA 2 and legacy overriding rules.
*
*/
public JoinColumn[] getOverriddenJoinColumn(String propertyName) {
JoinColumn[] result = getExactOverriddenJoinColumn( propertyName );
if ( result == null && propertyName.contains( ".collection&&element." ) ) {
//support for non map collections where no prefix is needed
//TODO cache the underlying regexp
result = getExactOverriddenJoinColumn( propertyName.replace( ".collection&&element.", "." ) );
}
return result;
}
/**
* Get column overriding, property first, then parent, then holder
*/
private JoinColumn[] getExactOverriddenJoinColumn(String propertyName) {
JoinColumn[] override = null;
if ( parent != null ) {
override = parent.getOverriddenJoinColumn( propertyName );
@ -180,26 +210,81 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
return override;
}
/**
* Get column overriding, property first, then parent, then holder
* replace the placeholder 'collection&&element' with nothing
*
* These rules are here to support both JPA 2 and legacy overriding rules.
*
*/
public JoinTable getJoinTable(XProperty property) {
final String propertyName = StringHelper.qualify( getPath(), property.getName() );
JoinTable result = getOverriddenJoinTable( propertyName );
if (result == null) {
result = property.getAnnotation( JoinTable.class );
}
return result;
}
/**
* Get column overriding, property first, then parent, then holder
* replace the placeholder 'collection&&element' with nothing
*
* These rules are here to support both JPA 2 and legacy overriding rules.
*
*/
public JoinTable getOverriddenJoinTable(String propertyName) {
JoinTable result = getExactOverriddenJoinTable( propertyName );
if ( result == null && propertyName.contains( ".collection&&element." ) ) {
//support for non map collections where no prefix is needed
//TODO cache the underlying regexp
result = getExactOverriddenJoinTable( propertyName.replace( ".collection&&element.", "." ) );
}
return result;
}
/**
* Get column overriding, property first, then parent, then holder
*/
private JoinTable getExactOverriddenJoinTable(String propertyName) {
JoinTable override = null;
if ( parent != null ) {
override = parent.getOverriddenJoinTable( propertyName );
}
if ( override == null && currentPropertyJoinColumnOverride != null ) {
override = currentPropertyJoinTableOverride.get( propertyName );
}
if ( override == null && holderJoinTableOverride != null ) {
override = holderJoinTableOverride.get( propertyName );
}
return override;
}
private void buildHierarchyColumnOverride(XClass element) {
XClass current = element;
Map<String, Column[]> columnOverride = new HashMap<String, Column[]>();
Map<String, JoinColumn[]> joinColumnOverride = new HashMap<String, JoinColumn[]>();
Map<String, JoinTable> joinTableOverride = new HashMap<String, JoinTable>();
while ( current != null && !mappings.getReflectionManager().toXClass( Object.class ).equals( current ) ) {
if ( current.isAnnotationPresent( Entity.class ) || current.isAnnotationPresent( MappedSuperclass.class )
|| current.isAnnotationPresent( Embeddable.class ) ) {
//FIXME is embeddable override?
Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath() );
Map<String, JoinColumn[]> currentJoinOverride = buildJoinColumnOverride( current, getPath() );
Map<String, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() );
currentOverride.putAll( columnOverride ); //subclasses have precedence over superclasses
currentJoinOverride.putAll( joinColumnOverride ); //subclasses have precedence over superclasses
currentJoinOverride.putAll( joinColumnOverride ); //subclasses have precedence over superclasses
columnOverride = currentOverride;
joinColumnOverride = currentJoinOverride;
joinTableOverride = currentJoinTableOverride;
}
current = current.getSuperclass();
}
holderColumnOverride = columnOverride.size() > 0 ? columnOverride : null;
holderJoinColumnOverride = joinColumnOverride.size() > 0 ? joinColumnOverride : null;
holderJoinTableOverride = joinTableOverride.size() > 0 ? joinTableOverride : null;
}
private static Map<String, Column[]> buildColumnOverride(XAnnotatedElement element, String path) {
@ -218,7 +303,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
overrides = null;
}
//fill overriden columns
//fill overridden columns
if ( overrides != null ) {
for (AttributeOverride depAttr : overrides) {
columnOverride.put(
@ -246,7 +331,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
overrides = null;
}
//fill overriden columns
//fill overridden columns
if ( overrides != null ) {
for (AssociationOverride depAttr : overrides) {
columnOverride.put(
@ -258,6 +343,36 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
return columnOverride;
}
private static Map<String, JoinTable> buildJoinTableOverride(XAnnotatedElement element, String path) {
Map<String, JoinTable> tableOverride = new HashMap<String, JoinTable>();
if ( element == null ) return tableOverride;
AssociationOverride singleOverride = element.getAnnotation( AssociationOverride.class );
AssociationOverrides multipleOverrides = element.getAnnotation( AssociationOverrides.class );
AssociationOverride[] overrides;
if ( singleOverride != null ) {
overrides = new AssociationOverride[] { singleOverride };
}
else if ( multipleOverrides != null ) {
overrides = multipleOverrides.value();
}
else {
overrides = null;
}
//fill overridden tables
if ( overrides != null ) {
for (AssociationOverride depAttr : overrides) {
if ( depAttr.joinColumns().length == 0 ) {
tableOverride.put(
StringHelper.qualify( path, depAttr.name() ),
depAttr.joinTable()
);
}
}
}
return tableOverride;
}
public void setParentProperty(String parentProperty) {
throw new AssertionFailure( "Setting the parent property to a non component" );
}

View File

@ -1234,8 +1234,8 @@ public final class AnnotationBinder {
( property.isAnnotationPresent( ManyToOne.class )
|| property.isAnnotationPresent( OneToOne.class ) )
) {
if ( property.isAnnotationPresent( JoinTable.class ) ) {
JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
JoinTable joinTableAnn = propertyHolder.getJoinTable( property );
if ( joinTableAnn != null ) {
joinColumns = Ejb3JoinColumn.buildJoinColumns(
joinTableAnn.inverseJoinColumns(), null, entityBinder.getSecondaryTables(),
propertyHolder, inferredData.getPropertyName(), mappings
@ -1412,7 +1412,7 @@ public final class AnnotationBinder {
boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
JoinTable assocTable = property.getAnnotation( JoinTable.class );
JoinTable assocTable = propertyHolder.getJoinTable( property );
if ( assocTable != null ) {
Join join = propertyHolder.addJoin( assocTable, false );
for (Ejb3JoinColumn joinColumn : joinColumns) {
@ -1447,7 +1447,7 @@ public final class AnnotationBinder {
boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
JoinTable assocTable = property.getAnnotation( JoinTable.class );
JoinTable assocTable = propertyHolder.getJoinTable( property );
if ( assocTable != null ) {
Join join = propertyHolder.addJoin( assocTable, false );
for (Ejb3JoinColumn joinColumn : joinColumns) {
@ -1477,7 +1477,7 @@ public final class AnnotationBinder {
Cascade hibernateCascade = property.getAnnotation( Cascade.class );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
JoinTable assocTable = property.getAnnotation( JoinTable.class );
JoinTable assocTable = propertyHolder.getJoinTable( property );
if ( assocTable != null ) {
Join join = propertyHolder.addJoin( assocTable, false );
for (Ejb3JoinColumn joinColumn : joinColumns) {
@ -1852,7 +1852,7 @@ public final class AnnotationBinder {
TableBinder associationTableBinder = new TableBinder();
JoinColumn[] annJoins;
JoinColumn[] annInverseJoins;
JoinTable assocTable = property.getAnnotation( JoinTable.class );
JoinTable assocTable = propertyHolder.getJoinTable( property );
CollectionTable collectionTable = property.getAnnotation( CollectionTable.class );
if ( assocTable != null || collectionTable != null ) {

View File

@ -647,4 +647,5 @@ public class BinderHelper {
mappings.getMappedSuperclass( mappings.getReflectionManager().toClass( declaringClass ) ) :
null;
}
}

View File

@ -27,6 +27,7 @@ import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.PersistentClass;
@ -72,6 +73,14 @@ public interface PropertyHolder {
*/
JoinColumn[] getOverriddenJoinColumn(String propertyName);
/**
* return
* - null if no join table is present,
* - the join table if not overridden,
* - the overridden join table otherwise
*/
JoinTable getJoinTable(XProperty property);
String getEntityName();
Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation);

View File

@ -470,7 +470,7 @@ public abstract class CollectionBinder {
if (isMappedBy
&& (property.isAnnotationPresent( JoinColumn.class )
|| property.isAnnotationPresent( JoinColumns.class )
|| property.isAnnotationPresent( JoinTable.class ) ) ) {
|| propertyHolder.getJoinTable( property ) != null ) ) {
String message = "Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: ";
message += StringHelper.qualify( propertyHolder.getPath(), propertyName );
throw new AnnotationException( message );
@ -1152,7 +1152,7 @@ public abstract class CollectionBinder {
);
}
else if ( anyAnn != null ) {
if ( !property.isAnnotationPresent( JoinTable.class ) ) {
if ( parentPropertyHolder.getJoinTable( property ) == null ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
throw new AnnotationException(
"@JoinTable is mandatory when @ManyToAny is used: " + path
@ -1160,7 +1160,7 @@ public abstract class CollectionBinder {
}
}
else {
JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
JoinTable joinTableAnn = parentPropertyHolder.getJoinTable( property );
if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
throw new AnnotationException(

View File

@ -4,6 +4,7 @@ import junit.framework.Assert;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.test.annotations.TestCase;
import org.hibernate.test.util.SchemaUtil;
import java.util.ArrayList;
import java.util.Collection;
@ -12,6 +13,16 @@ import java.util.List;
public class AssociationOverrideTest extends TestCase {
public void testDottedNotation() throws Exception {
assertTrue( SchemaUtil.isTablePresent( "Employee", getCfg() ) );
assertTrue( "Overridden @JoinColumn fails",
SchemaUtil.isColumnPresent( "Employee", "fld_address_fk", getCfg() ) );
assertTrue( "Overridden @JoinTable name fails", SchemaUtil.isTablePresent( "tbl_empl_sites", getCfg() ) );
assertTrue( "Overridden @JoinTable with default @JoinColumn fails",
SchemaUtil.isColumnPresent( "tbl_empl_sites", "employee_id", getCfg() ) );
assertTrue( "Overridden @JoinTable.inverseJoinColumn fails",
SchemaUtil.isColumnPresent( "tbl_empl_sites", "to_website_fk", getCfg() ) );
Session s = openSession();
Transaction tx = s.beginTransaction();
ContactInfo ci = new ContactInfo();

View File

@ -3,6 +3,8 @@ package org.hibernate.test.annotations.collectionelement;
import javax.persistence.CascadeType;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import java.util.List;
@ -10,10 +12,11 @@ import java.util.List;
@Embeddable
public class ContactInfo {
@ManyToOne(targetEntity=Address.class, cascade=CascadeType.ALL)
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="address_id_fk")
Address address;
@ManyToMany(targetEntity=PhoneNumber.class, cascade=CascadeType.ALL)
@ManyToMany(cascade = CascadeType.ALL)
List<PhoneNumber> phoneNumbers;
@Embedded

View File

@ -10,34 +10,31 @@ import javax.persistence.JoinTable;
@Entity
public class Employee {
@Id
@Id
int id;
/* @AssociationOverride(
name="social.website",
joinTable=@JoinTable(
name="xxxwebsites",
joinColumns=@JoinColumn(name="id"),
inverseJoinColumns=@JoinColumn(name="id" )
)
)
@AssociationOverride(
name="social.website",
joinColumns=@JoinColumn(name="id"))
*/
@AssociationOverride(
name="social.website",
joinTable=@JoinTable(
name="xxxwebsites",
joinColumns=@JoinColumn(name=""),
inverseJoinColumns=@JoinColumn(name="id" )
)
)
@Embedded
@AssociationOverrides({
@AssociationOverride(
name = "social.website",
joinTable = @JoinTable(
name = "tbl_empl_sites",
inverseJoinColumns = @JoinColumn(name = "to_website_fk")
)
),
@AssociationOverride(
name = "phoneNumbers",
joinTable = @JoinTable(
name = "tbl_empl_phone"
)
),
@AssociationOverride(
name="address",
joinColumns = @JoinColumn(name="fld_address_fk")
)
})
@Embedded
ContactInfo contactInfo;
public int getId() {
return id;
}

View File

@ -17,8 +17,8 @@ public class PhoneNumber {
}
int number;
@ManyToMany(mappedBy="contactInfo.phoneNumbers", cascade= CascadeType.ALL)
@ManyToMany(mappedBy = "contactInfo.phoneNumbers", cascade = CascadeType.ALL)
Collection<Employee> employees;
public Collection<Employee> getEmployees() {

View File

@ -9,7 +9,7 @@ import java.util.List;
public class SocialTouchPoints {
// owning side of many to many
@ManyToMany(targetEntity=SocialSite.class, cascade= CascadeType.ALL)
@ManyToMany(cascade= CascadeType.ALL)
List<SocialSite> website;
public List<SocialSite> getWebsite() {