Встановлення значень за замовчуванням для стовпців у JPA


245

Чи можна встановити значення за замовчуванням для стовпців у JPA, і якщо, як це робиться за допомогою приміток?


чи є в секпсифікації JPA, де я можу поставити значення за замовчуванням для стовпця як значення для іншого стовпця, тому що я хочу його приховати
shareef

3
Пов'язане: Hibernate має @ org.hibernate.annotations.ColumnDefault ("foo")
Ondra Žižka

Відповіді:


230

Насправді це можливо в JPA, хоча трохи зламати, використовуючи columnDefinitionвластивість @Columnанотації, наприклад:

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

3
10 - ціла частина, а 2 - десяткова частина числа, тому: 1234567890.12 буде підтримуваним числом.
Натан Фегер

23
Зауважте також, що цей стовпчикDefinition не обов'язково є незалежним від бази даних, і, звичайно, не автоматично прив'язує тип даних на Java.
Натан Фегер

47
Створивши сутність з нульовим полем та збереживши його, у вас би не було вказано значення. Не рішення ...
Паскаль Thivent

18
Ні @NathanFeger, 10 - це довжина і 2 - десяткова частина. Так 1234567890.12 не буде підтримуваним номером, але 12345678.90 є дійсним.
GPrimola

4
Вам знадобиться, insertable=falseякщо стовпець є нульовим (і щоб уникнути зайвого аргументу стовпця).
eckes

314

Ви можете зробити наступне:

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

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

Зверніть увагу, це послужить вам, якщо ви лише отримуєте доступ до бази даних з цієї програми. Якщо інші програми також використовують базу даних, вам слід зробити цю перевірку з бази даних, використовуючи атрибут анотації стовпця CameD ''s columnDefinition або іншим способом.


38
Це правильний спосіб зробити це, якщо ви хочете, щоб ваші сутності мали правильне значення після вставки. +1
Паскаль Thivent

16
Встановлення за замовчуванням атрибута, що зводиться до нуля (того, у якого є непомітивний тип), в класі моделі буде порушено запити критеріїв, які використовують Exampleоб'єкт як прототип для пошуку. Після встановлення значення за замовчуванням, запит зі сплячого режиму більше не буде ігнорувати пов’язаний стовпець, де раніше він ігнорував би його, оскільки він був недійсним. Кращим підходом є встановлення всіх значень за замовчуванням безпосередньо перед тим, як викликати сплячку save()або update(). Це краще імітує поведінку бази даних, яка встановлює значення за замовчуванням, коли вона зберігає рядок.
Дерек Махар

1
@Harry: Це правильний спосіб встановити значення за замовчуванням, за винятком випадків, коли ви використовуєте запити критеріїв, які використовують приклад як прототип для пошуку.
Дерек Махар

12
Це не забезпечує встановлення за замовчуванням для непомітивних типів ( nullнаприклад, встановлення). Використання @PrePersistі @PreUpdateє кращим варіантом imho.
Джаспер

3
Це правильний спосіб вказати значення стовпця за замовчуванням. columnDefinitionвластивість не залежить від бази даних і @PrePersistпереосмислює ваші настройки перед вставкою, "значення за замовчуванням" - це щось інше, значення за замовчуванням використовується, коли значення не встановлено явно.
Utku Özdemir

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" отримає поточний час на сервері баз даних, а "createdt = new Date ()" отримає поточний час у коді Java, але ці два рази не можуть бути однаковими, якщо клієнт підключається до сервера в іншому часовому поясі.
FrustratedWithFormsDesigner

Додано nullчек.
Ondra Žižka

49

У 2017 році JPA 2.1 все ще має лише те, @Column(columnDefinition='...')до чого ви ставите буквальне визначення SQL стовпця. Що досить негнучко і змушує вас також заявляти про інші аспекти, такі як тип, коротке замикання на думку щодо впровадження програми СВ.

Однак у сплячому режимі є таке:

@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.


Яку версію сплячих анотацій я можу використовувати для Wildfly 12, я виявляю помилку з конкретною версією цього? Дякую заздалегідь!
Фернандо

