ANN-857 implement @MapKeyJoinColumn and @MapKeyJoinColumns + _KEY default column name for key. Also keep legacy support for old naming solution

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@17187 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Emmanuel Bernard 2009-07-22 00:16:59 +00:00
parent 577024e7aa
commit e093ee46c4
7 changed files with 270 additions and 42 deletions

View File

@ -74,6 +74,8 @@ import javax.persistence.ElementCollection;
import javax.persistence.CollectionTable; import javax.persistence.CollectionTable;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import javax.persistence.MapKeyColumn; import javax.persistence.MapKeyColumn;
import javax.persistence.MapKeyJoinColumns;
import javax.persistence.MapKeyJoinColumn;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
@ -135,6 +137,7 @@ import org.hibernate.cfg.annotations.SimpleValueBinder;
import org.hibernate.cfg.annotations.TableBinder; import org.hibernate.cfg.annotations.TableBinder;
import org.hibernate.cfg.annotations.MapKeyColumnDelegator; import org.hibernate.cfg.annotations.MapKeyColumnDelegator;
import org.hibernate.cfg.annotations.CustomizableColumns; import org.hibernate.cfg.annotations.CustomizableColumns;
import org.hibernate.cfg.annotations.MapKeyJoinColumnDelegator;
import org.hibernate.engine.FilterDefinition; import org.hibernate.engine.FilterDefinition;
import org.hibernate.engine.Versioning; import org.hibernate.engine.Versioning;
import org.hibernate.id.MultipleHiLoPerTableGenerator; import org.hibernate.id.MultipleHiLoPerTableGenerator;
@ -1540,39 +1543,88 @@ public final class AnnotationBinder {
mappings mappings
); );
} }
{
Column[] keyColumns = null;
//JPA 2 has priority and has different default column values, differenciate legacy from JPA 2
Boolean isJPA2 = null;
if ( property.isAnnotationPresent( MapKeyColumn.class ) ) {
isJPA2 = Boolean.TRUE;
keyColumns = new Column[] { new MapKeyColumnDelegator( property.getAnnotation( MapKeyColumn.class ) ) };
}
else if ( property.isAnnotationPresent( org.hibernate.annotations.MapKey.class ) ) {
if ( isJPA2 == null) {
isJPA2 = Boolean.FALSE;
}
keyColumns = property.getAnnotation( org.hibernate.annotations.MapKey.class ).columns();
}
Column[] keyColumns = null; //not explicitly legacy
//JPA 2 has priority if ( isJPA2 == null) {
if ( property.isAnnotationPresent( MapKeyColumn.class ) ) { isJPA2 = Boolean.TRUE;
keyColumns = new Column[] { new MapKeyColumnDelegator( property.getAnnotation( MapKeyColumn.class ) ) }; }
//nullify empty array
keyColumns = keyColumns != null && keyColumns.length > 0 ? keyColumns : null;
PropertyData mapKeyVirtualProperty = new WrappedInferredData( inferredData, "mapkey" );
Ejb3Column[] mapColumns = Ejb3Column.buildColumnFromAnnotation(
keyColumns,
null,
Nullability.FORCED_NOT_NULL,
propertyHolder,
isJPA2 ? inferredData : mapKeyVirtualProperty,
isJPA2 ? "_KEY" : null,
entityBinder.getSecondaryTables(),
mappings
);
collectionBinder.setMapKeyColumns( mapColumns );
} }
else if ( property.isAnnotationPresent( org.hibernate.annotations.MapKey.class ) ) { {
keyColumns = property.getAnnotation( org.hibernate.annotations.MapKey.class ).columns(); JoinColumn[] joinKeyColumns = null;
//JPA 2 has priority and has different default column values, differenciate legacy from JPA 2
Boolean isJPA2 = null;
if ( property.isAnnotationPresent( MapKeyJoinColumns.class ) ) {
isJPA2 = Boolean.TRUE;
final MapKeyJoinColumn[] mapKeyJoinColumns = property.getAnnotation( MapKeyJoinColumns.class ).value();
joinKeyColumns = new JoinColumn[mapKeyJoinColumns.length];
int index = 0;
for ( MapKeyJoinColumn joinColumn : mapKeyJoinColumns ) {
joinKeyColumns[index] = new MapKeyJoinColumnDelegator( joinColumn );
index++;
}
if ( joinKeyColumns != null ) {
throw new AnnotationException( "@MapKeyJoinColumn and @MapKeyJoinColumns used on the same property: "
+ StringHelper.qualify( propertyHolder.getClassName(), property.getName() ) );
}
}
else if ( property.isAnnotationPresent( MapKeyJoinColumn.class ) ) {
isJPA2 = Boolean.TRUE;
joinKeyColumns = new JoinColumn[] { new MapKeyJoinColumnDelegator( property.getAnnotation( MapKeyJoinColumn.class ) ) };
}
else if ( property.isAnnotationPresent( org.hibernate.annotations.MapKeyManyToMany.class ) ) {
if ( isJPA2 == null) {
isJPA2 = Boolean.FALSE;
}
joinKeyColumns = property.getAnnotation( org.hibernate.annotations.MapKeyManyToMany.class ).joinColumns();
}
//not explicitly legacy
if ( isJPA2 == null) {
isJPA2 = Boolean.TRUE;
}
PropertyData mapKeyVirtualProperty = new WrappedInferredData( inferredData, "mapkey" );
Ejb3JoinColumn[] mapJoinColumns = Ejb3JoinColumn.buildJoinColumnsWithDefaultColumnSuffix(
joinKeyColumns,
null,
entityBinder.getSecondaryTables(),
propertyHolder,
isJPA2 ? inferredData.getPropertyName() : mapKeyVirtualProperty.getPropertyName(),
isJPA2 ? "_KEY" : null,
mappings
);
collectionBinder.setMapKeyManyToManyColumns( mapJoinColumns );
} }
//nullify empty array
keyColumns = keyColumns != null && keyColumns.length > 0 ? keyColumns : null;
PropertyData mapKeyVirtualProperty = new WrappedInferredData( inferredData, "mapkey" );
Ejb3Column[] mapColumns = Ejb3Column.buildColumnFromAnnotation(
keyColumns,
null,
Nullability.FORCED_NOT_NULL,
propertyHolder,
mapKeyVirtualProperty,
entityBinder.getSecondaryTables(),
mappings
);
collectionBinder.setMapKeyColumns( mapColumns );
MapKeyManyToMany mapKeyManyToMany = property.getAnnotation( MapKeyManyToMany.class );
Ejb3JoinColumn[] mapJoinColumns = Ejb3JoinColumn.buildJoinColumns(
mapKeyManyToMany != null ?
mapKeyManyToMany.joinColumns() :
null,
null, entityBinder.getSecondaryTables(),
propertyHolder, mapKeyVirtualProperty.getPropertyName(), mappings
);
collectionBinder.setMapKeyManyToManyColumns( mapJoinColumns );
//potential element //potential element
collectionBinder.setEmbedded( property.isAnnotationPresent( Embedded.class ) ); collectionBinder.setEmbedded( property.isAnnotationPresent( Embedded.class ) );

