在JPA中为列设置默认值

301

在JPA中,是否可以为列设置默认值?如果可以,使用注释应该如何实现?


在JPA规范中,是否有可以将一列的默认值设置为另一列的值的选项?因为我希望该列的值保持隐藏。 - shareef
4
相关内容:Hibernate有@org.hibernate.annotations.ColumnDefault("foo") - Ondra Žižka
2
如果您正在使用columnDefinition@ColumnDefault,建议坚持使用标准类型。https://dba.stackexchange.com/questions/53317/databases-are-there-universal-datatypes. - Oliver
20个回答

365

你可以按照以下步骤进行操作:

@Column(name="price")
private double price = 0.0;

好的!你刚刚使用了零作为默认值。

请注意,如果你只从应用程序访问数据库,则这将对你有所帮助。如果其他应用程序也使用该数据库,则应该使用CameroncolumnDefinition注释属性或其他方式在数据库中进行此检查。


41
如果你希望插入后实体具有正确的值,这是正确的做法。+1 - Pascal Thivent
18
在模型类中设置可空属性(具有非基本类型的属性)的默认值会破坏使用“Example”对象作为搜索原型的条件查询。在设置默认值后,Hibernate示例查询将不再忽略先前因为空值而被忽略的相关列。更好的方法是在调用Hibernate的“save()”或“update()”之前设置所有默认值。这样可以更好地模拟数据库的行保存时设置默认值的行为。 - Derek Mahar
2
@Harry:这是设置默认值的正确方式,除非您正在使用将示例作为搜索原型的条件查询。 - Derek Mahar
12
这并不保证对于非原始类型(例如设置为 null)会设置默认值。在我看来,使用 @PrePersist@PreUpdate 是更好的选择。 - Jasper
3
这是指定列的默认值的正确方式。columnDefinition属性不是数据库无关的,而@PrePersist在插入之前会覆盖您的设置。 "默认值"是另一回事,当未显式设置值时使用默认值。 - Utku Özdemir
显示剩余2条评论

277

实际上在JPA中是有可能的,虽然需要使用@Column注解的columnDefinition属性来实现一个小技巧,例如:

@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")

3
整数部分为10,小数部分为2,因此: 1234567890.12 是一个被支持的数字。 - Nathan Feger
27
请注意,这个columnDefinition并不一定独立于数据库,并且也不会自动与Java中的数据类型绑定。 - Nathan Feger
57
创建一个带有空字段的实体并将其持久化后,你不会在其中设置任何值。这不是一个解决方案... - Pascal Thivent
21
不,@NathanFeger,10是长度,2是小数位。因此,1234567890.12不是支持的数字,但12345678.90是有效的。 - GPrimola
7
如果该列可为空(且要避免不必要的列参数),则需要使用 insertable=false。这样做可以使内容更加易懂。 - eckes
显示剩余5条评论

129

另一种方法是使用javax.persistence.PrePersist

@PrePersist
void preInsert() {
   if (this.createdTime == null)
       this.createdTime = new Date();
}

2
我使用了类似这样的方法来添加和更新时间戳。它非常有效。 - Spina
10
这段代码应该改为if (createdt != null) createdt = new Date();或者其他类似的形式。现在这样写会覆盖明确给定的数值,这似乎不是真正的默认值。 - Max Nanasy
20
如果 createdt 为空,则创建一个新的日期对象。 - Shane
添加了 null 检查。 - Ondra Žižka
1
我喜欢这个,因为它允许新对象一开始具有空值,而不是数据库默认值。 - Adam B
显示剩余2条评论

68
在2017年,JPA 2.1仍然只有@Column(columnDefinition='...'),您需要在其中放置列的文字SQL定义。这相当不灵活,并且强制您还要声明其他方面,例如类型,这会使JPA实现对此问题的看法被短路。
然而,Hibernate有这个:
@Column(length = 4096, nullable = false)
@org.hibernate.annotations.ColumnDefault("")
private String description;

通过 DDL 确定要应用于关联列的默认值。

有两点需要注意:

1)不要害怕使用非标准的方法。作为 JBoss 开发人员,我见过很多规范流程。规范基本上是给定领域中的大玩家愿意承诺支持下一个十年左右的基线。对于安全、消息传递、ORM 也是如此(虽然 JPA 已经涵盖了很多)。作为开发人员,我的经验是,在复杂的应用程序中,迟早会需要非标准的 API。而 @ColumnDefault 就是一个例子,它优于使用非标准解决方案的负面影响。

2)大家都喜欢在@PrePersist或构造函数成员初始化时挥手致意,这很好。但这不一样。那么批量SQL更新怎么办?有些语句不设置列怎么办?DEFAULT有其作用,不能通过初始化Java类成员来替代它。


我可以在Wildfly 12中使用哪个版本的hibernate-annotations?我使用特定版本时遇到了错误。提前致谢! - Fernando Pie
谢谢Ondra。2019年还有其他解决方案吗? - Alan
1
很遗憾,JPA库存中没有Alan。 - jwenting
这很遗憾。要获取默认值,您必须连接特定的RDMBS数据类型。 - granadaCoder
1
我更喜欢这个解决方案而非其他。虽然还不完美,但可以保持数据库同步。同时感谢提供文档链接,让我明白在我的情况下需要使用 @DynamicInsert 才能使它正常运行。 - Marquee

14

JPA不支持此功能,如果支持会更加有用。使用columnDefinition是特定于数据库的,而且在许多情况下是不可接受的。在类中设置默认值是不够的,因为当你检索包含null值的记录时(这通常发生在重新运行旧的DBUnit测试时),它不会生效。我所做的是:

public class MyObject
{
    int attrib = 0;

    /** Default is 0 */
    @Column ( nullable = true )
    public int getAttrib()

    /** Falls to default = 0 when null */
    public void setAttrib ( Integer attrib ) {
       this.attrib = attrib == null ? 0 : attrib;
    }
}

Java的自动装箱在这方面有很大帮助。


不要滥用设置器!它们只是用于设置对象字段中的参数,而不是其他。最好使用默认构造函数设置默认值。 - Roland
@Roland 理论上很好,但在处理已经包含空值的现有数据库时,并不总是有效。您需要首先进行数据库转换,在同时使列非空并设置合理默认值的情况下,然后处理其他应用程序的反弹,这些应用程序假定它实际上是可空的(也就是说,如果您甚至可以修改数据库)。 - jwenting
如果涉及到 #JPA 和 setters/getters,它们必须始终是纯粹的setter/getter,否则有一天你的应用程序会不断增长,成为一个维护噩梦。最好的建议是这里要保持简单(我不是同性恋,哈哈)。 - Roland

10
@Column(columnDefinition="tinyint(1) default 1")

我刚刚测试了这个问题。它完全正常工作。谢谢你的提示。


关于评论:

@Column(name="price") 
private double price = 0.0;

这个并没有在数据库中设置默认列值(当然)。


11
在对象级别上它不能正常工作(在实体插入后你将无法获得数据库默认值)。Java级别上有默认值。 - Pascal Thivent
它可以工作(特别是如果您将insertable=false指定到数据库中,但不幸的是缓存版本不会刷新(即使JPA选择表和列)。至少在hibernate中不行 :-/ 但即使它能够工作,额外的往返也是不好的。 - eckes

10

既然我在谷歌上搜索同样的问题时偶然发现了这个,为了让其他人也能够受益,我会提供我想出的解决方案。

在我看来,对于这个问题实际上只有一种解决方案 - @PrePersist。如果你选择在@PrePersist中进行操作,那么你需要检查该值是否已经被设置。


4
+1 - 我肯定会选择 @PrePersist 来解决 OP 的问题。@Column(columnDefinition=...) 看起来不太优雅。 - Piotr Nowicki
1
如果存储器中已有空值数据,则 @PrePersist 不能帮助您。它不会神奇地为您进行数据库转换。 - jwenting

10
我使用columnDefinition,它运行得非常好。
@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;

7
这似乎会使您的JPA实现与特定供应商相关。 - Udo Held
它只会使DDL特定于供应商。但是您很可能已经有供应商特定的DDL黑客了。然而,如上所述,该值(即使insertable=false)会出现在数据库中,但不会出现在会话的实体缓存中(至少在Hibernate中不会)。 - eckes

9

您可以使用Java反射API:

    @PrePersist
    void preInsert() {
       PrePersistUtil.pre(this);
    }

这是常见的:

    public class PrePersistUtil {

        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");


        public static void pre(Object object){
            try {
                Field[] fields = object.getClass().getDeclaredFields();
                for(Field field : fields){
                    field.setAccessible(true);
                    if (field.getType().getName().equals("java.lang.Long")
                            && field.get(object) == null){
                        field.set(object,0L);
                    }else if    (field.getType().getName().equals("java.lang.String")
                            && field.get(object) == null){
                        field.set(object,"");
                    }else if (field.getType().getName().equals("java.util.Date")
                            && field.get(object) == null){
                        field.set(object,sdf.parse("1900-01-01"));
                    }else if (field.getType().getName().equals("java.lang.Double")
                            && field.get(object) == null){
                        field.set(object,0.0d);
                    }else if (field.getType().getName().equals("java.lang.Integer")
                            && field.get(object) == null){
                        field.set(object,0);
                    }else if (field.getType().getName().equals("java.lang.Float")
                            && field.get(object) == null){
                        field.set(object,0.0f);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

2
我喜欢这个解决方案,但有一个弱点,我认为在设置默认值之前检查列是否可为空是很重要的。 - Luis Carlos
如果您喜欢,也可以将Field[] fields = object.getClass().getDeclaredFields();的代码放入for()中。并且在参数/捕获异常中添加final,以防止意外修改object。还要添加一个对null的检查:if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }。这确保了object.getClass()是安全的调用,不会触发NPE。原因是为了避免懒惰程序员的错误。;-) - Roland

9
  1. @Column(columnDefinition='...')在插入数据时,如果你在数据库中设置默认约束条件,它就不起作用了。
  2. 你需要将insertable = false添加到注释中,并从注释中删除columnDefinition='...',然后数据库将自动从数据库中插入默认值。
  3. 例如当你在数据库中将varchar gender 默认为男性。
  4. 你只需要在Hibernate/JPA中添加insertable = false,它就能正常工作。

好的回答。这里是关于insertable-false-updatable-false的解释 - Shihe Zhang
1
而且如果您想有时设置其值,这并不是一个好选择。 - Shihe Zhang
@ShiheZhang,使用false无法让我设置该值? - fvildoso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接