Search code examples
javajpajparepository

Class entity returned from method findAll of JpaRepository has field value of null on field calculated inside the constructor


I have a Student class that has some fields, including "age" and "dateOfBirth". The age value is calculated and attributed inside the constructor, based on the dateOfBirth, as following:

//Student.java

private Integer id;
private String name;
private String email;
private LocalDate dateOfBirth;
@Transient
private Integer age;

public Student(String name, String email, LocalDate dateOfBirth) {
    this.name = name; // ex: "John Doe"
    this.email = email; // ex: "[email protected]"
    this.dateOfBirth = dateOfBirth; // ex: LocalDate.of(2002, Month.MAY, 1));
    this.age = Period.between(dateOfBirth, LocalDate.now()).getYears(); // result: 21
}

The getAge() method inside the Student class returns the age field value

//Student.java

public Integer getAge() {
    return age; // Returns the expected value (21)
}

(getters for every other field have the same structure as the getAge(): they return the field's value)

I also have a simple Repository, to query data from DB. It extends from the JpaRepository and has nothing inside it:

//StudentRepository.java

@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> { }

Inside the StudentController class, I have a route to "/", which simply gets all the students from DB and return them:

//StudentController.java

@GetMapping("/")
public List<Student> getStudents() {
     return studentService.getStudents();
}

The result I was expecting is the following, with the age field's value being 21:

[{"id":1,"name":"John Doe","email":"[email protected]","dateOfBirth":"2002-05-01","age":21}]

Instead, I got a null value for the age field:

[{"id":1,"name":"John Doe","email":"[email protected]","dateOfBirth":"2002-05-01","age":null}]

But if I change the getAge() method implementation of the Student class to:

// Student.java

 public Integer getAge() {
    return Period.between(dateOfBirth, LocalDate.now()).getYears();
}

I get the 21 as expected, not null.

[{"id":1,"name":"John Doe","email":"[email protected]","dateOfBirth":"2002-05-01","age":21}]

Solution

  • JPA does not use your constructor. JPA specification requires no-arg constructor. You JPA implementation can create one for you if you don't provide one no-arg constructor (For example openjpa does it, see https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_pc.html#jpa_overview_pc_no_arg).

    That's why your code works when you change the getter to compute the age value.

    If you don't want to recompute the age every time the getter is called you can :

    • compute the age when the birth date is set, in the setDateOfBirth() method.
    • lazily compute and store the age in the getAge() method. Something like :
    
        public Integer getAge() {
          if (age == null && dateOfBirth != null) {
            age = Period.between(dateOfBirth, LocalDate.now()).getYears();
           }
           return age;
        }