Java8在我的JPA EclipseLink 2.5.2環境中一直在做奇怪的事情。我必須刪除昨天的問題https://stackoverflow.com/questions/26806183/java-8-sorting-behaviour ,因爲在這種情況下排序受到了奇怪的JPA行爲的影響 - 我通過在進行最終排序之前強制執行第一個排序步驟,找到了解決方法。Java8 Collections.sort(有時)不對JPA返回列表進行排序
仍然在JPA Eclipselink 2.5.2的Java 8中,以下代碼在我的環境(Linux,MacOSX,均使用build 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測試中調用上述函數可以正常工作。 當在生產環境中debbuging我得到一個日誌條目:
INFORMATION: sorting 34 by modification date
但TimSort與nRemaining < 2 return語句被擊中 - 所以沒有排序發生。由JPA提供的間接列表(見What collections does jpa return?)被認爲是空的。
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 有一個示例項目,它具有與原始問題類似的結構。
它包含一個帶有JUnit測試的http://sscce.org/示例,該測試通過調用em.clear()來重現問題,從而分離所有對象並強制使用IndirectList。請參閱下面的JUnit案例以供參考。
隨着預先抓取:
// https://stackoverflow.com/questions/8301820/onetomany-relationship-is-not-working
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parentFolder", fetch=FetchType.EAGER)
該股情況下工作。如果使用FetchType.LAZY或在JDK 8中省略提取類型,則行爲可能與JDK 7中的不同(我現在必須檢查它)。 這是爲什麼呢? 在這個時候,我假設你需要指定Eager抓取或迭代一次在列表上進行排序,基本上是在排序之前手動抓取。 還能做些什麼?
JUnit測試
的persistence.xml和聚甲醛。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 [email protected] 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());
}
}
你確定你要排序的集合不會被改動由一些外部來源? – fge 2014-11-08 11:59:47
docs.size()和Collections.sort(docs,comparator)之間只有構造函數。我的調試表明這可能再次成爲JPA問題。該列表是一個IndirectList,並且排序似乎相信elementCount是零,modcount是2的大小是2. – 2014-11-08 12:18:39
Java8已於6個多月前發佈。您是否真的假設Java8集合中存在一個錯誤,而不是更仔細地查看自己的代碼?如果你絕望,使用比基本系統(比如JPDA)更好的東西,但我認爲你應該關注你的代碼。 – 2014-11-08 12:18:49