如何将 @ConstructorResult 与 Set<SomeEnum> 字段一起使用
How to use a @ConstructorResult with a Set<SomeEnum> field
我正在尝试为 class 创建一个带有 @ConstructorResult
的 @NamedNativeQuery
,该 class 的字段具有 Set
的 enum
值。
VeterinarianJPA.java:
@Entity
@Table(name = "veterinarians")
@Setter
@Getter
@NoArgsConstructor
@NamedNativeQueries({
@NamedNativeQuery(
name = VeterinarianJPA.FIND_ALL_VETS,
query = "SELECT v.id, v.name, vs.specialisations " +
"FROM veterinarians v " +
"JOIN veterinarian_specialisations vs ON v.id = vs.vet_id",
resultSetMapping = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER
)})
@SqlResultSetMappings({
@SqlResultSetMapping(
name = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER,
classes = @ConstructorResult(
targetClass = Veterinarian.class,
columns = {
@ColumnResult(name = "id", type = Long.class),
@ColumnResult(name = "name"),
@ColumnResult(name = "specialisations", type = Set.class)
}
)
)})
class VeterinarianJPA {
static final String FIND_ALL_VETS = "net.kemitix.naolo.gateway.data.jpa.findAllVets";
static final String VETERINARIAN_RESULT_MAPPER = "net.kemitix.naolo.gateway.data.jpa.Veterinarian";
@Id
@GeneratedValue
private Long id;
private String name;
@ElementCollection
@Enumerated(EnumType.STRING)
@CollectionTable(
name = "veterinarian_specialisations",
joinColumns = @JoinColumn(name = "vet_id")
)
private final Set<VetSpecialisation> specialisations = new HashSet<>();
}
Veterinarian.java:
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final Set<VetSpecialisation> specialisations) {
this.id = id;
this.name = name;
this.specialisations = new HashSet<>(specialisations);
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public Set<VetSpecialisation> getSpecialisations() {
return new HashSet<>(specialisations);
}
}
VetSpecialisation.java:
public enum VetSpecialisation {
RADIOLOGY,
DENTISTRY,
SURGERY
}
当我尝试执行命名查询时:
entityManager.createNamedQuery(VeterinarianJPA.FIND_ALL_VETS, Veterinarian.class)
.getResultStream()
我得到以下异常:
java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : net.kemitix.naolo.entities.Veterinarian
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92)
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45)
at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494)
at org.hibernate.loader.Loader.processResultSet(Loader.java:2213)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2169)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1930)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1892)
at org.hibernate.loader.Loader.scroll(Loader.java:2765)
at org.hibernate.loader.custom.CustomLoader.scroll(CustomLoader.java:383)
at org.hibernate.internal.SessionImpl.scrollCustomQuery(SessionImpl.java:2198)
at org.hibernate.internal.AbstractSharedSessionContract.scroll(AbstractSharedSessionContract.java:1058)
at org.hibernate.query.internal.NativeQueryImpl.doScroll(NativeQueryImpl.java:217)
at org.hibernate.query.internal.AbstractProducedQuery.scroll(AbstractProducedQuery.java:1462)
at org.hibernate.query.internal.AbstractProducedQuery.stream(AbstractProducedQuery.java:1486)
at org.hibernate.query.Query.getResultStream(Query.java:1110)
我希望 SQL 为多值 Set
返回多行而不是单个值,这会导致构造函数不匹配。如何更改 SQL 以向构造函数生成正确的输入,或者是否需要进行其他配置更改?
好吧,我不确定这是否可以按照您想要的方式进行。但是您可以使用 specialisations
table 上的 LISTAGG
函数通过使用某种分隔符将 specialisations
与 veterinarians
内联。
所以查询应该是这样的:
SELECT v.id, v.name
(SELECT LISTAGG(vs.type, ';')
WITHIN GROUP (ORDER BY vs.type)
FROM veterinarian_specialisations vs
WHERE vs.vet_id = v.id) specialisations
FROM veterinarians v;
查询将return veterinarian 和他的分号分隔专业:
1 NAME DENTISTRY;RADIOLOGY
然后在您的 Veterinarian
class 构造函数中,您必须将字符串结果重新映射回 VetSpecialisation
的集合。我使用 Java 8 流 api 只是为了方便。
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final String specialisations) {
this.id = id;
this.name = name;
this.specialisations = Arrays.asList(specialisations.split(";"))
.stream()
.map(VetSpecialisation::valueOf) //Map string to VetSpecialisation enum.
.collect(Collectors.toSet());
}
我正在尝试为 class 创建一个带有 @ConstructorResult
的 @NamedNativeQuery
,该 class 的字段具有 Set
的 enum
值。
VeterinarianJPA.java:
@Entity
@Table(name = "veterinarians")
@Setter
@Getter
@NoArgsConstructor
@NamedNativeQueries({
@NamedNativeQuery(
name = VeterinarianJPA.FIND_ALL_VETS,
query = "SELECT v.id, v.name, vs.specialisations " +
"FROM veterinarians v " +
"JOIN veterinarian_specialisations vs ON v.id = vs.vet_id",
resultSetMapping = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER
)})
@SqlResultSetMappings({
@SqlResultSetMapping(
name = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER,
classes = @ConstructorResult(
targetClass = Veterinarian.class,
columns = {
@ColumnResult(name = "id", type = Long.class),
@ColumnResult(name = "name"),
@ColumnResult(name = "specialisations", type = Set.class)
}
)
)})
class VeterinarianJPA {
static final String FIND_ALL_VETS = "net.kemitix.naolo.gateway.data.jpa.findAllVets";
static final String VETERINARIAN_RESULT_MAPPER = "net.kemitix.naolo.gateway.data.jpa.Veterinarian";
@Id
@GeneratedValue
private Long id;
private String name;
@ElementCollection
@Enumerated(EnumType.STRING)
@CollectionTable(
name = "veterinarian_specialisations",
joinColumns = @JoinColumn(name = "vet_id")
)
private final Set<VetSpecialisation> specialisations = new HashSet<>();
}
Veterinarian.java:
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final Set<VetSpecialisation> specialisations) {
this.id = id;
this.name = name;
this.specialisations = new HashSet<>(specialisations);
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public Set<VetSpecialisation> getSpecialisations() {
return new HashSet<>(specialisations);
}
}
VetSpecialisation.java:
public enum VetSpecialisation {
RADIOLOGY,
DENTISTRY,
SURGERY
}
当我尝试执行命名查询时:
entityManager.createNamedQuery(VeterinarianJPA.FIND_ALL_VETS, Veterinarian.class)
.getResultStream()
我得到以下异常:
java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : net.kemitix.naolo.entities.Veterinarian
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92)
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45)
at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494)
at org.hibernate.loader.Loader.processResultSet(Loader.java:2213)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2169)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1930)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1892)
at org.hibernate.loader.Loader.scroll(Loader.java:2765)
at org.hibernate.loader.custom.CustomLoader.scroll(CustomLoader.java:383)
at org.hibernate.internal.SessionImpl.scrollCustomQuery(SessionImpl.java:2198)
at org.hibernate.internal.AbstractSharedSessionContract.scroll(AbstractSharedSessionContract.java:1058)
at org.hibernate.query.internal.NativeQueryImpl.doScroll(NativeQueryImpl.java:217)
at org.hibernate.query.internal.AbstractProducedQuery.scroll(AbstractProducedQuery.java:1462)
at org.hibernate.query.internal.AbstractProducedQuery.stream(AbstractProducedQuery.java:1486)
at org.hibernate.query.Query.getResultStream(Query.java:1110)
我希望 SQL 为多值 Set
返回多行而不是单个值,这会导致构造函数不匹配。如何更改 SQL 以向构造函数生成正确的输入,或者是否需要进行其他配置更改?
好吧,我不确定这是否可以按照您想要的方式进行。但是您可以使用 specialisations
table 上的 LISTAGG
函数通过使用某种分隔符将 specialisations
与 veterinarians
内联。
所以查询应该是这样的:
SELECT v.id, v.name
(SELECT LISTAGG(vs.type, ';')
WITHIN GROUP (ORDER BY vs.type)
FROM veterinarian_specialisations vs
WHERE vs.vet_id = v.id) specialisations
FROM veterinarians v;
查询将return veterinarian 和他的分号分隔专业:
1 NAME DENTISTRY;RADIOLOGY
然后在您的 Veterinarian
class 构造函数中,您必须将字符串结果重新映射回 VetSpecialisation
的集合。我使用 Java 8 流 api 只是为了方便。
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final String specialisations) {
this.id = id;
this.name = name;
this.specialisations = Arrays.asList(specialisations.split(";"))
.stream()
.map(VetSpecialisation::valueOf) //Map string to VetSpecialisation enum.
.collect(Collectors.toSet());
}