HHH-7088 - Implement secondary table support in new metamodel code
This commit is contained in:
parent
d3a7e989f3
commit
72e5cf42cf
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue