Установка значений по умолчанию для столбцов в JPA

245

Можно ли установить значение по умолчанию для столбцов в JPA, и если, как это делается с использованием аннотаций?

homaxto
источник
есть ли в разделении JPA, где я могу поставить значение по умолчанию для столбца в качестве значения для другого столбца, потому что я хочу, чтобы он был скрыт
shareef
3
Связанный: Hibernate имеет @ org.hibernate.annotations.ColumnDefault ("foo")
Ондра Жижка

Ответы:

230

На самом деле это возможно в JPA, хотя немного взломать, используя columnDefinitionсвойство @Columnаннотации, например:

@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
Кэмерон Поуп
источник
3
10 - это целочисленная часть, а 2 - десятичная часть числа, поэтому 1234567890.12 будет поддерживаемым числом.
Натан Фегер
23
Также обратите внимание, что это columnDefinition не обязательно зависит от базы данных и, конечно, не связывается автоматически с типом данных в Java.
Натан Фегер
47
После создания сущности с нулевым полем и ее сохранения у вас не будет установленного значения. Не решение ...
Паскаль Тивент
18
Нет @NathanFeger, 10 - это длина, а 2 - десятичная часть. Таким образом, 1234567890.12 не будет поддерживаемым числом, но 12345678.90 является действительным.
GPrimola
4
Вам понадобится, insertable=falseесли столбец обнуляемый (и чтобы избежать ненужного аргумента столбца).
Eckes
314

Вы можете сделать следующее:

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

Там! Вы только что использовали ноль в качестве значения по умолчанию.

Обратите внимание, что это будет вам полезно, если вы обращаетесь к базе данных только из этого приложения. Если другие приложения также используют базу данных, вы должны выполнить эту проверку из базы данных, используя атрибут аннотации columnDefinition Камерона , или каким-либо другим способом.

Пабло Вентурино
источник
38
Это правильный способ сделать это, если вы хотите, чтобы ваши сущности имели правильное значение после вставки. +1
Паскаль Тивент
16
Установка значения по умолчанию для атрибута, допускающего значение NULL, (атрибута, который имеет непримитивный тип) в классе модели нарушит критерии запросов, которые используют Exampleобъект в качестве прототипа для поиска. После установки значения по умолчанию пример запроса Hibernate больше не будет игнорировать связанный столбец, где ранее он игнорировал его, поскольку он был нулевым. Лучше всего установить все значения по умолчанию непосредственно перед вызовом Hibernate save()или update(). Это лучше имитирует поведение базы данных, которая устанавливает значения по умолчанию при сохранении строки.
Дерек Махар
1
@Harry: это правильный способ установить значения по умолчанию, за исключением случаев, когда вы используете критерии запросов, которые используют пример в качестве прототипа для поиска.
Дерек Махар
12
Это не гарантирует, что по умолчанию установлено для не примитивных типов (настройка, nullнапример). Использование @PrePersistи @PreUpdateявляется лучшим вариантом imho.
Джаспер
3
Это правильный способ указать значение по умолчанию для столбца. columnDefinitionсвойство не зависит от базы данных и @PrePersistпереопределяет ваши настройки перед вставкой, «значение по умолчанию» - это нечто другое, значение по умолчанию используется, когда значение не задано явно.
Утку Оздемир
110

Другой подход заключается в использовании javax.persistence.PrePersist

@PrePersist
void preInsert() {
   if (this.createdTime == null)
       this.createdTime = new Date();
}
Хусин Виджая
источник
1
Я использовал такой подход для добавления и обновления временных меток. Это сработало очень хорошо.
Спина
10
Не должно ли это быть if (createdt != null) createdt = new Date();или что-то? Прямо сейчас это переопределит явно указанное значение, которое, по-видимому, не будет по умолчанию.
Макс Нанаси
16
@MaxNanasyif (createdt == null) createdt = new Date();
Шейн
Будет ли иметь значение, если клиент и база данных находятся в разных часовых поясах? Я предполагаю, что использование чего-то вроде «TIMESTAMP DEFAULT CURRENT_TIMESTAMP» получит текущее время на сервере базы данных, а «createt = new Date ()» получит текущее время в коде Java, но эти два раза могут отличаться, если клиент подключается к серверу в другом часовом поясе.
FrustratedWithFormsDesigner
Добавлена nullпроверка.
Ондра Жижка
49

