【问题标题】:Handling very large amount of data in MyBatis在 MyBatis 中处理非常大量的数据
【发布时间】:2011-09-26 15:08:21
【问题描述】:

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

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

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

【问题讨论】:

    标签: java mybatis large-data


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 您的答案缺少一个重要的细节:您需要将 @Options(fetchSize = Integer.MIN_VALUE) (或 XML 等效项)添加到您的语句中。它在 MyBatis 3.0.5 中不起作用(我用 Eclipse Memory Analyzer 进行了检查)。我升级到 MyBatis 3.1.1,现在流媒体可以正常工作了。
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        我已经成功地使用了带有光标的 MyBatis 流。游标已在 MyBatis 上实现,地址为 PR

        documentation它被描述为

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

        另外,code documentation

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

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

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

        【讨论】:

        【解决方案5】:

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

        sql 可能是这样的:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-10-13
          • 2017-09-23
          • 1970-01-01
          • 2015-03-02
          • 1970-01-01
          • 2017-02-03
          • 1970-01-01
          相关资源
          最近更新 更多