当前位置: 首页 > 知识库问答 >
问题:

Hibernate SQLServer/Batch仅插入新记录

李兴庆
2023-03-14

我正在尝试使用Hibernate 4.3和SQL Server 2014,仅对尚未存储的实体执行批量插入到表中。我创建了一个简单的表,其中定义了一个主键来忽略重复的键

create table items 
(
    itemid uniqueidentifier not null, 
    itemname nvarchar(30) not null, 
)
alter table items add constraint items_pk primary key ( itemid ) with ( ignore_dup_key = on );

尝试通过Stateless会话插入方法执行批处理插入,如果一个或多个实体已经存储到数据库表中,则批处理插入可能会失败:Hibernate抛出StaleStateException:

org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
    at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:81)
    at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:73)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:63)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3124)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3581)
    at org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:144)
    at org.hibernate.internal.StatelessSessionImpl.insert(StatelessSessionImpl.java:123)
    at it.test.testingestion.HibernateStatelessSessionPersisterImpl.persistData(HibernateStatelessSessionPersisterImpl.java:18)
    at it.test.testingestion.App.main(App.java:76)

当批处理语句完成时,Hibernate会检查返回的行数,由于忽略重复键,返回的行数与预期的不同。

使用JDBC,使用准备好的语句执行批插入,将跳过已存储到目标表中的实体,但会正确保存新实体。

如何将Hibernate配置为忽略现有数据执行批插入,或不执行受影响行的检查?

谢谢

更新#1

作为解决方法,为了强制影响的行数,即使发生重复插入,我创建了以下Hibernate Interceptor:

public class CustomInterceptor extends EmptyInterceptor {

    private static final long serialVersionUID = -8022068618978801796L;

    private String getTemporaryTableName() {
        String currentThreadName = Thread.currentThread().getName();
        return "##" + currentThreadName.replaceAll("[^A-Za-z0-9\\_]", "");
    }

    private void createTemporaryTable(Connection connection) {
        String tempTableName = this.getTemporaryTableName();
        String commandText = String.format("if (object_id('tempdb.dbo.%s') is null) begin create table [%s] ( dummyfield int ); insert into %s ( dummyfield ) values ( 0 ) end ", tempTableName, tempTableName, tempTableName);
        try (PreparedStatement statement = connection.prepareStatement(commandText)) {
            statement.execute();
            connection.commit();
        } catch (SQLException e) {
            throw new RuntimeException(String.format("An error has been occurred trying to create the temporary table %s", tempTableName), e);
        }
    }

    public CustomInterceptor(Connection connection) {
        this.createTemporaryTable(connection);
    }

    @Override
    public String onPrepareStatement(String sql) {
        int ps = sql.toLowerCase().indexOf("insert into ");
        if (ps == 0) {
            String tableName = this.getTemporaryTableName();
            return sql + "; if (@@rowcount = 0) update [" + tableName + "] set dummyfield = 1"; 
        }
        return super.onPrepareStatement(sql);
    }

}

当创建新实例时,拦截器会创建一个新的临时表,插入一条新记录。截取insert语句时,如果insert语句未影响任何行,则会执行保存到实例化临时表中的记录的更新:如果插入了重复实体且未引发无状态SessionImpl异常,则这会使Hibernate对返回的行事件保持Hibernate。

显然,这一技巧的缺点是,对未插入表中的每一行执行额外更新的成本。

有没有人知道一种更好的方法,不影响插入性能,将实体插入到使用Hibernate忽略重复条目的表中?

谢谢

共有1个答案

林鹏鹍
2023-03-14

为了获得更好的性能,我更喜欢使用JDBCBatchUpdate

方法1:

当您过滤新记录时,记录的数量不会成为约束。因此,您可以在实体层中指定关联映射,并可以执行Hibernate批处理插入或JDBC批处理更新。

方法2:使用本机SQl查询

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
//get Connction from Session
session.doWork(new Work() {
       @Override
       public void execute(Connection conn) throws SQLException {
          PreparedStatement pstmt = null;
          try{
           String sqlInsert = "insert into tbl(name) values (?) ";
           pstmt = conn.prepareStatement(sqlInsert );
           int i=0;
           for(String name : list){
               pstmt .setString(1, name);
               pstmt .addBatch();

               //20 : JDBC batch size
             if ( i % 20 == 0 ) { 
                pstmt .executeBatch();
              }
              i++;
           }
           pstmt .executeBatch();
         }
         finally{
           pstmt .close();
         }                                
     }
});
tx.commit();
session.close();
 类似资料:
  • 我的用户表有以下存储库界面。 我想禁用在用户表中插入新实体,并仅在存在现有条目的情况下使用保存功能来更新现有条目。我如何才能做到这一点?是否有可以传递到存储库界面的选项?或者,当没有匹配的实体时,我应该重写save方法而不执行任何操作吗?

  • 如果要将很多对象持久化,你必须通过经常的调用 flush() 以及稍后调用 clear() 来控制第一级缓存的大小。 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Custome

  • 我的php代码: 当我使用echo而不是mysql_query时,它返回: 它在我的phpmyadmin上运行得很好,但是当我将echo更改为mysql_query时,它只影响5行(两个表) P1_1-K1 P1_1-K4 P1_1-K3 P1_1-K2 P12-K1 我的完整数据库 --版本4.3.11 --主机:127.0.0.1--生成时间:2015年7月23日下午12:16--服务器版本:

  • OrientDB是一个NoSQL数据库,可以存储文档和面向图形的数据。 NoSQL数据库不包含任何表,那么要如何将数据作为记录插入?在这里,您可以以类,属性,顶点和边的形式查看表数据,类表就像表,属性就像表中的文件。 可以在OrientDB中使用模式定义所有这些实体。 属性数据可以被插入到一个类中。 插入命令在数据库模式中创建一条新记录。 记录可以无模式或遵循一些指定的规则。 以下语句是“插入记录

  • 问题内容: 我一直使用类似于以下内容的方法来实现它: …但是一旦加载,就会发生主键冲突。这是唯一插入到该表中的唯一语句。那么这是否意味着上述陈述不是原子的? 问题在于,这几乎不可能随意重建。 也许我可以将其更改为以下内容: 虽然,也许我使用了错误的锁,或者使用了过多的锁之类的东西。 看到了其他问题,那里的答案都提示“ IF(SELECT COUNT(*)…INSERT”等),但我始终处于(也许不正

  • 我使用Liquibase insert将两行添加到数据库中,并通过外键链接它们,如下所示: 除了表2中外键的值外,两条记录都正确插入。此值保持为空。因此,“computeValue”属性似乎没有正确执行,但我不明白为什么。我希望在Liquibase中有一个解决方案,而不是在普通SQL中,因为Liquibase提供了一种可能性,可以方便地插入clob字段。 表1:ID- 55228390499230