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

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

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 searchProducts(
@RequestParam(name = "text", required = false) String searchTerm,
@RequestParam(required = false) Origin origin) {
return wineService.searchWines(
searchTerm,
origin
);
}


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

public static Specification hasOrigin(Origin origin) {
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编码以及&lt;&gt;符号,以便更清晰地呈现代码。

英文:

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

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

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:

确定