0

I am trying to integrate Javers with a Spring Data REST project. Currently I have the following entities in my domain.

Student.class

@Entity
    public class Person  {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        private String firstName;

        private String lastName;

        private Long dob;

        @OneToOne
        private Gender gender;

        @OneToMany(cascade = CascadeType.ALL, mappedBy = "student", orphanRemoval = true)
        private List<ContactNumber> contactNumbers = new ArrayList<>();
    }

ContactNumber.class

    @Entity
    public class ContactNumber {

        @Id
        @GeneratedValue
        private Long id;

        private String phoneNumber;

        private Boolean isPrimary;

        @ManyToOne
        private Student student;

    }

In the javers docs it is mentioned that:

In the real world, domain objects often contain various kind of noisy properties you don’t want to audit, such as dynamic proxies (like Hibernate lazy loading proxies), duplicated data, technical flags, auto-generated data and so on.

So does that mean I put a @DiffIgnore on the @ManyToOne student field in the contact number class or the @OneToMany contacts field in the student class?

Charlie
  • 3,113
  • 3
  • 38
  • 60
  • What are you trying to achieve? What is it that you want to be ignored? e.g. do you want the ContactNumber object to never be logged/compared or do you want it to be ignored only when it's part of a Person? – Stef Jan 16 '18 at 13:40
  • The thing is whenever I add a contact number to a student Javers loads the whole Student entity because there is a ManyToOne reference of Student in ContactNumber class. This executes a lot queries which slows down updates. I have actually simplified the Student class for the question but the class is a lot bigger. – Charlie Jan 16 '18 at 13:58
  • I dont want to completely ignore the student reference as I want to know whenever a new contact number is added to the student – Charlie Jan 16 '18 at 13:59
  • 1
    It's not a direct answer to your question, but why would a contact number know the corresponding student. Conceptually thinking, the contact number is just a number, it shouldn't know anything other than the number. It makes sense that a person/student has a list of contact numbers (e.g. the number is associated to a student), but not the other way around. By cleaning up your model, modeling Javers would be easier as well, while you'd have a better model too. I'll answer the JaVers question below though, but I'd recommend having a clean domain model independently on how/if you're using JaVers. – Stef Jan 16 '18 at 19:52
  • You are right Stef, in terms of modeling, Students is a Entity and ContactNumber is a ValueObject owned by Student. ValueObject should not have back-references to master Entity. – Bartek Walacik Jan 16 '18 at 20:46
  • If I only have the @OneToMany of contact numbers in Student, entity then Javers is going to load the whole Student graph whenever there is a change to a contact number. This in turn executes a lot of queries which affects database performance. So how I avoid these extra queries if I were to change the mapping? – Charlie Jan 17 '18 at 17:05

1 Answers1

1

It depends how you're logging the objects and what you want to log. Consider these two lines (suppose that you have a link between p and contactNumber)

//This logs p and contactNumber as part of the array part of p. If you want to ignore contactNumber here,
//add @DiffIgnore on the @OneToMany property. If you want to ignore 
javers.commit("some_user", p);

//This would log contactNumber and p as the student. You can add @DiffIgnore here on the student property (@ManyToOne)
javers.commit("some_user", contactNumber);

Note that there is another annotation @ShallowReference that will log the id of the object instead of logging the entire object. E.g. if you add @ShallowReference to the student property it will not log the entire Person object, but only its ID. You can use that instead to have a link between those objects.

UPDATE:

Looking at your model, I'd recommend that you remove the student property. It doesn't make sense to have a link to the student from the phone number. The student has a number assigned, not the other way around. Thus your model would look like this.

@Entity
public class Person  {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    private Long dob;

    @OneToOne
    private Gender gender;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "student", orphanRemoval = true)
    private List<ContactNumber> contactNumbers = new ArrayList<>();
}

ContactNumber.class

@Entity
public class ContactNumber {

    @Id
    @GeneratedValue
    private Long id;

    private String phoneNumber;

    private Boolean isPrimary;
}

If you really need to find a Person/Student starting with a phone number you can have a Repository for your Person class that enables you to do that search. That would look like this:

//extend from the corresponding Spring Data repository interface based on what you're using. I'll use JPA for this example.
interface PersonRepository extends JpaRepository<Person, Long> {
    Person findByPhoneNumber(String phoneNumber);
}

With this, your model is cleaner and you don't need to use DiffIgnore at all.

Stef
  • 2,603
  • 2
  • 18
  • 28
  • Thank you for the answer. If I add @ShallowReference to student property is it possible to get a history of the contact numbers for a particular student from the audit logs? – Charlie Jan 16 '18 at 15:14
  • See the answer/suggestion above, I'd recommend to remove the student property altogether and replace it with a Repository finder - `PersonRepository.findByContactNumber(String number)` - maybe if you really need to. – Stef Jan 16 '18 at 19:54
  • The @ShallowReference means that when that property is to be logged, it will only log the ID. It really depends on how you log the objects. If you log the Person, then you should be able to see how numbers have changed from one save to another (id-s in the contactNumbers field will be change and then you can check the numbers yourself). If you commit the ContactNumber, then you should be able to get a history of students that that number has been assigned to. So, technically you should be able to find the numbers assigned to a student at a specific time, though it's not very intuitive. – Stef Jan 16 '18 at 20:00
  • 1
    I actually had the domain model as you mentioned but according to a blog post by Vlad Mihalcea he says unidirectional onetomany is very inefficient event with @JoinColumn. So I am really confused how I should model this relationship – Charlie Jan 21 '18 at 15:12
  • Vlad is right, that is inefficient. But a domain model that has unnatural links between models can impact a lot how your code looks like. Biggest problem with software development is scaling - which many times is due to other developers not understanding the design of the original developers (because they likely optimized something prematurely :) ). – Stef Jan 22 '18 at 16:21
  • As a general rule, code clarity comes first over optimization nearly every time (exceptions could be only for code where performance is uber important - e.g. some compiler optimization, or some realtime system e.g. in healthcare, where a millisecond can save someone's life - and in that case you'll want to isolate the optimization in some class or package). In this case, you could make that property LAZY loaded, so the additional select only happens when needed. Anyway, I wouldn't stress much about that until you actually have some performance issues - you likely won't notice a difference. – Stef Jan 22 '18 at 16:24
  • If I am not wrong @OneToMany is by default loaded lazily so that would not help in this case. Also if I model the relationship as you mentioned it gives me a 'A collection with cascade=\"all-delete-orphan\" was no longer referenced by the owning entity instance' if I were to patch the contactNumbers collection with an empty array. See this question https://stackoverflow.com/questions/48301383/spring-data-rest-exception-when-patch-is-sent-with-an-empty-array-in-request-b – Charlie Jan 23 '18 at 04:23
  • That's a different issue, not connected to JaVers. I've made some suggestions there. I'd have to get your code and test it to provide a solution, maybe try those first and see if any of them helps. We can chat on that issue about it, since they are unrelated issues. – Stef Jan 23 '18 at 15:26
  • In regards to the @OneToMany performance, I would not worry about that. Code clarity is more important and it's very unlikely that the user will notice any performance difference. Use Caching if you really need to improve performance instead EhCache or the like. But again, it seems premature to stress about that at this point. – Stef Jan 23 '18 at 15:29
  • I really appreciate your help and I would really like to implement the domain as you said. But I cannot do it since updates will not work due to the issue I mentioned before – Charlie Jan 23 '18 at 18:31