Ist es möglich, einen Standardwert für Spalten in JPA festzulegen, und wenn ja, wie erfolgt dies mithilfe von Anmerkungen?
Ist es möglich, einen Standardwert für Spalten in JPA festzulegen, und wenn ja, wie erfolgt dies mithilfe von Anmerkungen?
Antworten:
Tatsächlich ist es in JPA möglich, obwohl ein kleiner Hack die columnDefinition
Eigenschaft der @Column
Annotation verwendet, zum Beispiel:
@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
insertable=false
wenn die Spalte nullwertfähig ist (und um das nicht benötigte Spaltenargument zu vermeiden).
Sie können Folgendes tun:
@Column(name="price")
private double price = 0.0;
Dort! Sie haben gerade Null als Standardwert verwendet.
Beachten Sie, dass dies nur dann von Nutzen ist, wenn Sie nur über diese Anwendung auf die Datenbank zugreifen. Wenn andere Anwendungen die Datenbank ebenfalls verwenden, sollten Sie diese Prüfung in der Datenbank mithilfe des Annotation-Attributs columnDefinition von Cameron oder auf andere Weise durchführen.
Example
Objekt als Prototyp für die Suche verwenden. Nach dem Festlegen eines Standardwerts ignoriert eine Beispielabfrage im Ruhezustand die zugeordnete Spalte nicht mehr, wo sie zuvor ignoriert wurde, weil sie null war. Ein besserer Ansatz besteht darin, alle Standardwerte unmittelbar vor dem Aufrufen eines Ruhezustands save()
oder festzulegen update()
. Dies ahmt das Verhalten der Datenbank besser nach, die beim Speichern einer Zeile Standardwerte festlegt.
null
z. B. Einstellung ). Verwenden @PrePersist
und @PreUpdate
ist eine bessere Option imho.
columnDefinition
Die Eigenschaft ist nicht datenbankunabhängig und @PrePersist
überschreibt Ihre Einstellung vor dem Einfügen. "Standardwert" ist etwas anderes. Der Standardwert wird verwendet, wenn der Wert nicht explizit festgelegt wird.
Ein anderer Ansatz ist die Verwendung von javax.persistence.PrePersist
@PrePersist
void preInsert() {
if (this.createdTime == null)
this.createdTime = new Date();
}
if (createdt != null) createdt = new Date();
oder so? Im Moment wird dadurch ein explizit angegebener Wert überschrieben, was dazu führt, dass er nicht wirklich ein Standardwert ist.
if (createdt == null) createdt = new Date();
null
Scheck hinzugefügt .
Im Jahr 2017 hat JPA 2.1 nur noch das, @Column(columnDefinition='...')
in das Sie die wörtliche SQL-Definition der Spalte einfügen. Das ist ziemlich unflexibel und zwingt Sie, auch die anderen Aspekte wie Typ zu deklarieren, wodurch die Ansicht der JPA-Implementierung in dieser Angelegenheit kurzgeschlossen wird.
Hibernate hat jedoch Folgendes:
@Column(length = 4096, nullable = false)
@org.hibernate.annotations.ColumnDefault("")
private String description;
Gibt den DEFAULT-Wert an, der über DDL auf die zugeordnete Spalte angewendet werden soll.
Zwei Anmerkungen dazu:
1) Haben Sie keine Angst davor, nicht dem Standard zu entsprechen. Als JBoss-Entwickler habe ich einige Spezifikationsprozesse gesehen. Die Spezifikation ist im Grunde die Basis, die die großen Akteure in einem bestimmten Bereich bereit sind, sich für das nächste Jahrzehnt oder so zur Unterstützung zu verpflichten. Es ist wahr für die Sicherheit, für Messaging, ORM ist kein Unterschied (obwohl JPA ziemlich viel abdeckt). Meine Erfahrung als Entwickler zeigt, dass Sie in einer komplexen Anwendung früher oder später ohnehin eine nicht standardmäßige API benötigen. Und @ColumnDefault
ist ein Beispiel, wenn es die Nachteile einer nicht standardmäßigen Lösung überwiegt.
2) Es ist schön, wie jeder die Initialisierung von @PrePersist oder Konstruktormitgliedern winkt. Aber das ist NICHT dasselbe. Wie wäre es mit Massen-SQL-Updates? Wie wäre es mit Anweisungen, die die Spalte nicht setzen? DEFAULT
hat seine Rolle und kann nicht durch Initialisieren eines Java-Klassenmitglieds ersetzt werden.
JPA unterstützt das nicht und es wäre nützlich, wenn es dies tun würde. Die Verwendung von columnDefinition ist DB-spezifisch und in vielen Fällen nicht akzeptabel. Das Festlegen eines Standardwerts in der Klasse reicht nicht aus, wenn Sie einen Datensatz mit Nullwerten abrufen (was normalerweise passiert, wenn Sie alte DBUnit-Tests erneut ausführen). Was ich tue ist folgendes:
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 Auto-Boxing hilft dabei sehr.
Da ich bei dem Versuch, das gleiche Problem zu lösen, auf Google gestoßen bin, werde ich nur die Lösung einwerfen, die ich mir ausgedacht habe, falls jemand sie nützlich findet.
Aus meiner Sicht gibt es wirklich nur 1 Lösung für dieses Problem - @PrePersist. Wenn Sie dies in @PrePersist tun, müssen Sie überprüfen, ob der Wert bereits festgelegt wurde.
@PrePersist
von OP entscheiden. @Column(columnDefinition=...)
scheint nicht sehr elegant.
@Column(columnDefinition="tinyint(1) default 1")
Ich habe das Problem gerade getestet. Es funktioniert gut. Danke für den Tipp.
Über die Kommentare:
@Column(name="price")
private double price = 0.0;
Dieser legt (natürlich) nicht den Standardspaltenwert in der Datenbank fest.
Sie können die Java Reflect API verwenden:
@PrePersist
void preInsert() {
PrePersistUtil.pre(this);
}
Das ist üblich:
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()
einzufügen. Fügen final
Sie außerdem Parameter / abgefangene Ausnahmen hinzu, da Sie nicht versehentlich object
geändert werden möchten . Fügen Sie auch eine Überprüfung hinzu null
: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }
. Das stellt sicher, dass object.getClass()
das Aufrufen sicher ist und kein a auslöst NPE
. Der Grund ist, Fehler von faulen Programmierern zu vermeiden. ;-)
Ich benutze columnDefinition
und es funktioniert sehr gut
@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date createdDate;
Sie können dies nicht mit der Spaltenanmerkung tun. Ich denke, die einzige Möglichkeit besteht darin, den Standardwert festzulegen, wenn ein Objekt erstellt wird. Vielleicht wäre der Standardkonstruktor der richtige Ort dafür.
@Column
around. Und ich vermisse es auch, Kommentare zu setzen (entnommen aus Java doctag).
In meinem Fall habe ich den Quellcode des Ruhezustands geändert, um eine neue Anmerkung einzuführen @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 );
Nun, dies ist eine Lösung nur für den Ruhezustand.
@Column(columnDefinition='...')
funktioniert nicht, wenn Sie beim Einfügen der Daten die Standardeinschränkung in der Datenbank festlegen.insertable = false
und aus dieser entfernen columnDefinition='...'
, dann fügt die Datenbank automatisch den Standardwert aus der Datenbank ein.insertable = false
Hibernate / JPA hinzufügen , es wird funktionieren.@PrePersist
void preInsert() {
if (this.dateOfConsent == null)
this.dateOfConsent = LocalDateTime.now();
if(this.consentExpiry==null)
this.consentExpiry = this.dateOfConsent.plusMonths(3);
}
In meinem Fall wird dies aufgrund der Unabhängigkeit des Anbieters empfohlen, da das Feld LocalDateTime verwendet wird
Weder JPA- noch Hibernate-Annotationen unterstützen den Begriff eines Standardspaltenwerts. Um dieses Problem zu umgehen, legen Sie alle Standardwerte fest, bevor Sie einen Ruhezustand aufrufen save()
oderupdate()
die Sitzung . Dies ahmt das Verhalten der Datenbank nach, die beim Speichern einer Zeile in einer Tabelle Standardwerte festlegt (kurz bevor der Ruhezustand die Standardwerte festlegt).
Im Gegensatz zum Festlegen der Standardwerte in der Modellklasse, wie diese alternative Antwort vorschlägt, stellt dieser Ansatz auch sicher, dass Kriterienabfragen, die ein Example
Objekt als Prototyp für die Suche verwenden, weiterhin wie zuvor funktionieren. Wenn Sie den Standardwert eines nullfähigen Attributs (eines Attributs mit einem nicht primitiven Typ) in einer Modellklasse festlegen, ignoriert eine Beispielabfrage im Ruhezustand die zugeordnete Spalte nicht mehr, wo sie zuvor ignoriert wurde, weil sie null war.
Dies ist in JPA nicht möglich.
Mit der Spaltenanmerkung können Sie Folgendes tun: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html
Wenn Sie ein Double verwenden, können Sie Folgendes verwenden:
@Column(columnDefinition="double precision default '96'")
private Double grolsh;
Ja, es ist db-spezifisch.
Sie können den Standardwert im Datenbankdesigner oder beim Erstellen der Tabelle definieren. In SQL Server können Sie beispielsweise den Standard-Tresor eines Datumsfelds auf ( getDate()
) setzen. Verwenden Sie diese insertable=false
wie in Ihrer Spaltendefinition angegeben. JPA gibt diese Spalte in Einfügungen nicht an und die Datenbank generiert den Wert für Sie.
Sie müssen insertable=false
in Ihrer @Column
Anmerkung. JPA ignoriert dann diese Spalte beim Einfügen in die Datenbank und der Standardwert wird verwendet.
Siehe diesen Link: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html
nullable=false
wird mit einem scheitern SqlException
: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null
. Hier habe ich vergessen, den "erstellten" Zeitstempel mit zu setzen openingTime.setOpeningCreated(new Date())
. Dies ist eine gute Art, Konsistenz zu haben, aber das hat der Fragesteller nicht gefragt.