JPA存储库与单表继承(Hibernate)

huangapple go评论110阅读模式
英文:

JPA repository with single table inheritance (hibernate)

问题

我已经创建了两个实体(RegularEmployeeContactEntity),它们都继承自 Employee 实体。

  1. @Entity
  2. @Table(name="employees")
  3. @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  4. @DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
  5. @DiscriminatorValue(value="employee")
  6. public class Employee {
  7. @Id
  8. @GeneratedValue
  9. private Long id;
  10. private String name;
  11. ...

我在这些实现中使用了 SINGLE_TABLE 继承策略,并创建了一个用于操作数据的通用 JpaRepository:

  1. @Repository
  2. public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
  3. }

我还创建了一个 Service 类,自动装配了这三个通用仓库的实例,并为每个类编写了特定的方法。

  1. @Service
  2. public class EmployeeService {
  3. @Autowired
  4. private EmployeeRepository<Employee> employeeRepo;
  5. @Autowired
  6. private EmployeeRepository<RegularEmployee> regularRepo;
  7. @Autowired
  8. private EmployeeRepository<ContractEmployee> contractRepo;
  9. public List<Employee> getAllEmployee() {
  10. return employeeRepo.findAll();
  11. }
  12. public List<RegularEmployee> getAllRegularEmployee(){
  13. return regularRepo.findAll();
  14. }
  15. public List<ContractEmployee> getAllContractEmployee() {
  16. return contractRepo.findAll();
  17. }
  18. ...

我的问题是,当我尝试查找所有常规员工或合同员工时,我总是会得到所有类型的员工(包括员工、常规员工和合同员工)。

我不知道为什么会出现这种情况,尽管方法的签名表明它应该返回适当的类型。

英文:

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

  1. @Entity
  2. @Table(name=&quot;employees&quot;)
  3. @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  4. @DiscriminatorColumn(name = &quot;type&quot;, discriminatorType = DiscriminatorType.STRING)
  5. @DiscriminatorValue(value=&quot;employee&quot;)
  6. public class Employee {
  7. @Id
  8. @GeneratedValue
  9. private Long id;
  10. private String name;
  11. ...

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

  1. @Repository
  2. public interface EmployeeRepository&lt;T extends Employee&gt; extends JpaRepository&lt;T, Long&gt; {
  3. }

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

  1. @Service
  2. public class EmployeeService {
  3. @Autowired
  4. private EmployeeRepository&lt;Employee&gt; employeeRepo;
  5. @Autowired
  6. private EmployeeRepository&lt;RegularEmployee&gt; regularRepo;
  7. @Autowired
  8. private EmployeeRepository&lt;ContractEmployee&gt; contractRepo;
  9. public List&lt;Employee&gt; getAllEmployee() {
  10. return employeeRepo.findAll();
  11. }
  12. public List&lt;RegularEmployee&gt; getAllRegularEmployee(){
  13. return regularRepo.findAll();
  14. }
  15. public List&lt;ContractEmployee&gt; getAllContractEmployee() {
  16. return contractRepo.findAll();
  17. }
  18. ...

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.

答案1

得分: 4

  1. `EmployeeRepository`一种选择是使用`@Query`
  2. ```java
  3. public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
  4. @Query("from RegularEmployee")
  5. List<RegularEmployee> findAllRegularEmployees();
  6. }

第二种选择是为每个Employee子类创建一个额外的存储库。对于RegularEmployee,可以这样:

  1. public interface RegularEmployeeRepository extends EmployeeRepository<RegularEmployee> {}

以下是如何在EmployeeService中使用这两种选项:

  1. @Service
  2. public class EmployeeService {
  3. @Autowired EmployeeRepository<Employee> employeeRepo;
  4. @Autowired EmployeeRepository<RegularEmployee> regularRepoT;
  5. @Autowired RegularEmployeeRepository regularRepo;
  6. @PostConstruct
  7. public void init(){
  8. employeeRepo.save(new ContractEmployee("Mark"));
  9. employeeRepo.save(new RegularEmployee("Luke"));
  10. employeeRepo.findAll().forEach(System.out::println); // 打印 Mark 和 Luke
  11. regularRepo.findAll().forEach(System.out::println); // 仅打印 Luke
  12. regularRepoT.findAllRegularEmployees().forEach(System.out::println); // 仅打印 Luke
  13. }
  14. //...
  15. }

另外,你可以在EmployeeRepository的类顶部省略@Repository。Spring 已经知道它是一个存储库,因为它扩展了JpaRepository

附注:如果你不希望Spring创建EmployeeRepository,可以在其类顶部添加@NoRepositoryBean

  1. 注意:代码中的一些符号可能在显示上会有差异,但我已经尽力保留了原始代码的结构和内容。如果需要进一步的帮助,请随时提问。
  2. <details>
  3. <summary>英文:</summary>
  4. One option is to use `@Query` in `EmployeeRepository`:
  5. public interface EmployeeRepository&lt;T extends Employee&gt; extends JpaRepository&lt;T, Long&gt; {
  6. @Query(&quot;from RegularEmployee&quot;)
  7. List&lt;RegularEmployee&gt; findAllRegularEmployees();
  8. }
  9. A second option is to create an additional repository for each subclass of `Employee`. For `RegularEmployee` would be:
  10. public interface RegularEmployeeRepository extends EmployeeRepository&lt;RegularEmployee&gt;{}
  11. This is how to use both options in `EmployeeService`:
  12. @Service
  13. public class EmployeeService {
  14. @Autowired EmployeeRepository&lt;Employee&gt; employeeRepo;
  15. @Autowired EmployeeRepository&lt;RegularEmployee&gt; regularRepoT;
  16. @Autowired RegularEmployeeRepository regularRepo;
  17. @PostConstruct
  18. public void init(){
  19. employeeRepo.save(new ContractEmployee(&quot;Mark&quot;));
  20. employeeRepo.save(new RegularEmployee(&quot;Luke&quot;));
  21. employeeRepo.findAll().forEach(System.out::println); // prints Mark and Luke
  22. regularRepo.findAll().forEach(System.out::println); // prints only Luke
  23. regularRepoT.findAllRegularEmployees().forEach(System.out::println); // prints only Luke
  24. }
  25. //...
  26. }
  27. Also you can omit `@Repository` on top of `EmployeeRepository`. Spring already knows that is a Repository because it extends `JpaRepository`.
  28. **Side note**: if you don&#39;t need `EmployeeRepository` to be created by Spring add `@NoRepositoryBean` on top of its class.
  29. [1]: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query.spel-expressions
  30. </details>
  31. # 答案2
  32. **得分**: 1
  33. 我已经能够使用您的通用EmployeeRepository复制您遇到的问题。作为一种替代方法,我创建了两个单独的存储库:ContractualEmployeeRepositoryRegularEmployeeRepository
  34. ```java
  35. public interface ContractualEmployeeRepository extends JpaRepository<ContractualEmployee, String> {
  36. }
  37. public interface RegularEmployeeRepository extends JpaRepository<RegularEmployee, String> {
  38. }

然后,我创建了一个集成测试。

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = {Main.class})
  3. @TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
  4. TransactionalTestExecutionListener.class,
  5. DbUnitTestExecutionListener.class})
  6. @TestPropertySource(locations="classpath:application-test.properties")
  7. @DatabaseSetup("classpath:SingleTableDataSet.xml")
  8. public class IntegrationTest {
  9. @Autowired
  10. private RegularEmployeeRepository regularEmployeeRepository;
  11. @Autowired
  12. private ContractualEmployeeRepository contractualEmployeeRepository;
  13. @Test
  14. public void test() {
  15. Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
  16. Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
  17. }
  18. }

它有效运行。

至于在Spring Data JPA存储库中使用泛型的用途和限制,可以参考:https://stackoverflow.com/a/19443031/14180014 他在解释方面做得非常出色。

英文:

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.

  1. public interface ContractualEmployeeRepository extends JpaRepository&lt;ContractualEmployee, String&gt; {
  2. }
  3. public interface RegularEmployeeRepository extends JpaRepository&lt;RegularEmployee, String&gt; {
  4. }

Then, I created an integration test.

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(classes = {Main.class})
  3. @TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
  4. TransactionalTestExecutionListener.class,
  5. DbUnitTestExecutionListener.class})
  6. @TestPropertySource(locations=&quot;classpath:application-test.properties&quot;)
  7. @DatabaseSetup(&quot;classpath:SingleTableDataSet.xml&quot;)
  8. public class IntegrationTest {
  9. @Autowired
  10. private RegularEmployeeRepository regularEmployeeRepository;
  11. @Autowired
  12. private ContractualEmployeeRepository contractualEmployeeRepository;
  13. @Test
  14. public void test() {
  15. Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
  16. Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
  17. }
  18. }

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.

huangapple
  • 本文由 发表于 2020年8月30日 18:31:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/63656497.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定