无论如何,我无法在 Hibernate 中批处理 MySQL INSERT 语句

Posted

技术标签:

【中文标题】无论如何,我无法在 Hibernate 中批处理 MySQL INSERT 语句【英文标题】:No matter what, I can't batch MySQL INSERT statements in Hibernate 【发布时间】:2014-02-27 02:26:19 【问题描述】:

我目前正面临众所周知且常见的 Hibernate 插入批处理问题。

我需要保存 500 万行长的批次。我首先尝试使用更轻的有效载荷。由于我只需要插入 2 种类型的实体(首先是 A 类型的所有记录,然后是 B 类型的所有记录,都指向公共类型 C ManyToOne parent),因此我想充分利用 JDBC 批量插入的优势。

我已经阅读了很多文档,但我尝试过的都没有。

我知道为了使用批量插入,我不能使用实体生成器。所以我删除了AUTO_INCREMENT ID,并设置了一个技巧:SELECT MAX(ID) FROM ENTITIES,并且每次都递增。 我知道我必须定期刷新会话。我会提前发布代码,但无论如何我每 500 个元素执行一次事务。 我知道我必须将hibernate.jdbc.batch_size 设置为与我的应用程序的批量大小一致,因此我将其设置在LocalSessionFactoryBean(Spring ORM 集成)中 我知道我必须在连接 URL 中启用重写批处理语句。

这是我的实体

共同的父实体。这首先插入到单个事务中。我不关心这里的自动增量列。每个批处理作业只有 一个 记录

@Entity
@Table(...)
@SequenceGenerator(...)
public class Deal


    @Id
    @Column(
            name = "DEAL_ID",
            nullable = false)
    @GeneratedValue(
            strategy = GenerationType.AUTO)
    protected Long id;

    ................

其中一个孩子(假设每批 250 万条记录)

@Entity
@Table(
        name = "TA_LOANS")
public class Loan


    @Id
    @Column(
            name = "LOAN_ID",
            nullable = false)
    protected Long id;

    @ManyToOne(
            optional = false,
            targetEntity = Deal.class,
            fetch = FetchType.LAZY)
    @JoinColumn(
            name = "DEAL_ID",
            nullable = false)
    protected Deal deal;


    .............

其他孩子键入。假设其他 250 万条记录

@Entity
@Table(
        name = "TA_BONDS")
public class Bond


    @Id
    @Column(
            name = "BOND_ID")

    @ManyToOne(
            fetch = FetchType.LAZY,
            optional = false,
            targetEntity = Deal.class)
    @JoinColumn(
            name = "DEAL_ID",
            nullable = false,
            updatable = false)
    protected Deal deal;


插入记录的简化代码

    long loanIdCounter = loanDao.getMaxId(), bondIdCounter = bondDao.getMaxId(); //Perform SELECT MAX(ID)

    Deal deal = null;

    List<Bond> bondList = new ArrayList<Bond>(COMMIT_BATCH_SIZE); //500 constant value
    List<Loan> loanList = new ArrayList<Loan>(COMMIT_BATCH_SIZE);

    for (String msg: inputStreamReader)
    
        log.debug(msg.toString());

        if (this is a deal)
        
            Deal deal = parseDeal(msg.getMessage());

            deal = dealManager.persist(holder.deal); //Called in a separate transaction using Spring annotation @Transaction(REQUIRES_NEW)

        
        else if (this is a loan)
        

            Loan loan = parseLoan(msg.getMessage());
            loan.setId(++loanIdCounter);
            loan.setDeal(deal);

            loanList.add(loan);

            if (loanList.size() == COMMIT_BATCH_SIZE)
            
                loanManager.bulkInsert(loanList); //Perform a bulk insert in a single transaction, not annotated but handled manually this time
                loanList.clear();
            
        
        else if (this is a bond)
        
            Bond bond = parseBond(msg.getMessage());
            bond.setId(++bondIdCounter);
            bond.setDeal(deal);

            bondList.add(bond);



            if (bondList.size() == COMMIT_BATCH_SIZE) //As above
            
                bondManager.bulkInsert(bondList);
                bondList.clear();

            
        
    

    if (!bondList.isEmpty())
        bondManager.bulkInsert(bondList);
    if (!loanList.isEmpty())
        loanManager.bulkInsert(loanList);
    //Flush remaining items, not important

bulkInsert的实现:

@Override
public void bulkInsert(Collection<Bond> bonds)

    // StatelessSession session = sessionFactory.openStatelessSession();
    Session session = sessionFactory.openSession();
    try
    
        Transaction t = session.beginTransaction();
        try
        
            for (Bond bond : bonds)
                // session.persist(bond);
                // session.insert(bond);
                session.save(bond);
        
        catch (RuntimeException ex)
        
            t.rollback();
        
        finally
        
            t.commit();
        
    
    finally
    
        session.close();
    


