Ustawianie wartości domyślnych dla kolumn w JPA


Odpowiedzi:


230

W rzeczywistości jest to możliwe w JPA, choć trochę hack przy użyciu columnDefinitionwłaściwości @Columnadnotacji, na przykład:

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

3
10 to część całkowita, a 2 to część dziesiętna liczby, więc: 1234567890.12 będzie liczbą obsługiwaną.
Nathan Feger

23
Należy również zauważyć, że ta kolumna Definicja niekoniecznie jest niezależna od bazy danych, a na pewno nie jest automatycznie wiązana typ danych w Javie.
Nathan Feger

47
Po utworzeniu elementu z polem zerowym i utrwaleniu go nie miałbyś w nim ustawionej wartości. Nie rozwiązanie ...
Pascal Thivent,

18
Nie @NathanFeger, 10 to długość, a 2 to część dziesiętna. Zatem 1234567890.12 nie będzie obsługiwanym numerem, ale 12345678.90 jest prawidłowy.
GPrimola,

4
Potrzebujesz, insertable=falsejeśli kolumna ma wartość zerową (i aby uniknąć niepotrzebnego argumentu kolumny).
eckes

314

Możesz wykonać następujące czynności:

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

Tam! Właśnie wykorzystałeś zero jako wartość domyślną.

Pamiętaj, że będzie ci to służyć, jeśli tylko uzyskujesz dostęp do bazy danych z tej aplikacji. Jeśli inne aplikacje również korzystają z bazy danych, należy to sprawdzić w bazie danych, używając atrybutu adnotacji columnDefinition Camerona lub w inny sposób.


38
Jest to właściwy sposób, aby to zrobić, jeśli chcesz, aby twoje podmioty miały poprawną wartość po wstawieniu. +1
Pascal Thivent

16
Ustawienie domyślnej wartości atrybutu zerowalnego (takiego, który ma typ inny niż pierwotny) w klasie modelu spowoduje przerwanie kwerend kryteriów, które wykorzystują Exampleobiekt jako prototyp wyszukiwania. Po ustawieniu wartości domyślnej przykładowe zapytanie Hibernacja nie będzie już ignorowało skojarzonej kolumny, w której wcześniej ją ignorowało, ponieważ była pusta. Lepszym rozwiązaniem jest ustawienie wszystkich wartości domyślnych tuż przed wywołaniem hibernacji save()lub update(). To lepiej naśladuje zachowanie bazy danych, która ustawia wartości domyślne podczas zapisywania wiersza.
Derek Mahar

1
@Harry: Jest to prawidłowy sposób ustawiania wartości domyślnych, z wyjątkiem sytuacji, gdy używasz kryteriów, które wykorzystują przykład jako prototyp wyszukiwania.
Derek Mahar

12
Nie zapewnia to ustawienia domyślnego dla typów innych niż pierwotne ( nullna przykład ustawienie ). Korzystanie @PrePersisti @PreUpdatejest lepszą opcją imho.
Jasper

3
To jest właściwy sposób na określenie wartości domyślnej dla kolumny. columnDefinitionwłaściwość nie jest niezależna od bazy danych i @PrePersistprzesłania ustawienie przed wstawieniem, „wartość domyślna” to coś innego, wartość domyślna jest używana, gdy wartość nie jest jawnie ustawiona.
Utku Özdemir

110

innym podejściem jest użycie javax.persistence.PrePersist

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

1
Zastosowałem takie podejście do dodawania i aktualizowania znaczników czasu. Działa bardzo dobrze.
Spina,

10
Czy nie powinno tak być if (createdt != null) createdt = new Date();? W tej chwili zastąpi to wyraźnie określoną wartość, co sprawia, że ​​tak naprawdę nie jest to domyślna wartość.
Max Nanasy,

16
@MaxNanasyif (createdt == null) createdt = new Date();
Shane

Czy miałoby to znaczenie, gdyby klient i baza danych znajdowały się w różnych strefach czasowych? Wyobrażam sobie, że użycie czegoś takiego jak „TIMESTAMP DEFAULT CURRENT_TIMESTAMP” uzyska bieżący czas na serwerze bazy danych, a „createt = new Date ()” uzyska bieżący czas w kodzie Java, ale te dwa czasy mogą nie być takie same, jeśli klient łączy się z serwerem w innej strefie czasowej.
FrustratedWithFormsDesigner

