具有连接实体属性的 Micronaut 数据 DTO 投影
Micronaut Data DTO projection with properties from joined entities
我将 Micronaut 数据与 JPA 结合使用,并且有两个实体。第一个是 Recipe
:
@Entity
public class Recipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToOne
private Category category;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Step> steps;
// + other fields, getters and setters
}
第二个是ParseError
,指的是Recipe
:
@Entity
@Table(name = "parse_error")
public class ParseError implements Serializable {
@Id
@ManyToOne(fetch = FetchType.LAZY)
private Recipe recipe;
@Id
@Enumerated(EnumType.ORDINAL)
@Column(name = "problem_area")
private ProblemArea problemArea;
private String message;
// + other fields, getters and setters
}
现在我想为 API 中的 DTO 提供 ParseError
属性,但不提供整个 Recipe
实体,因为它包含在这种情况下不需要的 ManyToOne 和 OneToMany 关系。所以我为此创建了投影 DTO:
@Introspected
public class ParseErrorDto {
private Integer recipeId;
private String recipeName;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
并在 ParseErrorRepository
中添加了 listAll()
方法:
@Repository
public interface ParseErrorRepository extends CrudRepository<ParseError, Integer> {
List<ParseErrorDto> listAll();
}
但 Micronaut Data 似乎无法从嵌套实体投影属性,或者我在 DTO 或存储库方法中遗漏了一些东西:
ParseErrorRepository.java:22: error: Unable to implement Repository
method: ParseErrorRepository.listAll(). Property recipeId is not
present in entity: ParseError
我也试过创建RecipeDto
:
@Introspected
public class RecipeDto {
private Integer id;
private String name;
// + getters and setters
}
并相应地更新了 ParseErrorDto
:
@Introspected
public class ParseErrorDto {
private RecipeDto recipe;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
还是没有成功:
ParseErrorRepository.java:22: error: Unable to implement Repository
method: ParseErrorRepository.listAll(). Property [recipe] of type
[RecipeDto] is not compatible with equivalent property declared in
entity: ParseError
Micronaut Data 是否能够通过 DTO 投影处理此用例?如果没有,那么还有其他方法可以在 Micronaut Data 中解决它吗?
现在(最新版本1.0.0.M1)是不可能的。所以我为此创建了功能请求问题:https://github.com/micronaut-projects/micronaut-data/issues/184
当前的解决方法是将实体 bean 映射到 Java 流或反应流中的 DTO bean,然后手动或通过 Mapstruct 进行属性映射。
更新:这是对评论中问题的回答,并举例说明如何使用 Mapstruct 解决问题:
将Mapstruct依赖添加到build.gradle:
implementation "org.mapstruct:mapstruct:$mapstructVersion"
annotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
定义映射器:
import org.mapstruct.Mapper;
@Mapper(
componentModel = "jsr330"
)
public interface ParseErrorMapper {
ParseErrorDto entityToDto(@NotNull ParseError parseError);
EntityReference recipeToDto(@NotNull Recipe recipe);
}
下面是该映射器在控制器中的用法:
@Controller("/parse-error")
public class ParseErrorController {
private final ParseErrorRepository repository;
private final ParseErrorMapper mapper;
public ParseErrorController(ParseErrorRepository repository, ParseErrorMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}
@Get("all")
@Transactional
public Page<ParseErrorDto> getAll(final Pageable pageable) {
return repository.findAll(pageable).map(mapper::entityToDto);
}
}
我将 Micronaut 数据与 JPA 结合使用,并且有两个实体。第一个是 Recipe
:
@Entity
public class Recipe {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToOne
private Category category;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Step> steps;
// + other fields, getters and setters
}
第二个是ParseError
,指的是Recipe
:
@Entity
@Table(name = "parse_error")
public class ParseError implements Serializable {
@Id
@ManyToOne(fetch = FetchType.LAZY)
private Recipe recipe;
@Id
@Enumerated(EnumType.ORDINAL)
@Column(name = "problem_area")
private ProblemArea problemArea;
private String message;
// + other fields, getters and setters
}
现在我想为 API 中的 DTO 提供 ParseError
属性,但不提供整个 Recipe
实体,因为它包含在这种情况下不需要的 ManyToOne 和 OneToMany 关系。所以我为此创建了投影 DTO:
@Introspected
public class ParseErrorDto {
private Integer recipeId;
private String recipeName;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
并在 ParseErrorRepository
中添加了 listAll()
方法:
@Repository
public interface ParseErrorRepository extends CrudRepository<ParseError, Integer> {
List<ParseErrorDto> listAll();
}
但 Micronaut Data 似乎无法从嵌套实体投影属性,或者我在 DTO 或存储库方法中遗漏了一些东西:
ParseErrorRepository.java:22: error: Unable to implement Repository method: ParseErrorRepository.listAll(). Property recipeId is not present in entity: ParseError
我也试过创建RecipeDto
:
@Introspected
public class RecipeDto {
private Integer id;
private String name;
// + getters and setters
}
并相应地更新了 ParseErrorDto
:
@Introspected
public class ParseErrorDto {
private RecipeDto recipe;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
还是没有成功:
ParseErrorRepository.java:22: error: Unable to implement Repository method: ParseErrorRepository.listAll(). Property [recipe] of type [RecipeDto] is not compatible with equivalent property declared in entity: ParseError
Micronaut Data 是否能够通过 DTO 投影处理此用例?如果没有,那么还有其他方法可以在 Micronaut Data 中解决它吗?
现在(最新版本1.0.0.M1)是不可能的。所以我为此创建了功能请求问题:https://github.com/micronaut-projects/micronaut-data/issues/184
当前的解决方法是将实体 bean 映射到 Java 流或反应流中的 DTO bean,然后手动或通过 Mapstruct 进行属性映射。
更新:这是对评论中问题的回答,并举例说明如何使用 Mapstruct 解决问题:
将Mapstruct依赖添加到build.gradle:
implementation "org.mapstruct:mapstruct:$mapstructVersion"
annotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
定义映射器:
import org.mapstruct.Mapper;
@Mapper(
componentModel = "jsr330"
)
public interface ParseErrorMapper {
ParseErrorDto entityToDto(@NotNull ParseError parseError);
EntityReference recipeToDto(@NotNull Recipe recipe);
}
下面是该映射器在控制器中的用法:
@Controller("/parse-error")
public class ParseErrorController {
private final ParseErrorRepository repository;
private final ParseErrorMapper mapper;
public ParseErrorController(ParseErrorRepository repository, ParseErrorMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}
@Get("all")
@Transactional
public Page<ParseErrorDto> getAll(final Pageable pageable) {
return repository.findAll(pageable).map(mapper::entityToDto);
}
}