Java 在 Spring Data JPA 存储库中使用泛型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19417670/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using generics in Spring Data JPA repositories
提问by skyman
I have a number of simple object types that need to be persisted to a database. I am using Spring JPA to manage this persistence. For each object type I need to build the following:
我有许多需要持久化到数据库的简单对象类型。我正在使用 Spring JPA 来管理这种持久性。对于每种对象类型,我需要构建以下内容:
import org.springframework.data.jpa.repository.JpaRepository;
public interface FacilityRepository extends JpaRepository<Facility, Long> {
}
public interface FacilityService {
public Facility create(Facility facility);
}
@Service
public class FacilityServiceImpl implements FacilityService {
@Resource
private FacilityRepository countryRepository;
@Transactional
public Facility create(Facility facility) {
Facility created = facility;
return facilityRepository.save(created);
}
}
It occurred to me that it may be possible to replace the multiple classes for each object type with three generics based classes, thus saving a lot of boilerplate coding. I am not exactly sure how to go about it and in fact if it is a good idea?
我突然想到,可以用三个基于泛型的类替换每个对象类型的多个类,从而节省大量样板代码。我不确定如何去做,事实上,这是否是个好主意?
采纳答案by Oliver Drotbohm
First of all, I know we're raising the bar here quite a bit but this is already tremendously less code than you had to write without the help of Spring Data JPA.
首先,我知道我们在这里提高了很多标准,但这已经比没有 Spring Data JPA 的帮助而必须编写的代码少得多。
Second, I think you don't need the service class in the first place, if all you do is forward a call to the repository. We recommend using services in front of the repositories if you have business logic that needs orchestration of different repositories within a transaction or has other business logic to encapsulate.
其次,我认为您首先不需要服务类,如果您所做的只是将调用转发到存储库。如果您的业务逻辑需要在事务中协调不同的存储库或有其他业务逻辑要封装,我们建议在存储库前使用服务。
Generally speaking, you can of course do something like this:
一般来说,你当然可以这样做:
interface ProductRepository<T extends Product> extends CrudRepository<T, Long> {
@Query("select p from #{#entityName} p where ?1 member of p.categories")
Iterable<T> findByCategory(String category);
Iterable<T> findByName(String name);
}
This will allow you to use the repository on the client side like this:
这将允许您像这样在客户端使用存储库:
class MyClient {
@Autowired
public MyClient(ProductRepository<Car> carRepository,
ProductRepository<Wine> wineRepository) { … }
}
and it will work as expected. However there are a few things to notice:
它会按预期工作。不过有几点需要注意:
This only works if the domain classes use single table inheritance.The only information about the domain class we can get at bootstrap time is that it will be Product
objects. So for methods like findAll()
and even findByName(…)
the relevant queries will start with select p from Product p where…
. This is due to the fact that the reflection lookup will never ever be able to produce Wine
or Car
unlessyou create a dedicated repository interface for it to capture the concrete type information.
这仅在域类使用单表继承时才有效。我们可以在引导时获得的域类的唯一信息是它将是Product
对象。所以对于像这样的方法findAll()
,甚至findByName(…)
相关的查询都将以select p from Product p where…
. 这是因为反射查找永远无法生成,Wine
或者Car
除非您为其创建专用的存储库接口来捕获具体的类型信息。
Generally speaking, we recommend creating repository interfaces per aggregate root. This means you don't have a repo for every domain class per se. Even more important, a 1:1 abstraction of a service over a repository is completely missing the point as well. If you build services, you don't build one for every repository (a monkey could do that, and we're no monkeys, are we? ;). A service is exposing a higher level API, is much more use-case drive and usually orchestrates calls to multiple repositories.
一般来说,我们建议为每个聚合根创建存储库接口。这意味着您没有针对每个域类本身的存储库。更重要的是,通过存储库对服务进行 1:1 抽象也完全没有抓住要点。如果您构建服务,您不会为每个存储库构建一个(猴子可以做到,我们不是猴子,对吗?;)。服务公开了更高级别的 API,更多是用例驱动器,并且通常会编排对多个存储库的调用。
Also, if you build services on top of repositories, you usually want to enforce the clients to use the service instead of the repository (a classical example here is that a service for user management also triggers password generation and encryption, so that by no means it would be a good idea to let developers use the repository directly as they'd effectively work around the encryption). So you usually want to be selective about who can persist which domain objects to not create dependencies all over the place.
此外,如果您在存储库之上构建服务,您通常希望强制客户端使用该服务而不是存储库(这里的一个经典示例是用于用户管理的服务也会触发密码生成和加密,因此绝不让开发人员直接使用存储库是个好主意,因为他们可以有效地解决加密问题)。因此,您通常希望选择谁可以持久化哪些域对象,而不是到处创建依赖关系。
Summary
概括
Yes, you can build generic repositories and use them with multiple domain types but there are quite strict technical limitations. Still, from an architectural point of view, the scenario you describe above shouldn't even pop up as this means you're facing a design smell anyway.
是的,您可以构建通用存储库并将它们用于多种域类型,但存在非常严格的技术限制。尽管如此,从架构的角度来看,你上面描述的场景甚至不应该出现,因为这意味着无论如何你都面临着设计的味道。
回答by Ye Ma
I am working a project to create the generic repository for cassandra with spring data.
我正在做一个项目,用 spring 数据为 cassandra 创建通用存储库。
Firstly create a repository interface with code.
首先创建一个带有代码的存储库接口。
StringBuilder sourceCode = new StringBuilder();
sourceCode.append("import org.springframework.boot.autoconfigure.security.SecurityProperties.User;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.AllowFiltering;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.Query;\n");
sourceCode.append("import org.springframework.data.repository.CrudRepository;\n");
sourceCode.append("\n");
sourceCode.append("public interface TestRepository extends CrudRepository<Entity, Long> {\n");
sourceCode.append("}");
Compile the code and get the class, I use org.mdkt.compiler.InMemoryJavaCompiler
编译代码并获取类,我使用 org.mdkt.compiler.InMemoryJavaCompiler
ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
compiler = InMemoryJavaCompiler.newInstance();
compiler.useParentClassLoader(classLoader);
Class<?> testRepository = compiler.compile("TestRepository", sourceCode.toString());
And initialize the repository in spring data runtime. This is a little tricky as I debug the SpringData code to find how it initialize a repository interface in spring.
并在 spring 数据运行时初始化存储库。这有点棘手,因为我调试 SpringData 代码以查找它如何在 spring 中初始化存储库接口。
CassandraSessionFactoryBean bean = context.getBean(CassandraSessionFactoryBean.class);
RepositoryFragments repositoryFragmentsToUse = (RepositoryFragments) Optional.empty().orElseGet(RepositoryFragments::empty);
CassandraRepositoryFactory factory = new CassandraRepositoryFactory(
new CassandraAdminTemplate(bean.getObject(), bean.getConverter()));
factory.setBeanClassLoader(compiler.getClassloader());
Object repository = factory.getRepository(testRepository, repositoryFragmentsToUse);
Now you can try the save method of the repository and you can try other methods such as findById.
现在可以试试repository的save方法,也可以试试findById等其他方法。
Method method = repository.getClass().getMethod("save", paramTypes);
T obj = (T) method.invoke(repository, params.toArray());
A full sample code and implementation I have put in this repo https://github.com/maye-msft/generic-repository-springdata.
我在这个 repo https://github.com/maye-msft/generic-repository-springdata 中放入了完整的示例代码和实现 。
You can extend it to JPA with the similar logic.
您可以使用类似的逻辑将其扩展到 JPA。
回答by Jose Mhlanga
This is very possible! I am probably very late to the party. But this will certainly help someone in the future. Here is a complete solution that works like a charm!
这是很有可能的!我参加聚会可能很晚了。但这肯定会对将来的某人有所帮助。这是一个完整的解决方案,就像一个魅力!
Create BaseEntity
class for your entities as follows:
BaseEntity
为您的实体创建类,如下所示:
@MappedSuperclass
public class AbstractBaseEntity implements Serializable{
@Id @GeneratedValue
private Long id;
@Version
private int version;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public AbstractBaseEntity() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// getters and setters
}
Create a generic JPA Repository interface for your DAO persistence as follows:
NB. Remember to put the @NoRepositoryBean
so that JPA will not try to find an implementation for the repository!
为您的 DAO 持久性创建一个通用的 JPA 存储库接口,如下所示:请记住放置 ,@NoRepositoryBean
以便 JPA 不会尝试为存储库查找实现!
@NoRepositoryBean
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable>
extends JpaRepository<T, ID>{
}
Create a Base Service class that uses the above base JPA repository. This is the one that other service interfaces in your domain will simply extend as follows:
创建一个使用上述基本 JPA 存储库的基本服务类。这是您域中的其他服务接口将简单扩展的接口,如下所示:
public interface AbstractBaseService<T extends AbstractBaseEntity, ID extends Serializable>{
public abstract T save(T entity);
public abstract List<T> findAll(); // you might want a generic Collection if u prefer
public abstract Optional<T> findById(ID entityId);
public abstract T update(T entity);
public abstract T updateById(T entity, ID entityId);
public abstract void delete(T entity);
public abstract void deleteById(ID entityId);
// other methods u might need to be generic
}
Then create an abstract implementation for the base JPA repository & the basic CRUD methods will also be provided their implementations as in the following:
然后为基本 JPA 存储库创建一个抽象实现,基本 CRUD 方法也将提供它们的实现,如下所示:
@Service
@Transactional
public class AbstractBaseRepositoryImpl<T extends AbstractBaseEntity, ID extends Serializable>
implements AbstractBaseService<T, ID>{
private AbstractBaseRepository<T, ID> abstractBaseRepository;
@Autowired
public AbstractBaseRepositoryImpl(AbstractBaseRepository<T, ID> abstractBaseRepository) {
this.abstractBaseRepository = abstractBaseRepository;
}
@Override
public T save(T entity) {
return (T) abstractBaseRepository.save(entity);
}
@Override
public List<T> findAll() {
return abstractBaseRepository.findAll();
}
@Override
public Optional<T> findById(ID entityId) {
return abstractBaseRepository.findById(entityId);
}
@Override
public T update(T entity) {
return (T) abstractBaseRepository.save(entity);
}
@Override
public T updateById(T entity, ID entityId) {
Optional<T> optional = abstractBaseRepository.findById(entityId);
if(optional.isPresent()){
return (T) abstractBaseRepository.save(entity);
}else{
return null;
}
}
@Override
public void delete(T entity) {
abstractBaseRepository.delete(entity);
}
@Override
public void deleteById(ID entityId) {
abstractBaseRepository.deleteById(entityId);
}
}
How to use the above abstract entity
, service
, repository
, and implementation
:
如何使用上述抽象entity
,service
,repository
,和implementation
:
Example here will be a MyDomain
entity
1. Create a domain entity that extends the AbstractBaseEntity
as follows:
NB. ID
, createdAt
, updatedAt
, version
, etc will be automatically be included in the MyDomain
entity from the AbstractBaseEntity
这里的示例将是一个MyDomain
实体 1. 创建一个扩展AbstractBaseEntity
如下的域实体:注意。ID
、createdAt
、updatedAt
、version
等将自动包含在MyDomain
实体中AbstractBaseEntity
@Entity
public class MyDomain extends AbstractBaseEntity{
private String attribute1;
private String attribute2;
// getters and setters
}
Then create a repository
for the MyDomain
entity that extends the AbstractBaseRepository
as follows:
然后repository
为MyDomain
扩展AbstractBaseRepository
如下的实体创建一个:
@Repository
public interface MyDomainRepository extends AbstractBaseRepository<MyDomain, Long>{
}
Also, Create a service
interface for the MyDomain
entity as follows:
另外,service
为MyDomain
实体创建一个接口,如下所示:
public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{
}
Then provide an implementation for the MyDomain
entity that extends the AbstractBaseRepositoryImpl
implementation as follows:
然后为MyDomain
扩展AbstractBaseRepositoryImpl
实现的实体提供一个实现,如下所示:
@Service
@Transactional
public class MyDomainServiceImpl extends AbstractBaseRepositoryImpl<MyDomain, Long>
implements MyDomainService{
public MyDomainServiceImpl(AbstractBaseRepository<MyDomain, Long> abstractBaseRepository) {
super(abstractBaseRepository);
}
}
Now use your `MyDomainService` service in your controller as follows:
@RestController // or @Controller
@CrossOrigin
@RequestMapping(value = "/")
public class MyDomainController {
private final MyDomainService myDomainService;
@Autowired
public MyDomainController(MyDomainService myDomainService) {
this.myDomainService = myDomainService;
}
@GetMapping
public List<MyDomain> getMyDomains(){
return myDomainService.findAll();
}
// other controller methods
}
Now your service
, repository
, and implementations
are more reusable. We all hate boilerplate!
Hope this helps someone.
现在,您的service
、repository
和implementations
更具可重用性。我们都讨厌样板!希望这可以帮助某人。