Home > Software design >  How to handle Runtime Polymorphism in Spring Boot?
How to handle Runtime Polymorphism in Spring Boot?

Time:12-13

I have abstract class Employee

public abstract class Employee {

private int employeeId;

private String name;
}

I also have two concrete classes that extends Employee and that is OfficeEmployee and HomeEmployee which are currently empty. This my controller:

@RestController
@RequestMapping("/api/employee")
public class EmployeeController {

@Autowired
private EmployeeService employeeService;

@PostMapping("/office")
public EmployeeResponse saveOfficeEmployee(@RequestBody OfficeEmployee request) {
    return employeeService.save(request);
}

@PostMapping("/home")
public EmployeeResponse saveHomeEmployee(@RequestBody HomeEmployee request) {
    return employeeService.save(request);
 }
}

And finally EmployeeService class:

@Service
public class EmployeeService {

@Autowired
private EmployeeRepository employeeRepository;

  public Employee save(Employee request) {
  // here i think i should do something like this: Employee employee = new OfficeEmployee or
  // Employee employee = new HomeEmployee();
    Employee employee = employeeRepository.save(employee);
    return employee;
 }
}

How to determine what employee did i get from POST request? Am i approaching this problem wrong?

CodePudding user response:

Why would you think you need to determine anything? EmployeeRepository is already well-equipped to handle all types of Employees.

For this to work, Employee must be an @Entity. It can still be abstract, though.

As a side note, an alternative to having separate endpoints (/home, /office) is to use @JsonTypeInfo with one of the available strategies to determine Employee subtype from the input data.

CodePudding user response:

Yeah abstract class entity is something I use for adding a common column in multiple tables. For example if I want to add createdDate and updatedDate on many tables, I would defined those 2 columns in an abstract entity class (call it BaseDateEntity for example), and inherit it in all the entity classes I want to use it. Also annotate the base entity class with @MappedSuperclass. But repositories should be per specific entity classes. You can't use 1 repository for all entities that inherit your abstract Employee entity, otherwise the query will be executed on all subclass entities(OfficeEmployee, HomeEmployee, XyzEmployee, ..) of your superclass baseentity(=Employee), which could be sufficient from time to time.

Rough example of your entity codes.

import java.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class Employee { //body skipped for brevity}

There's an alternative. Use @Entity and @DiscriminatorColumn on your base entity. And use @Entity & @DiscriminatorValue on your child entity.

@Entity
@DiscriminatorColumn
@Inheritance(strategy=InheritanceType.JOINED) //more explanation on this below
public abstract class Employee {//body skipped for brevity}

@Entity
@DiscriminatorValue("Officeemployee")
public class OfficeEmplyee extends Employee {}

You cannnot use both MappedSuperclass and Entity. So choose one.

Rough example of your repositories

public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long>{}

public interface OfficeEmployeeRepository extends JpaRepository<OfficeEmployee,Long>{}

Obviously I skipped code examples for HomeEmployee because that will be same as OfficeEmployee.

Also you don't need OfficeEmployeeRepository if you never want query specifically on OfficeEmployees only. If you always query on all subclasses of Employees, then you only need EmployeeRepository. However in this case I think you need both EmployeeRepository for general Employee query and also OfficeRepository & HomeRepository for query on specific type of employees

To explain a bit further about the difference between MappedSuperclass methodology and DiscriminatorValue methodology, you have to think about tables in DB.

In simple cases where you don't want to create another table for parent (abstract) entity object, it's much simpler to use MappedSuperclass. It simply maps(adds) the additional columns that are described in abstract parent entity, onto child entities. In my usual usecase(createdDate, updadedDate column), this is the better approach since there's no reason to build a table for all datasets that have createdDate&updatedDate column. (A table of all the posts, announcements, comments, threads, re-replies, A2A, ...etc? Makes no sense)

However in your case you might want to keep a table of all kinds of employees. In that case use discriminatorcolumn & discriminatorvalue approach. Here's where @Inheritance(strategy=) annotation comes into play.

If @Inheritance doesn't exist, default inheritance strategy is SINGLE_TABLE. Which is self explanatory imo. All subclass entity columns are also added on this superclass (abstract entity) table. It will create a giant Employee table. Since it doesn't need join query it's faster and simpler in querying. But the down side is that table is giant and also there will be a lot of null values. (If OfficeWorker has column called 'OfficeLocation' and HomeWorker doesn't, then every HomeWorker rows will have OfficeLocation=null in the giant Employee table.)

What I used above is JOINED strategy. Also self explanatory. Makes a table of all Employee, a table of all OfficeWorker, a table of all HomeWorker. But in this case, Employee table only has common column values (id, name, .. what not) and type (OfficeWorker vs HomeWorker), and a foreignkey that is used for join query onto OfficeWorker table and HomeWorker table.

Last option is TABLE_PER_CLASS. It doesn't generate a table of all Employee. So it is the same as MappedSuperclass annotation but only more verbose. Never recommended.

  • Related