В 2017 году в JPA 2.1 остается только то, @Column(columnDefinition='...')к чему вы добавили буквальное определение SQL столбца. Что довольно негибко и заставляет вас также объявлять другие аспекты, такие как тип, кратко излагая взгляд реализации JPA по этому вопросу.

Hibernate, хотя, имеет это:

@Column(length = 4096, nullable = false)
@org.hibernate.annotations.ColumnDefault("")
private String description;

Определяет значение DEFAULT для применения к связанному столбцу с помощью DDL.

Два примечания к этому:

1) Не бойтесь идти нестандартно. Работая в качестве разработчика JBoss, я видел довольно много процессов спецификации. Спецификация в основном является базовой линией, которую крупные игроки в данной области готовы взять на себя обязательство поддержать в течение следующего десятилетия или около того. Это верно для безопасности, для обмена сообщениями, ORM не имеет значения (хотя JPA охватывает довольно много). Мой опыт как разработчика заключается в том, что в сложных приложениях рано или поздно вам все равно понадобится нестандартный API. И @ColumnDefaultэто пример, когда он перевешивает недостатки использования нестандартного решения.

2) Приятно, что все машут инициализацией @PrePersist или членом конструктора. Но это не то же самое. Как насчет массовых обновлений SQL? Как насчет утверждений, которые не устанавливают столбец? DEFAULTимеет свою роль, и это не может быть заменено путем инициализации члена класса Java.

Ондра Жижка
источник
Какую версию hibernate-аннотаций я могу использовать для Wildfly 12, я улавливаю ошибку с определенной версией этого? Спасибо заранее!
Фернандо Пирог
Спасибо Ондра. Есть ли еще решения в 2019 году?
Алан
1
@ Алана нет в наличии JPA, к сожалению.
jwenting
13

