HHH-7088 - Implement secondary table support in new metamodel code

This commit is contained in:
Steve Ebersole 2012-04-17 17:20:09 -05:00
parent d3a7e989f3
commit 72e5cf42cf
5 changed files with 308 additions and 78 deletions

View File

@ -1206,31 +1206,93 @@ public class Binder {
private void bindSecondaryTables( final EntityBinding entityBinding, final EntitySource entitySource ) {
final TableSpecification primaryTable = entityBinding.getPrimaryTable();
int position = 0;
for ( final SecondaryTableSource secondaryTableSource : entitySource.getSecondaryTables() ) {
final TableSpecification table = createTable( secondaryTableSource.getTableSource(), null );
// todo: really need a concept like SecondaryTableSource in the binding model as well
// so that EntityBinding can know the proper foreign key to use to build SQL statements.
ForeignKey foreignKey = createOrLocateForeignKey( secondaryTableSource.getForeignKeyName(), table, primaryTable );
for ( final PrimaryKeyJoinColumnSource joinColumnSource : secondaryTableSource.getJoinColumns() ) {
// todo : currently we only support columns here, not formulas
final ForeignKey foreignKey = createOrLocateForeignKey( secondaryTableSource.getExplicitForeignKeyName(), table, primaryTable );
final List<Column> fkTargetColumns = determineForeignKeyTargetColumns( entityBinding, secondaryTableSource );
final List<ColumnSource> pkColumnSources = secondaryTableSource.getPrimaryKeyColumnSources();
if ( fkTargetColumns.size() != pkColumnSources.size() ) {
throw bindingContext().makeMappingException(
String.format(
"Non-matching number columns in secondary table primary key [%s : %s] and primary table [%s : %s]",
table.getLogicalName().getName(),
pkColumnSources.size(),
primaryTable.getLogicalName().getName(),
fkTargetColumns.size()
)
);
}
for ( final ColumnSource joinColumnSource : pkColumnSources ) {
// todo : apply naming strategy to infer missing column name
Column column = table.locateColumn( joinColumnSource.getColumnName() );
Column column = table.locateColumn( joinColumnSource.getName() );
if ( column == null ) {
column = table.createColumn( joinColumnSource.getColumnName() );
if ( joinColumnSource.getColumnDefinition() != null ) {
column.setSqlType( joinColumnSource.getColumnDefinition() );
column = table.createColumn( joinColumnSource.getName() );
if ( joinColumnSource.getSqlType() != null ) {
column.setSqlType( joinColumnSource.getSqlType() );
}
}
if ( joinColumnSource.getReferencedColumnName() == null ) {
foreignKey.addColumn( column );
} else {
foreignKey.addColumnMapping( column, primaryTable.locateColumn( joinColumnSource.getReferencedColumnName() ) );
}
foreignKey.addColumnMapping( column, fkTargetColumns.get( position++ ) );
}
entityBinding.addSecondaryTable( new SecondaryTable( table, foreignKey ) );
}
}
private List<Column> determineForeignKeyTargetColumns(
final EntityBinding entityBinding,
ForeignKeyContributingSource foreignKeyContributingSource) {
final ForeignKeyContributingSource.JoinColumnResolutionDelegate fkColumnResolutionDelegate =
foreignKeyContributingSource.getForeignKeyTargetColumnResolutionDelegate();
if ( fkColumnResolutionDelegate == null ) {
return entityBinding.getPrimaryTable().getPrimaryKey().getColumns();
}
else {
final List<Column> columns = new ArrayList<Column>();
final ForeignKeyContributingSource.JoinColumnResolutionContext resolutionContext = new ForeignKeyContributingSource.JoinColumnResolutionContext() {
@Override
public List<Value> resolveRelationalValuesForAttribute(String attributeName) {
final AttributeBinding referencedAttributeBinding = entityBinding.locateAttributeBinding( attributeName );
if ( referencedAttributeBinding == null ) {
throw bindingContext().makeMappingException(
String.format(
"Could not resolve named property-ref [%s] against entity [%s]",
attributeName,
entityBinding.getEntity().getName()
)
);
}
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public Column resolveColumn(
String logicalColumnName,
String logicalTableName,
String logicalSchemaName,
String logicalCatalogName) {
// ignore table, schema, catalog name
Column column = entityBinding.getPrimaryTable().locateColumn( logicalColumnName );
if ( column == null ) {
entityBinding.getPrimaryTable().createColumn( logicalColumnName );
}
return column;
}
};
for ( Value relationalValue : fkColumnResolutionDelegate.getJoinColumns( resolutionContext ) ) {
if ( ! Column.class.isInstance( relationalValue ) ) {
throw bindingContext().makeMappingException( "Foreign keys can currently only name columns, not formulas" );
}
columns.add( (Column) relationalValue );
}
return columns;
}
}
private void bindSortingAndOrdering(
final AbstractPluralAttributeBinding attributeBinding,
final PluralAttributeSource attributeSource ) {

View File

@ -23,9 +23,14 @@
*/
package org.hibernate.metamodel.internal.source.annotations.entity;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.TruthValue;
import org.hibernate.metamodel.spi.relational.JdbcDataType;
import org.hibernate.metamodel.spi.relational.Size;
import org.hibernate.metamodel.spi.relational.Value;
import org.hibernate.metamodel.spi.source.ColumnSource;
import org.hibernate.metamodel.spi.source.PrimaryKeyJoinColumnSource;
import org.hibernate.metamodel.spi.source.SecondaryTableSource;
import org.hibernate.metamodel.spi.source.TableSpecificationSource;
@ -35,18 +40,34 @@ import org.hibernate.metamodel.spi.source.TableSpecificationSource;
*/
public class SecondaryTableSourceImpl implements SecondaryTableSource {
private final TableSpecificationSource joinTable;
private final List<PrimaryKeyJoinColumnSource> joinColumns;
private final List<ColumnSource> columnSources;
private final JoinColumnResolutionDelegate fkColumnResolutionDelegate;
public SecondaryTableSourceImpl(
TableSpecificationSource joinTable,
List<PrimaryKeyJoinColumnSource> joinColumns) {
this.joinTable = joinTable;
this.joinColumns = Collections.unmodifiableList( joinColumns );
}
@Override
public List<PrimaryKeyJoinColumnSource> getJoinColumns() {
return joinColumns;
// todo : following normal annotation idiom for source, we probably want to move this stuff up to EntityClass...
columnSources = new ArrayList<ColumnSource>();
final List<String> targetColumnNames = new ArrayList<String>();
boolean hadNamedTargetColumnReferences = false;
for ( PrimaryKeyJoinColumnSource primaryKeyJoinColumnSource : joinColumns ) {
columnSources.add(
new SecondaryTablePrimaryKeyColumnSource(
primaryKeyJoinColumnSource.getColumnName(),
primaryKeyJoinColumnSource.getColumnDefinition()
)
);
targetColumnNames.add( primaryKeyJoinColumnSource.getReferencedColumnName() );
if ( primaryKeyJoinColumnSource.getReferencedColumnName() != null ) {
hadNamedTargetColumnReferences = true;
}
}
this.fkColumnResolutionDelegate = ! hadNamedTargetColumnReferences
? null
: new JoinColumnResolutionDelegateImpl( targetColumnNames );
}
@Override
@ -55,8 +76,128 @@ public class SecondaryTableSourceImpl implements SecondaryTableSource {
}
@Override
public String getForeignKeyName() {
public List<ColumnSource> getPrimaryKeyColumnSources() {
return columnSources;
}
@Override
public String getExplicitForeignKeyName() {
// not supported from annotations, unless docs for @ForeignKey are wrong...
return null;
}
@Override
public JoinColumnResolutionDelegate getForeignKeyTargetColumnResolutionDelegate() {
return fkColumnResolutionDelegate;
}
public static class SecondaryTablePrimaryKeyColumnSource implements ColumnSource {
private final String name;
private final String columnDefinition;
public SecondaryTablePrimaryKeyColumnSource(String name, String columnDefinition) {
this.name = name;
this.columnDefinition = columnDefinition;
}
@Override
public String getName() {
return name;
}
@Override
public String getReadFragment() {
return null;
}
@Override
public String getWriteFragment() {
return null;
}
@Override
public TruthValue isNullable() {
return TruthValue.FALSE;
}
@Override
public String getDefaultValue() {
return null;
}
@Override
public String getSqlType() {
return columnDefinition;
}
@Override
public JdbcDataType getDatatype() {
return null;
}
@Override
public Size getSize() {
return null;
}
@Override
public boolean isUnique() {
return false;
}
@Override
public String getCheckCondition() {
return null;
}
@Override
public String getComment() {
return null;
}
@Override
public TruthValue isIncludedInInsert() {
return TruthValue.UNKNOWN;
}
@Override
public TruthValue isIncludedInUpdate() {
return TruthValue.UNKNOWN;
}
@Override
public String getContainingTableName() {
// ignored during binding anyway since we explicitly know we are dealing with secondary table pk columns...
return null;
}
@Override
public Nature getNature() {
return Nature.COLUMN;
}
}
private static class JoinColumnResolutionDelegateImpl implements JoinColumnResolutionDelegate {
private final List<String> targetColumnNames;
private JoinColumnResolutionDelegateImpl(List<String> targetColumnNames) {
this.targetColumnNames = targetColumnNames;
}
@Override
public List<Value> getJoinColumns(JoinColumnResolutionContext context) {
List<Value> columns = new ArrayList<Value>();
for ( String name : targetColumnNames ) {
// the nulls represent table, schema and catalog name which are ignored anyway...
columns.add( context.resolveColumn( name, null, null, null ) );
}
return columns;
}
@Override
public String getReferencedAttributeName() {
return null;
}
}
}

View File

@ -26,10 +26,11 @@ package org.hibernate.metamodel.internal.source.hbm;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.internal.jaxb.mapping.hbm.JaxbColumnElement;
import org.hibernate.internal.jaxb.mapping.hbm.JaxbJoinElement;
import org.hibernate.metamodel.spi.relational.Value;
import org.hibernate.metamodel.spi.source.ColumnSource;
import org.hibernate.metamodel.spi.source.InLineViewSource;
import org.hibernate.metamodel.spi.source.PrimaryKeyJoinColumnSource;
import org.hibernate.metamodel.spi.source.RelationalValueSource;
import org.hibernate.metamodel.spi.source.SecondaryTableSource;
import org.hibernate.metamodel.spi.source.TableSource;
import org.hibernate.metamodel.spi.source.TableSpecificationSource;
@ -40,60 +41,56 @@ import org.hibernate.metamodel.spi.source.TableSpecificationSource;
class SecondaryTableSourceImpl extends AbstractHbmSourceNode implements SecondaryTableSource {
private final JaxbJoinElement joinElement;
private final TableSpecificationSource joinTable;
private final List<PrimaryKeyJoinColumnSource> joinColumns;
private final List<ColumnSource> columnSources;
private final JoinColumnResolutionDelegate fkJoinColumnResolutionDelegate;
@SuppressWarnings("unchecked")
public SecondaryTableSourceImpl(
MappingDocument sourceMappingDocument,
JaxbJoinElement joinElement,
final JaxbJoinElement joinElement,
Helper.InLineViewNameInferrer inLineViewNameInferrer) {
super( sourceMappingDocument );
this.joinElement = joinElement;
this.joinTable = Helper.createTableSource( sourceMappingDocument(), joinElement, inLineViewNameInferrer );
joinColumns = new ArrayList<PrimaryKeyJoinColumnSource>();
if ( joinElement.getKey().getColumnAttribute() != null ) {
if ( joinElement.getKey().getColumn().size() > 0 ) {
throw makeMappingException( "<join/> defined both column attribute and nested <column/>" );
}
joinColumns.add(
new PrimaryKeyJoinColumnSource() {
@Override
public String getColumnName() {
return SecondaryTableSourceImpl.this.joinElement.getKey().getColumnAttribute();
}
// the cast is ok here because the adapter should never be returning formulas since the schema does not allow it
this.columnSources = extractColumnSources();
@Override
public String getReferencedColumnName() {
return null;
}
fkJoinColumnResolutionDelegate = joinElement.getKey().getPropertyRef() == null
? null
: new JoinColumnResolutionDelegateImpl( joinElement );
}
@Override
public String getColumnDefinition() {
return null;
}
private List<ColumnSource> extractColumnSources() {
final List<ColumnSource> columnSources = new ArrayList<ColumnSource>();
final List<RelationalValueSource> valueSources = Helper.buildValueSources(
sourceMappingDocument(),
new Helper.ValueSourcesAdapter() {
@Override
public String getContainingTableName() {
return joinElement.getTable();
}
);
}
for ( final JaxbColumnElement columnElement : joinElement.getKey().getColumn() ) {
joinColumns.add(
new PrimaryKeyJoinColumnSource() {
@Override
public String getColumnName() {
return columnElement.getName();
}
@Override
public String getReferencedColumnName() {
return null;
}
@Override
public String getColumnDefinition() {
return columnElement.getSqlType();
}
@Override
public String getColumnAttribute() {
return joinElement.getKey().getColumnAttribute();
}
);
@Override
public List getColumnOrFormulaElements() {
return joinElement.getKey().getColumn();
}
@Override
public boolean isForceNotNull() {
return true;
}
}
);
for ( RelationalValueSource valueSource : valueSources ) {
columnSources.add( (ColumnSource) valueSource );
}
return columnSources;
}
@Override
@ -102,13 +99,18 @@ class SecondaryTableSourceImpl extends AbstractHbmSourceNode implements Secondar
}
@Override
public String getForeignKeyName() {
public List<ColumnSource> getPrimaryKeyColumnSources() {
return columnSources;
}
@Override
public String getExplicitForeignKeyName() {
return joinElement.getKey().getForeignKey();
}
@Override
public List<PrimaryKeyJoinColumnSource> getJoinColumns() {
return joinColumns;
public JoinColumnResolutionDelegate getForeignKeyTargetColumnResolutionDelegate() {
return fkJoinColumnResolutionDelegate;
}
public String getLogicalTableNameForContainedColumns() {
@ -116,4 +118,22 @@ class SecondaryTableSourceImpl extends AbstractHbmSourceNode implements Secondar
? ( (TableSource) joinTable ).getExplicitTableName()
: ( (InLineViewSource) joinTable ).getLogicalName();
}
private static class JoinColumnResolutionDelegateImpl implements JoinColumnResolutionDelegate {
private final JaxbJoinElement joinElement;
public JoinColumnResolutionDelegateImpl(JaxbJoinElement joinElement) {
this.joinElement = joinElement;
}
@Override
public List<Value> getJoinColumns(JoinColumnResolutionContext context) {
return context.resolveRelationalValuesForAttribute( getReferencedAttributeName() );
}
@Override
public String getReferencedAttributeName() {
return joinElement.getKey().getPropertyRef();
}
}
}

View File

@ -28,7 +28,7 @@ import java.util.List;
/**
* @author Steve Ebersole
*/
public interface SecondaryTableSource {
public interface SecondaryTableSource extends ForeignKeyContributingSource {
/**
* Obtain the table being joined to.
*
@ -37,16 +37,11 @@ public interface SecondaryTableSource {
public TableSpecificationSource getTableSource();
/**
* Retrieves the columns used to define the foreign key back to the entity table.
*
* @return The columns used to define the foreign key for this secondary table
*/
public List<PrimaryKeyJoinColumnSource> getJoinColumns();
/**
* Retrieve any user-specified foreign key name.
* Retrieves the columns defines as making up this secondary tables primary key. Each entry should have
* a corresponding entry in the foreign-key columns described by the {@link ForeignKeyContributingSource}
* aspect of this contract.
*
* @return The user-specified foreign key name, or {@code null} if the user did not specify.
* @return The columns defining the primary key for this secondary table
*/
public String getForeignKeyName();
public List<ColumnSource> getPrimaryKeyColumnSources();
}

View File

@ -29,12 +29,18 @@ import javax.persistence.Id;
import javax.persistence.SecondaryTable;
import org.hibernate.AssertionFailure;
import org.hibernate.metamodel.spi.binding.AttributeBinding;
import org.hibernate.metamodel.spi.binding.BasicAttributeBinding;
import org.hibernate.metamodel.spi.binding.EntityBinding;
import org.hibernate.metamodel.spi.binding.RelationalValueBinding;
import org.hibernate.metamodel.spi.binding.SingularAttributeBinding;
import org.hibernate.metamodel.spi.relational.Table;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@ -63,6 +69,12 @@ public class SecondaryTableTest extends BaseAnnotationBindingTestCase {
assertEquals( 1, table.values().size() );
org.hibernate.metamodel.spi.relational.Column column = (org.hibernate.metamodel.spi.relational.Column) table.values().get( 0 );
assertEquals( "Wrong column name", "name", column.getColumnName().getName() );
BasicAttributeBinding nameAttrBinding = (BasicAttributeBinding) binding.locateAttributeBinding( "name" );
assertEquals( 1, nameAttrBinding.getRelationalValueBindings().size() );
RelationalValueBinding valueBinding = nameAttrBinding.getRelationalValueBindings().get( 0 );
assertFalse( valueBinding.isDerived() );
assertSame( table, valueBinding.getValue().getTable() );
}
@Test