HHH-6054 support for discriminator-based multitenancy
added the @TenantId annotation also allow @TenantId @Formula
This commit is contained in:
parent
b2e6965577
commit
505bea6ffd
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.annotations;
|
||||||
|
|
||||||
|
import org.hibernate.tuple.TenantIdGeneration;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
import static java.lang.annotation.ElementType.TYPE;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies a field of an entity that holds a tenant id
|
||||||
|
* in discriminator-based multitenancy.
|
||||||
|
*
|
||||||
|
* @author Gavin King
|
||||||
|
*/
|
||||||
|
@ValueGenerationType(generatedBy = TenantIdGeneration.class)
|
||||||
|
@Target({METHOD, FIELD})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface TenantId {}
|
|
@ -67,7 +67,6 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
import org.hibernate.cfg.AccessType;
|
import org.hibernate.cfg.AccessType;
|
||||||
import org.hibernate.cfg.AnnotationBinder;
|
import org.hibernate.cfg.AnnotationBinder;
|
||||||
import org.hibernate.cfg.BinderHelper;
|
import org.hibernate.cfg.BinderHelper;
|
||||||
import org.hibernate.cfg.Ejb3DiscriminatorColumn;
|
|
||||||
import org.hibernate.cfg.Ejb3JoinColumn;
|
import org.hibernate.cfg.Ejb3JoinColumn;
|
||||||
import org.hibernate.cfg.InheritanceState;
|
import org.hibernate.cfg.InheritanceState;
|
||||||
import org.hibernate.cfg.ObjectNameSource;
|
import org.hibernate.cfg.ObjectNameSource;
|
||||||
|
@ -394,6 +393,10 @@ public class EntityBinder {
|
||||||
processNamedEntityGraphs();
|
processNamedEntityGraphs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PersistentClass getPersistentClass() {
|
||||||
|
return persistentClass;
|
||||||
|
}
|
||||||
|
|
||||||
private void processNamedEntityGraphs() {
|
private void processNamedEntityGraphs() {
|
||||||
processNamedEntityGraph( annotatedClass.getAnnotation( NamedEntityGraph.class ) );
|
processNamedEntityGraph( annotatedClass.getAnnotation( NamedEntityGraph.class ) );
|
||||||
final NamedEntityGraphs graphs = annotatedClass.getAnnotation( NamedEntityGraphs.class );
|
final NamedEntityGraphs graphs = annotatedClass.getAnnotation( NamedEntityGraphs.class );
|
||||||
|
|
|
@ -20,9 +20,11 @@ import org.hibernate.annotations.Generated;
|
||||||
import org.hibernate.annotations.Immutable;
|
import org.hibernate.annotations.Immutable;
|
||||||
import org.hibernate.annotations.NaturalId;
|
import org.hibernate.annotations.NaturalId;
|
||||||
import org.hibernate.annotations.OptimisticLock;
|
import org.hibernate.annotations.OptimisticLock;
|
||||||
|
import org.hibernate.annotations.TenantId;
|
||||||
import org.hibernate.annotations.ValueGenerationType;
|
import org.hibernate.annotations.ValueGenerationType;
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
|
import org.hibernate.boot.spi.InFlightMetadataCollector;
|
||||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
import org.hibernate.cfg.AccessType;
|
import org.hibernate.cfg.AccessType;
|
||||||
import org.hibernate.cfg.AnnotationBinder;
|
import org.hibernate.cfg.AnnotationBinder;
|
||||||
|
@ -31,6 +33,7 @@ import org.hibernate.cfg.Ejb3Column;
|
||||||
import org.hibernate.cfg.InheritanceState;
|
import org.hibernate.cfg.InheritanceState;
|
||||||
import org.hibernate.cfg.PropertyHolder;
|
import org.hibernate.cfg.PropertyHolder;
|
||||||
import org.hibernate.cfg.PropertyPreloadedData;
|
import org.hibernate.cfg.PropertyPreloadedData;
|
||||||
|
import org.hibernate.engine.spi.FilterDefinition;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.mapping.Collection;
|
import org.hibernate.mapping.Collection;
|
||||||
|
@ -45,11 +48,15 @@ import org.hibernate.metamodel.EmbeddableInstantiator;
|
||||||
import org.hibernate.property.access.spi.PropertyAccessStrategy;
|
import org.hibernate.property.access.spi.PropertyAccessStrategy;
|
||||||
import org.hibernate.tuple.AnnotationValueGeneration;
|
import org.hibernate.tuple.AnnotationValueGeneration;
|
||||||
import org.hibernate.tuple.GenerationTiming;
|
import org.hibernate.tuple.GenerationTiming;
|
||||||
|
import org.hibernate.tuple.TenantIdGeneration;
|
||||||
import org.hibernate.tuple.ValueGeneration;
|
import org.hibernate.tuple.ValueGeneration;
|
||||||
import org.hibernate.tuple.ValueGenerator;
|
import org.hibernate.tuple.ValueGenerator;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyMap;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Emmanuel Bernard
|
* @author Emmanuel Bernard
|
||||||
*/
|
*/
|
||||||
|
@ -191,9 +198,36 @@ public class PropertyBinder {
|
||||||
);
|
);
|
||||||
basicValueBinder.setReferencedEntityName( referencedEntityName );
|
basicValueBinder.setReferencedEntityName( referencedEntityName );
|
||||||
basicValueBinder.setAccessType( accessType );
|
basicValueBinder.setAccessType( accessType );
|
||||||
|
|
||||||
|
|
||||||
SimpleValue propertyValue = basicValueBinder.make();
|
SimpleValue propertyValue = basicValueBinder.make();
|
||||||
setValue( propertyValue );
|
setValue( propertyValue );
|
||||||
return makeProperty();
|
Property prop = makeProperty();
|
||||||
|
|
||||||
|
if ( property.isAnnotationPresent(TenantId.class) ) {
|
||||||
|
InFlightMetadataCollector collector = buildingContext.getMetadataCollector();
|
||||||
|
collector.addFilterDefinition(
|
||||||
|
new FilterDefinition(
|
||||||
|
TenantIdGeneration.FILTER_NAME,
|
||||||
|
"",
|
||||||
|
singletonMap(
|
||||||
|
TenantIdGeneration.PARAMETER_NAME,
|
||||||
|
collector.getTypeConfiguration().getBasicTypeRegistry()
|
||||||
|
.getRegisteredType(returnedClassName)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
String columnOrFormula = columns[0].isFormula() ? columns[0].getFormulaString() : columns[0].getName();
|
||||||
|
entityBinder.getPersistentClass()
|
||||||
|
.addFilter(
|
||||||
|
TenantIdGeneration.FILTER_NAME,
|
||||||
|
columnOrFormula + " = :" + TenantIdGeneration.PARAMETER_NAME,
|
||||||
|
true,
|
||||||
|
emptyMap(), emptyMap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
//used when value is provided
|
//used when value is provided
|
||||||
|
|
|
@ -148,15 +148,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
||||||
this.flushMode = options.getInitialSessionFlushMode();
|
this.flushMode = options.getInitialSessionFlushMode();
|
||||||
|
|
||||||
this.tenantIdentifier = options.getTenantIdentifier();
|
this.tenantIdentifier = options.getTenantIdentifier();
|
||||||
if ( !factory.getSettings().isMultiTenancyEnabled() ) {
|
if ( factory.getSettings().isMultiTenancyEnabled() && tenantIdentifier == null ) {
|
||||||
if ( tenantIdentifier != null ) {
|
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
|
||||||
throw new HibernateException( "SessionFactory was not configured for multi-tenancy" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( tenantIdentifier == null ) {
|
|
||||||
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.interceptor = interpret( options.getInterceptor() );
|
this.interceptor = interpret( options.getInterceptor() );
|
||||||
|
|
|
@ -129,6 +129,7 @@ import org.hibernate.resource.transaction.spi.TransactionStatus;
|
||||||
import org.hibernate.stat.SessionStatistics;
|
import org.hibernate.stat.SessionStatistics;
|
||||||
import org.hibernate.stat.internal.SessionStatisticsImpl;
|
import org.hibernate.stat.internal.SessionStatisticsImpl;
|
||||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
import org.hibernate.tuple.TenantIdGeneration;
|
||||||
|
|
||||||
import jakarta.persistence.CacheRetrieveMode;
|
import jakarta.persistence.CacheRetrieveMode;
|
||||||
import jakarta.persistence.CacheStoreMode;
|
import jakarta.persistence.CacheStoreMode;
|
||||||
|
@ -242,6 +243,18 @@ public class SessionImpl
|
||||||
setHibernateFlushMode( initialMode );
|
setHibernateFlushMode( initialMode );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( factory.getDefinedFilterNames().contains( TenantIdGeneration.FILTER_NAME ) ) {
|
||||||
|
String tenantIdentifier = getTenantIdentifier();
|
||||||
|
if ( tenantIdentifier == null ) {
|
||||||
|
throw new HibernateException( "SessionFactory configured for multi-tenancy, but no tenant identifier specified" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getLoadQueryInfluencers()
|
||||||
|
.enableFilter( TenantIdGeneration.FILTER_NAME )
|
||||||
|
.setParameter( TenantIdGeneration.PARAMETER_NAME, tenantIdentifier );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( log.isTraceEnabled() ) {
|
if ( log.isTraceEnabled() ) {
|
||||||
log.tracef( "Opened Session [%s] at timestamp: %s", getSessionIdentifier(), getTimestamp() );
|
log.tracef( "Opened Session [%s] at timestamp: %s", getSessionIdentifier(), getTimestamp() );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.tuple;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.annotations.TenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value generation implementation for {@link TenantId}.
|
||||||
|
*
|
||||||
|
* @author Gavin King
|
||||||
|
*/
|
||||||
|
public class TenantIdGeneration implements AnnotationValueGeneration<TenantId>, ValueGenerator<Object> {
|
||||||
|
|
||||||
|
public static final String FILTER_NAME = "_tenantId";
|
||||||
|
public static final String PARAMETER_NAME = "tenantId";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(TenantId annotation, Class<?> propertyType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GenerationTiming getGenerationTiming() {
|
||||||
|
return GenerationTiming.INSERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueGenerator<?> getValueGenerator() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object generateValue(Session session, Object owner) {
|
||||||
|
return session.getTenantIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean referenceColumnInSql() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDatabaseGeneratedReferencedColumnValue() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.test.tenantid;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.TenantId;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class Account {
|
||||||
|
|
||||||
|
@Id @GeneratedValue Long id;
|
||||||
|
|
||||||
|
@TenantId String tenantId;
|
||||||
|
|
||||||
|
public Account() {}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.test.tenantid;
|
||||||
|
|
||||||
|
import org.hibernate.boot.SessionFactoryBuilder;
|
||||||
|
import org.hibernate.boot.spi.MetadataImplementor;
|
||||||
|
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryProducer;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
import org.hibernate.testing.orm.junit.Setting;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.cfg.AvailableSettings.HBM2DDL_DATABASE_ACTION;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
@SessionFactory
|
||||||
|
@DomainModel(annotatedClasses = Account.class)
|
||||||
|
@ServiceRegistry(
|
||||||
|
settings = {
|
||||||
|
@Setting(name = HBM2DDL_DATABASE_ACTION, value = "create-drop")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public class TenantIdTest implements SessionFactoryProducer {
|
||||||
|
|
||||||
|
String currentTenant;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) {
|
||||||
|
final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder();
|
||||||
|
sessionFactoryBuilder.applyCurrentTenantIdentifierResolver( new CurrentTenantIdentifierResolver() {
|
||||||
|
@Override
|
||||||
|
public String resolveCurrentTenantIdentifier() {
|
||||||
|
return currentTenant;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean validateExistingCurrentSessions() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
return (SessionFactoryImplementor) sessionFactoryBuilder.build();
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void test(SessionFactoryScope scope) {
|
||||||
|
currentTenant = "mine";
|
||||||
|
Account acc = new Account();
|
||||||
|
scope.inTransaction( session -> session.persist(acc) );
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
assertNotNull( session.find(Account.class, acc.id) );
|
||||||
|
assertEquals( 1, session.createQuery("from Account").getResultList().size() );
|
||||||
|
} );
|
||||||
|
assertEquals("mine", acc.tenantId);
|
||||||
|
|
||||||
|
currentTenant = "yours";
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
assertNull( session.find(Account.class, acc.id) );
|
||||||
|
assertEquals( 0, session.createQuery("from Account").getResultList().size() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue