È possibile impostare un valore predefinito per le colonne in JPA e se, come si fa utilizzando le annotazioni?
È possibile impostare un valore predefinito per le colonne in JPA e se, come si fa utilizzando le annotazioni?
Risposte:
In realtà è possibile in JPA, anche se un po 'di un hack utilizzando la columnDefinition
proprietà @Column
dell'annotazione, ad esempio:
@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
insertable=false
se la colonna è nullable (e per evitare l'argomento colonna non necessario).
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.
Example
oggetto 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.
null
ad esempio). Utilizzando @PrePersist
e @PreUpdate
è un'opzione migliore imho.
columnDefinition
La proprietà non è indipendente dal database e @PrePersist
ignora le impostazioni prima di inserire, "valore predefinito" è qualcos'altro, il valore predefinito viene utilizzato quando il valore non è impostato in modo esplicito.
un altro approccio sta usando javax.persistence.PrePersist
@PrePersist
void preInsert() {
if (this.createdTime == null)
this.createdTime = new Date();
}
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.
if (createdt == null) createdt = new Date();
null
controllo.
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? DEFAULT
ha il suo ruolo e non è sostituibile inizializzando un membro della classe Java.
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.
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.
@PrePersist
caso d'uso di OP. @Column(columnDefinition=...)
non sembra molto elegante.
@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).
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();
}
}
}
Field[] fields = object.getClass().getDeclaredFields();
in for()
potrebbe andare bene. E aggiungi anche final
al tuo parametro / rilevate le eccezioni perché non vuoi object
essere 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. ;-)
Uso columnDefinition
e funziona molto bene
@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date createdDate;
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.
@Column
around. E mi mancano anche i commenti impostati (presi dal doctag Java).
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.
@Column(columnDefinition='...')
non funziona quando si imposta il vincolo predefinito nel database durante l'inserimento dei dati.insertable = false
e rimuovere columnDefinition='...'
dall'annotazione, quindi il database inserirà automaticamente il valore predefinito dal database.insertable = false
Hibernate / JPA, funzionerà.@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
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 Example
oggetto 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.
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
Se si utilizza un doppio, è possibile utilizzare quanto segue:
@Column(columnDefinition="double precision default '96'")
private Double grolsh;
Sì, è specifico per db.
È 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=false
come indicato nella definizione della colonna. JPA non specificherà quella colonna sugli inserti e il database genererà il valore per te.
Hai bisogno di insertable=false
te@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
nullable=false
fallirà 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.