代码之家  ›  专栏  ›  技术社区  ›  Cleyton Gonçalves

通用Spring数据JPA存储库findAll

  •  2
  • Cleyton Gonçalves  · 技术社区  · 7 年前

    有没有办法让一个通用的Spring数据JPA存储库正确地处理以下方法 findAll() ? 例如 AnimalRepository<Dog>.findAll 只归还狗,而不是所有动物?或者至少,最好的解决方法是什么?

    假设我有这个:

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    public abstract class Animal extends BaseEntity {
        ...
    }
    

    @Entity
    public class Dog extends Animal {
        ...
    }
    
    @Entity
    public class Capybara extends Animal {
        ...
    }
    

    public interface AnimalRepository<T extends Animal> extends JpaRepository<T, Long> {
    
    }
    

    @Service("animalService")
    public class AnimalServiceImpl<T extends Animal> implements AnimalService<T> {
    
        private final AnimalRepository<T> animalRepository;
    
        @Autowired
        public AnimalServiceImpl(AnimalRepository<T> aR) {
            this.animalRepository = aR;
        }
    
        @Override
        public void save(T animal) {
            animalRepository.save(animal);
        }
    
        @Override
        public List<T> findAll() {
            return animalRepository.findAll();
        }
    
        ...
    
    }
    

    它几乎完美地工作,把每只动物都放在自己的桌子上,等等。唯一的问题是: findAll() 退货 两者都有 水豚和狗。这 answer 解释:

    这仅在域类使用单表继承时有效。我们在引导时可以获得的关于域类的唯一信息是它将是产品对象。因此,对于像findAll()甚至findByName()这样的方法,相关查询将从产品p where中的select p开始。这是因为反射查找永远无法生成Wine或Car,除非您为其创建一个专用的存储库接口来捕获具体的类型信息。

    好吧,遗憾的是,有多个存储库而不是只有一个存储库的代码变得不那么干净了。但我仍然想保留一个AnimalService类。我就是这样做的:

    @Service("animalService")
    public class AnimalServiceImpl<T extends Animal> implements AnimalService<T> {
    
        private final DogRepository dogRepository;
        private final CapybaraRepository capybaraRepository;
    
        @Autowired
        public AnimalServiceImpl(AnimalRepository<T> aR) {
            this.animalRepository = aR;
        }
    
        @Override
        public void save(T animal) {
            animalRepository.save(animal);
        }
    
        @Override
        public List<T> findAllDogs() {
            return dogRepository.findAll();
        }
    
        @Override
        public List<T> findAllCapybaras() {
            return capybaraRepository.findAll();
        }
    
        ...
    
    }
    

    如果存储库真的无法处理 findAll() 根据类型 <T> ,拥有单一动物服务的最干净方式是什么?当然,肯定有比我做的更好的方法,因为如果你的服务更复杂,而且不止是一对动物,那么它会变得非常丑陋,非常快。


    编辑:考虑到Spring的DI考虑 AnimalRepository<Dog> and AnimalRepository<Capybara> as being the same thing (将相同的存储库注入到使用Capybara one的服务和使用Dog one的另一个服务中),我必须为每个服务创建不同的存储库和服务(ESala answer的选项B):

    @NoRepositoryBean
    public interface AnimalRepository<T extends Animal> extends JpaRepository<T, Long> {
    }
    
    public interface DogRepository extends AnimalRepository<Dog> {   
    }
    
    public interface CapybaraRepository extends AnimalRepository<Capybara> {   
    }
    

    public abstract class AnimalService<T extends Animal> {
        private final AnimalRepository<T> animalRepository;
    
        AnimalService(AnimalRepository<T> repo) {
            this.animalRepository = repo;
        }
    
        public void salvar(T palavra) {
            animalRepository.save(palavra);
        }
    
        public List<T> findAll() {
            return animalRepository.findAll();
        }
    
    }
    
    @Service
    public class DogService extends AnimalService<Dog> {
    
        @Autowired
        public DogService(DogRepository repo) {
            super(repo);
        }
    }
    
    @Service
    public class CapybaraService extends AnimalService<Capybara> {
    
        @Autowired
        public CapybaraService(CapybaraRepository repo) {
            super(repo);
        }
    }
    

    可能还有更好的方法,所以我会接受建议。

    2 回复  |  直到 7 年前
        1
  •  3
  •   ESala    7 年前

    在存储库上:由于不使用单表继承,因此需要为每个 T ,没办法。这意味着您将有一个 AnimalRepository<Dog> 以及 AnimalRepository<Capybara> .

    在服务上:在应用程序的某个地方,您需要一个开关/案例,根据类型将您引导到正确的存储库。

    您有2个选项,a)有一个单独的服务实例来处理所有类型并在内部将查询定向到相应的存储库,或b)每个类型都有一个服务实例 T 并在其他位置选择实例。

    我更喜欢选项a)。您可以调整当前的实现,以使用单个 findAll() 通过获取对 T 键入并使用开关/案例选择适当的存储库。

    获取对的引用 <T> 在运行时,由于类型擦除,会造成混乱,但这是可以做到的。

        2
  •  0
  •   Garry Taylor    7 年前

    如何向动物添加类别属性/枚举

    @Override
    public List<T> findAll(Animal animal) {
        if(animal.getCategory() == "Dog") 
           return findAllDogs();
        if(animal.getCategory() == "Capybara") 
           return findAllCapybaras();
    }
    
    @Override
    private List<T> findAllDogs() {
        return dogRepository.findAll();
    }
    
    @Override
    private List<T> findAllCapybaras() {
        return capybaraRepository.findAll();
    }
    

    注意:也可以获取泛型的类类型,但这有点棘手

    How to get a class instance of generics type T