Search code examples
javaspring-bootautowiredtransient

How to autowire a transient attribute?


I have an Entity for example Employee that contains a @Transient Object Salary which will be derived from a related table/Entity DailyTimeRecord (DTR). DTR object data retrieval uses Joins and it is also autowired in the Employee object. The list of DTR objects will be the basis in computing the value of the Salary Object.

I found here [1]: Why is my Spring @Autowired field null? that using new keyword should be avoided, and let IoC Container create objects. In addition, I want to avoid using new keyword to minimize the coupling of my codes and ensure future compatibility and support scalability as much as possible. Therefore, I have interface Salary and implemented by a SalaryImpl class.

But Each time I tried to run the codes the autowired on a transient attribute Salary, it is always null. And I found the root cause here [2]: How to populate @Transient field in JPA? that Transient will always be null in JPA.

How will I ever create a object that avoiding the use of new keyword while it is a transient attribute?

Entity Class

   @Entity
   Class Employee implements Serializable {
          //Attributes from DB here

          @OneToMany
          @JoinColumn(name="empNumber", referencedColumnName = "empNumber")
          private List<DTR> dtr;

          @Autowired
          @Transient
          private Salary salary;

          //getters ang setters here

          public double computeSalary(){

          }
   }

Interface of Salary

   public interface Salary {

          public double computeSalary(List<Benefit> benefits, List<Deduction> deductions);

   }

Base/Implementation class of interface salary

   @Service
   public class SalaryImpl implements Salary, Serializable {

          //other attributes here

          //getter and setters

          //other methods

          @Override
          public double computeSalary(List<Benefit> benefits, List<Deduction> deductions){
                 return 0;
          }
   }

Solution

  • First, @Transient is from JPA which is nothing to do with Spring .

    Second, to be able to let Spring to inject beans into Employee, Employee is also required to be registered as a spring bean. But in realty, you can think that Employee is created using "new" by JPA implementation behind scene. That 's why spring cannot auto wire other beans to it.

    If you really need do it, you can use AspectJ to do it as described by the docs.

    I personally did not try this approach as you can simply make your SalaryService to accept an Employee as one of its argument to compute his salary, which is much simpler and easy to understand than the AspectJ approach.

    public interface SalaryService {
        public double computeSalary(Employee employee , List<Benefit> benefits, List<Deduction> deductions);
    } 
    

    And the client code looks like:

    @Service
    public class EmployeeService {
    
        @Autowired
        private SalaryService salaryService;
    
        @Transactional
        public void computeEmployeeSalary(Integer employeeId){
            Employee employee = entityManager.find(Employee.class , employeeId);
            salaryService.computeSalary(employee, .... ,.....);
        }
    
    }