Spring JPA无法搜索嵌套对象,尽管设置了级联属性。

huangapple go评论95阅读模式
英文:

Spring JPA can't search for nested object despite cascade attribute set

问题

  1. 所以我有一个名为Product的对象,拥有私有的String名称和私有的Origin origin 代码如下:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public abstract class Product {

  1. @Id
  2. @GeneratedValue(strategy = GenerationType.AUTO)
  3. private long id;
  4. @NonNull
  5. private String name;
  6. @NonNull
  7. @OneToOne(cascade = CascadeType.ALL)
  8. private Origin origin;

}

  1. 现在我正在使用数据加载器在启动时将一些产品添加到数据库中,如下所示:
  2. (Wine扩展了Products并添加了一些更多的字符串)

repository.save(new Wine(
"Test wine",
new Origin(
"Crete",
"Greece"
)
));

  1. 此外,我有一个搜索终端,我可以搜索名称或Origin

public Page searchProducts(
@RequestParam(name = "text", required = false) String searchTerm,
@RequestParam(required = false) Origin origin) {
return wineService.searchWines(
searchTerm,
origin
);
}

  1. wine Service中,我使用规格创建查询。将它们都放在这里会过度冗长,所以只看看这个:

public static Specification hasOrigin(Origin origin) {
return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get(Wine_.ORIGIN), origin);
}

  1. 奇怪的是,它似乎在一段时间前能够工作,但现在不行了。现在,如果我搜索具有特定起源的葡萄酒,它只会说:
  2. > org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: [censored].backend.model.Origin
  3. 我该如何修复这个问题?我已经看到了大约5篇关于这个问题的不同帖子,所有这些帖子都说:“好吧,只需将cascade ALL添加到对象中”,但我从一开始就已经这样做了。
  4. **编辑:**
  5. Origin实体代码:

@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Table(name = "regions")
public class Origin {

  1. @Id
  2. @GeneratedValue(strategy = GenerationType.AUTO)
  3. @JsonIgnore
  4. private long id;
  5. @NonNull
  6. private String region;
  7. @NonNull
  8. private String country;

}

英文:

so I have an object Product with, let's say, private String name and private Origin origin.
Code is as follows:

  1. @Entity
  2. @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  3. @Data
  4. @NoArgsConstructor
  5. @RequiredArgsConstructor
  6. @AllArgsConstructor
  7. public abstract class Product {
  8. @Id
  9. @GeneratedValue(strategy = GenerationType.AUTO)
  10. private long id;
  11. @NonNull
  12. private String name;
  13. @NonNull
  14. @OneToOne(cascade = CascadeType.ALL)
  15. private Origin origin;
  16. }

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)

  1. repository.save(new Wine(
  2. "Test wine"
  3. new Origin(
  4. "Crete",
  5. "Greece"
  6. )
  7. ));

Additionally, I have a search endpoint where I can search for either the name or the Origin.

  1. public Page<Wine> searchProducts(
  2. @RequestParam(name = "text", required = false) String searchTerm,
  3. @RequestParam(required = false) Origin origin) {
  4. return wineService.searchWines(
  5. searchTerm,
  6. origin
  7. );
  8. }

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:

  1. public static Specification<Wine> hasOrigin(Origin origin) {
  2. return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get(Wine_.ORIGIN), origin);
  3. }

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:

  1. @Entity
  2. @Data
  3. @NoArgsConstructor
  4. @RequiredArgsConstructor
  5. @AllArgsConstructor
  6. @Table(name = "regions")
  7. public class Origin {
  8. @Id
  9. @GeneratedValue(strategy = GenerationType.AUTO)
  10. @JsonIgnore
  11. private long id;
  12. @NonNull
  13. private String region;
  14. @NonNull
  15. private String country;
  16. }

答案1

得分: 1

首先与 Origin 进行连接,然后创建两个 Predicate 并将它们添加到构建器中,然后返回。

  1. public static Specification<Wine> hasOrigin(Origin origin) {
  2. return new Specification<Wine>() {
  3. @Override
  4. public Predicate toPredicate(Root<Wine> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
  5. final List<Predicate> predicates = new ArrayList<>();
  6. Join<Wine, Origin> originRoot = root.join("origin");
  7. predicates.add(criteriaBuilder.equal(originRoot.get("country"), origin.getCountry()));
  8. predicates.add(criteriaBuilder.equal(originRoot.get("region"), origin.getRegion()));
  9. return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
  10. }
  11. };
  12. }

请注意,我已经移除了HTML编码以及&lt;&gt;符号,以便更清晰地呈现代码。

英文:

First join with Origin then create two Predicate and add them in builder and return.

  1. public static Specification&lt;Wine&gt; hasOrigin(Origin origin) {
  2. return new Specification&lt;Wine&gt;() {
  3. @Override
  4. public Predicate toPredicate(Root&lt;Wine&gt; root, CriteriaQuery&lt;?&gt; criteriaQuery, CriteriaBuilder criteriaBuilder) {
  5. final List&lt;Predicate&gt; predicates = new ArrayList&lt;&gt;();
  6. Join&lt;Wine, Origin&gt; originRoot = root.join(&quot;origin&quot;);
  7. predicates.add(criteriaBuilder.equal(originRoot.get(&quot;country&quot;), origin.getCountry()));
  8. predicates.add(criteriaBuilder.equal(originRoot.get(&quot;region&quot;), origin.getRegion()));
  9. return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
  10. }
  11. };
  12. }

huangapple
  • 本文由 发表于 2020年8月9日 21:58:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/63327192.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定