【问题标题】:Why does Hibernate Envers not respect the column type in the original table?为什么 Hibernate Envers 不尊重原始表中的列类型?
【发布时间】:2017-10-14 02:58:18
【问题描述】:

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

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 表,它输出以下内容:

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) 字段。

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

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

TIMESTAMP时区属性被忽略

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

数值精度类型已更改

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

为什么Hibernate Envers在生成_AUD表时不尊重原表的列类型?

例外情况

我还发现它尊重一张桌子的大小: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>

原来的表定义是:

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 表是:

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 表:

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>

为什么它在一种情况下正确应用而不在另一种情况下正确应用?

【问题讨论】:

  • 您能提供交易实体的实体映射吗?您能否还指定您使用的是哪个版本的 Hibernate 和 Hibernate Envers?
  • @Naros 谢谢!我已经添加了 XML 映射文件...我在 SQL 中手动创建了原始表。你让我想到也许我应该在 XML 映射文件中添加所有这些大小? (让Envers从那里接他们)是否无法查询数据库中的表结构?
  • Envers 的映射完全基于 Hibernate 的映射模型提供的数据,因此它不直接查看数据库模式。由于您的 XML 映射未指定任何列长度,因此 JPA 默认值 255 用于字符串。如果您在 XML 映射文件中指定列长度,Envers 应该会自动调整。
  • @Naros 我已将大小/列定义添加到映射文件中,但审计表中的列仍然生成不正确!我在其他地方有什么遗漏吗?
  • 您使用的是哪种 Postgres 方言?我很好奇你是否允许 Hibernate 和 Envers 创建他们的表,如果你观察到所有表都使用相同的不同字符数据类型,区别在于你使用的方言是手动创建还是自动创建的产物。您可以做的一件事是为org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl 启用跟踪日志记录,以查看为每个实体映射生成的 XML Envers。您将看到 Envers 生成 type="string",它只是 Hibernate 和选择字符的方言选择。

标签: hibernate-envers


【解决方案1】:

正如我在评论中指出的那样,Envers 不会以任何方式从现有数据库架构中解释其架构。其实它的整个映射模型制作过程都是基于检查Hibernate ORM的启动时映射模型,你可以在org.hibernate.mapping找到。

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

如果您在 XML 映射文件中指定长度并重新生成 Envers 架构,它应该与您的 Hibernate 实体架构非常紧密地对齐。

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

您的映射应该是:

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

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

【讨论】:

    猜你喜欢
    • 2016-07-10
    • 2017-03-27
    • 2014-03-03
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 2011-07-12
    • 2021-02-15
    相关资源
    最近更新 更多