Embedded @TenantId

In my application I tend to create value objects with records to increase type safety. Thanks to @EmbeddedId this is already much more convenient nowadays. However, when I try to combine @TenantId with @Embedded the application crashes with an exception:
error processing @AttributeBinderType annotation ‘@org.hibernate.annotations.TenantId()’

Trying my own converter from UUID to TenantId wasn’t successful either. Any recommendations besides using UUID as a type?

Support for this was implemented in ORM 7.0 via HHH-17117, so all you have to do is upgrade.

1 Like

Thank you, so we just need to update to Spring Boot 4, which ships with Hibernate 7 as far as I can see.

@beikov unfortunately, even with hibernate version 7.1.8.Final this does not work. I tried the following approaches:

@Entity
public class Todo {

    @EmbeddedId
    private Id id;

    @TenantId
    @Embedded
    private OrganizationId organizationId;
    
    private String description;

    public record Id(UUID id) {
        public static Id create() {
            return new Id(UUID.randomUUID());
        }
    }
}

//And OrganizationId defined as:
public record OrganizationId(UUID id) {}

This still leads to a NullPointerException:

Caused by: org.hibernate.AnnotationException: error processing @AttributeBinderType annotation ‘org.hibernate.annotations.TenantId’ for property ‘com.test.demo.mandantentrennung.internal.Todo.organizationId'

If I try to use a converter:


@TenantId
@Convert(converter = OrganizationIdConverter.class)
private OrganizationId organizationId;


@Converter(autoApply = true)
public static class OrganizationIdConverter implements AttributeConverter<OrganizationId, UUID> {
    @Override
    public UUID convertToDatabaseColumn(OrganizationId attribute) {
        return attribute != null ? attribute.id() : null;
    }

    @Override
    public OrganizationId convertToEntityAttribute(UUID dbData) {
        return dbData != null ? new OrganizationId(dbData) : null;
    }
}

I get a different exception at startup:
Caused by: jakarta.persistence.PersistenceException: Unable to build Hibernate SessionFactory [persistence unit: default] ; nested exception is java.lang.NullPointerException: Cannot invoke “org.hibernate.metamodel.mapping.JdbcMapping.getJavaTypeDescriptor()” because “jdbcMapping” is null

Let me know, if you need further information or if I should push my test project to a Github repository.

This mapping simply can’t work. You’re mixing things here. Try something like this:

@Entity
public class Todo {

    @EmbeddedId
    private Id id;

    @Embedded
    private OrganizationId organizationId;
    
    private String description;

    public record Id(UUID id) {
        public static Id create() {
            return new Id(UUID.randomUUID());
        }
    }
}

@Embeddable
public record OrganizationId(@TenantId UUID id) {}
1 Like

Thanks, that worked perfectly.

Only thing I needed to adjust (in case others are looking for a complete solution) is the column mapping, so the OrganizationId now looks like this:

@Embeddable
public record OrganizationId(
  @Column(name = "organization_id")
  @TenantId 
  UUID id
) {}

Another point I figured out, with the current approach the CurrentTenantIdentifierResolver still needs to return a UUID (not OrganizationId). I think the reason is that under the hood we’re still dealing with a UUID. Anyway I’ll accept the solution - I can still fully type the rest of the application.

Just for the sake of completeness:

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver<UUID>, HibernatePropertiesCustomizer {

    @Override
    public UUID resolveCurrentTenantIdentifier() {
        return TenantContext.getCurrentTenant().id();
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return false;
    }

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put(MultiTenancySettings.MULTI_TENANT_IDENTIFIER_RESOLVER, this);
    }
}