Skip to content

Commit

Permalink
HHH-17117 allow @TenantID to form part of composite key
Browse files Browse the repository at this point in the history
Signed-off-by: Gavin King <[email protected]>
  • Loading branch information
gavinking committed Aug 26, 2024
1 parent d306aad commit f1fd58f
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* @author Gavin King
*/
@ValueGenerationType(generatedBy = TenantIdGeneration.class)
@IdGeneratorType(TenantIdGeneration.class)
@AttributeBinderType(binder = TenantIdBinder.class)
@Target({METHOD, FIELD})
@Retention(RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1358,24 +1358,6 @@ else if ( clazz.hasDirectAnnotationUsage( Imported.class ) ) {
}
}

private static AnnotatedClassType getAnnotatedClassType2(ClassDetails classDetails) {
if ( classDetails.hasDirectAnnotationUsage( Entity.class ) ) {
return AnnotatedClassType.ENTITY;
}
else if ( classDetails.hasDirectAnnotationUsage( Embeddable.class ) ) {
return AnnotatedClassType.EMBEDDABLE;
}
else if ( classDetails.hasDirectAnnotationUsage( jakarta.persistence.MappedSuperclass.class ) ) {
return AnnotatedClassType.MAPPED_SUPERCLASS;
}
else if ( classDetails.hasDirectAnnotationUsage( Imported.class ) ) {
return AnnotatedClassType.IMPORTED;
}
else {
return AnnotatedClassType.NONE;
}
}

@Override
public void addMappedSuperclass(Class<?> type, MappedSuperclass mappedSuperclass) {
if ( mappedSuperClasses == null ) {
Expand Down Expand Up @@ -2022,10 +2004,10 @@ private void processEndOfQueue(List<FkSecondPass> endOfQueueFkSecondPasses) {
}
}
}
stopProcess = failingSecondPasses.size() == 0 || failingSecondPasses.size() == endOfQueueFkSecondPasses.size();
stopProcess = failingSecondPasses.isEmpty() || failingSecondPasses.size() == endOfQueueFkSecondPasses.size();
endOfQueueFkSecondPasses = failingSecondPasses;
}
if ( endOfQueueFkSecondPasses.size() > 0 ) {
if ( !endOfQueueFkSecondPasses.isEmpty() ) {
throw originalException;
}
}
Expand Down Expand Up @@ -2212,7 +2194,8 @@ private void processExportableProducers() {
handleIdentifierValueBinding(
entityBinding.getIdentifier(),
dialect,
(RootClass) entityBinding
(RootClass) entityBinding,
entityBinding.getIdentifierProperty()
);

}
Expand All @@ -2225,22 +2208,20 @@ private void processExportableProducers() {
handleIdentifierValueBinding(
( (IdentifierCollection) collection ).getIdentifier(),
dialect,
null,
null
);
}
}

private void handleIdentifierValueBinding(
KeyValue identifierValueBinding,
Dialect dialect,
RootClass entityBinding) {
KeyValue identifierValueBinding, Dialect dialect, RootClass entityBinding, Property identifierProperty) {
// todo : store this result (back into the entity or into the KeyValue, maybe?)
// This process of instantiating the id-generator is called multiple times.
// It was done this way in the old code too, so no "regression" here; but
// it could be done better
try {
final Generator generator = identifierValueBinding.createGenerator( dialect, entityBinding );

final Generator generator = identifierValueBinding.createGenerator( dialect, entityBinding, identifierProperty );
if ( generator instanceof ExportableProducer ) {
( (ExportableProducer) generator ).registerExportables( getDatabase() );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.spi.EventSource;
import org.hibernate.id.Assigned;
import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.internal.CoreLogging;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,26 @@ public EnumSet<EventType> getEventTypes() {
@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
final SessionFactoryImplementor sessionFactory = session.getSessionFactory();
final JavaType<Object> tenantIdentifierJavaType = sessionFactory.getTenantIdentifierJavaType();

final Object tenantId = session.getTenantIdentifierValue();
if ( currentValue != null ) {
final CurrentTenantIdentifierResolver<Object> resolver = sessionFactory.getCurrentTenantIdentifierResolver();
final CurrentTenantIdentifierResolver<Object> resolver =
sessionFactory.getCurrentTenantIdentifierResolver();
if ( resolver != null && resolver.isRoot( tenantId ) ) {
// the "root" tenant is allowed to set the tenant id explicitly
return currentValue;
}
if ( !tenantIdentifierJavaType.areEqual( currentValue, tenantId ) ) {
throw new PropertyValueException(
"assigned tenant id differs from current tenant id: " +
tenantIdentifierJavaType.toString( currentValue ) +
"!=" +
tenantIdentifierJavaType.toString( tenantId ),
entityName,
propertyName
);
else {
final JavaType<Object> tenantIdJavaType = sessionFactory.getTenantIdentifierJavaType();
if ( !tenantIdJavaType.areEqual( currentValue, tenantId ) ) {
throw new PropertyValueException(
"assigned tenant id differs from current tenant id ["
+ tenantIdJavaType.toString( currentValue )
+ " != "
+ tenantIdJavaType.toString( tenantId ) + "]",
entityName,
propertyName
);
}
}
}
return tenantId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,16 @@ public Map<String,?> getParameters() {
* of the passed value did not match the configured type.
*/
public Filter setParameter(String name, Object value) throws IllegalArgumentException {
Object argument = definition.processArgument(value);
final Object argument = definition.processArgument( value );

// Make sure this is a defined parameter and check the incoming value type
JdbcMapping type = definition.getParameterJdbcMapping( name );
final JdbcMapping type = definition.getParameterJdbcMapping( name );
if ( type == null ) {
throw new IllegalArgumentException( "Undefined filter parameter [" + name + "]" );
throw new IllegalArgumentException( "Undefined filter parameter '" + name + "'" );
}
if ( argument != null && !type.getJavaTypeDescriptor().isInstance( argument ) ) {
throw new IllegalArgumentException( "Incorrect type for parameter [" + name + "]" );
throw new IllegalArgumentException( "Argument assigned to filter parameter '" + name
+ "' is not of type '" + type.getJavaTypeDescriptor().getTypeName() + "'" );
}
if ( parameters == null ) {
parameters = new TreeMap<>();
Expand All @@ -133,14 +134,15 @@ public Filter setParameterList(String name, Collection<?> values) throws Hiberna
if ( values == null ) {
throw new IllegalArgumentException( "Collection must be not null" );
}
JdbcMapping type = definition.getParameterJdbcMapping( name );
final JdbcMapping type = definition.getParameterJdbcMapping( name );
if ( type == null ) {
throw new HibernateException( "Undefined filter parameter [" + name + "]" );
throw new HibernateException( "Undefined filter parameter '" + name + "'" );
}
if ( !values.isEmpty() ) {
final Object element = values.iterator().next();
if ( !type.getJavaTypeDescriptor().isInstance( element ) ) {
throw new HibernateException( "Incorrect type for parameter [" + name + "]" );
throw new IllegalArgumentException( "Argument assigned to filter parameter '" + name
+ "' is not of type '" + type.getJavaTypeDescriptor().getTypeName() + "'" );
}
}
if ( parameters == null ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ private static Map<String, Generator> createGenerators(
for ( PersistentClass model : bootMetamodel.getEntityBindings() ) {
if ( !model.isInherited() ) {
final KeyValue id = model.getIdentifier();
final Generator generator = id.createGenerator( dialect, (RootClass) model );
final Generator generator = id.createGenerator( dialect, (RootClass) model, model.getIdentifierProperty() );
if ( generator instanceof Configurable ) {
final Configurable identifierGenerator = (Configurable) generator;
identifierGenerator.initialize( sqlStringGenerationContext );
Expand Down
54 changes: 23 additions & 31 deletions hibernate-core/src/main/java/org/hibernate/mapping/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -546,10 +545,6 @@ public void setKey(boolean isKey) {
this.isKey = isKey;
}

public boolean hasPojoRepresentation() {
return componentClassName!=null;
}

/**
* Returns the {@link Property} at the specified position in this {@link Component}.
*
Expand Down Expand Up @@ -661,37 +656,30 @@ public String toString() {
}

@Override
public Generator createGenerator(Dialect dialect, RootClass rootClass) throws MappingException {
public Generator createGenerator(Dialect dialect, RootClass rootClass, Property property) {
if ( builtIdentifierGenerator == null ) {
builtIdentifierGenerator = buildIdentifierGenerator( dialect, rootClass );
}
return builtIdentifierGenerator;
}

private Generator buildIdentifierGenerator( Dialect dialect, RootClass rootClass) throws MappingException {
if ( !getCustomIdGeneratorCreator().isAssigned() ) {
return super.createGenerator( dialect, rootClass );
}

final Class<?> entityClass = rootClass.getMappedClass();
final Class<?> attributeDeclarer = getAttributeDeclarer( rootClass, entityClass );

final CompositeNestedGeneratedValueGenerator.GenerationContextLocator locator =
new StandardGenerationContextLocator( rootClass.getEntityName() );
private Generator buildIdentifierGenerator(Dialect dialect, RootClass rootClass) {
final CompositeNestedGeneratedValueGenerator generator =
new CompositeNestedGeneratedValueGenerator( locator, getType() );

new CompositeNestedGeneratedValueGenerator(
new StandardGenerationContextLocator( rootClass.getEntityName() ),
getType()
);
final List<Property> properties = getProperties();
for ( int i = 0; i < properties.size(); i++ ) {
final Property property = properties.get( i );
if ( property.getValue().isSimpleValue() ) {
final SimpleValue value = (SimpleValue) property.getValue();
if ( !value.getCustomIdGeneratorCreator().isAssigned() ) {
// skip any 'assigned' generators, they would have been handled by
// the StandardGenerationContextLocator
// skip any 'assigned' generators, they would have been
// handled by the StandardGenerationContextLocator
generator.addGeneratedValuePlan( new ValueGenerationPlan(
value.createGenerator( dialect, rootClass ),
getType().isMutable() ? injector( property, attributeDeclarer ) : null,
value.createGenerator( dialect, rootClass, property ),
getType().isMutable() ? injector( property, getAttributeDeclarer( rootClass ) ) : null,
i
) );
}
Expand All @@ -700,23 +688,27 @@ private Generator buildIdentifierGenerator( Dialect dialect, RootClass rootClass
return generator;
}

private Class<?> getAttributeDeclarer(RootClass rootClass, Class<?> entityClass) {
final Class<?> attributeDeclarer; // what class is the declarer of the composite pk attributes
// IMPL NOTE : See the javadoc discussion on CompositeNestedGeneratedValueGenerator wrt the
// various scenarios for which we need to account here
/**
* Return the class that declares the composite pk attributes,
* which might be an {@code @IdClass}, an {@code @EmbeddedId},
* of the entity class itself.
*/
private Class<?> getAttributeDeclarer(RootClass rootClass) {
// See the javadoc discussion on CompositeNestedGeneratedValueGenerator
// for the various scenarios we need to account for here
if ( rootClass.getIdentifierMapper() != null ) {
// we have the @IdClass / <composite-id mapped="true"/> case
attributeDeclarer = resolveComponentClass();
return resolveComponentClass();
}
else if ( rootClass.getIdentifierProperty() != null ) {
// we have the "@EmbeddedId" / <composite-id name="idName"/> case
attributeDeclarer = resolveComponentClass();
return resolveComponentClass();
}
else {
// we have the "straight up" embedded (again the Hibernate term) component identifier
attributeDeclarer = entityClass;
// we have the "straight up" embedded (again the Hibernate term)
// component identifier: the entity class itself is the id class
return rootClass.getMappedClass();
}
return attributeDeclarer;
}

private Setter injector(Property property, Class<?> attributeDeclarer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public interface KeyValue extends Value {

boolean isUpdateable();

Generator createGenerator(Dialect dialect, RootClass rootClass);
@Deprecated(since = "7.0")
default Generator createGenerator(Dialect dialect, RootClass rootClass) {
return createGenerator( dialect, rootClass, null );
}

Generator createGenerator(Dialect dialect, RootClass rootClass, Property property);

}
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,10 @@ public GeneratorCreator getCustomIdGeneratorCreator() {
}

@Override
public Generator createGenerator(Dialect dialect, RootClass rootClass) {
public Generator createGenerator(Dialect dialect, RootClass rootClass, Property property) {
if ( generator == null ) {
if ( customIdGeneratorCreator != null ) {
generator = customIdGeneratorCreator.createGenerator( new IdGeneratorCreationContext( rootClass ) );
generator = customIdGeneratorCreator.createGenerator( new IdGeneratorCreationContext( rootClass, property ) );
}
}
return generator;
Expand Down Expand Up @@ -1011,9 +1011,11 @@ public Long[] getColumnLengths() {

private class IdGeneratorCreationContext implements GeneratorCreationContext {
private final RootClass rootClass;
private final Property property;

public IdGeneratorCreationContext(RootClass rootClass) {
public IdGeneratorCreationContext(RootClass rootClass, Property property) {
this.rootClass = rootClass;
this.property = property;
}

@Override
Expand Down Expand Up @@ -1048,7 +1050,7 @@ public PersistentClass getPersistentClass() {

@Override
public Property getProperty() {
return rootClass.getIdentifierProperty();
return property;
}

// we could add these if it helps integrate old infrastructure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ else if ( indexedCollection instanceof org.hibernate.mapping.Map
}

private BeforeExecutionGenerator createGenerator(RuntimeModelCreationContext context, IdentifierCollection collection) {
final Generator generator = collection.getIdentifier().createGenerator( context.getDialect(), null );
final Generator generator = collection.getIdentifier().createGenerator( context.getDialect(), null, null );
if ( generator.generatedOnExecution() ) {
throw new MappingException("must be an BeforeExecutionGenerator"); //TODO fix message
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.hibernate.orm.test.tenantidpk;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import org.hibernate.annotations.TenantId;

import java.util.UUID;

@Entity
public class Account {

@Id @GeneratedValue Long id;

@Id @TenantId UUID tenantId;

@ManyToOne(optional = false)
Client client;

public Account(Client client) {
this.client = client;
}

Account() {}
}
Loading

0 comments on commit f1fd58f

Please sign in to comment.