为什么 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
忽略字段类型和长度
所有 CHAR
和 VARCHAR
字段,无论其长度如何,都已转换为 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 ZONE
和 NUMERIC
字段(精度已更改),以及 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
。
我正在使用 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
忽略字段类型和长度
所有 CHAR
和 VARCHAR
字段,无论其长度如何,都已转换为 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 ZONE
和 NUMERIC
字段(精度已更改),以及 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
。