14

我的目标实际上是将数据库的所有数据转储到 XML 文件中。数据库不是很大,大约300MB。问题是我的内存限制只有 256MB(在 JVM 中)。所以很明显我不能把所有的东西都读入内存。

我设法使用 iBatis(是的,我的意思是 iBatis,而不是 myBatis)通过getList(... int skip, int max)多次调用它来解决这个问题,并增加skip. 这确实解决了我的记忆问题,但我对速度并不满意。变量名称表明该方法在后台执行的操作是读取整个结果集,然后跳过指定的记录。这对我来说听起来很多余(我并不是说这就是方法正在做的事情,我只是根据变量名猜测)。

现在,我为我的应用程序的下一个版本切换到 myBatis 3。我的问题是:有没有更好的方法在 myBatis 中逐块处理大量数据?无论如何要让 myBatis 处理前 N 条记录,将它们返回给调用者,同时保持结果集连接打开,这样下次用户调用 getList(...) 时,它将开始从 N+1 记录读取而不做任何事情“跳过”?

4

5 回答 5

16

myBatis CAN 流式传输结果。您需要的是自定义结果处理程序。有了这个,您可以单独获取每一行并将其写入您的 XML 文件。整体方案如下所示:

session.select(
    "mappedStatementThatFindsYourObjects",
    parametersForStatement,
    resultHandler);

其中 resultHandler 是实现 ResultHandler 接口的类的实例。这个接口只有一个方法handleResult。此方法为您提供了一个 ResultContext 对象。在此上下文中,您可以检索当前正在读取的行并对其进行处理。

handleResult(ResultContext context) {
  Object result = context.getResultObject();
  doSomething(result);
}
于 2011-11-02T07:06:28.880 回答
7

不,mybatis还没有完整的流结果能力。

编辑 1: 如果您不需要嵌套结果映射,那么您可以实现自定义结果处理程序来流式传输结果。在当前发布的 MyBatis 版本上。(3.1.1) 当前的限制是当您需要进行复杂的结果映射时。NestedResultSetHandler 不允许自定义结果处理程序。有一个可用的修复程序,它看起来目前是针对 3.2 的。见问题 577

总之,要使用 MyBatis 流式传输大型结果集,您将需要。

  1. 实现您自己的 ResultSetHandler
  2. 增加抓取大小。(如下纪尧姆·佩罗(Guillaume Perrot)所述)
  3. 对于嵌套结果映射,请使用问题 577中讨论的修复。此修复还解决了大型结果集的一些内存问题。
于 2011-07-11T18:51:57.807 回答
2

handleResult 接收与查询一样多的记录,没有暂停。

当要处理的记录太多时,我使用了 sqlSessionFactory.getSession().getConnection()。然后像普通的JDBC一样,获取Statement,获取Resultset,对记录一一处理。不要忘记关闭会话。

于 2014-07-05T02:08:16.163 回答
2

我已经成功地使用了带有光标的 MyBatis 流。Cursor 已经在这个PR上的 MyBatis 上实现了。

文档中它被描述为

Cursor 提供与 List 相同的结果,只是它使用 Iterator 懒惰地获取数据。

此外,代码文档

游标非常适合处理数百万通常不适合内存的项目查询。

这是我已经完成并且能够成功使用它的实现示例:

import org.mybatis.spring.SqlSessionFactoryBean;

// You have your SqlSessionFactory somehow, if using Spring you can use 
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();

然后定义映射器,例如,UserMapper使用返回目标对象的游标而不是列表的 SQL 查询。整个想法是不要将所有元素都存储在内存中:

import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.cursor.Cursor;

public interface UserMapper {

    @Select(
        "SELECT * FROM users"
    )
    Cursor<User> getAll();
}

然后,您编写将使用工厂的开放 SQL 会话并使用映射器进行查询的代码:

try(SqlSession sqlSession = sqlSessionFactory.openSession()) {
    Iterator<User> iterator = sqlSession.getMapper(UserMapper.class)
                                        .getAll()
                                        .iterator();
    while (iterator.hasNext()) {
        doSomethingWithUser(iterator.next());
    }
}
于 2020-02-26T10:05:41.410 回答
0

如果只是将所有数据转储而不需要从表中排序,为什么不直接在 SQL 中进行分页呢?对查询语句设置一个限制,指定不同的记录id作为偏移量,将整个表分成块,如果行数限制是合理的数量,每个块都可以直接读入内存。

sql可能是这样的:

SELECT * FROM resource 
    WHERE "ID" >= continuation_id LIMIT 300;

我认为这可以被视为您将所有数据按块转储的替代解决方案,摆脱 mybatis 或任何持久层支持中的不同功能问题。

于 2017-08-24T13:23:56.847 回答