gpt4 book ai didi

java - Stream API 不适用于 EclipseLink/Glassfish 中的延迟加载集合?

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:09:23 27 4
gpt4 key购买 nike

在我的一个 Web 服务中检测到缺陷后,我将错误追踪到以下单行:

return this.getTemplate().getDomains().stream().anyMatch(domain -> domain.getName().equals(name));

当我确定域列表包含一个名称等于提供的 name 的域时,此行返回 false。因此,在摸索了一会儿之后,我最终拆分了整条线以查看发生了什么。我在调试 session 中得到以下信息:

Screenshot of debugging session

请注意以下行:

List<Domain> domains2 = domains.stream().collect(Collectors.toList());

根据调试器,domains 是一个包含两个元素的列表。但是在应用 .stream().collect(Collectors.toList()) 之后,我得到了一个完全空的列表。如果我错了,请纠正我,但据我了解,这应该是身份操作并返回相同的列表(如果我们严格的话,或者返回它的副本)。那么这里发生了什么???

在你问之前:不,我根本没有操纵那个屏幕截图。

为了说明这一点,这段代码在有状态请求范围内的 EJB 中执行,使用 JPA 管理的实体,在扩展的持久性上下文中进行字段访问。这里有一些与手头问题相关的代码部分:

@Stateful
@RequestScoped
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class DomainResources {
@PersistenceContext(type = PersistenceContextType.EXTENDED) @RequestScoped
private EntityManager entityManager;

public boolean templateContainsDomainWithName(String name) { // Extra code included to diagnose the problem
MetadataTemplate template = this.getTemplate();
List<Domain> domains = template.getDomains();
List<Domain> domains2 = domains.stream().collect(Collectors.toList());
List<String> names = domains.stream().map(Domain::getName).collect(Collectors.toList());
boolean exists1 = names.contains(name);
boolean exists2 = this.getTemplate().getDomains().stream().anyMatch(domain -> domain.getName().equals(name));
return this.getTemplate().getDomains().stream().anyMatch(domain -> domain.getName().equals(name));
}

@POST
@RolesAllowed({"root"})
public Response createDomain(@Valid @EmptyID DomainDTO domainDTO, @Context UriInfo uriInfo) {
if (this.getTemplate().getLastVersionState() != State.DRAFT) {
throw new UnmodifiableTemplateException();
} else if (templateContainsDomainWithName(domainDTO.name)) {
throw new DuplicatedKeyException("name", domainDTO.name);
} else {
Domain domain = this.getTemplate().createNewDomain(domainDTO.name);
this.entityManager.flush();
return Response.created(uriInfo.getAbsolutePathBuilder().path(domain.getId()).build()).entity(new DomainDTO(domain)).type(MediaType.APPLICATION_JSON).build();
}
}
}

@Entity
public class MetadataTemplate extends IdentifiedObject {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "metadataTemplate", orphanRemoval = true) @OrderBy(value = "creationDate")
private List<Version> versions = new LinkedList<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @OrderBy(value = "name")
private List<Domain> domains = new LinkedList<>();

public List<Version> getVersions() {
return Collections.unmodifiableList(versions);
}

public List<Domain> getDomains() {
return Collections.unmodifiableList(domains);
}
}

我包含了 getVersionsgetDomains 方法,因为我有类似的操作在版本上完美运行。我能找到的唯一显着区别是 versions 是急切获取的,而 domains 是延迟获取的。但据我所知,代码正在事务中执行,并且正在加载域列表。如果不是,我会得到一个惰性初始化异常,不是吗?

更新:根据@Ferrybig 的建议,我进一步调查了这个问题,似乎与不正确的延迟加载没有任何关系。如果我以经典方式遍历集合,我仍然无法使用流获得正确的结果:

boolean found = false;
for (Domain domain: this.getTemplate().getDomains()) {
if (domain.getName().equals(name)) {
found = true;
}
}

List<Domain> domains = this.getTemplate().getDomains();
long estimatedSize = domains.spliterator().estimateSize(); // This returns 0!
domains.spliterator().forEachRemaining(domain -> {
// Execution flow never reaches this point!
});

所以看起来即使集合已加载,您仍然会有这种奇怪的行为。这似乎是用于管理惰性集合的代理中缺少或空的拆分器实现。你怎么看?

顺便说一句,这是部署在 Glassfish/EclipseLink 上

最佳答案

这里的问题来自其他人在几个地方的错误的组合。所有这些错误的总和引发了这种错误行为。

第一个错误:可疑的继承。 EclipseLink 似乎创建了一个代理来管理 org.eclipse.persistence.indirection.IndirectList 类型的惰性集合.此类扩展 java.util.Vector尽管它覆盖了除 removeRange 之外的所有内容.亲爱的 Eclipse 开发人员,到底为什么要扩展一个类来覆盖父类中的几乎所有内容,而不是声明该类来实现合适的接口(interface)(Iterable<E>Collection<E>List<E>)?

第二个错误:嘿,我继承了你的东西,但我对你的内部结构不屑一顾。所以IndirectList使用 delegate 来实现延迟加载的魔力。但是,天哪!我如何计算大小?我是否使用(并保持更新) parent 的 elementCount属性(property)?不,当然,我只是将该任务委托(delegate)给我的委托(delegate)...所以如果父类需要做任何与大小相关的事情,那么,运气不好。无论如何,我已经覆盖了所有内容...他们不会向该类添加任何新内容,对吗?

第三个错误:封装破损。输入 Vector .在 Java 1.8 中,此类得到了扩充,现在提供了一个 spliterator。支持新流功能的方法。他们创建了一个静态内部类 (VectorSpliterator),允许客户端使用 Shiny 的新 API 遍历 vector 。一切正常,直到您注意到为了知道何时完成遍历,他们使用了 protected 实例变量 elementCount而不是使用公共(public) API 方法 size() .因为谁会扩展非最终类并返回不基于 elementCount 的大小?你看到灾难来临了吗?

我们到了,IndirectList无意中继承了 Vector 的新功能(请记住,它一开始可能不应该从它继承),并用这种错误组合破坏事物。

总而言之,当使用 EclipseLink(Glassfish 中的默认 JPA 提供程序)时,似乎惰性集合的流遍历即使对于已经加载的集合也不起作用。请记住,这些产品来自同一供应商。万岁!

解决方法:如果您遇到此问题但仍想利用 stream() 提供的函数式编程风格您可以制作集合的副本,以便构建适当的迭代器。在我的例子中,我能够将域的所有类似用途保留为修改 getDomains 的单行代码。方法。在这种情况下,我更喜欢代码的可读性(具有函数式风格)而不是性能:

public List<Domain> getDomains() {
return Collections.unmodifiableList(new ArrayList<>(domains));
}

读者须知:抱歉讽刺,但我不想在这些事情上浪费我宝贵的开发时间。

感谢@Ferrybig 提供的初步线索

更新:错误报告。如果这对您有影响,您可以在 https://bugs.eclipse.org/bugs/show_bug.cgi?id=487799 关注它的进展。

关于java - Stream API 不适用于 EclipseLink/Glassfish 中的延迟加载集合?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35362581/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com