不同粒度的DTO
DTOs with different granularity
我正在进行一个项目,该项目使用最新的 Spring+Hibernate 来实现持久性和实现 REST API。
数据库中不同的 tables 包含大量记录,这些记录又非常大。因此,我创建了许多 DAO 来检索不同级别的详细信息及其附带的 DTO。
例如,如果我在数据库中有一些员工 table,其中包含关于每个员工的大量信息。如果我知道任何使用我的应用程序的客户都将从检索 Employee 实体的不同级别的详细信息中受益匪浅(而不是每次都被整个实体轰炸),那么到目前为止我所做的是这样的:
class EmployeeL1DetailsDto
{
String id;
String firstName;
String lastName;
}
class EmployeeL2DetailsDto extends EmployeeL1DetailsDto
{
Position position;
Department department;
PhoneNumber workPhoneNumber;
Address workAddress;
}
class EmployeeL3DetailsDto extends EmployeeL2DetailsDto
{
int yearsOfService;
PhoneNumber homePhoneNumber;
Address homeAddress;
BidDecimal salary;
}
等等...
在这里您可以看到我将员工信息划分为不同的详细级别。
附带的 DAO 看起来像这样:
class EmployeeDao
{
...
public List<EmployeeL1DetailsDto> getEmployeeL1Detail()
{
...
// uses a criteria-select query to retrieve only L1 columns
return list;
}
public List<EmployeeL2DetailsDto> getEmployeeL2Detail()
{
...
// uses a criteria-select query to retrieve only L1+L2 columns
return list;
}
public List<EmployeeL3DetailsDto> getEmployeeL3Detail()
{
...
// uses a criteria-select query to retrieve only L1+L2+L3 columns
return list;
}
.
.
.
// And so on
}
我一直在使用 hibernate 的 aliasToBean() 将检索到的实体自动映射到 DTO。尽管如此,我还是觉得整个过程中的样板数量(所有 DTO、DAO 方法、URL 所需详细程度的参数等)有点令人担忧,让我觉得可能会有是一种更清洁的方法。
所以,我的问题是:是否有更好的模式可以遵循以从持久实体中检索不同级别的详细信息?
我是 Spring 和 Hibernate 的新手,所以请随时指出任何您认为我不知道的被认为是基础知识的内容。
谢谢!
我会尽可能少地提出不同的查询。我宁愿在我的映射中使关联惰性化,然后使用适当的 Hibernate 提取策略让它们按需初始化。
我认为每个业务模型实体有多个不同的 DTO 类 并没有错,而且它们通常使代码更具可读性和可维护性。
但是,如果DTO的数量类有爆炸的趋势,那么我会在可读性(可维护性)和性能之间做一个平衡。
例如,如果 DTO 字段未在上下文中使用,我会将其保留为 null 或填充它,如果这真的不昂贵的话。然后,如果它为空,您可以指示您的对象编组器在生成 REST 服务响应(JSON、XML 等)时排除空字段,如果它真的困扰服务消费者的话。或者,如果您正在填写它,那么当您在应用程序中添加新功能并开始在上下文中使用它时,它总是受欢迎的。
您将不得不以一种或另一种方式定义不同的粒度版本。您可以尝试让不是 loaded/set 的子对象为 null(如其他答案中所建议的那样),但它很容易变得很尴尬,因为您将开始根据安全问题而不是域模型来构建数据。
所以用个人类来做毕竟不是一个糟糕的方法。
您可能希望它更具动态性(可能是因为您希望在数据库端使用更多数据扩展数据模型)。
如果是这种情况,您可能希望将定义从代码中移出到某些配置中(甚至可以在运行时是动态的)。这当然也需要 Java 端的动态数据模型,例如使用哈希图(请参阅 here 了解如何做到这一点)。您因此获得了一个动态数据模型,但失去了类型安全性(至少在一定程度上)。在其他语言中可能感觉很自然,但在 Java 中不太常见。
现在由您的 HQL 来定义您希望如何填充对象。
你想要采取的路径现在很大程度上取决于上下文,你的对象将如何被使用
另一种方法是仅在 Dao 级别使用域对象,并将所需的信息子集定义为每次使用的 DTO。然后使用通用 DTO 转换器将 Employee 实体转换为每个 DTO,就像我最近在我的专业 Spring 活动中使用的那样。 MIT 许可模块在 Maven 存储库工件 dtoconverter 中可用。
以及作者维基上的更多信息和用户指南:
http://ratamaa.fi/trac/dtoconverter
您从那里的示例页面获得的最快想法:
狩猎愉快...
Blaze-Persistence Entity Views 正是为这样的用例而创建的。您将 DTO 结构定义为接口或抽象 class 并映射到实体的属性。查询时,您只需传入 class,库将负责为投影生成优化查询。
这是一个简单的例子
@EntityView(Cat.class)
public interface CatView {
@IdMapping("id")
Integer getId();
String getName();
}
CatView 是 DTO 定义,这里是查询部分
CriteriaBuilder<Cat> cb = criteriaBuilderFactory.create(entityManager, Cat.class);
cb.from(Cat.class, "theCat")
.where("father").isNotNull()
.where("mother").isNotNull();
EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class);
List<CatView> list = entityViewManager
.applySetting(setting, cb)
.getResultList();
请注意,重要部分是 EntityViewSetting 具有应用于现有查询的 CatView 类型。生成的 JPQL/HQL 针对 CatView 进行了优化,即它只选择(并加入!)它真正需要的东西。
SELECT
theCat.id,
theCat.name
FROM
Cat theCat
WHERE theCat.father IS NOT NULL
AND theCat.mother IS NOT NULL
我正在进行一个项目,该项目使用最新的 Spring+Hibernate 来实现持久性和实现 REST API。 数据库中不同的 tables 包含大量记录,这些记录又非常大。因此,我创建了许多 DAO 来检索不同级别的详细信息及其附带的 DTO。
例如,如果我在数据库中有一些员工 table,其中包含关于每个员工的大量信息。如果我知道任何使用我的应用程序的客户都将从检索 Employee 实体的不同级别的详细信息中受益匪浅(而不是每次都被整个实体轰炸),那么到目前为止我所做的是这样的:
class EmployeeL1DetailsDto
{
String id;
String firstName;
String lastName;
}
class EmployeeL2DetailsDto extends EmployeeL1DetailsDto
{
Position position;
Department department;
PhoneNumber workPhoneNumber;
Address workAddress;
}
class EmployeeL3DetailsDto extends EmployeeL2DetailsDto
{
int yearsOfService;
PhoneNumber homePhoneNumber;
Address homeAddress;
BidDecimal salary;
}
等等...
在这里您可以看到我将员工信息划分为不同的详细级别。 附带的 DAO 看起来像这样:
class EmployeeDao
{
...
public List<EmployeeL1DetailsDto> getEmployeeL1Detail()
{
...
// uses a criteria-select query to retrieve only L1 columns
return list;
}
public List<EmployeeL2DetailsDto> getEmployeeL2Detail()
{
...
// uses a criteria-select query to retrieve only L1+L2 columns
return list;
}
public List<EmployeeL3DetailsDto> getEmployeeL3Detail()
{
...
// uses a criteria-select query to retrieve only L1+L2+L3 columns
return list;
}
.
.
.
// And so on
}
我一直在使用 hibernate 的 aliasToBean() 将检索到的实体自动映射到 DTO。尽管如此,我还是觉得整个过程中的样板数量(所有 DTO、DAO 方法、URL 所需详细程度的参数等)有点令人担忧,让我觉得可能会有是一种更清洁的方法。
所以,我的问题是:是否有更好的模式可以遵循以从持久实体中检索不同级别的详细信息? 我是 Spring 和 Hibernate 的新手,所以请随时指出任何您认为我不知道的被认为是基础知识的内容。
谢谢!
我会尽可能少地提出不同的查询。我宁愿在我的映射中使关联惰性化,然后使用适当的 Hibernate 提取策略让它们按需初始化。
我认为每个业务模型实体有多个不同的 DTO 类 并没有错,而且它们通常使代码更具可读性和可维护性。
但是,如果DTO的数量类有爆炸的趋势,那么我会在可读性(可维护性)和性能之间做一个平衡。
例如,如果 DTO 字段未在上下文中使用,我会将其保留为 null 或填充它,如果这真的不昂贵的话。然后,如果它为空,您可以指示您的对象编组器在生成 REST 服务响应(JSON、XML 等)时排除空字段,如果它真的困扰服务消费者的话。或者,如果您正在填写它,那么当您在应用程序中添加新功能并开始在上下文中使用它时,它总是受欢迎的。
您将不得不以一种或另一种方式定义不同的粒度版本。您可以尝试让不是 loaded/set 的子对象为 null(如其他答案中所建议的那样),但它很容易变得很尴尬,因为您将开始根据安全问题而不是域模型来构建数据。 所以用个人类来做毕竟不是一个糟糕的方法。
您可能希望它更具动态性(可能是因为您希望在数据库端使用更多数据扩展数据模型)。
如果是这种情况,您可能希望将定义从代码中移出到某些配置中(甚至可以在运行时是动态的)。这当然也需要 Java 端的动态数据模型,例如使用哈希图(请参阅 here 了解如何做到这一点)。您因此获得了一个动态数据模型,但失去了类型安全性(至少在一定程度上)。在其他语言中可能感觉很自然,但在 Java 中不太常见。
现在由您的 HQL 来定义您希望如何填充对象。 你想要采取的路径现在很大程度上取决于上下文,你的对象将如何被使用
另一种方法是仅在 Dao 级别使用域对象,并将所需的信息子集定义为每次使用的 DTO。然后使用通用 DTO 转换器将 Employee 实体转换为每个 DTO,就像我最近在我的专业 Spring 活动中使用的那样。 MIT 许可模块在 Maven 存储库工件 dtoconverter 中可用。 以及作者维基上的更多信息和用户指南:
http://ratamaa.fi/trac/dtoconverter
您从那里的示例页面获得的最快想法:
狩猎愉快...
Blaze-Persistence Entity Views 正是为这样的用例而创建的。您将 DTO 结构定义为接口或抽象 class 并映射到实体的属性。查询时,您只需传入 class,库将负责为投影生成优化查询。
这是一个简单的例子
@EntityView(Cat.class)
public interface CatView {
@IdMapping("id")
Integer getId();
String getName();
}
CatView 是 DTO 定义,这里是查询部分
CriteriaBuilder<Cat> cb = criteriaBuilderFactory.create(entityManager, Cat.class);
cb.from(Cat.class, "theCat")
.where("father").isNotNull()
.where("mother").isNotNull();
EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class);
List<CatView> list = entityViewManager
.applySetting(setting, cb)
.getResultList();
请注意,重要部分是 EntityViewSetting 具有应用于现有查询的 CatView 类型。生成的 JPQL/HQL 针对 CatView 进行了优化,即它只选择(并加入!)它真正需要的东西。
SELECT
theCat.id,
theCat.name
FROM
Cat theCat
WHERE theCat.father IS NOT NULL
AND theCat.mother IS NOT NULL