我正在从事一些涉及将邮政编码、城市和国家/地区存储在一起的 J2EE 项目。我们开发了一个 Java 类来处理每个国家/地区文件(包含每个邮政编码和每个城市)的集成。问题是对于某些国家(英国、荷兰...),文件非常大(400.000 到 800.000 行)。
我有一个 while()
循环,它读取下一行,获取信息并将其存储到我的数据库中。问题是,对于 1000 或 10.000 行的第一行,过程很快,非常快,然后每次循环时似乎都在变慢,然后恰好在 150.000 行后抛出 HeapSpaceOverflowException
。
我首先想到有些对象没有被垃圾回收并减慢了我的算法,但我无法弄清楚是哪一个。此外,当我在我的 PC 上运行这个算法时,JConsole 告诉我堆空间被定期清理(似乎是垃圾收集),但这个过程仍然越来越慢。
方法代码如下:
FileReader fr = new FileReader(nomFichier);
BufferedReader br = new BufferedReader(fr);
int index = 0; String ligne; String codePostal; String nomVille;
String codePays; PPays pays; String[] colonnes;
while ((ligne = br.readLine()) != null)
{
System.out.println("line "+ ++index);
colonnes = ligne.split(Pattern.quote(";"));
codePostal = colonnes[9];
nomVille = colonnes[8];
codePays = colonnes[0];
pays = this.pc.getByCodePays(codePays);
this.pc.getByCodePostalAndVilleAndINSEE(codePostal, nomVille, pays.getNomPays(), "");
}
通过@Inject注解注入(inject)变量this.pc
。
谁能帮我弄清楚为什么这段代码变得越来越慢?
为了完整起见,我添加了 get...()
方法的代码:
public Codepostalville getByCodePostalAndVilleAndINSEE(String codePostal, String ville,
String pays, String codeINSEE) throws DatabaseException
{
Codepostal cp = null; Ville v = null; PPays p = null; Codepostalville cpv = null;
try
{
// Tout d'abord, il faut retrouver l'objet CodePostal
cp = (Codepostal) this.em
.createNamedQuery("Codepostal.findByCodePostal")
.setParameter("codePostal", codePostal)
.getSingleResult();
}
catch (NoResultException nre1)
{
// Si on ne l'a pas trouvé, on le crée
if (cp == null)
{
cp = new Codepostal();
cp.setCodePostal(codePostal);
cpc.getFacade().create(cp);
}
}
// On retrouve la ville...
try
{
// Le nom de la ville passé par l'utilisateur doit être purgé (enlever
// les éventuels tirets, caractères spéciaux...)
// On crée donc un nouvel objet Ville, auquel on affecte le nom à purger
// On effectue la purge, et on récupère le nom purgé
Ville purge = new Ville();
purge.setNomVille(ville);
purge.purgerNomVille();
ville = purge.getNomVille();
v = (Ville) this.em
.createNamedQuery("Ville.findByNomVille")
.setParameter("nomVille", ville)
.getSingleResult();
}
catch (NoResultException nre2)
{
// ... ou on la crée si elle n'existe pas
if (v == null)
{
v = new Ville();
v.setNomVille(ville);
vc.getFacade().create(v);
}
}
// On retrouve le pays
try
{
p = (PPays) this.em
.createNamedQuery("PPays.findByNomPays")
.setParameter("nomPays", pays)
.getSingleResult();
}
catch (NoResultException nre2)
{
// ... ou on la crée si elle n'existe pas
if (p == null)
{
p = new PPays();
p.setNomPays(pays);
pc.getFacade().create(p);
}
}
// Et on retrouve l'objet CodePostalVille
try
{
cpv = (Codepostalville) this.em
.createNamedQuery("Codepostalville.findByIdVilleAndIdCodePostalAndIdPays")
.setParameter("idVille", v)
.setParameter("idCodePostal", cp)
.setParameter("idPays", p)
.getSingleResult();
// Si on a trouvé l'objet CodePostalVille, on met à jour son code INSEE
cpv.setCodeINSEE(codeINSEE);
this.getFacade().edit(cpv);
}
catch (NoResultException nre3)
{
if (cpv == null)
{
cpv = new Codepostalville();
cpv.setIdCodePostal(cp);
cpv.setIdVille(v);
cpv.setCodeINSEE(codeINSEE);
cpv.setIdPays(p);
this.getFacade().create(cpv);
}
}
return cpv;
}
所以,我有一些更多的信息。 getCodePostal...()
方法在循环开始时需要大约 15 毫秒的时间来执行,在 10.000 行之后,需要超过 100 毫秒的时间来执行(几乎多 10 倍!)。
在这个新版本中,我禁用了提交/回滚代码,因此每个查询都是动态提交的。
我真的找不到为什么它需要越来越多的时间。
我试图搜索一些关于 JPA 缓存的信息: 我当前的配置是这样的(在 persistence.xml 中):
<property name="eclipselink.jdbc.bind-parameters" value="true"/>
<property name="eclipselink.jdbc.cache-statements" value="true"/>
<property name="eclipselink.cache.size.default" value="10000"/>
<property name="eclipselink.query-results-cache" value="true"/>
我不知道这是否是最有效的配置,我将不胜感激一些关于 JPA 缓存的帮助和解释。
最佳答案
您可能想要阅读 JPA 概念。简而言之,EntityManager 与持久性上下文相关联,持久性上下文保留对通过它操作的所有持久性对象的引用,因此它可以将对这些对象所做的任何更改写回数据库。
由于您从不关闭持久性上下文,这可能是内存泄漏的原因。此外,持久性提供者必须在发出查询之前将对持久对象的更改写入数据库,如果这些更改可能会改变查询的结果的话。要检测这些更改,需要迭代与当前持久上下文关联的所有对象。在您的代码中,您发出的每个查询都有将近一百万个对象。
因此,至少,您应该定期(比如每 1000 行)清除持久性上下文。
还值得注意的是,除非您的数据库位于同一台服务器上,否则您发出的每个查询都必须通过网络传输到数据库,然后将结果返回到应用程序服务器,然后您的程序才能继续。根据网络延迟,每次这很容易花费一毫秒 - 而您正在这样做几百万次。如果它需要真正高效,将整个表加载到内存中并执行内存中是否存在的检查,可能会快得多。
关于java - 循环是否每次都需要更长的时间来执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24933492/