Generic Spring Data JPA Repository findAll

时间:2018-01-06 08:28:08

标签: java spring architecture spring-data-jpa

有没有办法让通用Spring Data 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()返回 BOTH Capybaras和Dogs。这个answer解释了:

  

仅当域类使用单表继承时才有效。关于我们可以在bootstrap时获得的域类的唯一信息是它将是Product对象。因此对于像findAll()甚至findByName(...)这样的方法,相关的查询将从Product p中的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>,那么拥有单个AnimalService最简单的方法是什么?当然,必须有一种比我做得更好的方式,因为如果你在服务中有更多的复杂性而且超过几只动物,它会变得非常丑陋,非常快。

编辑:鉴于Spring的DI考虑AnimalRepository<Dog> and AnimalRepository<Capybara> as being the same thing(将同一个存储库注入使用Capybara的服务,另一个使用Dog的服务),我必须为每个服务器创建一个不同的存储库和服务。他们(@ESala答案的选项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 个答案:

答案 0 :(得分:2)

在存储库上:由于您不使用单表继承,因此每个T都需要一个存储库实例,而无法解决这个问题。这意味着您将拥有AnimalRepository<Dog>的实例和AnimalRepository<Capybara>的实例。

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

您有两个选项,a)具有处理所有类型的单个服务实例,并在内部将查询定向到相应的存储库,或b)为每个T提供服务实例并在其他地方选择实例。 / p>

我更喜欢选项a)。您可以通过获取findAll()类型的引用并使用开关/案例来选择适当的存储库来调整当前实现以使用单个T返回正确的类型。

在运行时获取<T>的引用因类型擦除而变得混乱,但可以完成。

答案 1 :(得分:0)

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

@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();
}

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

How to get a class instance of generics type T