zoukankan      html  css  js  c++  java
  • optional=false导致的问题之Hibernate源码分析

    8月初,帮助同事接手了一个hibernate实体保存出错的问题。解决过程比较有意思,最终还是需要分析hibnate源码来解决,现记录如下:

    实体类定义如下:

    @Entity

    @Proxy(lazy=true)

    @DiscriminatorValue("1")

    @SecondaryTable(name="act_order", pkJoinColumns=@primaryKeyJoinColumn(name="order_id"))

    @Table(appliesTo="act_order",fetch=FetchMode.SELECT)

    public class ActOrder extends Order{

    @Column(name="txt",table="act_order")

    private String text;

    ...

    }

     @Table(appliesTo="order")

    public class Order {

    @id(name="order_id")

    private String orderId;

    ...

    }

    1 背景:

    子表act_order 通过主键order_id关联 基表order 表主键order_id,一对一的关联,但order的记录有可能是找不到一条对应的act_order.

    本次操作是修改act_order 子表的txt值从null -> 'aaa'。

    库表里面已经存在一条数据:

     2 日志:

    Hibernate: insert into SUB_ORDER(TXT, ORDER_ID) values (?, ?)
    09 Aug 2019 06:21:36,590 -ERROR insert into SUB_ORDER (TXT, ORDER_ID) values ('aaa', 51598892) com.reserveamerica.framework.persistence.toplinkimpl.BaseOraclePooledConnector$2.sqlException(BaseOraclePooledConnector.java:442)
    java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (LIVE_TX.PK_ACT_ORDER) violated

    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:450)
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:399)
    at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1059)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:522)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:257)
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:587)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:225)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:53)
    at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:943)
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1150)
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:4798)
    at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:4875)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1361)
    at sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at oracle.ucp.jdbc.proxy.StatementProxyFactory.invoke(StatementProxyFactory.java:353)
    at oracle.ucp.jdbc.proxy.PreparedStatementProxyFactory.invoke(PreparedStatementProxyFactory.java:178)
    at com.sun.proxy.$Proxy78.executeUpdate(Unknown Source)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2421)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2485)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2805)
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:114)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:260)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:180)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1206)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:375)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)

    根据日志是主键冲突导致插入失败,看到这条日志会感觉很奇怪,本应update语句为何变成了insert?

    org.hibernate.persister.entity.AbstractEntityPersister:

     

     程序会判断act_order库表是一个NullableTable (默认optional=true),该条记录的所有旧值(除了primary key)是null,所以isRowToUpdate=false的错误结论。

    3 初步的解决方案如下,参考红色字体:

    @Table(appliesTo="act_order",fetch=FetchMode.SELECT,optional=false)

    public class ActOrder extends Order

    这样的话即便txt字段为null或者空串"",都会生成一条记录到act_order 库表。order有一条记录,则act_order也会对应生成一条记录。

     本以为问题轻松解决,但第二天就遭到打脸。新数据创建没有任何问题,历史数据有可能一条order找不到对应act_order记录.因为一旦 设置为optional=false,hibernate就会认为一条order记录一定会对应一条act_order.如果查找不到,系统就会抛出异常。

    4 最终解决方案是。

    • 回滚代码改动
    • 提供的一个sql脚本,删除txt为null的所有act_order记录。
    • 修改程序代码,如果txt="",程序强制设置txt=null。原因如下:当txt="",hibernate会认为至少有一个字段为不为空(NullableTable=false),hibernate将生成一条记录将会插入实体到库表中,但是存到库表的字段值依然是null。
  • 相关阅读:
    cmanformat
    mysql-sql语言参考
    jQuery 判断多个 input checkbox 中至少有一个勾选
    Java实现 蓝桥杯 算法提高 计算行列式
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 数独游戏
    Java实现 蓝桥杯 算法提高 成绩排序2
    Java实现 蓝桥杯 算法提高 成绩排序2
    Java实现 蓝桥杯 算法提高 成绩排序2
  • 原文地址:https://www.cnblogs.com/pmh905001/p/12245096.html
Copyright © 2011-2022 走看看