Impostazione dei valori predefiniti per le colonne in JPA


Risposte:


230

In realtà è possibile in JPA, anche se un po 'di un hack utilizzando la columnDefinitionproprietà @Columndell'annotazione, ad esempio:

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

3
10 è la parte intera e 2 è la parte decimale del numero, quindi: 1234567890.12 sarebbe un numero supportato.
Nathan Feger,

23
Nota anche che questa definizione di colonna non è necessariamente indipendente dal database e non è certamente legata automaticamente al tipo di dati in Java.
Nathan Feger,

47
Dopo aver creato un'entità con un campo nullo e averlo persistente, non avresti impostato il valore. Non una soluzione ...
Pascal Thivent,

18
No @NathanFeger, 10 è la lunghezza e 2 la parte decimale. Quindi 1234567890.12 non sarà un numero supportato, ma 12345678.90 è valido.
GPrimola,

4
Avresti bisogno insertable=falsese la colonna è nullable (e per evitare l'argomento colonna non necessario).
Controlla il

314

Puoi fare quanto segue:

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

Là! Hai appena usato zero come valore predefinito.

Nota che ti servirà se accedi solo al database da questa applicazione. Se anche altre applicazioni utilizzano il database, è necessario effettuare questo controllo dal database utilizzando l' attributo annotazione columnDefinition di Cameron o in altro modo.


38
Questo è il modo giusto per farlo se si desidera che le entità abbiano il valore corretto dopo l'inserimento. +1
Pascal Thivent,

16
L'impostazione del valore predefinito di un attributo nullable (uno che ha un tipo non primitivo) nella classe del modello interromperà le query sui criteri che utilizzano un Exampleoggetto come prototipo per la ricerca. Dopo aver impostato un valore predefinito, una query di esempio di Hibernate non ignorerà più la colonna associata dove precedentemente la ignorerebbe perché era nulla. Un approccio migliore consiste nell'impostare tutti i valori predefiniti appena prima di invocare un ibernazione save()o update(). Ciò imita meglio il comportamento del database che imposta i valori predefiniti quando salva una riga.
Derek Mahar,

1
@Harry: questo è il modo corretto di impostare i valori predefiniti tranne quando si utilizzano query con criteri che utilizzano un esempio come prototipo per la ricerca.
Derek Mahar,

12
Ciò non garantisce che l'impostazione predefinita sia impostata per tipi non primitivi (impostazione nullad esempio). Utilizzando @PrePersiste @PreUpdateè un'opzione migliore imho.
Jasper,

3
Questo è il modo giusto per specificare il valore predefinito per la colonna. columnDefinitionLa proprietà non è indipendente dal database e @PrePersistignora le impostazioni prima di inserire, "valore predefinito" è qualcos'altro, il valore predefinito viene utilizzato quando il valore non è impostato in modo esplicito.
Utku Özdemir,

110

un altro approccio sta usando javax.persistence.PrePersist

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

1
Ho usato un approccio come questo per aggiungere e aggiornare i timestamp. Ha funzionato molto bene.
Spina,

10
Questo non dovrebbe essere if (createdt != null) createdt = new Date();o qualcosa del genere? In questo momento, questo sovrascriverà un valore esplicitamente specificato, che sembra renderlo davvero non predefinito.
Max Nanasy,

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

Importa se il client e il database sono in fusi orari diversi? Immagino che usando qualcosa come "TIMESTAMP DEFAULT CURRENT_TIMESTAMP" otterresti l'ora corrente sul server di database, e "Createdt = new Date ()" otterrebbe l'ora corrente nel codice java, ma queste due volte potrebbero non essere uguali se il client si sta connettendo a un server in un fuso orario diverso.
FrustratedWithFormsDesigner

Aggiunto il nullcontrollo.
Ondra Žižka,

49

Nel 2017, JPA 2.1 ha ancora solo @Column(columnDefinition='...')quello in cui hai inserito la definizione SQL letterale della colonna. Il che è abbastanza poco flessibile e ti costringe a dichiarare anche altri aspetti come il tipo, cortocircuitando il punto di vista dell'implementazione dell'APP su tale questione.

Hibernate, però, ha questo:

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

Identifica il valore DEFAULT da applicare alla colonna associata tramite DDL.

Due note a questo:

1) Non aver paura di andare fuori standard. Lavorando come sviluppatore JBoss, ho visto alcuni processi di specifica. Le specifiche sono fondamentalmente la linea di base che i grandi attori in un determinato campo sono disposti a impegnarsi a sostenere per il prossimo decennio o giù di lì. È vero per la sicurezza, per i messaggi, ORM non fa differenza (anche se JPA copre parecchio). La mia esperienza come sviluppatore è che in un'applicazione complessa, prima o poi avrai comunque bisogno di un'API non standard. Ed @ColumnDefaultè un esempio quando supera i negativi dell'utilizzo di una soluzione non standard.

