Java8 Collections.sort(有时)不会对 JPA 返回的列表进行排序

2023-12-05

Java8 在我的 JPA EclipseLink 2.5.2 环境中不断执行奇怪的操作。我不得不删除这个问题https://stackoverflow.com/questions/26806183/java-8-sorting-behaviour昨天,因为这种情况下的排序受到奇怪的 JPA 行为的影响 - 我找到了一种解决方法,方法是在进行最终排序之前强制执行第一个排序步骤。

仍然在使用 JPA Eclipselink 2.5.2 的 Java 8 中,以下代码有时在我的环境中无法排序(Linux、MacOSX,均使用构建版本 1.8.0_25-b17)。它在 JDK 1.7 环境中按预期工作。

public List<Document> getDocumentsByModificationDate() {
    List<Document> docs=this.getDocuments();
    LOGGER.log(Level.INFO,"sorting "+docs.size()+" by modification date");
    Comparator<Document> comparator=new ByModificationComparator();
    Collections.sort(docs,comparator);
    return docs;
}

当从 JUnit 测试调用时,上述函数可以正常工作。 在生产环境中调试时,我收到一条日志条目:

INFORMATION: sorting 34 by modification date

但在 TimSort 中,nRemaining jpa返回哪些集合?) 由 JPA 提供的被认为是空的。

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                     T[] work, int workBase, int workLen) {
    assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

    int nRemaining  = hi - lo;
    if (nRemaining < 2)
        return;  // Arrays of size 0 and 1 are always sorted

此解决方法可以正确排序:

   if (docs instanceof IndirectList) {
        IndirectList iList = (IndirectList)docs;
        Object sortTargetObject = iList.getDelegateObject();
        if (sortTargetObject instanceof List<?>) {
            List<Document> sortTarget=(List<Document>) sortTargetObject;
            Collections.sort(sortTarget,comparator);
        }
    } else {
        Collections.sort(docs,comparator);
    }

问题:

这是 JPA Eclipselink 错误吗?或者我通常可以在自己的代码中对此做些什么?

请注意 - 我还无法将软件更改为符合 Java8 源代码。当前环境是Java8运行时。

我对这种行为感到惊讶 - 尤其令人烦恼的是,测试用例运行正确,而在生产环境中却出现问题。

有一个示例项目位于https://github.com/WolfgangFahl/JPAJava8Sorting它具有与原始问题类似的结构。

它包含一个http://sscce.org/JUnit 测试的示例通过调用 em.clear() 来重现问题,从而分离所有对象并强制使用 IndirectList。请参阅下面的 JUnit 案例以供参考。

急切地获取:

// https://stackoverflow.com/questions/8301820/onetomany-relationship-is-not-working
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parentFolder", fetch=FetchType.EAGER)

Unit 案例有效。如果在 JDK 8 中使用 FetchType.LAZY 或省略获取类型,则行为可能与 JDK 7 中不同(我现在必须检查这一点)。为什么会这样?此时,我假设需要指定“急切获取”或在要排序的列表上迭代一次,基本上是在排序之前手动获取。还能做什么?

JUnit测试

persistence.xml 和 pom.xml 可以取自https://github.com/WolfgangFahl/JPAJava8Sorting测试可以使用 MYSQL 数据库运行,也可以使用 DERBY 在内存中运行(默认)

package com.bitplan.java8sorting;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.Table;

import org.eclipse.persistence.indirection.IndirectList;
import org.junit.Test;

/**
 * Testcase for 
 * https://stackoverflow.com/questions/26816650/java8-collections-sort-sometimes-does-not-sort-jpa-returned-lists
 * @author wf
 *
 */
public class TestJPASorting {

  // the number of documents we want to sort
  public static final int NUM_DOCUMENTS = 3;

  // Logger for debug outputs
  protected static Logger LOGGER = Logger.getLogger("com.bitplan.java8sorting");

  /**
   * a classic comparator
   * @author wf
   *
   */
  public static class ByNameComparator implements Comparator<Document> {

    // @Override
    public int compare(Document d1, Document d2) {
      LOGGER.log(Level.INFO,"comparing " + d1.getName() + "<=>" + d2.getName());
      return d1.getName().compareTo(d2.getName());
    }
  }

  // Document Entity - the sort target
  @Entity(name = "Document")
  @Table(name = "document")
  @Access(AccessType.FIELD)
  public static class Document {
    @Id
    String name;

    @ManyToOne
    Folder parentFolder;

    /**
     * @return the name
     */
    public String getName() {
      return name;
    }
    /**
     * @param name the name to set
     */
    public void setName(String name) {
      this.name = name;
    }
    /**
     * @return the parentFolder
     */
    public Folder getParentFolder() {
      return parentFolder;
    }
    /**
     * @param parentFolder the parentFolder to set
     */
    public void setParentFolder(Folder parentFolder) {
      this.parentFolder = parentFolder;
    }
  }

  // Folder entity - owning entity for documents to be sorted
  @Entity(name = "Folder")
  @Table(name = "folder")
  @Access(AccessType.FIELD)
  public static class Folder {
    @Id
    String name;

    // https://stackoverflow.com/questions/8301820/onetomany-relationship-is-not-working
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "parentFolder", fetch=FetchType.EAGER)
    List<Document> documents;

    /**
     * @return the name
     */
    public String getName() {
      return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
      this.name = name;
    }

    /**
     * @return the documents
     */
    public List<Document> getDocuments() {
      return documents;
    }

    /**
     * @param documents the documents to set
     */
    public void setDocuments(List<Document> documents) {
      this.documents = documents;
    }

    /**
     * get the documents of this folder by name
     * 
     * @return a sorted list of documents
     */
    public List<Document> getDocumentsByName() {
      List<Document> docs = this.getDocuments();
      LOGGER.log(Level.INFO, "sorting " + docs.size() + " documents by name");
      if (docs instanceof IndirectList) {
        LOGGER.log(Level.INFO, "The document list is an IndirectList");
      }
      Comparator<Document> comparator = new ByNameComparator();
      // here is the culprit - do or don't we sort correctly here?
      Collections.sort(docs, comparator);
      return docs;
    }

    /**
     * get a folder example (for testing)
     * @return - a test folder with NUM_DOCUMENTS documents
     */
    public static Folder getFolderExample() {
      Folder folder = new Folder();
      folder.setName("testFolder");
      folder.setDocuments(new ArrayList<Document>());
      for (int i=NUM_DOCUMENTS;i>0;i--) {
        Document document=new Document();
        document.setName("test"+i);
        document.setParentFolder(folder);
        folder.getDocuments().add(document);
      }
      return folder;
    }
  }

  /** possible Database configurations
  using generic persistence.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- generic persistence.xml which only specifies a persistence unit name -->
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
      version="2.0">
      <persistence-unit name="com.bitplan.java8sorting" transaction-type="RESOURCE_LOCAL">
        <description>sorting test</description>
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <exclude-unlisted-classes>false</exclude-unlisted-classes> 
        <properties>
        <!--  set programmatically -->
         </properties>
      </persistence-unit>
    </persistence>
  */
  // in MEMORY database
  public static final JPASettings JPA_DERBY=new JPASettings("Derby","org.apache.derby.jdbc.EmbeddedDriver","jdbc:derby:memory:test-jpa;create=true","APP","APP");
  // MYSQL Database
  //  needs preparation:
  //    create database testsqlstorage;
  //    grant all privileges on testsqlstorage to cm@localhost identified by 'secret';
  public static final JPASettings JPA_MYSQL=new JPASettings("MYSQL","com.mysql.jdbc.Driver","jdbc:mysql://localhost:3306/testsqlstorage","cm","secret");

  /**
   * Wrapper class for JPASettings
   * @author wf
   *
   */
  public static class JPASettings {
    String driver;
    String url;
    String user;
    String password;
    String targetDatabase;

    EntityManager entityManager;
    /**
     * @param driver
     * @param url
     * @param user
     * @param password
     * @param targetDatabase
     */
    public JPASettings(String targetDatabase,String driver, String url, String user, String password) {
      this.driver = driver;
      this.url = url;
      this.user = user;
      this.password = password;
      this.targetDatabase = targetDatabase;
    }

    /**
     * get an entitymanager based on my settings
     * @return the EntityManager
     */
    public EntityManager getEntityManager() {
      if (entityManager == null) {
        Map<String, String> jpaProperties = new HashMap<String, String>();
        jpaProperties.put("eclipselink.ddl-generation.output-mode", "both");
        jpaProperties.put("eclipselink.ddl-generation", "drop-and-create-tables");
        jpaProperties.put("eclipselink.target-database", targetDatabase);
        jpaProperties.put("eclipselink.logging.level", "FINE");

        jpaProperties.put("javax.persistence.jdbc.user", user);
        jpaProperties.put("javax.persistence.jdbc.password", password);
        jpaProperties.put("javax.persistence.jdbc.url",url);
        jpaProperties.put("javax.persistence.jdbc.driver",driver);

        EntityManagerFactory emf = Persistence.createEntityManagerFactory(
            "com.bitplan.java8sorting", jpaProperties);
        entityManager = emf.createEntityManager();
      }
      return entityManager;
    }
  }

  /**
   * persist the given Folder with the given entityManager
   * @param em - the entityManager
   * @param folderJpa - the folder to persist
   */
  public void persist(EntityManager em, Folder folder) {
    em.getTransaction().begin();
    em.persist(folder);
    em.getTransaction().commit();    
  }

  /**
   * check the sorting - assert that the list has the correct size NUM_DOCUMENTS and that documents
   * are sorted by name assuming test# to be the name of the documents
   * @param sortedDocuments - the documents which should be sorted by name
   */
  public void checkSorting(List<Document> sortedDocuments) {
    assertEquals(NUM_DOCUMENTS,sortedDocuments.size());
    for (int i=1;i<=NUM_DOCUMENTS;i++) {
      Document document=sortedDocuments.get(i-1);
      assertEquals("test"+i,document.getName());
    }
  }

  /**
   * this test case shows that the list of documents retrieved will not be sorted if 
   * JDK8 and lazy fetching is used
   */
  @Test
  public void testSorting() {
    // get a folder with a few documents
    Folder folder=Folder.getFolderExample();
    // get an entitymanager JPA_DERBY=inMemory JPA_MYSQL=Mysql disk database
    EntityManager em=JPA_DERBY.getEntityManager();
    // persist the folder
    persist(em,folder);
    // sort list directly created from memory
    checkSorting(folder.getDocumentsByName());

    // detach entities;
    em.clear();
    // get all folders from database
    String sql="select f from Folder f";
    Query query = em.createQuery(sql);
    @SuppressWarnings("unchecked")
    List<Folder> folders = query.getResultList();
    // there should be exactly one
    assertEquals(1,folders.size());
    // get the first folder
    Folder folderJPA=folders.get(0);
    // sort the documents retrieved
    checkSorting(folderJPA.getDocumentsByName());
  }
}

