Czy można ustawić domyślną wartość dla kolumn w JPA, a jeśli tak, to jak to zrobić za pomocą adnotacji?
Czy można ustawić domyślną wartość dla kolumn w JPA, a jeśli tak, to jak to zrobić za pomocą adnotacji?
Odpowiedzi:
W rzeczywistości jest to możliwe w JPA, choć trochę hack przy użyciu columnDefinition
właściwości @Column
adnotacji, na przykład:
@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
insertable=false
jeśli kolumna ma wartość zerową (i aby uniknąć niepotrzebnego argumentu kolumny).
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.
Example
obiekt 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.
null
na przykład ustawienie ). Korzystanie @PrePersist
i @PreUpdate
jest lepszą opcją imho.
columnDefinition
właściwość nie jest niezależna od bazy danych i @PrePersist
przesłania ustawienie przed wstawieniem, „wartość domyślna” to coś innego, wartość domyślna jest używana, gdy wartość nie jest jawnie ustawiona.
innym podejściem jest użycie javax.persistence.PrePersist
@PrePersist
void preInsert() {
if (this.createdTime == null)
this.createdTime = new Date();
}
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ść.
if (createdt == null) createdt = new Date();
null
czek.
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 @ColumnDefault
jest 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? DEFAULT
ma swoją rolę i nie można jej zastąpić przez zainicjowanie członka klasy Java.
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.
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.
@PrePersist
wybrałbym przypadek użycia OP. @Column(columnDefinition=...)
nie wydaje się bardzo elegancki.
@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).
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();
}
}
}
Field[] fields = object.getClass().getDeclaredFields();
w for()
pliku może być również w porządku. Dodaj także final
do 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. ;-)
Używam columnDefinition
i działa bardzo dobrze
@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date createdDate;
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.
@Column
. I tęsknię też za ustawianymi komentarzami (zaczerpniętymi z doctag Java).
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.
@Column(columnDefinition='...')
nie działa po ustawieniu domyślnego ograniczenia w bazie danych podczas wstawiania danych.insertable = false
i usunąć columnDefinition='...'
adnotację, a następnie baza danych automatycznie wstawi wartość domyślną z bazy danych.insertable = false
Hibernate / JPA, to zadziała.@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
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ą Example
obiekt 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 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
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.
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=false
zgodnie z definicją w kolumnie. JPA nie określi tej kolumny na wstawkach, a baza danych wygeneruje dla ciebie wartość.
Musisz insertable=false
w sobie @Column
adnotację. 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
nullable=false
nie 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ł.