Java 聚合到JPA实体映射
在我参与的Java 聚合到JPA实体映射,java,jpa,domain-driven-design,ddd-repositories,Java,Jpa,Domain Driven Design,Ddd Repositories,在我参与的DDD-项目中,我们正在寻找一些方便的解决方案,将实体对象映射到域对象,反之亦然 该项目的开发人员同意将域模型与数据模型完全解耦。 数据层使用JPA(Hibernate)作为持久化技术 由于我们都认为持久性是DDD中的一个实现细节,从开发人员的角度来看,我们都在寻找应用程序各个方面最合适的解决方案 我们最大的担忧是,当一个包含实体列表的聚合映射到一个JPA实体时,该实体又包含一对多关系 请看下面的示例: 域模型 数据模型 存储库 类ProductJpaRepository实现Prod
DDD
-项目中,我们正在寻找一些方便的解决方案,将实体对象
映射到域对象
,反之亦然
该项目的开发人员同意将域模型与数据模型完全解耦。
数据层使用JPA(Hibernate)
作为持久化技术
由于我们都认为持久性是DDD中的一个实现细节,从开发人员的角度来看,我们都在寻找应用程序各个方面最合适的解决方案
我们最大的担忧是,当一个包含实体列表的聚合
映射到一个JPA实体
时,该实体又包含一对多关系
请看下面的示例:
域模型
数据模型
存储库
类ProductJpaRepository实现ProductRepository{
@凌驾
公共产品查找方式(ProductId ProductId){
ProductJpaEntity entity=//按productId查找实体
ProductBackLogitemJPAEEntity backlogItemEntities=entity.getBacklogItemEntities();
设置backlogItems=toBackLogItems(backlogItemEntities);
返回新产品(新产品ID(entity.getProductId()),backlogItems);
}
@凌驾
公共作废保存(产品){
ProductJpaEntity entity=//按productId查找实体
if(实体==null){
//将Product和ProductBackLogiItem映射到其相应的实体并保存
返回;
}
Set backlogItems=product.getProductBacklogItems();
//我们如何知道哪些备份项是:新的、删除的或修改的。。。?
}
}
当DB
中已经存在ProductJpaEntity
时,我们需要更新所有内容。
在进行更新时,ProductJpaEntity
已在HibernatePersistenceContext
中可用。
但是,我们需要确定哪些productbacklogitem
发生了更改
更具体地说:
ProductBacklogItem
可能已添加到集合中
ProductBacklogItem
可能已从集合中删除
每个ProductBacklogItemJpaEntity
都有一个指向ProductJpaEntity
的主键。
似乎检测新的或删除的productbacklogitem
的唯一方法是通过主键来匹配它们。
但是,主键不属于域模型
还可以首先删除ProductJpaEntity
的所有ProductBacklogItemJpaEntity
实例(存在于数据库中),刷新到数据库,创建新的ProductBacklogItemJpaEntity
实例并保存到数据库。
这将是一个糟糕的解决方案。产品的每次保存
都会导致数据库中出现多个delete
和insert
语句
有哪种解决方案可以在不牺牲太多域和数据模型的情况下解决此问题?这是一个完美的应用案例
我创建了这个库,以便在JPA模型和自定义接口或抽象类定义的模型之间进行简单的映射,类似于类固醇上的Spring数据投影。其思想是以您喜欢的方式定义目标结构(域模型),并通过JPQL表达式将属性(getter)映射到实体模型
实体视图也可以是可更新和/或可创建的,即支持刷新更改,这可以用作DDD设计的基础。
可更新实体视图实现脏状态跟踪。您可以内省实际更改或刷新更改的值
您可以将可更新的实体视图定义为抽象类,以隐藏“实现细节”,例如,保护修饰符后面的主键,如下所示:
@UpdatableEntityView
@EntityView(ProductJpaEntity.class)
public abstract class Product extends Aggregate {
@IdMapping
protected abstract ProductId getProductId();
public abstract Set<ProductBacklogItem> getBacklogItems();
}
@UpdatableEntityView
@EntityView(ProductBacklogItemJpaEntity.class)
public abstract class ProductBacklogItem extends DomainEntity {
@IdMapping
protected abstract BacklogItemId getBacklogItemId();
protected abstract ProductId getProductId();
public abstract int getOrdering();
}
@updateableEntityView
@EntityView(ProductJpaEntity.class)
公共抽象类产品扩展了聚合{
@IdMapping
受保护的抽象ProductId getProductId();
公共抽象集getBacklogItems();
}
@UpdateEntityView
@EntityView(ProductBacklogItemJpaEntity.class)
公共抽象类ProductBacklogItem扩展了DomainEntity{
@IdMapping
受保护的抽象BacklogItemId getBacklogItemId();
受保护的抽象ProductId getProductId();
公共抽象int getOrdering();
}
查询是将实体视图应用于查询的问题,最简单的就是按id进行查询
Product p=entityViewManager.find(entityManager,Product.class,id)代码>
保存,即刷新更改也很容易
entityViewManager.save(entityManager,产品)代码>
Spring数据集成允许您像使用Spring数据投影一样使用它:对于刷新更改,您可以在存储库中定义一个save
方法,该方法接受可更新的实体视图库看起来很有趣,但在上面的示例中我看到了问题,通过注释,视图类仍然依赖于Jpa类。问题方法(以及洋葱架构背后)的想法是避免这种依赖性。您希望如何避免这种依赖性?在某个时刻,你必须以某种方式连接模型。由于此依赖项仅在注释级别上,因此您可以使用运行时
依赖项范围,因此域模型的使用者不需要依赖于您的持久性模型。您的产品BackLogitemJPaeEntity
应该是一个拥有自己的@PrimaryKey
的@实体,然后,当您收到产品
时,只需将ProductBacklogItem
列表转换为ProductBacklogItemJpaEntity
列表,将其设置为ProductJpaEntity
并保存即可。如果产品
域对象中的列表包含所有待办事项,则在您可以
public class ProductBacklogItem extends DomainEntity {
private BacklogItemId backlogItemId;
private int ordering;
private ProductId productId;
// constructor & methods omitted for brevity
}
public class ProductJpaEntity {
private String productId;
@OneToMany
private Set<ProductBacklogItemJpaEntity> backlogItems;
// constructor & methods omitted for brevity
}
public class ProductBacklogItemJpaEntity {
private String backlogItemId;
private int ordering;
private String productId;
// constructor & methods omitted for brevity
}
public interface ProductRepository {
Product findBy(ProductId productId);
void save(Product product);
}
class ProductJpaRepository implements ProductRepository {
@Override
public Product findBy(ProductId productId) {
ProductJpaEntity entity = // lookup entity by productId
ProductBacklogItemJpaEntity backlogItemEntities = entity.getBacklogItemEntities();
Set<ProductBacklogItem> backlogItems = toBackLogItems(backlogItemEntities);
return new Product(new ProductId(entity.getProductId()), backlogItems);
}
@Override
public void save(Product product) {
ProductJpaEntity entity = // lookup entity by productId
if (entity == null) {
// map Product and ProductBacklogItems to their corresponding entities and save
return;
}
Set<ProductBacklogItem> backlogItems = product.getProductBacklogItems();
// how do we know which backlogItems are: new, deleted or adapted...?
}
}
@UpdatableEntityView
@EntityView(ProductJpaEntity.class)
public abstract class Product extends Aggregate {
@IdMapping
protected abstract ProductId getProductId();
public abstract Set<ProductBacklogItem> getBacklogItems();
}
@UpdatableEntityView
@EntityView(ProductBacklogItemJpaEntity.class)
public abstract class ProductBacklogItem extends DomainEntity {
@IdMapping
protected abstract BacklogItemId getBacklogItemId();
protected abstract ProductId getProductId();
public abstract int getOrdering();
}