• 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。
  • 相关阅读:
    [经验交流] kubernetes v1.11 更新了高可用方案
    Java泛型和编译优化的一个例子
    Java泛型和编译优化的一个例子
    Java泛型和编译优化的一个例子
    浅析JSONP-解决Ajax跨域访问问题
    java设计原则---开闭原则
    for,foreach,iterator的用法和区别
    List,Set,Map三种接口的区别
    为什么接口中定义的变量必须为常量?
    重写,string创建内存问题
  • 原文地址:https://www.cnblogs.com/pmh905001/p/12245096.html
Copyright © 2020-2023  润新知