Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Appending custom conditions on spring data jpa repository method queries

Short Version

I am looking for a way to have all the findBy methods of a repository class appended with a particular condition

Full Version

Let's assume I have a Product entity and Customer entity. Both of them extends the OwnerAwareEntity and they inherit ownerRef field which identifies the owner of the entity( It could be a merchant or a partner ). I want to have the findBy methods of the Product and Customer modified in runtime such that they are appended with an additional condition of the ownerRef. The ownerRef value could be identified from the user session.

Example

The parent entity class that provides the common ownerRef field

public class OwnerAwareEntity implements Serializable {

 private String ownerRef;

}

Customer entity extending OwnerAwareEntity

public class Customer extends OwnerAwareEntity {

  private String firstname;

  private String mobile ;

}

Product entity extending OwnerAwareEntity

public class Product extends OwnerAwareEntity {

  private String code;

  private String name;

}

Repository class for Product & Customer extending an OwnerAwareRepository

public interface OwnerAwareRepository extends JpaRepository {

}

public interface ProductRepository extends OwnerAwareRepository {

  Product findByCode(String code );

}

public interface CustomerRepository extends OwnerAwareRepository {

  Customer findByFirstname(String firstname );

}

This, when executed, should result in a query like below

select P from Product P where P.code=?1 and P.ownerRef='aValue'
&
select C from Customer C where C.firstname=?1 and C.ownerRef='aValue'

What should be my approach to have this appending of condition achieved?. I only want this appending to be happening when the parent repository is OwnerAwareRepository.

like image 293
Sandheep Avatar asked Jul 13 '17 12:07

Sandheep


1 Answers

TL;DR: I used @Filter of Hibernate and then created an Aspect to intercept the methods

Defined a base class entity with the following structure

OwnerAwareEntity.java

import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;    
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;

@MappedSuperclass
@FilterDef(name = "ownerFilter", parameters = {@ParamDef(name = "ownerRef", type = "long")})
@Filter(name = "ownerFilter", condition = "OWNER_REF = :ownerRef")
public class OwnerAwareEntity implements Serializable{

    @Column(name = "OWNER_REF",nullable = true)
    private Long ownerRef;

}

We set the filter on this entity. The hibernate @Filter allows us to set a condition to be appended to the select where clause.

Next, defined a base repository for the entity of type OwnerAwareEntity

OwnerAwareRepository.java

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface OwnerAwareRepository<T, ID extends java.io.Serializable> extends JpaRepository<T, ID> {

}

Created an Aspect that will intercept all the methods from the repositories that extend OwnerAwareRepository

OwnerFilterAdvisor.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Aspect
@Component
@Slf4j
public class OwnerFilterAdvisor {

    @PersistenceContext
    private EntityManager entityManager;

    @Pointcut("execution(public * com.xyz.app.repository.OwnerAwareRepository+.*(..))")
    protected void ownerAwareRepositoryMethod(){

    }

    @Around(value = "ownerAwareRepositoryMethod()")
    public Object enableOwnerFilter(ProceedingJoinPoint joinPoint) throws Throwable{

        // Variable holding the session
        Session session = null;

        try {

            // Get the Session from the entityManager in current persistence context
            session = entityManager.unwrap(Session.class);

            // Enable the filter
            Filter filter = session.enableFilter("ownerFilter");

            // Set the parameter from the session
            filter.setParameter("ownerRef", getSessionOwnerRef());

        } catch (Exception ex) {

            // Log the error
            log.error("Error enabling ownerFilter : Reason -" +ex.getMessage());

        }

        // Proceed with the joint point
        Object obj  = joinPoint.proceed();

        // If session was available
        if ( session != null ) {

            // Disable the filter
            session.disableFilter("ownerFilter");

        }

        // Return
        return obj;

    }


    private Long getSessionOwnerRef() {

// Logic to return the ownerRef from current session

    }
}

The advisor is set to intercept all the methods from classes that extends the OwnerAwareRepository. On the interception, the current hibernate Session is obtained from entityManager ( of current persistence context ) and the filter is enabled with the param value of "ownerRef".

Also have a configuration file created to have the advisor scanned

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"com.xyz.app.advisors"})
public class AOPConfig {
}

Once these files are in place, you need to have the following things done for the entities that need to be owner aware

  1. The entity needs to extend OwnerAwareEntity
  2. The entity repository class need to extend OwnerAwareRepository

Dependencies

This setup requires the spring aop to be in the dependencies. You may add the following to the pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

Advantages

  1. Works with all select queries ( findBy methods, findAll etc )
  2. @Query methods also gets intercepted
  3. Simple implementation

Caveats

  • The where clause of delete or update is not affected by
    this filter.
  • If the repository contains a save/update/delete method and if the
    method is not tagged as @Transactional, then interceptor will give
    error ( You can catch and have the method proceed normally in these
    cases)
like image 67
Sandheep Avatar answered Sep 21 '22 22:09

Sandheep