Dodano nullczek.
Ondra Žižka

49

W 2017 r. JPA 2.1 wciąż ma tylko tę, @Column(columnDefinition='...')do której wstawiono dosłowną definicję SQL kolumny. Co jest dość nieelastyczne i zmusza Cię do zadeklarowania innych aspektów, takich jak typ, zwężając pogląd implementacji JPA na ten temat.

Hibernacja ma jednak:

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

Określa wartość DOMYŚLNĄ do zastosowania w powiązanej kolumnie za pośrednictwem DDL.

Dwie uwagi na ten temat:

1) Nie bój się pójść niestandardowo. Pracując jako programista JBoss, widziałem całkiem sporo procesów specyfikacji. Specyfikacja jest w gruncie rzeczy punktem odniesienia dla dużych graczy w danej dziedzinie, którzy chcą się zobowiązać do wsparcia przez następną dekadę. Dotyczy to bezpieczeństwa, w przypadku wiadomości ORM nie ma różnicy (chociaż JPA obejmuje całkiem sporo). Moje doświadczenie jako programisty polega na tym, że w złożonej aplikacji wcześniej czy później będziesz potrzebować niestandardowego interfejsu API. I @ColumnDefaultjest przykładem, gdy przeważa nad negatywnymi skutkami stosowania niestandardowego rozwiązania.

2) Fajnie, jak wszyscy machają @PrePersist lub inicjatorem członka konstruktora. Ale to NIE to samo. Co powiesz na masowe aktualizacje SQL? Co powiesz na stwierdzenia, które nie ustawiają kolumny? DEFAULTma swoją rolę i nie można jej zastąpić przez zainicjowanie członka klasy Java.


Jakiej wersji adnotacji hibernacji mogę użyć dla Wildfly 12, wychwytuję błąd przy konkretnej wersji tego? Dzięki z góry!
Fernando Pie

Dzięki Ondra. Czy są jakieś rozwiązania w 2019 roku?
Alan

1
@Alan nie jest w magazynie JPA, niestety.
jwenting

13

JPA nie obsługuje tego i byłoby przydatne, gdyby tak było. Użycie columnDefinition jest specyficzne dla DB i w wielu przypadkach jest niedopuszczalne. ustawienie wartości domyślnej w klasie nie wystarczy, gdy pobierzesz rekord o wartości zerowej (co zwykle dzieje się, gdy ponownie uruchomisz stare testy DBUnit). To, co robię, to:

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;
    }
}

Auto-boxing Java bardzo w tym pomaga.


Nie nadużywaj seterów! Służą do ustawiania parametru w polu obiektu. Nic więcej! Lepiej użyj domyślnego konstruktora dla wartości domyślnych.
Roland

@Roland niezły w teorii, ale nie zawsze będzie działał, gdy mamy do czynienia z istniejącą bazą danych, która zawiera już wartości zerowe, w przypadku których aplikacja chciałaby ich nie mieć. Najpierw musisz wykonać konwersję bazy danych, w której kolumna ma wartość inną niż null, a jednocześnie ustawić rozsądną wartość domyślną, a następnie poradzić sobie z luzem z innych aplikacji, które zakładają, że jest ona zerowa (to znaczy, jeśli może nawet modyfikować bazę danych).
jwenting

Jeśli chodzi o #JPA i ustawiające / pobierające, muszą one zawsze być czystym ustawiającym / pobierającym, inaczej pewnego dnia Twoja aplikacja urosła i wyrosła i stała się koszmarem konserwacyjnym. Najlepszą radą jest tutaj KISS (nie jestem gejem, LOL).
Roland

10

Widząc, jak natknąłem się na to od Google, próbując rozwiązać ten sam problem, po prostu wrzucę rozwiązanie, które przygotowałem, na wypadek, gdyby ktoś uznał to za przydatne.

Z mojego punktu widzenia jest naprawdę tylko 1 rozwiązanie tego problemu - @PrePersist. Jeśli zrobisz to w @PrePersist, musisz jednak sprawdzić, czy wartość została już ustawiona.