嗯,这是一个完美的说教游戏,告诉你为什么程序员不应该扩展不是为子类化而设计的类。像《Effective Java》这样的书会告诉你原因:当超类演变时,拦截每个方法以改变其行为的尝试将会失败。

Here, IndirectList延伸Vector并重写几乎所有方法来修改其行为,这是一个明显的反模式。现在,随着 Java 8 的发展,基类已经发生了变化。

从 Java 8 开始,接口可以具有default方法等等方法sort添加的优点是,与Collections.sort,实现可以重写该方法并提供更适合特定的实现interface执行。Vector这样做有两个原因:现在所有方法都是合同synchronized也扩展到排序,优化的实现可以将其内部数组传递给Arrays.sort方法跳过先前实现中已知的复制操作(ArrayList做同样的事)。

即使对于现有代码,为了立即获得此好处,Collections.sort已改装。它委托给List.sort默认情况下,它将委托给另一个方法来实现旧的复制行为toArray并使用TimSort。但如果一个List实施覆盖List.sort它会影响到的行为Collections.sort以及。

                  interface method              using internal
                  List.sort                     array w/o copying
Collections.sort ─────────────────> Vector.sort ─────────────────> Arrays.sort
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java8 Collections.sort(有时)不会对 JPA 返回的列表进行排序 的相关文章

