Home » Java » java – Is there a way to reference an audit table for Specifications (Spring)?-Exceptionshub

java – Is there a way to reference an audit table for Specifications (Spring)?-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I’ve been looking for a similar question to this, without success.

My goal: I need to obtain the username of whoever created a certain entity, which is stored in the audit table of said entity referenced via Spring’s @AuditTable.

A bit (hopefully) explained: In this particular situation I’m adding to a previously existing query built with the org.springframework.data.jpa.repository.JpaSpecificationExecutor class, along with the Predicate class (javax.persistence.criteria.Predicate). Joins tend to be made with the name of the class (example Join), from which then Spring gets the table name.
My question is, is there a way to make Spring look in the audit table name, instead of the Entity’s table name?

An example would be:

@Entity @Table(name = "persons")
@Audited @AuditTable(name = "audit_persons")
public class Person {
        private String id;
        private String name;
        private Job job;
        /*getters and setters*/
}

@Entity @Table(name = "jobs")
@Audited @AuditTable(name = "audit_jobs")
public class Job {
        private String id;
        private String name;
        /*getters and setters*/
}

public final class PersonSpecification {
    private PersonSpecification(){}

    public static Specification<Person> findBy(final String attributeName, final Object filterValue) {
        return new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.equal(root.get(attributeName), filterValue);
            }
        };
    }

    public static Specification<Person> findByJob(final Job job) {
        return new Specification<Person>() {

            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

                Join<Person, Job> joinJob = root.join("job", JoinType.LEFT);
                return cb.equal(joinJob.<Job>get("job"), job);
            }
        };
    }

/*
    public static Specification<Person> findByUsername(final String username) {
        return new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Join<Person, audit person?> join? = root.join(?, JoinType.LEFT);
                return I don't know how to access;
            }
        };
    }
*/
}

import static ().PersonSpecification.findBy;
import static ().PersonSpecification.findByJob;
@Service(value="personService")
public class PersonService {

    @Autowired
    private PersonDao personDao;

    public List<Person> find(String id, String name, Job job /*, String username*/){

        Specifications<Person> specifications = null;

        if (!StringUtils.isBlank(id) && StringUtils.isNumeric(id)) {
            specifications = loadFilter(specifications, findBy("id", id));
        }else{
            if (!StringUtils.isBlank(name)) {
                specifications = loadFilter(specifications, findBy("name", name));
            }
            if (job != null) {
                specifications = loadFilter(specifications, findByJob("job", job));
            }
            /*
            if (!StringUtils.isBlank(username)) {
                specifications = I don't know what to do here;
            }
            */
        }
        return personDao.findAll(specifications);
    }
}

A possible solution: I was thinking about adding a new entity which would reference the audited entity, like this:

@Entity @Table(name = "audit_persons")
public class AuditedPerson {
       private String id;
       private Integer rev;
       private Integer revtype;
       private String username;
       /*getters and setters*/
}

And then my methoud would be

public static Specification<Person> findByUsername(final String username) {
       return new Specification<Person>() {
           @Override
           public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
               Join<Person, AuditedPerson> joinAudit = root.join(id, JoinType.LEFT);
               return cb.and(cb.equal(joinAudit.get("username"), username),
                               cb.equal(joinAudit.get("revtype"), 0)); //this is so I get the creation instance's username
           }
       };
   }

I’ll be trying to make this work. Is there already a way to do this? Should I just add a native query that does this?
Thanks!

How to&Answers:

One solution would be to use JPA @SecondaryTable with a database view.

Firstly, create a view (say vw_person_additional_data) from the audit table that selects only the initial ‘created’ record for each Person.

Update your person Entity as below. The @SecondaryTable annotation lets us map an Entity to one or more tables or views.

@Entity 
@Table(name = "persons")
@Audited 
@AuditTable(name = "audit_persons")
//secondary table referencing our new view
@SecondaryTable(name = "vw_person_additional_data", 
                   pkJoinColumns = {@PrimaryKeyJoinColumn(name = "person_id", 
                        referencedColumnName = "id")}) 
public class Person {
        private String id;
        private String name;
        private Job job;

        @Column(name = "username", table="vw_person_additional_data") //column in view
        private String createdBy;
}

The createdBy property is now just like any other property of Person when it comes to writing specifications.