GEDOPLAN
coffee gee8e882c6 640 1

Dank JPQL oder der Criteria API sind Queries keine große Sache. Doch oft entsteht in Projekten die Anforderung, Daten basierend auf z.B. Berechtigungen zu filtern. Alle Abfragen mit entsprechenden where-Clauseln zu versehen mag pragmatisch sein, ist aber sehr anfällig für Fehler und die Wartbarkeit leidet.

Hibernate bietet für genauso diesen Zweck @Filter Annotation an, die es uns erlauben auf Entity-Ebene Filter zu deklarieren, die bei Bedarf aktiviert werden können. Die Definition solcher Filter geschieht per Annotation auf z.B. der Entity selbst oder aber auch auf Package Ebene:

@FilterDefs(
        @FilterDef(
                name = FilterNames.BY_MATERIAL_NUMBER,
                parameters = @ParamDef(name = FilterNames.BY_MATERIAL_NUMBER_P_PREFIX, type = "string"),
                defaultCondition = "material_number like :" + FilterNames.BY_MATERIAL_NUMBER_P_PREFIX
        )
)

Unser Filter definiert einen Namen (der später dazu verwendet, wird die, Aktivierung durchzuführen), ein oder mehrere Parameter und natürlich die konkrete SQL Kondition.

Dieser Filter muss nun lediglich auf Entity-Ebene durch eine entsprechende Annotation konfiguriert werden

@Entity
@Filter(name = FilterNames.BY_MATERIAL_NUMBER)
public class Material {...}

Solche Filter sind per Default deaktiviert und müssen nun für die aktive Hibernate Session aktiviert werden. Dies lässt sich über den Entitymanager ganz leicht durchführen

Session session = em.unwrap(Session.class);
Filter filter = session.enableFilter(FilterNames.BY_MATERIAL_NUMBER);
filter.setParameter(FilterNames.BY_MATERIAL_NUMBER_P_PREFIX, "99%");

em.createQuery(...)

In der laufenden Session werden jetzt alle Queries, die direkt „Material“ adressieren, zusätzlich mit unserem Filter versehen. Wichtig: das betrifft lediglich alle Filter Queries, ein direktes Laden über die Id mittels z.B. „load“ führt keine Filterung durch und auch das Laden abhängiger Entities (OneToMany, ManyToMany) bedarf zusätzlicher Annotationen (@FilterJoinTable).

Filter by Default

Oft wollen wir solche Filter immer aktivieren, um z.B. über die Berechtigungen des Users Einschränkungen durchzuführen. In Spring Boot bietet sich dafür eine Customizer Methode an

@Configuration
public class MaterialFilter {
    @Bean
    @ConditionalOnMissingBean
    public PlatformTransactionManager transactionManager(
            ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        JpaTransactionManager transactionManager = new JpaTransactionManager() {
            @Override
            protected EntityManager createEntityManagerForTransaction() {
                final EntityManager entityManager = super.createEntityManagerForTransaction();
                Session session = entityManager.unwrap(Session.class);
                activateMaterialFilterByRole(session); // read roles, activate filter, set parameters
                return entityManager;
            }
        };
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
        return transactionManager;
    }

(als Alternative wäre auch eine Lösung mittels AOP Methoden möglich)

Und wie immer hier noch mal alles Live und in Farbe: https://github.com/GEDOPLAN/hibernate-filter