英文:
Spring JPA can't search for nested object despite cascade attribute set
问题
所以我有一个名为Product的对象,拥有私有的String名称和私有的Origin origin。 代码如下:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NonNull
private String name;
@NonNull
@OneToOne(cascade = CascadeType.ALL)
private Origin origin;
}
现在我正在使用数据加载器在启动时将一些产品添加到数据库中,如下所示:
(Wine扩展了Products并添加了一些更多的字符串)
repository.save(new Wine(
"Test wine",
new Origin(
"Crete",
"Greece"
)
));
此外,我有一个搜索终端,我可以搜索名称或Origin。
public Page
@RequestParam(name = "text", required = false) String searchTerm,
@RequestParam(required = false) Origin origin) {
return wineService.searchWines(
searchTerm,
origin
);
}
在wine Service中,我使用规格创建查询。将它们都放在这里会过度冗长,所以只看看这个:
public static Specification
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get(Wine_.ORIGIN), origin);
}
奇怪的是,它似乎在一段时间前能够工作,但现在不行了。现在,如果我搜索具有特定起源的葡萄酒,它只会说:
> org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: [censored].backend.model.Origin
我该如何修复这个问题?我已经看到了大约5篇关于这个问题的不同帖子,所有这些帖子都说:“好吧,只需将cascade ALL添加到对象中”,但我从一开始就已经这样做了。
**编辑:**
Origin实体代码:
@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Table(name = "regions")
public class Origin {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonIgnore
private long id;
@NonNull
private String region;
@NonNull
private String country;
}
英文:
so I have an object Product with, let's say, private String name and private Origin origin.
Code is as follows:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NonNull
private String name;
@NonNull
@OneToOne(cascade = CascadeType.ALL)
private Origin origin;
}
Now I'm using a data loader to add some products into the database on startup like this:
(Wine extends Products and adds a few more Strings)
repository.save(new Wine(
"Test wine"
new Origin(
"Crete",
"Greece"
)
));
Additionally, I have a search endpoint where I can search for either the name or the Origin.
public Page<Wine> searchProducts(
@RequestParam(name = "text", required = false) String searchTerm,
@RequestParam(required = false) Origin origin) {
return wineService.searchWines(
searchTerm,
origin
);
}
and in the wine Service, I create a query using specifications. Putting them all in here would be overkill so just take a look at this:
public static Specification<Wine> hasOrigin(Origin origin) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get(Wine_.ORIGIN), origin);
}
Strangely enough, it seemed to work some time ago but now it doesn't. Now, if I search for a Wine with a certain origin, it just says:
> org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: [censored].backend.model.Origin
How do I fix this? I've seen like 5 different posts regarding this problem and ALL of them said "well, just add cascade ALL to the Object" but I've already done that from the beginning.
Edit:
Origin Entity code:
@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Table(name = "regions")
public class Origin {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@JsonIgnore
private long id;
@NonNull
private String region;
@NonNull
private String country;
}
答案1
得分: 1
首先与 Origin
进行连接,然后创建两个 Predicate
并将它们添加到构建器中,然后返回。
public static Specification<Wine> hasOrigin(Origin origin) {
return new Specification<Wine>() {
@Override
public Predicate toPredicate(Root<Wine> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
final List<Predicate> predicates = new ArrayList<>();
Join<Wine, Origin> originRoot = root.join("origin");
predicates.add(criteriaBuilder.equal(originRoot.get("country"), origin.getCountry()));
predicates.add(criteriaBuilder.equal(originRoot.get("region"), origin.getRegion()));
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
请注意,我已经移除了HTML编码以及<
和>
符号,以便更清晰地呈现代码。
英文:
First join with Origin
then create two Predicate
and add them in builder and return.
public static Specification<Wine> hasOrigin(Origin origin) {
return new Specification<Wine>() {
@Override
public Predicate toPredicate(Root<Wine> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
final List<Predicate> predicates = new ArrayList<>();
Join<Wine, Origin> originRoot = root.join("origin");
predicates.add(criteriaBuilder.equal(originRoot.get("country"), origin.getCountry()));
predicates.add(criteriaBuilder.equal(originRoot.get("region"), origin.getRegion()));
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论