随机推荐

  • Composer 需要本地包

    我有几个正在协同开发的库 Foo 和 Bar 但在技术上仍然是独立的 以前我刚刚重新定义了自动加载器 Foo Foo src 但现在我已经向 Foo 添加了 Guzzle 依赖项 Bar 翻转了它的盖子 因为它不是它的依赖项之一 目录结构
  • Python中递归子集和

    我很乐意得到一些帮助 我有以下问题 我得到了一个数字列表seq和一个目标数字 我需要写两件事 返回的递归解决方案True如果存在等于目标数的子序列之和并且False否则 例子 subset sum 1 1 5 4 0 True subset
  • 正则表达式匹配不带连续空格的用户名

    我正在努力制作一个 javascript 正则表达式来满足以下要求 第一个字符必须是字母 a zA Z 其余的可以是任何字母 任何数字 连字符 点 下划线和空格 但没有连续的空格 例如 连续两个或多个空格 长度必须在 3 到 25 之间 含
  • 在Qt中fork后获取进程的PID

    我正在创建一个成功分叉的 Qt C 控制台应用程序 当我在 fork 之前调用 QCoreApplication applicationPid 然后在 fork 之后 在子进程中 调用 QCoreApplication applicatio
  • 如何正确锁定 Task.Run() 块

    我正在编写一个应用程序 其中使用多种方法来访问某些共享资源 因此通过以下方式实现了一些安全性lock thisLock 一切都很好 直到我不得不在异步任务中使用资源 这是代码 private object thisLock new obje
  • 如何使用 Razor 语法在 ASP.NET MVC 4 中获取文本中 URL 的链接?

    我有一个带有文本字段的模型 文本can包含多个 URL 它不必包含 URL 也没有特定的格式 Using Html DisplayFor model gt model TextWithSomeUrls 当然 文本和 URL 的显示方式与普通
  • 使用 python2 和 python3 的相同代码进行编码+加密+填充时出现问题[重复]

    这个问题在这里已经有答案了 免责声明 我了解以下内容not适合在生产环境中提供 安全 它只是比对存储在我的系统上的敏感数据使用 XOR 或 rot13 更好一点 我将以下代码放在一起 以允许我对这些敏感值使用 AES 加密 AES 需要 1
  • 无法在真实设备上使用 Appium 识别 iOS hyprid 应用程序自动化中 WEBVIEW 中的元素

    我试图使用 ionic2 Angular2 和 typescript 来自动化混合应用程序构建 我正在使用 C 来编写代码 测试在 BDD specflow 中 版本 iOS 9 3 1 代码 7 3 阿皮姆 1 4 13 将上下文切换到
  • TypeScript 实用程序类型优于可区分的联合类型

    给定一个像这样的受歧视联合类型 type HomeRoute name Home type PageRoute name Page id number type SearchRoute name Search text string lim
  • 如果您可以解码 JWT,它们的安全性如何?

    如果我得到一个JWT我可以解码有效负载 这如何安全 难道我不能从标头中获取令牌 解码并更改有效负载中的用户信息 然后使用相同的正确编码秘密将其发送回来吗 我知道它们必须是安全的 但我真的很想了解这些技术 我缺少什么 JWT 可以进行签名 加
  • 从 VBA 运行 Telnet 会话

    我有一个可以执行 FTP 功能的 VBA 库 我也想执行 telnet 操作 目前 我正在编写一个 Perl 脚本 该脚本基于文本文件执行 telnet 但我想从 VBA 内部本地驱动 telnet 连接 有人有这方面的资料吗 我不想使用加
  • 如何使用依赖属性来替换UserControl构造函数中的参数?

    我注意到以前有人问过类似的问题 但我没有找到任何详细的例子 我有一个winform程序 它的构造函数有一个参数cn public AddFailure ProSimConnect cn constructor in winform this
  • 为什么BFS的复杂度是O(V+E)而不是O(E)? [复制]

    这个问题在这里已经有答案了 这是一个通用的 BFS 实现 For a connected graph with V nodes and E total number of edges we know that every edge will
  • 自定义GridView删除按钮

    如何自定义自动生成的命令按钮 例如Delete 我想在删除时添加客户端确认 同时我希望在设置时生成此按钮AutoGenerateDeleteButton true 是否可以 我可以这样添加自定义按钮
  • 您请求的商品无法购买

    我正在尝试在 Android 应用程序中测试订阅 我用地下城的例子 我上传的 apk 未发布 但订阅项目已发布 我在开发控制台中添加了新的测试帐户 gmail 它不是开发人员帐户 我将手机重置为出厂状态并添加了测试帐户 我安装了签名的apk
  • 如何从以破折号开头的远程名称中提取

    在 git 中 可以使用以下命令处理远程名称 人物明星 例如 我们可以添加一个以 只需使用选项更新它 在 git 命令中 命令选项和远程名称之间分开 但它不起作用 git pull myremotename master 而且 我收到此错误
  • 将 pandas 系列时间戳转换为唯一日期列表

    我在 pandas 数据框中有一列时间戳格式的列 想要将唯一日期 没有时间 提取到列表中 我尝试了以下方法并没有真正起作用 1 dates datetime datetime df EventTime tolist date 2 dates
  • 从 Google 应用脚本访问 Google 文档评论

    我正在与几个人同时编写谷歌文档 为了跟踪谁必须做什么 我正在使用评论 然后 文档的每个部分都会分配给某人 并且他必须在评论中更新其部分的状态 以结构化格式 感谢 VBA 中的宏 我将其提取出来并将结果放入电子表格中 这样就可以轻松跟踪文档的
  • 具有一个或多个(多个)参数的搜索表单

    我已经掌握了基础知识 在其中创建了两个文件 用户输入搜索参数的搜索表单 以及生成输入项目的结果文件 为了简单起见 我们将搜索表单文件指定为 search php 将结果页面指定为 results php 搜索 php
  • Java8 Collections.sort(有时)不会对 JPA 返回的列表进行排序

    Java8 在我的 JPA EclipseLink 2 5 2 环境中不断执行奇怪的操作 我不得不删除这个问题https stackoverflow com questions 26806183 java 8 sorting behavio