避免通过 JPA 将“null”值插入数据库表
Posted
技术标签:
【中文标题】避免通过 JPA 将“null”值插入数据库表【英文标题】:Avoid insert 'null' values to database table via JPA 【发布时间】:2011-05-29 08:39:40 【问题描述】:我从 JPA2 开始,到目前为止感觉很舒服。但是在为具有默认值的 NON NULL 数据库字段保留具有 null 属性值的实体时,我遇到了问题。
我希望能够将实体属性保留为空并让数据库插入默认值。
我当前的设置是带有 PostgreSQL 的 openJPA。
我有这个 VERSION 数据库表(Vorgabewert = 默认值):
Spalte | Typ | Attribute
----------------+-----------------------------+----------------------------
status_ | smallint | not null Vorgabewert 0
time_ | timestamp without time zone | not null
system_time | timestamp without time zone | not null Vorgabewert now()
version | character varying(20) | not null
activationtime | timestamp without time zone |
importtime | timestamp without time zone |
我有一个实体 (Java DTO),它通过 xml 配置映射数据库字段(“状态”除外)。
我希望我可以插入一个没有设置system_time
的实体,并希望数据库将当前时间填充为默认值。
JPA 构造以下 SQL 查询:
INSERT INTO public.version (version, activationtime, importtime, system_time, time_) VALUES (?, ?, ?, ?, ?) [params=?, ?, ?, ?, ?]
Postgres 的反应是:
FEHLER: NULL-Wert in Spalte »system_time« verletzt Not-Null-Constraint
(对不起德语,但此消息表示“system_time”上的 Not-Null-Constraint 违规)。
那我该怎么办?这是 JPA 还是数据库问题。 我可以将 JPA 配置为从 INSERT SQL 语句中排除空属性吗?
我希望能够在我的实体中设置“system_time”或让它为“null”并让数据库放置默认值。
欢迎任何帮助!
问候 克劳斯
【问题讨论】:
【参考方案1】:我不会将数据库中的默认值与 JPA 结合使用。您必须在插入后回读实体,否则实体状态和数据库状态之间不匹配。
这里选择务实的方法,初始化java中的所有值。从未听说过告诉 JPA/Hibernate 在插入/更新中省略空值的方法。
【讨论】:
好点 - 我希望持久化对象并将其置于“托管”状态将包括一种回读操作。 接受了这个答案,因为重要的“状态不匹配”注释。答案还指出,没有办法做我想做的事情:-(。【参考方案2】:使用注解
@Column(insertable = false)
将阻止在 sql 中生成值。
【讨论】:
提示 +1 - 但这也可以防止生成非空值。在这种情况下,我想在 SQL 语句中有值。似乎没有办法实现这种行为.. 啊,我明白了。没有办法做到这一点。您必须决定是由 Java 还是 DBMS 负责时间,您不能同时拥有两者。 您是否可以将两个实体映射到同一个表,其中一个将列定义为insertable=false
,而另一个没有,通过使用一个或另一个来控制自动生成?我只是出于理论上的兴趣才问这个问题——这显然是一件很疯狂的事情。
我希望 Hibernate 对这类事情进行健全性检查,尽管我从未尝试过。也许如果您同时使用两个完全独立的 Hibernate 实例......
还有 updatable = false
从更新语句中删除该列。不是 OP 所要求的,而是相关的【参考方案3】:
在注释 @Column 中将属性 insertable 设置为 false,如下所示:
`@Column(name="system_time", insertable = false)`或者,如果您需要检查该值何时为 NULL,则在表上插入前触发。
【讨论】:
【参考方案4】:来自文档:columnDefinition:为列生成 DDL 时使用的 SQL 片段。
通过使用columnDefinition,您可以根据需要指定约束。
@Column(name="COLUMN_NAME",
columnDefinition="DATE DEFAULT CURRENT_DATE",table="TABLE_NAME")
否则,您可以尝试在实体本身中初始化字段以摆脱这种情况。
@Column(name = "somedate", nullable = false)
private Date someDate = new Date();
因此,如果您不设置,默认情况下会插入当前日期。
【讨论】:
正如您所引用的,此定义仅在为列生成 DDL 时使用(用于创建表)。因此,在运行应用程序时,它对 SQL 语句中的空值没有影响(尝试在此列中插入 NULL 时我仍然遇到违规行为)。这也发生在普通 SQL 中,所以我正在 JPA 中寻找一种配置方式,以便在生成的(INSERT)SQL 语句为空时排除列。 默认值仍然无济于事,因为只有在 INSERT INTO 子句中没有明确列出列时才会使用默认值 初始化默认值肯定会有所帮助 - 但如果某些进程将值设置回 NULL,则插入将失败。 可以在实体的setter-method中配置,只有不为null时才设置值。 是否可以在列定义或其他方式中指定约束名称。如果是,请说明。谢谢【参考方案5】:我找到了一个简单的解决方案: 在 pojo 中定义一个默认值。
@Column(name="system_time")
private Date systemTime = new Date();
【讨论】:
【参考方案6】:要在 hibernate insert sql stmt 中排除 null 属性值,请使用属性 dynamic-insert=true。
1.@org.hibernate.annotations.Entity(dynamicInsert = true) 在类上添加这个 2.在xml映射中,在class标签中使用dynamic-insert=true属性。
【讨论】:
【参考方案7】:对于那些将 Spring Framework 与 JPA 一起使用的人,请查看本文的第 4 节以获得一个优雅的解决方案。 所有功劳归于 BAELDUNG
https://www.baeldung.com/database-auditing-jpa
注意:粘贴文章摘录以保留内容
Spring Data JPA 是一个框架,它通过在 JPA 提供者的顶部添加一个额外的抽象层来扩展 JPA。该层允许通过扩展 Spring JPA 存储库接口来支持创建 JPA 存储库。
出于我们的目的,您可以扩展 CrudRepository
4.1。启用 JPA 审计 首先,我们希望通过注释配置启用审计。为此,只需在您的 @Configuration 类中添加 @EnableJpaAuditing:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig ...
4.2。添加 Spring 的实体回调监听器 我们已经知道,JPA 提供了@EntityListeners 注解来指定回调监听器类。 Spring Data 提供了自己的 JPA 实体监听器类:AuditingEntityListener。所以让我们为 Bar 实体指定监听器:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar ...
现在,监听器将在持久化和更新 Bar 实体时捕获审计信息。
4.3。跟踪创建日期和最后修改日期 接下来,我们将添加两个新属性,用于将创建日期和最后修改日期存储到我们的 Bar 实体中。属性由 @CreatedDate 和 @LastModifiedDate 注释相应地注释,并且它们的值是自动设置的:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar
//...
@Column(name = "created_date", nullable = false, updatable = false)
@CreatedDate
private long createdDate;
@Column(name = "modified_date")
@LastModifiedDate
private long modifiedDate;
//...
通常,您会将属性移动到一个基类(由@MappedSuperClass 注释),该基类将由您的所有审计实体进行扩展。在我们的示例中,为了简单起见,我们将它们直接添加到 Bar。
4.4。使用 Spring Security 审计更改的作者 如果您的应用使用 Spring Security,您不仅可以跟踪更改的时间,还可以跟踪更改的人员:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar
//...
@Column(name = "created_by")
@CreatedBy
private String createdBy;
@Column(name = "modified_by")
@LastModifiedBy
private String modifiedBy;
//...
使用 @CreatedBy 和 @LastModifiedBy 注释的列填充有创建或最后修改实体的主体的名称。该信息是从 SecurityContext 的 Authentication 实例中提取的。如果您想自定义设置为注释字段的值,您可以实现 AuditorAware 接口:
public class AuditorAwareImpl implements AuditorAware<String>
@Override
public String getCurrentAuditor()
// your custom logic
为了将应用程序配置为使用 AuditorAwareImpl 来查找当前主体,请声明使用 AuditorAwareImpl 实例初始化的 AuditorAware 类型的 bean,并在 @EnableJpaAuditing 中将 bean 的名称指定为 auditAwareRef 参数的值:
@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig
//...
@Bean
AuditorAware<String> auditorProvider()
return new AuditorAwareImpl();
//...
【讨论】:
【参考方案8】:我使用这个(针对 Oracle 数据库):
@Temporal(TemporalType.TIMESTAMP)
@Column(name="CREATION_TIME",
columnDefinition="TIMESTAMP DEFAULT SYSTIMESTAMP",
nullable=false,
insertable=false,
updatable=false)
private Date creationTime;
【讨论】:
【参考方案9】:您可以通过在 @column 注释中添加 nullable=false 来避免 null 属性。像这样
@Column(name = "muColumn", nullable = false)
how to avoid the null property in insert or update
【讨论】:
【参考方案10】:只需从 INSERT 语句的列中省略 system_time。
【讨论】:
INSERT 语句由 JPA EntityManager 在持久化实体 (javax.persistence.EntityManager.persist(Object)) 时生成。如果为空,如何配置 JPA 以省略该列? 不确定 JPA,但 Hibernate 支持注释“dynamicInsert = true, dynamicUpdate = true”,它不会发送任何未更改的行。 Hibernate 还支持属性映射上的“insert='false'”和“update='false'”,以从生成的静态插入/更新 SQL 中省略某些列。在这里映射“insert='false' generated='insert'”可能是合适的(虽然不确定如何将其转换为注释配置)。【参考方案11】:我不知道在 JPA 中有什么方法可以做到这一点。
但是您可以向数据库添加一个触发器,该触发器会捕获将 NULL 插入相关列的尝试,并重写插入以插入默认值?本质上,您将默认生成行为完全下移到数据库中,而不是在 JPA 和数据库之间拆分它。
但是,正如 bert 在他的回答中指出的那样,这会使对象和数据库不一致,这几乎可以肯定是一个糟糕的想法。
【讨论】:
【参考方案12】:你可以这样使用:
@JoinColumn(name = "team_id", nullable = false)
使用这种方式,JPA 将检查NULL
值。
【讨论】:
以上是关于避免通过 JPA 将“null”值插入数据库表的主要内容,如果未能解决你的问题,请参考以下文章