任何JPA实现(或更广泛的Java ORM实现)都支持可更新的游标

时间:2012-10-18 17:43:00

标签: java hibernate orm eclipselink ibatis

我希望可用于Java的对象/关系映射(ORM)工具之一满足这些要求:

  • 使用JPA或本机SQL查询获取大量行并将其作为实体对象返回。
  • 在我对其进行更改后允许迭代行(实体)和当前实体的持久性。

我想逐行执行复杂的批处理操作(实际上我正在比较和协调文件中的已知良好数据与数据库中的数据)。如果它更简单,我只需要使用JDBC并执行一些SQL;但在这种情况下,我确实从直接从bean到数据库中获益。

在SQL中,我可以使用可更新的游标来有效地实现我的目标。

作为参考,我正在嵌入式Java H2环境中测试所有这些。

JPA查询

我的第一个天真的尝试是调用Query.getResultList(),它返回实体bean很好,但它们“断开连接”。如果我致电persistenceUnitUtil.getIdentifier(myEntity),那么它会抱怨它不是实体类型。

休眠

然后我研究了支持ScrollableResults的Hibernate。界面允许我按名称获取单个列值,但不允许实体。

的EclipseLink

接下来是EclipseLink,它支持ScrollableCursor。我对这一个抱有很好的希望,用它来:

Query query = entityManager.createQuery(jpaQuery);
query.setHint("eclipselink.cursor", true);
CursoredStream cursoredStream = (CursoredStream)query.getSingleResult();

不幸的是,cursoredStream.next();再次返回实体的“断开连接”版本。所以,我看不到回写实体的方法。

结论

我正在研究至少让实体的@Id作为查询的一部分传回的方法(遗憾的是,我希望保持工具的灵活性,有时我将字符串作为键,其他时候复合键对象)。这至少可以让我遍历行,然后单独查找和保存每个实体。

但是,我更喜欢的是有一个游标支持的迭代器,它可以获取一个JPA连接的实体,并允许我对它进行更改并保留它。

如果这不是其中一个ORM工具的已知功能,我可能不得不放弃并使用旧的JDBC。

1 个答案:

答案 0 :(得分:1)

伪代码(C#)

void Execute(ISession session, string filepath)
{
    int page = 0;
    int pagesize = 5000;
    int batchindex = int.MaxValue;
    List<Entity> batch = new List<Entity>();

    TextReader file = new StreamReader(filepath)

    string line;
    while ((line = file.ReadLine) != null)
    {
        if (batchindex > batch.Count)
        {
            session.Flush();
            session.Clear();
            batch = session.CreateCriteria<Entity>()
                .AddOrder(Order.Asc(<same order as in file>))
                .SetFirstResult(page * pagesize)
                .SetMaxResults(pagesize)
                .List<Entity>();
            page++;
            batchindex = 0;
        }
        if (database has more rows than the file
        while (!LineIsForEntity(batch[batchindex], line))
        {
            batchindex++;
            // same if (batchindex > batch.Count) as above
        }

        UpdateEntity(batch[batchindex], line);
    }
    session.Flush();
    session.Clear();
}

根据数据类型和上下文,可能会有更好的代码。

更新:使用C#进行随机访问,这在使用(N)Hibernate

时应该是高效的
const int pagesize = 2000;
var nextbatch = Enumerable.Repeat(0, pagesize)
    .Select(_ => file.ReadLine())
    .TakeWhile(line => line != null);

string[] batch;
while ((batch = nextbatch.ToArray()).Length > 0)
{
    // ignore results, we only want the entities in cache
    session.QueryOver<Entity>()
        .WhereRestrictionOn(e => e.Id).In(batch.Select(line => ExtractId(line)).ToList())
        .List();

    foreach(string line in batch)
    {
        Update(session.Get<Entity>(ExtractId(line)), line);
    }
    session.Flush();
    session.Clear();
}

如评论中所述,将替换使用session与entityManager和C#构造与Java。如果实体是独立的,你甚至可以使用多个线程,Sessions来并行化while。