Можно ли установить значение по умолчанию для столбцов в JPA, и если, как это делается с использованием аннотаций?
Можно ли установить значение по умолчанию для столбцов в JPA, и если, как это делается с использованием аннотаций?
Ответы:
На самом деле это возможно в JPA, хотя немного взломать, используя columnDefinition
свойство @Column
аннотации, например:
@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
insertable=false
если столбец обнуляемый (и чтобы избежать ненужного аргумента столбца).
Вы можете сделать следующее:
@Column(name="price")
private double price = 0.0;
Там! Вы только что использовали ноль в качестве значения по умолчанию.
Обратите внимание, что это будет вам полезно, если вы обращаетесь к базе данных только из этого приложения. Если другие приложения также используют базу данных, вы должны выполнить эту проверку из базы данных, используя атрибут аннотации columnDefinition Камерона , или каким-либо другим способом.
Example
объект в качестве прототипа для поиска. После установки значения по умолчанию пример запроса Hibernate больше не будет игнорировать связанный столбец, где ранее он игнорировал его, поскольку он был нулевым. Лучше всего установить все значения по умолчанию непосредственно перед вызовом Hibernate save()
или update()
. Это лучше имитирует поведение базы данных, которая устанавливает значения по умолчанию при сохранении строки.
null
например). Использование @PrePersist
и @PreUpdate
является лучшим вариантом imho.
columnDefinition
свойство не зависит от базы данных и @PrePersist
переопределяет ваши настройки перед вставкой, «значение по умолчанию» - это нечто другое, значение по умолчанию используется, когда значение не задано явно.
Другой подход заключается в использовании javax.persistence.PrePersist
@PrePersist
void preInsert() {
if (this.createdTime == null)
this.createdTime = new Date();
}
if (createdt != null) createdt = new Date();
или что-то? Прямо сейчас это переопределит явно указанное значение, которое, по-видимому, не будет по умолчанию.
if (createdt == null) createdt = new Date();
null
проверка.
В 2017 году в JPA 2.1 остается только то, @Column(columnDefinition='...')
к чему вы добавили буквальное определение SQL столбца. Что довольно негибко и заставляет вас также объявлять другие аспекты, такие как тип, кратко излагая взгляд реализации JPA по этому вопросу.
Hibernate, хотя, имеет это:
@Column(length = 4096, nullable = false)
@org.hibernate.annotations.ColumnDefault("")
private String description;
Определяет значение DEFAULT для применения к связанному столбцу с помощью DDL.
Два примечания к этому:
1) Не бойтесь идти нестандартно. Работая в качестве разработчика JBoss, я видел довольно много процессов спецификации. Спецификация в основном является базовой линией, которую крупные игроки в данной области готовы взять на себя обязательство поддержать в течение следующего десятилетия или около того. Это верно для безопасности, для обмена сообщениями, ORM не имеет значения (хотя JPA охватывает довольно много). Мой опыт как разработчика заключается в том, что в сложных приложениях рано или поздно вам все равно понадобится нестандартный API. И @ColumnDefault
это пример, когда он перевешивает недостатки использования нестандартного решения.
2) Приятно, что все машут инициализацией @PrePersist или членом конструктора. Но это не то же самое. Как насчет массовых обновлений SQL? Как насчет утверждений, которые не устанавливают столбец? DEFAULT
имеет свою роль, и это не может быть заменено путем инициализации члена класса Java.
JPA не поддерживает это, и было бы полезно, если бы это было. Использование columnDefinition зависит от БД и во многих случаях неприемлемо. установка значения по умолчанию в классе недостаточна, когда вы извлекаете запись с нулевыми значениями (что обычно происходит при повторном запуске старых тестов DBUnit). Что я делаю, так это:
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-бокс помогает в этом.
Видя, как я наткнулся на это от Google, пытаясь решить ту же самую проблему, я просто добавлю решение, которое я приготовил, на случай, если кто-то посчитает его полезным.
С моей точки зрения, на самом деле есть только 1 решение этой проблемы - @PrePersist. Если вы делаете это в @PrePersist, вы должны проверить, установлено ли уже значение.
@PrePersist
вариант использования ОП. @Column(columnDefinition=...)
не выглядит очень элегантно
@Column(columnDefinition="tinyint(1) default 1")
Я только что проверил проблему. Работает просто отлично. Спасибо за подсказку.
О комментариях:
@Column(name="price")
private double price = 0.0;
Этот не устанавливает значение столбца по умолчанию в базе данных (конечно).
Вы можете использовать Java отражает API:
@PrePersist
void preInsert() {
PrePersistUtil.pre(this);
}
Это распространено:
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();
в также for()
может быть хорошо. А также добавьте final
в ваш параметр / перехваченные исключения, так как вы не хотите, чтобы object
их модифицировали случайно. Кроме того, добавить проверку на null
: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }
. Это гарантирует, что object.getClass()
это безопасно для вызова и не вызывает NPE
. Причина в том, чтобы избежать ошибок ленивых программистов. ;-)
Я использую, columnDefinition
и это работает очень хорошо
@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date createdDate;
Вы не можете сделать это с аннотацией столбца. Я думаю, что единственный способ - установить значение по умолчанию при создании объекта. Может быть, конструктор по умолчанию будет правильным местом для этого.
@Column
вокруг. И я также скучаю по комментариям (взятым из Java doctag).
В моем случае я изменил исходный код ядра Hibernate, ну, чтобы ввести новую аннотацию @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 );
Ну, это решение только для гибернации.
@Column(columnDefinition='...')
не работает при установке ограничения по умолчанию в базе данных при вставке данных.insertable = false
и удалить columnDefinition='...'
из аннотации, тогда база данных автоматически вставит значение по умолчанию из базы данных.insertable = false
в Hibernate / JPA, это будет работать.@PrePersist
void preInsert() {
if (this.dateOfConsent == null)
this.dateOfConsent = LocalDateTime.now();
if(this.consentExpiry==null)
this.consentExpiry = this.dateOfConsent.plusMonths(3);
}
В моем случае из-за поля LocalDateTime я использовал это, это рекомендуется из-за независимости поставщика
Ни JPA, ни аннотации Hibernate не поддерживают понятие значения столбца по умолчанию. Чтобы обойти это ограничение, установите все значения по умолчанию непосредственно перед вызовом Hibernate save()
или update()
в сеансе. Это настолько близко, насколько это возможно (если не считать Hibernate, устанавливающий значения по умолчанию), имитирующее поведение базы данных, которая устанавливает значения по умолчанию при сохранении строки в таблице.
В отличие от установки значений по умолчанию в классе модели, как предполагает этот альтернативный ответ , этот подход также гарантирует, что запросы критериев, которые используют Example
объект в качестве прототипа для поиска, будут продолжать работать как прежде. Когда вы устанавливаете значение по умолчанию для атрибута, допускающего значение NULL, (атрибута, который имеет непримитивный тип) в классе модели, запрос Hibernate по примеру больше не будет игнорировать связанный столбец, где ранее он игнорировал его, поскольку он был нулевым.
Это невозможно в JPA.
Вот что вы можете сделать с аннотацией столбца: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html
Вы можете определить значение по умолчанию в конструкторе базы данных или при создании таблицы. Например, в SQL Server вы можете установить хранилище по умолчанию для поля Дата в ( getDate()
). Используйте, insertable=false
как указано в определении вашего столбца. JPA не будет указывать этот столбец на вставках, и база данных сгенерирует значение для вас.
Вам нужно insertable=false
в вас @Column
аннотации. JPA будет игнорировать этот столбец при вставке в базу данных, и будет использоваться значение по умолчанию.
Смотрите эту ссылку: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html
nullable=false
потерпит неудачу с SqlException
: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null
. Здесь я забыл установить «созданную» метку времени с помощью openingTime.setOpeningCreated(new Date())
. Это хороший способ иметь последовательность, но это то, что спрашивающий не спрашивал.