使用分区 postgresql 进行休眠插入批处理
-
01-07-2019 - |
题
有没有通过hibernate在分区postgresql表中批量插入的解决方案?目前我收到这样的错误...
ERROR org.hibernate.jdbc.AbstractBatcher - Exception executing batch:
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:61)
at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:46)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:68)....
我找到了这个链接 http://lists.jboss.org/pipermail/hibernate-dev/2007-October/002771.html 但我在网上找不到这个问题是否已解决或如何解决
解决方案
您可能想通过设置 hibernate.jdbc.factory_class 属性来尝试使用自定义批处理程序。确保 hibernate 不会检查批处理操作的更新计数可能会解决您的问题,您可以通过使自定义 Batcher 扩展类 BatchingBatcher,然后重写方法 doExecuteBatch(...) 来实现这一点,如下所示:
@Override
protected void doExecuteBatch(PreparedStatement ps) throws SQLException, HibernateException {
if ( batchSize == 0 ) {
log.debug( "no batched statements to execute" );
}
else {
if ( log.isDebugEnabled() ) {
log.debug( "Executing batch size: " + batchSize );
}
try {
// checkRowCounts( ps.executeBatch(), ps );
ps.executeBatch();
}
catch (RuntimeException re) {
log.error( "Exception executing batch: ", re );
throw re;
}
finally {
batchSize = 0;
}
}
}
请注意,新方法不会检查执行准备语句的结果。请记住,进行此更改可能会以某种意想不到的方式影响休眠(也可能不会)。
其他提示
他们说在分区表中使用两个触发器或在此处使用 @SQLInsert 注释: http://www.redhat.com/f/pdf/jbw/jmlodgenski_940_scaling_hibernate.pdf 第 21-26 页(它还提到了 @SQLInsert 指定 String 方法)。
下面是一个使用 after 触发器删除主数据中多余行的示例: https://gist.github.com/copiousfreetime/59067
出现如果可以使用RULES而不是触发器进行插入,那么它可以返回正确的数字,但只能使用单个RULE而无需WHERE语句。
另一种选择可能是创建一个“包装”分区表的视图,然后返回新行以指示行更新成功,而不会意外地将额外不需要的行添加到主表中。
create view tablename_view as select * from tablename; -- create trivial wrapping view
CREATE OR REPLACE FUNCTION partitioned_insert_trigger() -- partitioned insert trigger
RETURNS TRIGGER AS $$
BEGIN
IF (NEW.partition_key>= 5500000000 AND
NEW.partition_key < 6000000000) THEN
INSERT INTO tablename_55_59 VALUES (NEW.*);
ELSIF (NEW.partition_key >= 5000000000 AND
NEW.partition_key < 5500000000) THEN
INSERT INTO tablename_50_54 VALUES (NEW.*);
ELSIF (NEW.partition_key >= 500000000 AND
NEW.partition_key < 1000000000) THEN
INSERT INTO tablename_5_9 VALUES (NEW.*);
ELSIF (NEW.partition_key >= 0 AND
NEW.partition_key < 500000000) THEN
INSERT INTO tablename_0_4 VALUES (NEW.*);
ELSE
RAISE EXCEPTION 'partition key is out of range. Fix the trigger function';
END IF;
RETURN NEW; -- RETURN NEW in this case, typically you'd return NULL from this trigger, but for views we return NEW
END;
$$
LANGUAGE plpgsql;
CREATE TRIGGER insert_view_trigger
INSTEAD OF INSERT ON tablename_view
FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger(); -- create "INSTEAD OF" trigger
参考: http://www.postgresql.org/docs/9.2/static/trigger-definition.html
如果您采用视图包装器路线,一种选择是也为删除和更新定义简单的“而不是”触发器,那么您可以在所有事务中使用视图表的名称来代替普通表。
使用视图的另一个选项是创建一个插入规则,以便主表上的任何插入都转到视图[使用其触发器],例如(假设您已经有 partitioned_insert_trigger
以及按上面列出的方式创建的 tablename_view 和 insert_view_trigger)
create RULE use_right_inserter_tablename AS
ON INSERT TO tablename
DO INSTEAD insert into tablename_view VALUES (NEW.*);
然后它将使用新的工作视图包装器插入。
谢谢!它成功了,到目前为止没有出现任何问题:)....你有一件事...我必须实施 BatcherFactory
类并将其放入 persistence.xml
文件,这样:
property name="hibernate.jdbc.factory_class" value="path.to.my.batcher.factory.implementation"
从那个工厂我用上面的代码调用了我的批处理程序实现
PS Hibernate Core 3.2.6 GA
再次感谢
在大量搜索后,我在通过休眠插入文档时遇到了同样的问题,发现应该返回更新的行,因此不要将 null 更改为触发器过程中的新值,这将解决问题,如下所示
返回新品
我找到了同一问题的另一个解决方案 在这个网页上:
这表明了@rogerdpack所说的相同解决方案,改变 返回空值 到 返回新品, ,并添加一个新的触发器,通过查询删除主数据库中的重复元组:
DELETE FROM ONLY master_table;