4
+1 - Zdecydowanie @PrePersistwybrałbym przypadek użycia OP. @Column(columnDefinition=...)nie wydaje się bardzo elegancki.
Piotr Nowicki

@ PrePersist nie pomoże ci, jeśli w sklepie są już dane o zerowych wartościach. To nie magicznie zrobi dla ciebie konwersji bazy danych.
jwenting

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

Właśnie przetestowałem problem. Działa dobrze. Dzięki za podpowiedź.


O komentarzach:

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

Ten nie ustawia domyślnej wartości kolumny w bazie danych (oczywiście).


9
Nie działa to dobrze na poziomie obiektu (domyślna wartość bazy danych nie pojawi się po wstawieniu do encji). Domyślnie na poziomie Java.
Pascal Thivent,

Działa (zwłaszcza jeśli w bazie danych podasz insertable = false, ale niestety wersja buforowana nie jest odświeżana (nawet gdy JPA wybiera tabelę i kolumny). Przynajmniej nie z hibernacją: - / ale nawet jeśli działałoby to dodatkowo
objazd

9

możesz użyć Java odzwierciedlają API:

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

Jest to powszechne:

    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
Podoba mi się to rozwiązanie, ale mam jeden punkt słabości, myślę, że byłoby ważne sprawdzić, czy kolumna ma wartość zerową, czy nie przed ustawieniem wartości domyślnej.
Luis Carlos

Jeśli wolisz, umieszczenie kodu Field[] fields = object.getClass().getDeclaredFields();w for()pliku może być również w porządku. Dodaj także finaldo parametru / wychwycone wyjątki, ponieważ nie chcesz object, aby przypadkowo je zmodyfikowano. Również dodać czek na null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Dzięki temu object.getClass()można bezpiecznie wywoływać i nie uruchamia komendy NPE. Powodem jest unikanie błędów przez leniwych programistów. ;-)
Roland

7

Używam columnDefinitioni działa bardzo dobrze

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;

6
Wygląda na to, że sprawia, że ​​twój dostawca implementacji jpa jest specyficzny.
Udo odbył się

To tylko sprawia, że ​​dostawca DDL jest specyficzny. Ale i tak najprawdopodobniej masz włamania DDL specyficzne dla dostawcy. Jak jednak wspomniano powyżej, wartość (nawet gdy insertable = false) pojawia się w DB, ale nie w pamięci podręcznej encji sesji (przynajmniej nie w hibernacji).
eckes

7

Nie możesz tego zrobić z adnotacją w kolumnie. Myślę, że jedynym sposobem jest ustawienie wartości domyślnej podczas tworzenia obiektu. Być może domyślny konstruktor byłby właściwym miejscem do tego.


Dobry pomysł. Niestety nie ma żadnej ogólnej adnotacji ani atrybutu @Column. I tęsknię też za ustawianymi komentarzami (zaczerpniętymi z doctag Java).
Roland

5

W moim przypadku zmodyfikowałem kod źródłowy hibernacji, cóż, aby wprowadzić nową adnotację @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 );

Cóż, jest to rozwiązanie tylko do hibernacji.


2
Doceniam wysiłek ludzi, którzy aktywnie uczestniczą w projekcie open source, ale głosowałem za odpowiedzią, ponieważ JPA jest standardową specyfikacją Java na każdym OR / M, OP poprosił o sposób JPA na określenie wartości domyślnych i łatka działa tylko dla hibernacji. Jeśli to był NHibernate, w którym nie ma super partes specyfikacja wytrwałością (NPA nie jest jeszcze obsługiwany przez MS EF), bym upvoted taki rodzaj plastra. Prawda jest taka, że ​​JPA jest dość ograniczony w porównaniu z wymaganiami ORM (jeden przykład: brak wtórnych indeksów). W każdym razie hołd dla wysiłku
usr-local-ΕΨΗΕΛΩΝ

Czy to nie stwarza problemu z konserwacją? Trzeba będzie powtórzyć te zmiany za każdym razem, gdy hibernacja uaktualnień lub przy każdej nowej instalacji?
user1242321

Ponieważ pytanie dotyczy JPA, a Hibernate nie jest nawet wspomniane, to nie odpowiada na pytanie
Neil Stockton