2) È bello come tutti agitano @PrePersist o l'inizializzazione dei membri del costruttore. Ma NON è lo stesso. Che ne dici di aggiornamenti SQL in blocco? Che ne dici di dichiarazioni che non impostano la colonna? DEFAULTha il suo ruolo e non è sostituibile inizializzando un membro della classe Java.


Quale versione delle annotazioni di ibernazione posso usare per Wildfly 12, riscontro un errore con una versione specifica di questo? Grazie ind anticipo!
Fernando Pie,

Grazie Ondra. Ci sono altre soluzioni nel 2019?
Alan,

1
@Alan non in stock JPA, purtroppo.
jwenting

13

JPA non lo supporta e sarebbe utile se lo facesse. L'uso di columnDefinition è specifico del DB e in molti casi non è accettabile. l'impostazione di un valore predefinito nella classe non è sufficiente quando si recupera un record con valori null (ciò si verifica in genere quando si eseguono nuovamente i test DBUnit precedenti). Quello che faccio è questo:

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

Il boxing automatico Java aiuta molto in questo.


Non abusare dei setter! Sono per l'impostazione di un parametro in un campo oggetto. Niente di più! Meglio usare il costruttore predefinito per i valori predefiniti.
Roland,

@Roland è bello in teoria, ma non funzionerà sempre quando si ha a che fare con un database esistente che contiene già valori null dove l'applicazione vorrebbe non averli. Dovresti prima fare una conversione del database in cui rendi la colonna non nulla e imposti un valore predefinito sensibile allo stesso tempo, quindi gestisci il gioco proveniente da altre applicazioni che presumono che sia effettivamente nullable (ovvero, se tu può persino modificare il database).
jwenting

Se si tratta di #JPA e setter / getter, devono sempre essere setter / getter puri un giorno in cui la tua applicazione è cresciuta e cresciuta e diventa un incubo per la manutenzione. Il miglior consiglio è KISS qui (non sono gay, LOL).
Roland

10

Visto che mi sono imbattuto in questo da Google mentre cercavo di risolvere lo stesso problema, lancerò la soluzione che ho preparato nel caso qualcuno lo trovasse utile.

Dal mio punto di vista c'è davvero solo 1 soluzione a questo problema: @PrePersist. Se lo fai in @PrePersist, devi verificare se il valore è già stato impostato.


4
+1 - Opterei sicuramente per il @PrePersistcaso d'uso di OP. @Column(columnDefinition=...)non sembra molto elegante.
Piotr Nowicki

@PrePersist non ti aiuterà se ci sono già dati nell'archivio con valori nulli. Magicamente non farà una conversione del database per te.
jwenting

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

Ho appena provato il problema. Funziona benissimo. Grazie per il suggerimento.


A proposito dei commenti:

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

Questo non imposta il valore di colonna predefinito nel database (ovviamente).


