为什么 Hibernate Envers 不尊重原始 table 中的列类型?

Why does Hibernate Envers not respect the column type in the original table?

我正在使用 Hibernate 5.2.9.Final(核心版本和 Envers)。将审核添加到 table 时,创建方式:

CREATE TABLE Transaction (
    id                      BIGINT PRIMARY KEY
   ,name                    VARCHAR(120) NOT NULL
   ,currency                CHAR(3) REFERENCES Currency(code)
   ,country                 VARCHAR(3)
   ,status                  INTEGER
   ,description             VARCHAR(1000)
   ,amount                  NUMERIC(30,5)
   ,closingDate             TIMESTAMP WITH TIME ZONE
   ,rangeStart              TIMESTAMP WITH TIME ZONE
   ,rangeEnd                TIMESTAMP WITH TIME ZONE
   ,isin                    VARCHAR(12)
   ,version                 INTEGER NOT NULL DEFAULT 0
);

Hibernate Envers 创建了 _AUD table,它输出了以下内容:

CREATE TABLE transaction_aud (
    id bigint NOT NULL,
    rev integer NOT NULL,
    revtype smallint,
    closingdate timestamp without time zone,
    closingdate_mod boolean,
    country character varying(255),
    country_mod boolean,
    currency character varying(255),
    currency_mod boolean,
    description character varying(255),
    description_mod boolean,
    amount numeric(19,2),
    isin character varying(255),
    isin_mod boolean,
    name character varying(255),
    name_mod boolean,
    rangeend timestamp without time zone,
    rangeend_mod boolean,
    rangestart timestamp without time zone,
    rangestart_mod boolean,
    status integer,
    status_mod boolean
);

实体映射文件为:

<entity name="Transaction" class="com.project.datamodel.TransactionTO" access="FIELD">
    <attributes>
        <id name="id" access="PROPERTY">
            <generated-value strategy="SEQUENCE" generator="TransactionIdSeq" />
            <sequence-generator name="TransactionIdSeq" sequence-name="TransactionIdSeq" allocation-size="1" />
        </id>
        <basic name="name" optional="false"> <column length="120" /> </basic>
        <basic name="currency"> <column columnDefinition="CHAR(3)" /> </basic>
        <basic name="country"> <column length="3" /> </basic>
        <basic name="status" />
        <basic name="description"> <column length="1000" /> </basic>
        <basic name="amount"> <column columnDefinition="NUMERIC(30,5)" /> </basic>
        <basic name="closingDate"> <column columnDefinition="TIMESTAMP WITH TIME ZONE" /> </basic>
        <basic name="rangeStart"> <column columnDefinition="TIMESTAMP WITH TIME ZONE" /> </basic>
        <basic name="rangeEnd"> <column columnDefinition="TIMESTAMP WITH TIME ZONE" /> </basic>
        <basic name="isin"> <column length="12" /> </basic>
        <transient name="favorite" />
        <version name="version" />
    </attributes>
</entity>

注意: 我已经从实体中删除了所有加入的 many-to-one 关系,因为它们只是增加了体积而没有对我的主要问题做出贡献。

问题是:

VARCHAR/CHAR 忽略字段类型和长度

所有 CHARVARCHAR 字段,无论其长度如何,都已转换为 VARCHAR(255)/character varying(255) 字段。

这导致在 description 字段中保存数据时出现问题,该字段通常应包含 1000 个字符。超过 255 限制大小后,数据库引擎通过异常,因为数据对于列大小来说太宽了。

此外,将 CHAR(3) 转换为 VARCHAR(255) 有什么意义?这对我来说似乎非常浪费,但我希望还有其他一些我不知道的原因。

TIMESTAMP 忽略时区属性

TIMESTAMP 类型的字段从明确定义 WITH 定义的时区更改为未定义时区(我知道 TIMESTAMP 输入 PostgreSQL - 这是我的后备数据库引擎 - 默认情况下定义它们时没有时区)。

数值精度类型已更改

定义为 NUMERIC (30,5) 的金额字段在审计 table 中定义为 NUMERIC(19,2)

为什么 Hibernate Envers 在生成 _AUD table 时不考虑原始 table 的列类型?

特例

我还发现它在一个 table 的情况下尊重大小:LocalizedMessage

<entity name="LocalizedMessage" class="com.project.datamodel.to.LocalizedMessageTO" access="FIELD">
        <attributes>
                <id name="id" access="PROPERTY">
                        <generated-value strategy="SEQUENCE" generator="LocalizedMessageIdSeq" />
                        <sequence-generator name="LocalizedMessageIdSeq" sequence-name="LocalizedMessageIdSeq" allocation-size="1" />
                </id>
                <many-to-one name="adminMessage"> <join-column name="adminMessageId" /> </many-to-one>
                <basic name="localeCode"> <column length="7" /> </basic>
                <basic name="message"> <column length="500" /> </basic>
                <version name="version" />
        </attributes>
</entity>

原table定义为:

CREATE TABLE LocalizedMessage (
    id             BIGINT PRIMARY KEY
   ,adminMessageId BIGINT REFERENCES AdminMessage(id)
   ,localeCode     VARCHAR(7)
   ,message        VARCHAR(500)
   ,version        INTEGER NOT NULL DEFAULT 0
);

生成的 _AUD table 是:

CREATE TABLE localizedmessage_aud (
    id bigint NOT NULL,
    rev integer NOT NULL,
    revtype smallint,
    localecode character varying(7),
    localecode_mod boolean,
    message character varying(500),
    message_mod boolean,
    adminmessageid bigint,
    adminmessage_mod boolean
);

Envers 映射文件定义

注意:这是在将长度指令添加到映射文件之后完成的。现在唯一有问题的字段是 TIMESTAMP WITH TIME ZONENUMERIC 字段(精度已更改),以及 CHAR() 字段。 本质上,JPA XML 映射文件的 columnDefinition 属性未得到遵守。

org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl 启用跟踪日志记录生成的日志条目如下所示:

2017-06-10 15:52:19,981 TRACE org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl ~ ------------------------------------------------------------
2017-06-10 15:52:19,984 TRACE org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl ~ Envers-generate entity mapping -----------------------------
<hibernate-mapping auto-import="false">
 <class entity-name="com.project.datamodel.to.TransactionTO_AUD" discriminator-value="Transaction" table="Transaction_AUD" schema="app" abstract="false">
  <composite-id name="originalId">
   <key-property name="id" type="long">
    <column name="id" length="255" scale="2" precision="19"/>
   </key-property>
   <key-many-to-one type="integer" class="com.project.datamodel.to.CustomRevisionEntity" name="REV">
    <column name="REV"/>
   </key-many-to-one>
  </composite-id>
  <property insert="true" update="false" name="REVTYPE" type="org.hibernate.envers.internal.entities.RevisionTypeType"/>
  <property insert="true" update="false" name="closingDate" type="timestamp">
   <column name="closingDate" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="closingDate_MOD" type="boolean"/>
  <property insert="true" update="false" name="country" type="string">
   <column name="country" length="3" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="country_MOD" type="boolean"/>
  <property insert="true" update="false" name="currency" type="string">
   <column name="currency" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="currency_MOD" type="boolean"/>
  <property insert="true" update="false" name="description" type="string">
   <column name="description" length="1000" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="description_MOD" type="boolean"/>
  <property insert="true" update="false" name="amount" type="big_decimal">
   <column name="amount" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="amount_MOD" type="boolean"/>
  <property insert="true" update="false" name="isin" type="string">
   <column name="isin" length="12" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="isin_MOD" type="boolean"/>
  <property insert="true" update="false" name="name" type="string">
   <column name="name" length="120" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="name_MOD" type="boolean"/>
  <property insert="true" update="false" name="rangeEnd" type="timestamp">
   <column name="rangeEnd" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="rangeEnd_MOD" type="boolean"/>
  <property insert="true" update="false" name="rangeStart" type="timestamp">
   <column name="rangeStart" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="rangeStart_MOD" type="boolean"/>
  <property insert="true" update="false" name="status" type="converted::com.project.datamodel.converter.StatusConverter">
   <column name="status" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="status_MOD" type="boolean"/>
 </class>
</hibernate-mapping>

对于 LocalizedMessage table:

2017-06-10 15:52:19,947 TRACE org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl ~ ------------------------------------------------------------
2017-06-10 15:52:19,949 TRACE org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl ~ Envers-generate entity mapping -----------------------------
<?xml version="1.0" encoding="UTF-8"?>

<hibernate-mapping auto-import="false">
 <class entity-name="com.project.datamodel.to.LocalizedMessageTO_AUD" discriminator-value="LocalizedMessage" table="LocalizedMessage_AUD" schema="app" abstract="false">
  <composite-id name="originalId">
   <key-property name="id" type="long">
    <column name="id" length="255" scale="2" precision="19"/>
   </key-property>
   <key-many-to-one type="integer" class="com.project.datamodel.to.CustomRevisionEntity" name="REV">
    <column name="REV"/>
   </key-many-to-one>
  </composite-id>
  <property insert="true" update="false" name="REVTYPE" type="org.hibernate.envers.internal.entities.RevisionTypeType"/>
  <property insert="true" update="false" name="localeCode" type="string">
   <column name="localeCode" length="7" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="localeCode_MOD" type="boolean"/>
  <property insert="true" update="false" name="message" type="string">
   <column name="message" length="500" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="message_MOD" type="boolean"/>
  <property insert="true" update="false" name="adminMessage_id" type="long">
   <column name="adminMessageId" length="255" scale="2" precision="19"/>
  </property>
  <property insert="true" update="false" name="adminMessage_MOD" type="boolean"/>
 </class>
</hibernate-mapping>

为什么它适用于一种情况而不适用于另一种情况?

如我的评论所述,Envers 不会以任何方式从现有数据库架构中解释其架构。其实它的整个映射模型的制作过程都是基于考察Hibernate ORM的boot-time映射模型,你可以在org.hibernate.mapping.

中找到

这意味着对于诸如列长度之类的东西,它们应该通过注释或在您的 XML 映射文件中作为映射模型的一部分提供,以便 Envers 生成使用相同长度的属性或者在某些情况下自定义列定义。

如果您在 XML 映射文件中指定长度并重新生成 Envers 模式,它应该与您的 Hibernate 实体模式非常接近。

更新
您看不到在 Envers 映射中呈现的列定义的原因是因为您的 JPA ORM.XML 文件无效,它没有遵循模式定义,因此 Envers 不仅看不到您的列定义, Hibernate 也不会。

您的映射应该是:

<column column-definition="TIMESTAMP WITH TIME ZONE"/>

您会注意到该属性被命名为 column-definition 而不是 columnDefinition