从 cmets 可以看出,我尝试了几种有状态/无状态 session 的组合。没有任何效果。

我的dataSourceComboPooledDataSource,带有以下网址

<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&amp;rewriteBatchedStatements=true" />

我的SessionFactory

<b:bean id="sessionFactory" class="class.that.extends.org.springframework.orm.hibernate3.LocalSessionFactoryBean" lazy-init="false" depends-on="dataSource">
        <b:property name="dataSource" ref="phoenixDataSource" />
        <b:property name="hibernateProperties">
            <b:props>
                <b:prop key="hibernate.dialect">$hibernate.dialect</b:prop> <!-- MySQL5InnoDb-->
                <b:prop key="hibernate.show_sql">$hibernate.showSQL</b:prop>
                <b:prop key="hibernate.jdbc.batch_size">500</b:prop>
                <b:prop key="hibernate.jdbc.use_scrollable_resultset">false</b:prop>
                <b:prop key="hibernate.cache.use_second_level_cache">false</b:prop>
                <b:prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</b:prop>
                <b:prop key="hibernate.cache.use_query_cache">false</b:prop>
                <b:prop key="hibernate.validator.apply_to_ddl">false</b:prop>
                <b:prop key="hibernate.validator.autoregister_listeners">false</b:prop>
                <b:prop key="hibernate.order_inserts">true</b:prop>
                <b:prop key="hibernate.order_updates">true</b:prop>
            </b:props>
        </b:property>
</b:bean>

即使我的项目范围的类扩展了LocalSessionFactoryBean,它不会覆盖它的方法(只添加了几个项目范围的方法)

这几天我开始生气了。我阅读了几篇文章,但没有一篇文章帮助我启用批量插入。我从使用 Spring 上下文检测的 JUnit 测试中运行我的所有代码(所以我可以@Autowire 我的类)。我所有的尝试只会产生很多单独的INSERT 语句

https://***.com/questions/12011343/how-do-you-enable-batch-inserts-in-hibernate https://***.com/questions/3469364/faster-way-to-batch-saves-with-hibernate https://forum.hibernate.org/viewtopic.php?p=2374413 https://***.com/questions/3026968/high-performance-hibernate-insert

我错过了什么?

【问题讨论】:

我真的很绝望,我需要 mysql + hibernate 批量插入,你做到了吗? 唯一的方法是向底层驱动程序发出直接查询。或者切换到 C#,您可以在其中使用 Entity Framework 进行本机批量插入。 你能看看这个吗,我会很感激的。 ***.com/questions/59153072/… 【参考方案1】:

很可能您的查询正在被重写,但您不会通过查看 Hibernate SQL 日志知道是否。 Hibernate 不会重写插入语句 - MySQL 驱动程序会重写它们。也就是说,Hibernate 会向驱动发送多个插入语句,然后驱动会重写它们。因此,Hibernate 日志只会显示 Hibernate 发送给驱动程序的 SQL,而不是驱动程序发送给数据库的 SQL。

您可以通过在连接 url 中启用 MySQL 的 profileSQL 参数来验证这一点:

<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&amp;rewriteBatchedStatements=true&amp;profileSQL=true" />

使用与您类似的示例,我的输出如下所示:

insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
Wed Feb 05 13:29:52 MST 2014 INFO: Profiler Event: [QUERY]  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) duration: 1 ms, connection-id: 81, statement-id: 33, resultset-id: 0, message: insert into Person (firstName, lastName, id) values ('person1', 'Name', 1),('person2', 'Name', 2),('person3', 'Name', 3),('person4', 'Name', 4),('person5', 'Name', 5),('person6', 'Name', 6),('person7', 'Name', 7),('person8', 'Name', 8),('person9', 'Name', 9),('person10', 'Name', 10)

Hibernate 正在记录前 10 行,但这并不是实际发送到 MySQL 数据库的内容。最后一行来自 MySQL 驱动程序,它清楚地显示了具有多个值的单个批处理插入,这就是实际发送到 MySQL 数据库的内容。

【讨论】:

以上是关于无论如何,我无法在 Hibernate 中批处理 MySQL INSERT 语句的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 和 Spring Data JPA 无法处理带有特殊字符的 Oracle 表名?

请求处理失败;嵌套异常是 org.hibernate.exception.ConstraintViolationException:无法执行 JDBC 批量更新

如何使用 Hibernate 在 Spring Boot 中处理数据库迁移?

如何使用 Hibernate 和 JPA 处理填充下拉列表?

org.hibernate.exception.ConstraintViolationException:无法执行 JDBC 批量更新

Informix 数据库中的 Hibernate 批量插入(获取 sql 日志跟踪)