JPA не поддерживает это, и было бы полезно, если бы это было. Использование columnDefinition зависит от БД и во многих случаях неприемлемо. установка значения по умолчанию в классе недостаточна, когда вы извлекаете запись с нулевыми значениями (что обычно происходит при повторном запуске старых тестов 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-бокс помогает в этом.

Marco
источник
Не ругайте сеттеров! Они предназначены для установки параметра в поле объекта. Ничего более! Лучше использовать конструктор по умолчанию для значений по умолчанию.
Роланд
@Roland хорош в теории, но не всегда будет работать при работе с существующей базой данных, которая уже содержит нулевые значения, где ваше приложение хотело бы, чтобы их не было. Сначала вам нужно будет выполнить преобразование базы данных, где вы сделаете столбец не равным NULL и в то же время установите разумное значение по умолчанию, а затем справитесь с негативной реакцией со стороны других приложений, которые предполагают, что это фактически обнуляется (то есть, если вы Можно даже изменить базу данных).
jwenting
Если речь идет о #JPA и установщиках / получателях, они всегда должны быть чистыми установщиками / получателями, иначе однажды ваше приложение выросло и выросло и стало кошмаром обслуживания. Лучший совет - ПОЦЕЛУЙ здесь (я не гей, LOL).
Роланд
10

Видя, как я наткнулся на это от Google, пытаясь решить ту же самую проблему, я просто добавлю решение, которое я приготовил, на случай, если кто-то посчитает его полезным.

С моей точки зрения, на самом деле есть только 1 решение этой проблемы - @PrePersist. Если вы делаете это в @PrePersist, вы должны проверить, установлено ли уже значение.

ТС1
источник
4
+1 - я бы определенно выбрал @PrePersistвариант использования ОП. @Column(columnDefinition=...)не выглядит очень элегантно
Петр Новицкий,
@PrePersist не поможет вам, если в хранилище уже есть данные с нулевыми значениями. Он волшебным образом не сделает преобразование базы данных для вас.
jwenting
10
@Column(columnDefinition="tinyint(1) default 1")

Я только что проверил проблему. Работает просто отлично. Спасибо за подсказку.


О комментариях:

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

Этот не устанавливает значение столбца по умолчанию в базе данных (конечно).

ASD
источник
9
Это не работает нормально на уровне объекта (вы не получите значение по умолчанию для базы данных после вставки в вашу сущность). По умолчанию на уровне Java.
Паскаль Тивент
Это работает (особенно если вы указываете вставку = false в базу данных, но, к сожалению, кешированная версия не обновляется (даже когда JPA выбирает таблицу и столбцы). По крайней мере, не с 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
Мне нравится это решение, но есть одна слабость, я думаю, что было бы важно проверить, является ли столбец обнуляемым или нет до того, как установить значение по умолчанию.
Луис Карлос
Если вы предпочитаете, вставьте код Field[] fields = object.getClass().getDeclaredFields();в также for()может быть хорошо. А также добавьте finalв ваш параметр / перехваченные исключения, так как вы не хотите, чтобы objectих модифицировали случайно. Кроме того, добавить проверку на null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Это гарантирует, что object.getClass()это безопасно для вызова и не вызывает NPE. Причина в том, чтобы избежать ошибок ленивых программистов. ;-)
Роланд
7

Я использую, columnDefinitionи это работает очень хорошо

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;
Tong
источник
6
Похоже, что это делает ваш поставщик реализации jpa конкретным.
Удо состоялся
Это только делает конкретного поставщика DDL. Но, скорее всего, в любом случае у вас есть DDL-хаки, зависящие от поставщика. Однако, как уже упоминалось выше, значение (даже когда вставляемый = false) отображается в БД, но не в кеше сущностей сеанса (по крайней мере, в спящем режиме).
Eckes
7

Вы не можете сделать это с аннотацией столбца. Я думаю, что единственный способ - установить значение по умолчанию при создании объекта. Может быть, конструктор по умолчанию будет правильным местом для этого.

Timo
источник
Хорошая идея. К сожалению, нет общих аннотаций или атрибутов @Columnвокруг. И я также скучаю по комментариям (взятым из Java doctag).
Роланд
5

В моем случае я изменил исходный код ядра Hibernate, ну, чтобы ввести новую аннотацию @DefaultValue:

commit 34199cba96b6b1dc42d0d19c066bd4d119b553d5
Author: Lenik <xjl at 99jsj.com>
Date:   Wed Dec 21 13:28:33 2011 +0800

    Add default-value ddl support with annotation @DefaultValue.

diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/DefaultValue.java b/hibernate-core/src/main/java/org/hibernate/annotations/DefaultValue.java
new file mode 100644
index 0000000..b3e605e
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/annotations/DefaultValue.java
@@ -0,0 +1,35 @@
+package org.hibernate.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Specify a default value for the column.
+ *
+ * This is used to generate the auto DDL.
+ *
+ * WARNING: This is not part of JPA 2.0 specification.
+ *
+ * @author 谢继雷
+ */
+@java.lang.annotation.Target({ FIELD, METHOD })
+@Retention(RUNTIME)
+public @interface DefaultValue {
+
+    /**
+     * The default value sql fragment.
+     *
+     * For string values, you need to quote the value like 'foo'.
+     *
+     * Because different database implementation may use different 
+     * quoting format, so this is not portable. But for simple values
+     * like number and strings, this is generally enough for use.
+     */
+    String value();
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java
index b289b1e..ac57f1a 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java
@@ -29,6 +29,7 @@ import org.hibernate.AnnotationException;
 import org.hibernate.AssertionFailure;
 import org.hibernate.annotations.ColumnTransformer;
 import org.hibernate.annotations.ColumnTransformers;
+import org.hibernate.annotations.DefaultValue;
 import org.hibernate.annotations.common.reflection.XProperty;
 import org.hibernate.cfg.annotations.Nullability;
 import org.hibernate.mapping.Column;
@@ -65,6 +66,7 @@ public class Ejb3Column {
    private String propertyName;
    private boolean unique;
    private boolean nullable = true;
+   private String defaultValue;
    private String formulaString;
    private Formula formula;
    private Table table;
@@ -175,7 +177,15 @@ public class Ejb3Column {
        return mappingColumn.isNullable();
    }

-   public Ejb3Column() {
+   public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public void setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    public Ejb3Column() {
    }

    public void bind() {
@@ -186,7 +196,7 @@ public class Ejb3Column {
        }
        else {
            initMappingColumn(
-                   logicalColumnName, propertyName, length, precision, scale, nullable, sqlType, unique, true
+                   logicalColumnName, propertyName, length, precision, scale, nullable, sqlType, unique, defaultValue, true
            );
            log.debug( "Binding column: " + toString());
        }
@@ -201,6 +211,7 @@ public class Ejb3Column {
            boolean nullable,
            String sqlType,
            boolean unique,
+           String defaultValue,
            boolean applyNamingStrategy) {
        if ( StringHelper.isNotEmpty( formulaString ) ) {
            this.formula = new Formula();
@@ -217,6 +228,7 @@ public class Ejb3Column {
            this.mappingColumn.setNullable( nullable );
            this.mappingColumn.setSqlType( sqlType );
            this.mappingColumn.setUnique( unique );
+           this.mappingColumn.setDefaultValue(defaultValue);

            if(writeExpression != null && !writeExpression.matches("[^?]*\\?[^?]*")) {
                throw new AnnotationException(
@@ -454,6 +466,11 @@ public class Ejb3Column {
                    else {
                        column.setLogicalColumnName( columnName );
                    }
+                   DefaultValue _defaultValue = inferredData.getProperty().getAnnotation(DefaultValue.class);
+                   if (_defaultValue != null) {
+                       String defaultValue = _defaultValue.value();
+                       column.setDefaultValue(defaultValue);
+                   }

                    column.setPropertyName(
                            BinderHelper.getRelativePath( propertyHolder, inferredData.getPropertyName() )
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
index e57636a..3d871f7 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
@@ -423,6 +424,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
                getMappingColumn() != null ? getMappingColumn().isNullable() : false,
                referencedColumn.getSqlType(),
                getMappingColumn() != null ? getMappingColumn().isUnique() : false,
+               null, // default-value
                false
        );
        linkWithValue( value );
@@ -502,6 +504,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
                getMappingColumn().isNullable(),
                column.getSqlType(),
                getMappingColumn().isUnique(),
+               null, // default-value
                false //We do copy no strategy here
        );
        linkWithValue( value );

Ну, это решение только для гибернации.

Xiè Jìléi
источник
2
Хотя я действительно ценю усилия людей, которые активно участвуют в проекте с открытым исходным кодом, я отклонил этот ответ, потому что JPA - это стандартная спецификация Java поверх любого OR / M, OP попросил JPA способ указать значения по умолчанию, и ваш патч работает только для Hibernate. Если бы это был NHibernate, в котором нет спецификации super-partes для персистентности (NPA даже не поддерживается MS EF), я бы проголосовал за такой патч. Правда в том, что JPA довольно ограничен по сравнению с требованиями ORM (один пример: никаких вторичных индексов). В любом случае, похвала усилиям
usr-local-ΕΨΗΕΛΩΝ
Разве это не создает проблему обслуживания? Нужно будет повторять эти изменения каждый раз при обновлении в спящем режиме или при каждой новой установке?
user1242321
Поскольку вопрос касается JPA, а Hibernate даже не упоминается, это не отвечает на вопрос
Нил Стоктон
5
  1. @Column(columnDefinition='...') не работает при установке ограничения по умолчанию в базе данных при вставке данных.
  2. Вы должны сделать insertable = falseи удалить columnDefinition='...'из аннотации, тогда база данных автоматически вставит значение по умолчанию из базы данных.
  3. Например, если вы установите varchar пол по умолчанию мужской в ​​базе данных.
  4. Вам просто нужно добавить insertable = falseв Hibernate / JPA, это будет работать.
Appesh
источник
И не очень хороший вариант, если вы хотите установить его значение иногда.
Шихе Чжан
@ShiheZhang с использованием false не позволяет мне установить значение?
fvildoso
3
@PrePersist
void preInsert() {
    if (this.dateOfConsent == null)
        this.dateOfConsent = LocalDateTime.now();
    if(this.consentExpiry==null)
        this.consentExpiry = this.dateOfConsent.plusMonths(3);
}

В моем случае из-за поля LocalDateTime я использовал это, это рекомендуется из-за независимости поставщика

Мохаммед Рафик
источник
2

Ни JPA, ни аннотации Hibernate не поддерживают понятие значения столбца по умолчанию. Чтобы обойти это ограничение, установите все значения по умолчанию непосредственно перед вызовом Hibernate save()или update()в сеансе. Это настолько близко, насколько это возможно (если не считать Hibernate, устанавливающий значения по умолчанию), имитирующее поведение базы данных, которая устанавливает значения по умолчанию при сохранении строки в таблице.

В отличие от установки значений по умолчанию в классе модели, как предполагает этот альтернативный ответ , этот подход также гарантирует, что запросы критериев, которые используют Exampleобъект в качестве прототипа для поиска, будут продолжать работать как прежде. Когда вы устанавливаете значение по умолчанию для атрибута, допускающего значение NULL, (атрибута, который имеет непримитивный тип) в классе модели, запрос Hibernate по примеру больше не будет игнорировать связанный столбец, где ранее он игнорировал его, поскольку он был нулевым.

Дерек Махар
источник
В предыдущем решении автор использовал ColumnDefault ("")
nikolai.serdiuk
@ nikolai.serdiuk, что аннотация ColumnDefault была добавлена ​​спустя годы после написания этого ответа. В 2010 году это было правильно, такой аннотации не было (на самом деле аннотаций не было, только конфигурация xml).
jwenting
1

Это невозможно в JPA.

Вот что вы можете сделать с аннотацией столбца: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html

PEELY
источник
1
Невозможность указать значение по умолчанию кажется серьезным недостатком аннотаций JPA.
Дерек Махар
2
JDO позволяет это в своем определении ORM, поэтому JPA следует включить это ... однажды
user383680
Вы определенно можете сделать это с атрибутом columnDefinition, как ответил Кэмерон Поуп.
IntelliData
@DerekMahar это не единственный недостаток в спецификации JPA. Это хорошая спецификация, но она не совершенна
jwenting
1

Если вы используете двойной, вы можете использовать следующее:

@Column(columnDefinition="double precision default '96'")

private Double grolsh;

Да, это специфично для БД.

Гал Брача
источник
0

Вы можете определить значение по умолчанию в конструкторе базы данных или при создании таблицы. Например, в SQL Server вы можете установить хранилище по умолчанию для поля Дата в ( getDate()). Используйте, insertable=falseкак указано в определении вашего столбца. JPA не будет указывать этот столбец на вставках, и база данных сгенерирует значение для вас.

Дейв Андерсон
источник
-2

Вам нужно insertable=falseв вас @Columnаннотации. JPA будет игнорировать этот столбец при вставке в базу данных, и будет использоваться значение по умолчанию.

Смотрите эту ссылку: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html

MariemJab
источник
2
Тогда как вы вставите данное значение @runtime ??
Ашиш Ратан
Да это правда. nullable=falseпотерпит неудачу с SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Здесь я забыл установить «созданную» метку времени с помощью openingTime.setOpeningCreated(new Date()). Это хороший способ иметь последовательность, но это то, что спрашивающий не спрашивал.
Роланд