Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA repository with single table inheritance (hibernate)

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.

like image 658
whatspoppin Avatar asked Oct 26 '25 10:10

whatspoppin


2 Answers

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.

like image 84
Marc Avatar answered Oct 28 '25 23:10

Marc


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.

like image 20
emyasa Avatar answered Oct 29 '25 01:10

emyasa



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!