[英]Using generics in Spring Data JPA repositories
我有许多需要持久化到数据库的简单对象类型。 我正在使用 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);
}
}
我突然想到可以用三个基于泛型的类替换每个对象类型的多个类,从而节省大量样板代码。 我不确定如何去做,事实上,这是否是个好主意?
首先,我知道我们在这里提高了很多标准,但这已经比没有 Spring Data JPA 的帮助而必须编写的代码少得多。
其次,我认为您首先不需要服务类,如果您所做的只是将调用转发到存储库。 如果您的业务逻辑需要在事务中协调不同的存储库或有其他业务逻辑要封装,我们建议在存储库前使用服务。
一般来说,你当然可以这样做:
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);
}
这将允许您像这样在客户端使用存储库:
class MyClient {
@Autowired
public MyClient(ProductRepository<Car> carRepository,
ProductRepository<Wine> wineRepository) { … }
}
它会按预期工作。 不过有几点需要注意:
这仅在域类使用单表继承时才有效。 我们可以在引导时获得的域类的唯一信息是它将是Product
对象。 因此,对于像findAll()
甚至findByName(…)
这样的方法,相关查询将从select p from Product p where…
。 这是因为反射查找永远无法生成Wine
或Car
除非您为其创建专用的存储库接口来捕获具体的类型信息。
一般来说,我们建议为每个聚合根创建存储库接口。 这意味着您没有针对每个域类本身的存储库。 更重要的是,通过存储库对服务进行 1:1 抽象也完全没有抓住要点。 如果您构建服务,则不会为每个存储库构建一个(猴子可以做到,我们不是猴子,对吗?;)。 服务公开了更高级别的 API,更多是用例驱动器,并且通常会编排对多个存储库的调用。
此外,如果您在存储库之上构建服务,您通常希望强制客户端使用该服务而不是存储库(这里的一个经典示例是用于用户管理的服务也会触发密码生成和加密,因此绝不让开发人员直接使用存储库是个好主意,因为他们可以有效地解决加密问题)。 因此,您通常希望选择谁可以持久化哪些域对象,而不是到处创建依赖关系。
是的,您可以构建通用存储库并将它们用于多种域类型,但存在非常严格的技术限制。 尽管如此,从架构的角度来看,你上面描述的场景甚至不应该出现,因为这意味着无论如何你都面临着设计的味道。
这是很有可能的! 我参加聚会可能很晚了。 但这肯定会对将来的某人有所帮助。 这是一个完整的解决方案,就像一个魅力!
为您的实体创建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
}
为您的 DAO 持久性创建一个通用的 JPA 存储库接口,如下所示: 请记住放置@NoRepositoryBean
以便 JPA 不会尝试为存储库查找实现!
@NoRepositoryBean
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable>
extends JpaRepository<T, ID>{
}
创建一个使用上述基本 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
}
然后为基本 JPA 存储库创建一个抽象实现,基本 CRUD 方法也将提供它们的实现,如下所示:
@Service
@Transactional
public abstract 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);
}
}
如何使用上述抽象entity
、 service
、 repository
和implementation
:
此处的示例将是MyDomain
实体。 创建一个扩展AbstractBaseEntity
的域实体,如下所示: ID
, createdAt
, updatedAt
, version
等将被自动包括在MyDomain
从实体AbstractBaseEntity
@Entity
public class MyDomain extends AbstractBaseEntity{
private String attribute1;
private String attribute2;
// getters and setters
}
然后为扩展AbstractBaseRepository
的MyDomain
实体创建一个repository
,如下所示:
@Repository
public interface MyDomainRepository extends AbstractBaseRepository<MyDomain, Long>{
}
另外,为MyDomain
实体创建一个service
接口,如下所示:
public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{
}
然后为MyDomain
实体提供一个扩展AbstractBaseRepositoryImpl
实现的实现,如下所示:
@Service
@Transactional
public class MyDomainServiceImpl extends AbstractBaseRepositoryImpl<MyDomain, Long>
implements MyDomainService{
private MyDomainRepository myDomainRepository;
public MyDomainServiceImpl(MyDomainRepository myDomainRepository) {
super(myDomainRepository);
}
// other specialized methods from the MyDomainService interface
}
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
}
注意。 确保AbstractBaseRepository
使用@NoRepositoryBean
注释,以便JPA
不会尝试查找 bean 的实现。 此外, AbstractBaseServiceImpl
必须标记为抽象,否则 JPA 将尝试在类的构造函数中自动装配AbstractBaseRepository
所有子 daos,从而导致NoUniqueBeanDefinitionException
因为在创建 bean 时将注入 1 个以上的 daos(存储库)! 现在您的service
、 repository
和implementations
的可重用性更高。 我们都讨厌样板!
希望这可以帮助某人。
我正在做一个项目,用 spring 数据为 cassandra 创建通用存储库。
首先创建一个带有代码的存储库接口。
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("}");
编译代码并获取类,我使用 org.mdkt.compiler.InMemoryJavaCompiler
ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
compiler = InMemoryJavaCompiler.newInstance();
compiler.useParentClassLoader(classLoader);
Class<?> testRepository = compiler.compile("TestRepository", sourceCode.toString());
并在 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);
现在可以试试repository的save方法,也可以试试findById等其他方法。
Method method = repository.getClass().getMethod("save", paramTypes);
T obj = (T) method.invoke(repository, params.toArray());
我在这个 repo https://github.com/maye-msft/generic-repository-springdata 中放入了完整的示例代码和实现。
您可以使用类似的逻辑将其扩展到 JPA。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.