避免通过 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,即通用 CRUD 操作的接口。一旦您创建了存储库并将其注入另一个组件,Spring Data 就会自动提供实现,您就可以添加审计功能了。

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”值插入数据库表的主要内容,如果未能解决你的问题,请参考以下文章

Spring data jpa 插入多个表以避免锁定表

如果属性对 JPA 无效,我如何在数据库中插入默认值

在将数据从文件插入表时,如何用 NULL 替换 NA 值?

使用 jpa 将数据插入到 2 个连接表中

sql表插入行问题,部分列插入NULL值

如何使用类中的 setters 方法将数据插入 JPA 实体表?