Дякую, Ондра. Чи є ще рішення у 2019 році?
Алан

1
@Alan немає на складі JPA, на жаль.
jwenting

13

JPA це не підтримує, і було б корисно, якби це було. Використання колонкиDefinition є специфічною для БД і не прийнятна у багатьох випадках. встановлення за замовчуванням у класі недостатньо, коли ви отримуєте запис із нульовими значеннями (що зазвичай відбувається при повторному запуску старих тестів 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 приємно в теорії, але не завжди працюватиме при роботі з існуючою базою даних, яка вже містить нульові значення, де ваша програма не хотіла б їх мати. Спочатку вам потрібно буде перетворити базу даних, коли ви зробите стовпець ненульовим і одночасно встановите розумний за замовчуванням, а потім вирішите зворотній зв'язок інших програм, які припускають, що він насправді є нульовим (тобто якщо ви може навіть змінити базу даних).
jwenting

Якщо справа стосується #JPA та сеттерів / гетерів, вони завжди повинні бути чистими сеттерами / геттерами одного дня, коли ваша заявка зростає і зростає, і стає кошмаром технічного обслуговування. Найкраща порада - тут KISS (я не гей, LOL).
Роланд

10

Побачивши, як я натрапив на це від Google, намагаючись вирішити ту саму проблему, я просто кину в рішення, яке я приготував, якщо хтось вважає це корисним.

З моєї точки зору, існує справді лише 1 рішення цієї проблеми - @PrePersist. Якщо ви робите це в @PrePersist, ви повинні перевірити, чи значення вже встановлено.


4
+1 - Я б напевно обрав для @PrePersistсебе корпус ОП. @Column(columnDefinition=...)не здається дуже елегантним.
Пьотр Новіцький

@PrePersist не допоможе вам, якщо в магазині вже є дані з нульовими значеннями. Це не буде магічно робити перетворення бази даних для вас.
jwenting

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

Я щойно перевірив це питання. Це працює просто чудово. Дякую за підказку.


Про коментарі:

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

Цей параметр не встановлює значення бази стовпців за замовчуванням у базі даних (звичайно).


9
На рівні об'єкта він не працює нормально (після вставки у вашій сутності ви не отримаєте значення бази даних за замовчуванням). За замовчуванням на рівні Java.
Паскаль Thivent

Це працює (особливо, якщо ви вказали inserttable = false у базі даних, але, на жаль, кешована версія не оновлюється (навіть коли JPA вибирає таблицю та стовпці). Принаймні, не зі сплячим режимом: - /, але навіть він, якщо буде працювати додатковий туди і назад погано
eckes

9

ви можете використовувати java refle 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();Into The 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;

6
Це виглядає так, що це робить вашого постачальника реалізації jpa специфічним.
Удо відбувся

Це лише робить постачальника DDL специфічним. Але у вас, швидше за все, є специфічні для постачальника хакі DDL у будь-якому випадку. Однак, як було зазначено вище, значення (навіть коли вставляється = false) відображається в БД, але не в кеш-пам'яті сеансу (принаймні, не в сплячому режимі).
eckes

7

Це не можна зробити за допомогою анотації стовпців. Я думаю, що єдиний спосіб - встановити значення за замовчуванням, коли створюється об’єкт. Можливо, конструктор за замовчуванням був би правильним місцем для цього.


Хороша ідея. На жаль, немає загальних анотацій або атрибутів для @Columnнавколо. І я також пропускаю коментарі, встановлені (взяті з Java doctag).
Роланд

5

У моєму випадку я змінив вихідний код на сплячий ядро, щоб ввести нову анотацію @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 );

Що ж, це рішення, яке є лише в сплячку.


2
Хоча я ціную зусилля людей, які активно беруть участь у проекті з відкритим кодом, я відмовився від цієї відповіді, оскільки JPA - це стандартна специфікація Java поверх будь-якого АБО / М, ОП попросив спосіб JPA вказати значення за замовчуванням і ваш патч працює тільки для сплячого. Якби це був NHibernate, в якому немає специфікації суперпартій щодо стійкості (NPA навіть не підтримується MS EF), я б застосував такий виправлення. Правда полягає в тому, що JPA досить обмежена порівняно з вимогами ORM (один приклад: відсутність вторинних індексів). Так чи інакше, кудос до зусиль
usr-local-ΕΨΗΕΛΩΝ

Це не створює проблеми з технічним обслуговуванням? Потрібно було б переробити ці зміни кожного разу, коли перебуває в сплячому режимі або після кожного нового встановлення?
користувач1242321

Оскільки питання про JPA, а про сплячку навіть не згадується, це не відповідає на питання
Ніл Стоктон

5
  1. @Column(columnDefinition='...') не працює, якщо ви встановлюєте обмеження за замовчуванням у базі даних під час вставлення даних.
  2. Вам потрібно зробити insertable = falseта видалити columnDefinition='...'з анотації, тоді база даних автоматично вставить значення за замовчуванням із бази даних.
  3. Наприклад, коли ви встановлюєте стать varchar, чоловік за замовчуванням у базі даних.
  4. Вам просто потрібно додати insertable = falseв сплячку / JPA, вона буде працювати.


І не вдалий варіант, якщо ви хочете іноді встановлювати його значення.
Шихе Чжан

@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 не підтримують поняття значення стовпця за замовчуванням. Для вирішення цього обмеження встановіть усі значення за замовчуванням безпосередньо перед тим, як викликати сплячку save()або update()на сеансі. Це максимально наближено (за винятком режиму Hibernate, який встановлює значення за замовчуванням) імітує поведінку бази даних, яка встановлює значення за замовчуванням, коли вона зберігає рядок у таблиці.

На відміну від встановлення значень за замовчуванням у модельному класі, як підказує цей альтернативний варіант відповіді , цей підхід також забезпечує запити критеріїв, які використовуютьExample об’єкт як прототип для пошуку, продовжуватимуть працювати, як і раніше. Коли ви встановите за замовчуванням атрибут nullable (той, який має непомітивний тип) у модельному класі, Hibernate запит за прикладом більше не буде ігнорувати пов'язаний стовпець, де раніше він ігнорував би його, оскільки він був нульовим.


У попередньому авторському рішенні автор наставив ColumnDefault ("")
nikolai.serdiuk

@ nikolai.serdiuk, що анотація ColumnDefault була додана через роки після написання цієї відповіді. У 2010 році це було правильно, такої анотації не було (насправді не було анотацій, лише конфігурація xml).
jwenting

1

Це неможливо в JPA.

Ось що можна зробити з анотацією стовпця: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html


1
Неможливо вказати значення за замовчуванням, здається серйозним недоліком анотацій JPA.
Дерек Махар

2
JDO дозволяє це у своєму визначенні ORM, тому JPA повинен включити це ... один день
user383680

Ви, безумовно, можете це зробити за допомогою атрибута columnDefinition, як відповів Камерон Папа.
IntelliData

@DerekMahar - це не єдиний недолік у специфікації JPA. Це хороша специфікація, але вона не є досконалою
вперше

1

Якщо ви використовуєте подвійний, ви можете використовувати наступне:

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

private Double grolsh;

Так, це специфічно для db.


0

Ви можете визначити значення за замовчуванням у дизайнері бази даних або під час створення таблиці. Наприклад, у SQL Server ви можете встановити для знамення знаходження для дату поля Date ( getDate()). Використовуйте, insertable=falseяк зазначено у визначенні стовпця. JPA не вказуватиме цей стовпчик на вставках, і база даних генеруватиме значення для вас.


-2

Вам потрібно insertable=falseв собі@Column анотацію. JPA тоді ігнорує цей стовпець під час вставки в базу даних і буде використано значення за замовчуванням.

Перейдіть за цим посиланням: http://mariemjabloun.blogspot.com/2014/03/ резо-set-database-default-value-in.html


2
Тоді як u буде вставити задане значення @runtime ??
Ashish Ratan

Так це правда. nullable=falseзазнає невдачі з SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Тут я забув встановити "створену" часову позначку openingTime.setOpeningCreated(new Date()). Це приємний спосіб послідовності, але це не запитував запитуючий.
Роланд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.