View File

@ -336,6 +336,18 @@ public class Ejb3Column {
PropertyData inferredData, PropertyData inferredData,
Map<String, Join> secondaryTables, Map<String, Join> secondaryTables,
ExtendedMappings mappings ExtendedMappings mappings
){
return buildColumnFromAnnotation(
anns,
formulaAnn, nullability, propertyHolder, inferredData, null, secondaryTables, mappings);
}
public static Ejb3Column[] buildColumnFromAnnotation(
javax.persistence.Column[] anns,
org.hibernate.annotations.Formula formulaAnn, Nullability nullability, PropertyHolder propertyHolder,
PropertyData inferredData,
String suffixForDefaultColumnName,
Map<String, Join> secondaryTables,
ExtendedMappings mappings
) { ) {
Ejb3Column[] columns; Ejb3Column[] columns;
if ( formulaAnn != null ) { if ( formulaAnn != null ) {
@ -361,7 +373,12 @@ public class Ejb3Column {
log.debug( "Column(s) overridden for property {}", inferredData.getPropertyName() ); log.debug( "Column(s) overridden for property {}", inferredData.getPropertyName() );
} }
if ( actualCols == null ) { if ( actualCols == null ) {
columns = buildImplicitColumn( inferredData, secondaryTables, propertyHolder, nullability, mappings ); columns = buildImplicitColumn( inferredData,
suffixForDefaultColumnName,
secondaryTables,
propertyHolder,
nullability,
mappings );
} }
else { else {
final int length = actualCols.length; final int length = actualCols.length;
@ -376,6 +393,12 @@ public class Ejb3Column {
column.setPrecision( col.precision() ); column.setPrecision( col.precision() );
column.setScale( col.scale() ); column.setScale( col.scale() );
column.setLogicalColumnName( col.name() ); column.setLogicalColumnName( col.name() );
//support for explicit property name + suffix
if ( StringHelper.isEmpty( column.getLogicalColumnName() )
&& ! StringHelper.isEmpty( suffixForDefaultColumnName ) ) {
column.setLogicalColumnName( inferredData.getPropertyName() + suffixForDefaultColumnName );
}
column.setPropertyName( column.setPropertyName(
BinderHelper.getRelativePath( propertyHolder, inferredData.getPropertyName() ) BinderHelper.getRelativePath( propertyHolder, inferredData.getPropertyName() )
); );
@ -398,8 +421,12 @@ public class Ejb3Column {
} }
private static Ejb3Column[] buildImplicitColumn( private static Ejb3Column[] buildImplicitColumn(
PropertyData inferredData, Map<String, Join> secondaryTables, PropertyHolder propertyHolder, PropertyData inferredData,
Nullability nullability, ExtendedMappings mappings String suffixForDefaultColumnName,
Map<String, Join> secondaryTables,
PropertyHolder propertyHolder,
Nullability nullability,
ExtendedMappings mappings
) { ) {
Ejb3Column[] columns; Ejb3Column[] columns;
columns = new Ejb3Column[1]; columns = new Ejb3Column[1];
@ -412,12 +439,22 @@ public class Ejb3Column {
column.setNullable( false ); column.setNullable( false );
} }
column.setLength( DEFAULT_COLUMN_LENGTH ); column.setLength( DEFAULT_COLUMN_LENGTH );
final String propertyName = inferredData.getPropertyName();
column.setPropertyName( column.setPropertyName(
BinderHelper.getRelativePath( propertyHolder, inferredData.getPropertyName() ) BinderHelper.getRelativePath( propertyHolder, propertyName )
); );
column.setPropertyHolder( propertyHolder ); column.setPropertyHolder( propertyHolder );
column.setJoins( secondaryTables ); column.setJoins( secondaryTables );
column.setMappings( mappings ); column.setMappings( mappings );
// property name + suffix is an "explicit" column name
if ( !StringHelper.isEmpty( suffixForDefaultColumnName ) ) {
column.setLogicalColumnName( propertyName + suffixForDefaultColumnName );
column.setImplicit( false );
}
else {
column.setImplicit( true );
}
column.bind(); column.bind();
columns[0] = column; columns[0] = column;
return columns; return columns;

View File

@ -133,6 +133,17 @@ public class Ejb3JoinColumn extends Ejb3Column {
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
String propertyName, String propertyName,
ExtendedMappings mappings ExtendedMappings mappings
) {
return buildJoinColumnsWithDefaultColumnSuffix(anns, mappedBy, joins, propertyHolder, propertyName, "", mappings);
}
public static Ejb3JoinColumn[] buildJoinColumnsWithDefaultColumnSuffix(
JoinColumn[] anns,
String mappedBy, Map<String, Join> joins,
PropertyHolder propertyHolder,
String propertyName,
String suffixForDefaultColumnName,
ExtendedMappings mappings
) { ) {
JoinColumn[] actualColumns = propertyHolder.getOverriddenJoinColumn( JoinColumn[] actualColumns = propertyHolder.getOverriddenJoinColumn(
StringHelper.qualify( propertyHolder.getPath(), propertyName ) StringHelper.qualify( propertyHolder.getPath(), propertyName )
@ -140,7 +151,14 @@ public class Ejb3JoinColumn extends Ejb3Column {
if ( actualColumns == null ) actualColumns = anns; if ( actualColumns == null ) actualColumns = anns;
if ( actualColumns == null || actualColumns.length == 0 ) { if ( actualColumns == null || actualColumns.length == 0 ) {
return new Ejb3JoinColumn[] { return new Ejb3JoinColumn[] {
buildJoinColumn( (JoinColumn) null, mappedBy, joins, propertyHolder, propertyName, mappings ) buildJoinColumn(
(JoinColumn) null,
mappedBy,
joins,
propertyHolder,
propertyName,
suffixForDefaultColumnName,
mappings )
}; };
} }
else { else {
@ -153,6 +171,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
joins, joins,
propertyHolder, propertyHolder,
propertyName, propertyName,
suffixForDefaultColumnName,
mappings mappings
); );
} }
@ -168,6 +187,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
String mappedBy, Map<String, Join> joins, String mappedBy, Map<String, Join> joins,
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
String propertyName, String propertyName,
String suffixForDefaultColumnName,
ExtendedMappings mappings ExtendedMappings mappings
) { ) {
if ( ann != null ) { if ( ann != null ) {
@ -179,6 +199,10 @@ public class Ejb3JoinColumn extends Ejb3Column {
} }
Ejb3JoinColumn joinColumn = new Ejb3JoinColumn(); Ejb3JoinColumn joinColumn = new Ejb3JoinColumn();
joinColumn.setJoinAnnotation( ann, null ); joinColumn.setJoinAnnotation( ann, null );
if ( StringHelper.isEmpty( joinColumn.getLogicalColumnName() )
&& ! StringHelper.isEmpty( suffixForDefaultColumnName ) ) {
joinColumn.setLogicalColumnName( propertyName + suffixForDefaultColumnName );
}
joinColumn.setJoins( joins ); joinColumn.setJoins( joins );
joinColumn.setPropertyHolder( propertyHolder ); joinColumn.setPropertyHolder( propertyHolder );
joinColumn.setPropertyName( BinderHelper.getRelativePath( propertyHolder, propertyName ) ); joinColumn.setPropertyName( BinderHelper.getRelativePath( propertyHolder, propertyName ) );
@ -192,8 +216,17 @@ public class Ejb3JoinColumn extends Ejb3Column {
joinColumn.setMappedBy( mappedBy ); joinColumn.setMappedBy( mappedBy );
joinColumn.setJoins( joins ); joinColumn.setJoins( joins );
joinColumn.setPropertyHolder( propertyHolder ); joinColumn.setPropertyHolder( propertyHolder );
joinColumn.setPropertyName( BinderHelper.getRelativePath( propertyHolder, propertyName ) ); joinColumn.setPropertyName(
joinColumn.setImplicit( true ); BinderHelper.getRelativePath( propertyHolder, propertyName )
);
// property name + suffix is an "explicit" column name
if ( !StringHelper.isEmpty( suffixForDefaultColumnName ) ) {
joinColumn.setLogicalColumnName( propertyName + suffixForDefaultColumnName );
joinColumn.setImplicit( false );
}
else {
joinColumn.setImplicit( true );
}
joinColumn.setMappings( mappings ); joinColumn.setMappings( mappings );
joinColumn.bind(); joinColumn.bind();
return joinColumn; return joinColumn;

View File

@ -0,0 +1,54 @@
package org.hibernate.cfg.annotations;
import java.lang.annotation.Annotation;
import javax.persistence.Column;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.JoinColumn;
/**
* @author Emmanuel Bernard
*/
@SuppressWarnings({ "ClassExplicitlyAnnotation" })
public class MapKeyJoinColumnDelegator implements JoinColumn {
private final MapKeyJoinColumn column;
public MapKeyJoinColumnDelegator(MapKeyJoinColumn column) {
this.column = column;
}
public String name() {
return column.name();
}
public String referencedColumnName() {
return column.referencedColumnName();
}
public boolean unique() {
return column.unique();
}
public boolean nullable() {
return column.nullable();
}
public boolean insertable() {
return column.insertable();
}
public boolean updatable() {
return column.updatable();
}
public String columnDefinition() {
return column.columnDefinition();
}
public String table() {
return column.table();
}
public Class<? extends Annotation> annotationType() {
return Column.class;
}
}

View File

@ -12,10 +12,10 @@ import javax.persistence.Column;
import javax.persistence.JoinTable; import javax.persistence.JoinTable;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn; import javax.persistence.MapKeyColumn;
import javax.persistence.MapKeyJoinColumn;
import org.hibernate.annotations.MapKey; import org.hibernate.annotations.MapKey;
import org.hibernate.annotations.CollectionOfElements; import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.MapKeyManyToMany;
/** /**
* @author Emmanuel Bernard * @author Emmanuel Bernard
@ -31,13 +31,31 @@ public class Atmosphere {
public Map<String, Gas> gases = new HashMap<String, Gas>(); public Map<String, Gas> gases = new HashMap<String, Gas>();
@ManyToMany(cascade = CascadeType.ALL) @ManyToMany(cascade = CascadeType.ALL)
@MapKeyManyToMany(joinColumns = @JoinColumn(name="gas_id") ) @MapKeyJoinColumn(name="gas_id" )
@JoinTable(name = "Gas_per_key") @JoinTable(name = "Gas_per_key")
public Map<GasKey, Gas> gasesPerKey = new HashMap<GasKey, Gas>(); public Map<GasKey, Gas> gasesPerKey = new HashMap<GasKey, Gas>();
@CollectionOfElements //TODO migrate to @ElementCollection ; @MapKeyManyToMany ?? @CollectionOfElements //TODO migrate to @ElementCollection ; @MapKeyManyToMany ??
@Column(name="composition_rate") @Column(name="composition_rate")
@MapKeyManyToMany(joinColumns = @JoinColumn(name="gas_id")) @MapKeyJoinColumn(name="gas_id" )
@JoinTable(name = "Composition", joinColumns = @JoinColumn(name = "atmosphere_id")) @JoinTable(name = "Composition", joinColumns = @JoinColumn(name = "atmosphere_id"))
public Map<Gas, Double> composition = new HashMap<Gas, Double>(); public Map<Gas, Double> composition = new HashMap<Gas, Double>();
//use default JPA 2 column name for map key
@ManyToMany(cascade = CascadeType.ALL)
@MapKeyColumn
@JoinTable(name="Atm_Gas_Def")
public Map<String, Gas> gasesDef = new HashMap<String, Gas>();
//use default HAN legacy column name for map key
@ManyToMany(cascade = CascadeType.ALL)
@MapKey
@JoinTable(name="Atm_Gas_DefLeg")
public Map<String, Gas> gasesDefLeg = new HashMap<String, Gas>();
@ManyToMany(cascade = CascadeType.ALL)
@MapKeyJoinColumn
@JoinTable(name = "Gas_p_key_def")
public Map<GasKey, Gas> gasesPerKeyDef = new HashMap<GasKey, Gas>();
} }

View File

@ -5,10 +5,14 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Iterator;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.dialect.HSQLDialect; import org.hibernate.dialect.HSQLDialect;
import org.hibernate.test.annotations.RequiresDialect; import org.hibernate.test.annotations.RequiresDialect;
import org.hibernate.test.annotations.TestCase; import org.hibernate.test.annotations.TestCase;
@ -19,6 +23,35 @@ import org.hibernate.test.annotations.TestCase;
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
public class IndexedCollectionTest extends TestCase { public class IndexedCollectionTest extends TestCase {
public void testJPA2DefaultMapColumns() throws Exception {
isDefaultKeyColumnPresent( "gasesDef" );
isDefaultKeyColumnPresent( "gasesPerKeyDef" );
isNotDefaultKeyColumnPresent( "gasesDefLeg" );
}
private void isDefaultKeyColumnPresent(String propertyName) {
final Collection collection = getCfg().getCollectionMapping( Atmosphere.class.getName() + "." + propertyName );
final Iterator columnIterator = collection.getCollectionTable().getColumnIterator();
boolean hasDefault = false;
while ( columnIterator.hasNext() ) {
Column column = (Column) columnIterator.next();
if ( (propertyName + "_KEY").equals( column.getName() ) ) hasDefault = true;
}
assertTrue( "Could not find " + propertyName + "_KEY", hasDefault);
}
private void isNotDefaultKeyColumnPresent(String propertyName) {
final Collection collection = getCfg().getCollectionMapping( Atmosphere.class.getName() + "." + propertyName );
final Iterator columnIterator = collection.getCollectionTable().getColumnIterator();
boolean hasDefault = false;
while ( columnIterator.hasNext() ) {
Column column = (Column) columnIterator.next();
if ( (propertyName + "_KEY").equals( column.getName() ) ) hasDefault = true;
}
assertFalse( "Could not find " + propertyName + "_KEY", hasDefault);
}
public void testFkList() throws Exception { public void testFkList() throws Exception {
Wardrobe w = new Wardrobe(); Wardrobe w = new Wardrobe();
Drawer d1 = new Drawer(); Drawer d1 = new Drawer();

View File

@ -9,6 +9,7 @@ import javax.persistence.GeneratedValue;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.ManyToMany; import javax.persistence.ManyToMany;
import javax.persistence.MapKeyClass; import javax.persistence.MapKeyClass;
import javax.persistence.JoinColumn;
import org.hibernate.annotations.MapKey; import org.hibernate.annotations.MapKey;
import org.hibernate.annotations.MapKeyManyToMany; import org.hibernate.annotations.MapKeyManyToMany;
@ -27,8 +28,8 @@ public class Brand {
private Map<Size, Luggage> luggagesBySize = new HashMap<Size, Luggage>(); private Map<Size, Luggage> luggagesBySize = new HashMap<Size, Luggage>();
@ElementCollection(targetClass = SizeImpl.class) @ElementCollection(targetClass = SizeImpl.class)
@MapKeyManyToMany(targetEntity = LuggageImpl.class) @MapKeyClass(LuggageImpl.class)
//TODO @MapKeyClass(LuggageImpl.class) @MapKeyManyToMany //legacy column name: was never officially supported BTW
private Map<Luggage, Size> sizePerLuggage = new HashMap<Luggage, Size>(); private Map<Luggage, Size> sizePerLuggage = new HashMap<Luggage, Size>();