5
  1. @Column(columnDefinition='...') nie działa po ustawieniu domyślnego ograniczenia w bazie danych podczas wstawiania danych.
  2. Musisz utworzyć insertable = falsei usunąć columnDefinition='...'adnotację, a następnie baza danych automatycznie wstawi wartość domyślną z bazy danych.
  3. Np. Po ustawieniu varchar płeć domyślnie w bazie danych jest męski.
  4. Musisz tylko dodać insertable = falseHibernate / JPA, to zadziała.


I nie jest to dobra opcja, jeśli czasami chcesz ustawić jej wartość.
Shihe Zhang,

@ShiheZhang przy użyciu wartości false nie pozwala mi ustawić wartości?
fvildoso

3
@PrePersist
void preInsert() {
    if (this.dateOfConsent == null)
        this.dateOfConsent = LocalDateTime.now();
    if(this.consentExpiry==null)
        this.consentExpiry = this.dateOfConsent.plusMonths(3);
}

W moim przypadku, ponieważ użyłem tego pola jako LocalDateTime, jest to zalecane ze względu na niezależność dostawcy


2

Adnotacje JPA ani Hibernacja nie obsługują pojęcia domyślnej wartości kolumny. Aby obejść to ograniczenie, ustaw wszystkie wartości domyślne tuż przed wywołaniem stanu hibernacji save()lub update()sesji. To ściśle jak to możliwe (poza Hibernacją ustawienie wartości domyślnych) naśladuje zachowanie bazy danych, która ustawia wartości domyślne, gdy zapisuje wiersz w tabeli.

W przeciwieństwie do ustawiania wartości domyślnych w klasie modelu, jak sugeruje ta alternatywna odpowiedź , takie podejście zapewnia również, że zapytania o kryteria, które wykorzystują Exampleobiekt jako prototyp do wyszukiwania, będą działać tak jak poprzednio. Po ustawieniu domyślnej wartości atrybutu zerowalnego (takiego, który ma typ nieprymitywny) w klasie modelu, Hibernacja zapytanie po przykładzie nie będzie już ignorować powiązanej kolumny, w której poprzednio byłaby ignorowana, ponieważ była pusta.


W poprzednim rozwiązaniu autor wspomniał o ColumnDefault („”)
nikolai.serdiuk

@ nikolai.serdiuk, że adnotacja ColumnDefault została dodana wiele lat po napisaniu tej odpowiedzi. W 2010 roku było to poprawne, nie było takich adnotacji (w rzeczywistości nie było adnotacji, tylko konfiguracja xml).
jwenting

1

W JPA nie jest to możliwe.

Oto, co można zrobić z dopiskiem kolumna: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html


1
Brak możliwości określenia wartości domyślnej wydaje się poważnym niedociągnięciem adnotacji JPA.
Derek Mahar

2
JDO zezwala na to w swojej definicji ORM, więc JPA powinien uwzględnić to ... pewnego dnia
użytkownik383680

Z pewnością możesz to zrobić za pomocą atrybutu columnDefinition, jak odpowiedział Cameron Pope.
IntelliData,

@DerekMahar to nie jedyny brak w specyfikacji JPA. To dobra specyfikacja, ale nie idealna
zapisano

1

Jeśli używasz podwójnego, możesz użyć następujących opcji:

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

private Double grolsh;

Tak, jest to specyficzne dla db.


0

Możesz zdefiniować wartość domyślną w projektancie bazy danych lub podczas tworzenia tabeli. Na przykład w programie SQL Server można ustawić domyślną przechowalnię pola Data na ( getDate()). Użyj insertable=falsezgodnie z definicją w kolumnie. JPA nie określi tej kolumny na wstawkach, a baza danych wygeneruje dla ciebie wartość.


-2

Musisz insertable=falsew sobie @Columnadnotację. JPA zignoruje tę kolumnę podczas wstawiania do bazy danych i zostanie użyta wartość domyślna.

Zobacz ten link: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html


2
Jak wstawisz podaną wartość @ runtime?
Ashish Ratan

Tak, to prawda. nullable=falsenie powiedzie się z SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Tutaj zapomniałem ustawić znacznik czasu „utworzony” openingTime.setOpeningCreated(new Date()). To dobry sposób na zachowanie spójności, ale o to pytający nie pytał.
Roland
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.