9
Non funziona bene a livello di oggetto (non si otterrà il valore predefinito del database dopo un inserimento nell'entità). Predefinito a livello Java.
Pascal Thivent,

Funziona (soprattutto se si specifica insertable = false nel database, ma sfortunatamente la versione memorizzata nella cache non viene aggiornata (nemmeno quando JPA seleziona la tabella e le colonne). Almeno non con ibernazione: - / ma anche se funzionasse l'ulteriore il viaggio di andata e ritorno è male.
Verifica il

9

puoi usare l'API java reflection:

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

Questo è comune:

    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
Mi piace questa soluzione, ma ho un punto debole, penso che sarebbe importante controllare se la colonna è nullable o no prima di impostare il valore predefinito.
Luis Carlos,

Se preferisci, anche l'inserimento del codice Field[] fields = object.getClass().getDeclaredFields();in for()potrebbe andare bene. E aggiungi anche finalal tuo parametro / rilevate le eccezioni perché non vuoi objectessere modificato per caso. Anche aggiungere un controllo su null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Ciò garantisce che object.getClass()sia sicuro invocare e non attiva a NPE. Il motivo è evitare errori da programmatori pigri. ;-)
Roland

7

Uso columnDefinitione funziona molto bene

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;

6
Questo sembra rendere specifico il tuo fornitore di implementazione jpa.
Udo tenuto il

Rende specifico il fornitore DDL. Ma hai comunque probabilmente degli hack DDL specifici del fornitore. Tuttavia, come menzionato sopra, il valore (anche quando inseribile = false) viene visualizzato nel DB ma non nella cache dell'entità della sessione (almeno non in ibernazione).
Controlla il

7

Non puoi farlo con l'annotazione di colonna. Penso che l'unico modo sia quello di impostare il valore predefinito quando viene creato un oggetto. Forse il costruttore predefinito sarebbe il posto giusto per farlo.


Bella idea Purtroppo non ci sono annotazioni o attributi generici per @Columnaround. E mi mancano anche i commenti impostati (presi dal doctag Java).
Roland,

5

Nel mio caso, ho modificato il codice sorgente hibernate-core, beh, per introdurre una nuova annotazione @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 );

Bene, questa è una soluzione solo in letargo.


2
Mentre apprezzo lo sforzo delle persone che contribuiscono attivamente al progetto open source, ho declassato questa risposta perché JPA è una specifica Java standard in cima a qualsiasi OR / M, l'OP ha chiesto un modo JPA per specificare i valori predefiniti e la patch funziona solo per Hibernate. Se questo fosse NHibernate, in cui non esiste una specifica di superpartes per la persistenza (NPA non è nemmeno supportato da MS EF), avrei annullato tale tipo di patch. La verità è che JPA è piuttosto limitata rispetto ai requisiti di ORM (un esempio: nessun indice secondario). Comunque complimenti per lo sforzo
usr-local-ΕΨΗΕΛΩΝ

Questo non crea un problema di manutenzione? Uno dovrebbe rifare queste modifiche ogni volta che si ibernano gli aggiornamenti o su ogni nuova installazione?
user1242321

Poiché la domanda riguarda l'APP e Hibernate non è nemmeno menzionata, questo non risponde alla domanda
Neil Stockton,

5
  1. @Column(columnDefinition='...') non funziona quando si imposta il vincolo predefinito nel database durante l'inserimento dei dati.
  2. È necessario creare insertable = falsee rimuovere columnDefinition='...'dall'annotazione, quindi il database inserirà automaticamente il valore predefinito dal database.
  3. Ad esempio, quando si imposta varchar il genere è maschile per impostazione predefinita nel database.
  4. Devi solo aggiungere insertable = falseHibernate / JPA, funzionerà.


E non è una buona opzione, se vuoi impostarne il valore a volte.
Shihe Zhang,

@ShiheZhang utilizzando false non mi consente di impostare il valore?
fvildoso,

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

Nel mio caso a causa del campo LocalDateTime che ho usato questo, è consigliato a causa dell'indipendenza del fornitore


2

Né le annotazioni JPA né Hibernate supportano l'idea di un valore di colonna predefinito. Per ovviare a questa limitazione, impostare tutti i valori predefiniti appena prima di invocare un ibernazione save()o update()sulla sessione. Il più vicino possibile (a parte l'ibernazione che imposta i valori predefiniti) imita il comportamento del database che imposta i valori predefiniti quando salva una riga in una tabella.

A differenza dell'impostazione dei valori predefiniti nella classe del modello, come suggerisce questa risposta alternativa , questo approccio garantisce anche che le query sui criteri che utilizzano un Exampleoggetto come prototipo per la ricerca continuino a funzionare come prima. Quando si imposta il valore predefinito di un attributo nullable (uno che ha un tipo non primitivo) in una classe di modello, una query Hibernate per esempio non ignorerà più la colonna associata dove precedentemente lo ignorerebbe perché era null.


Nella precedente soluzione l'autore menzionava ColumnDefault ("")
nikolai.serdiuk

@ nikolai.serdiuk che l'annotazione ColumnDefault è stata aggiunta anni dopo la stesura di questa risposta. Nel 2010 era corretto, non c'erano annotazioni del genere (in realtà non c'erano annotazioni, solo configurazione XML).
jwenting

1

Questo non è possibile in JPA.

Ecco cosa puoi fare con l'annotazione della colonna: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html


1
Non essere in grado di specificare un valore predefinito sembra un grave difetto delle annotazioni JPA.
Derek Mahar,

2
JDO lo consente nella sua definizione ORM, quindi JPA dovrebbe includere questo ... un giorno
user383680

È assolutamente possibile farlo, con l'attributo ColumnDefinition, come rispose Cameron Papa.
IntelliData,

@DerekMahar non è l'unico difetto nella specifica JPA. È una buona specifica, ma non è perfetta
jwenting

1

Se si utilizza un doppio, è possibile utilizzare quanto segue:

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

private Double grolsh;

Sì, è specifico per db.


0

È possibile definire il valore predefinito nella finestra di progettazione del database o quando si crea la tabella. Ad esempio in SQL Server è possibile impostare il deposito predefinito di un campo Data su ( getDate()). Utilizzare insertable=falsecome indicato nella definizione della colonna. JPA non specificherà quella colonna sugli inserti e il database genererà il valore per te.


-2

Hai bisogno di insertable=falsete@Column annotazione. JPA ignorerà quindi quella colonna durante l'inserimento nel database e verrà utilizzato il valore predefinito.

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


2
Quindi come inserirai il valore dato @runtime ??
Ashish Ratan,

Sì è vero. nullable=falsefallirà con un SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Qui ho dimenticato di impostare il timestamp "creato" con openingTime.setOpeningCreated(new Date()). Questo è un bel modo di avere coerenza, ma questo è ciò che l'interrogante non stava chiedendo.
Roland,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.