在MyBatis中处理大量数据

时间:2011-07-01 09:31:45

标签: java mybatis large-data

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

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

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

5 个答案:

答案 0 :(得分:16)

myBatis CAN流结果。您需要的是自定义结果处理程序。通过这种方式,您可以单独获取每一行并将其写入XML文件。总体方案如下:

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

其中resultHandler是实现ResultHandler接口的类的实例。此接口只有一个方法handleResult。此方法为您提供ResultContext对象。从这个上下文中,您可以检索当前正在读取的行并使用它执行某些操作。

handleResult(ResultContext context) {
  Object result = context.getResultObject();
  doSomething(result);
}

答案 1 :(得分:7)

不,mybatis没有完整的流式传输 的功能。

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

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

  1. Implement your own ResultSetHandler
  2. 增加提取大小。 (如Guillaume Perrot所述)
  3. 对于嵌套结果图,请使用Issue 577中讨论的修复。此修复程序还解决了大型结果集的一些内存问题。

答案 2 :(得分:2)

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

当处理的记录太多时,我使用了sqlSessionFactory.getSession()。getConnection()。 然后,正常的JDBC,获取一个Statement,获取Resultset,并逐个处理记录。别忘了关闭会议。

答案 3 :(得分:1)

我已成功将MyBatis流与Cursor一起使用。此PR的MyBatis上已实现了Cursor。

documentation中被描述为

游标提供与列表相同的结果,除了它获取数据外 懒惰地使用Iterator。

此外,code documentation

游标非常适合处理数以百万计的项目查询 通常不会容纳在内存中。

这是我已完成并可以成功使用的实现示例:

import org.mybatis.spring.SqlSessionFactoryBean;

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

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

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());
    }
}

答案 4 :(得分:0)

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

sql可能是这样的:

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

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