4

I have created two entites (RegularEmployee and ContactEntity) that extends the Employee entity.

@Entity
@Table(name="employees")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(value="employee")
public class Employee {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

...

Im using SINGLE_TABLE inheritance for this implementations, and created a generic JpaRepository for manipulating data:

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

I've created also the Service class that autowire three instance of these generic repositories, and specific methods for each class.

@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository<Employee> employeeRepo;

    @Autowired
    private EmployeeRepository<RegularEmployee> regularRepo;

    @Autowired
    private EmployeeRepository<ContractEmployee> contractRepo;

    public List<Employee> getAllEmployee() {
        return employeeRepo.findAll();
    }

    public List<RegularEmployee> getAllRegularEmployee(){
        return regularRepo.findAll();
    }

    public List<ContractEmployee> getAllContractEmployee() {
        return contractRepo.findAll();
    }
...

My problem is, that when I try to find all regular employees or contract employees, I always get all type of employees (employees, regular employees and contract employees all together).

I do not know why it behaves like this, even though the method's signature says it returns the appropriate type.

Giorgi Tsiklauri
  • 9,715
  • 8
  • 45
  • 66
whatspoppin
  • 353
  • 4
  • 14

2 Answers2

4

One option is to use @Query in EmployeeRepository:

public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
    @Query("from RegularEmployee")
    List<RegularEmployee> findAllRegularEmployees();
}

A second option is to create an additional repository for each subclass of Employee. For RegularEmployee would be:

public interface RegularEmployeeRepository extends EmployeeRepository<RegularEmployee>{}

This is how to use both options in EmployeeService:

@Service
public class EmployeeService {
    @Autowired EmployeeRepository<Employee> employeeRepo;

    @Autowired EmployeeRepository<RegularEmployee> regularRepoT;

    @Autowired RegularEmployeeRepository regularRepo;

    @PostConstruct
    public void init(){
        employeeRepo.save(new ContractEmployee("Mark"));
        employeeRepo.save(new RegularEmployee("Luke"));
        employeeRepo.findAll().forEach(System.out::println); // prints Mark and Luke
        regularRepo.findAll().forEach(System.out::println); // prints only Luke
        regularRepoT.findAllRegularEmployees().forEach(System.out::println); // prints only Luke
    }
//...
}

Also you can omit @Repository on top of EmployeeRepository. Spring already knows that is a Repository because it extends JpaRepository.

Side note: if you don't need EmployeeRepository to be created by Spring add @NoRepositoryBean on top of its class.

Marc
  • 2,738
  • 1
  • 17
  • 21
  • Is it necessary to create the `RegularEmployeeRepository`, Why I can not use the generic interface !!! Im tryring to understand how it works, image having multiple sub-entites that extends a top level entity, I don't to create repository for each one of them – whatspoppin Aug 30 '20 at 17:40
  • 1
    @whatspoppin it's not necessary, I updated my answer. However, if you need to query by a specific property of a subclass, creating the additional repository might make more sense. – Marc Aug 31 '20 at 03:16
1

I've been able to replicate what you've encountered using your generic EmployeeRepository. As an alternative I created two separate repositories: ContractualEmployeeRepository and RegularEmployeeRepository.

public interface ContractualEmployeeRepository extends JpaRepository<ContractualEmployee, String> {
}

public interface RegularEmployeeRepository extends JpaRepository<RegularEmployee, String> {
}

Then, I created an integration test.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Main.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@TestPropertySource(locations="classpath:application-test.properties")
@DatabaseSetup("classpath:SingleTableDataSet.xml")
public class IntegrationTest {

    @Autowired
    private RegularEmployeeRepository regularEmployeeRepository;

    @Autowired
    private ContractualEmployeeRepository contractualEmployeeRepository;

    @Test
    public void test() {
        Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
        Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
    }

}

and it works.

As for the usage and limitations of Generics in Spring Data JPA repositories: https://stackoverflow.com/a/19443031/14180014 He had done a great job explaining it.

emyasa
  • 86
  • 4
  • I've tried your implementation, it works, but why with generic interface that use `T extends Employee` is not working how it supposed to work. Imagine having multiple inheritance and entities – whatspoppin Aug 30 '20 at 17:44
  • Regarding that, I've come across this answer (Using generics in Spring Data JPA repositories) where a Product has 2 subclass: Wine and Car (in our case Employee: Regular and Contractual) https://stackoverflow.com/a/19443031/14180014 and I'll quote here part of his answer "This is due to the fact that the reflection lookup will never ever be able to produce Wine or Car unless you create a dedicated repository interface for it to capture the concrete type information." – emyasa Aug 31 '20 at 05:14