Menetapkan nilai default untuk kolom di JPA


Jawaban:


230

Sebenarnya ini dimungkinkan di JPA, walaupun sedikit peretasan menggunakan columnDefinitionproperti @Columnanotasi, misalnya:

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

3
10 adalah bagian Integer, dan 2 adalah bagian desimal dari angka sehingga: 1234567890.12 akan menjadi angka yang didukung.
Nathan Feger

23
Perhatikan juga, definisi kolom ini tidak harus independen dari basis data, dan tentu saja tidak secara otomatis mengikat datatype di Java.
Nathan Feger

47
Setelah membuat entitas dengan bidang nol dan bertahan, Anda tidak akan memiliki nilai yang ditetapkan di dalamnya. Bukan solusi ...
Pascal Thivent

18
Tidak @NathanFeger, 10 adalah panjang dan 2 bagian desimal. Jadi 1234567890.12 tidak akan menjadi nomor yang didukung, tetapi 12345678.90 adalah valid.
GPrimola

4
Anda perlu insertable=falsejika kolom tersebut dapat dibatalkan (dan untuk menghindari argumen kolom yang tidak dibutuhkan).
eckes

314

Anda dapat melakukan hal berikut:

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

Sana! Anda baru saja menggunakan nol sebagai nilai default.

Catatan ini akan membantu Anda jika Anda hanya mengakses database dari aplikasi ini. Jika aplikasi lain juga menggunakan database, maka Anda harus melakukan pemeriksaan ini dari database menggunakan atribut anotasi columnDefinition Cameron's , atau cara lain.


38
Ini adalah cara yang tepat untuk melakukannya jika Anda ingin entitas Anda memiliki nilai yang benar setelah dimasukkan. +1
Pascal Thivent

16
Menetapkan nilai default atribut nullable (atribut yang memiliki tipe non-primitif) di kelas model akan memecah kueri kriteria yang menggunakan Exampleobjek sebagai prototipe untuk pencarian. Setelah menetapkan nilai default, kueri contoh Hibernate tidak akan lagi mengabaikan kolom terkait di mana sebelumnya akan mengabaikannya karena itu nol. Pendekatan yang lebih baik adalah mengatur semua nilai default sesaat sebelum menjalankan Hibernate save()atau update(). Ini lebih baik meniru perilaku database yang menetapkan nilai default saat menyimpan baris.
Derek Mahar 3-10

1
@ Harry: Ini adalah cara yang benar untuk menetapkan nilai default kecuali ketika Anda menggunakan kueri kriteria yang menggunakan contoh sebagai prototipe untuk pencarian.
Derek Mahar

12
Ini tidak memastikan bahwa default diatur untuk tipe non primitif (pengaturan nullmisalnya). Menggunakan @PrePersistdan @PreUpdatemerupakan pilihan yang lebih baik.
Jasper

3
Ini adalah cara yang tepat untuk menentukan nilai default untuk kolom. columnDefinitionproperti tidak independen-database dan @PrePersistmenimpa pengaturan Anda sebelum menyisipkan, "nilai default" adalah sesuatu yang lain, nilai default digunakan ketika nilai tidak diatur secara eksplisit.
Utku Özdemir

110

pendekatan lain menggunakan javax.persistence.PrePersist

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

1
Saya menggunakan pendekatan seperti ini untuk menambah dan memperbarui cap waktu. Itu bekerja dengan sangat baik.
Spina

10
Bukankah ini seharusnya if (createdt != null) createdt = new Date();atau sesuatu? Saat ini, ini akan menimpa nilai yang ditentukan secara eksplisit, yang membuatnya tidak benar-benar default.
Max Nanasy

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

Apakah penting jika klien dan basis data berada di zona waktu yang berbeda? Saya membayangkan bahwa menggunakan sesuatu seperti "TIMESTAMP DEFAULT CURRENT_TIMESTAMP" akan mendapatkan waktu saat ini di server database, dan "Createdt = new Date ()" akan mendapatkan waktu saat ini dalam kode java, tetapi dua kali ini mungkin tidak akan sama jika klien tersambung ke server di zona waktu yang berbeda.
FrustratedWithFormsDesigner

Menambahkan nullcek.
Ondra Žižka

49

Pada 2017, JPA 2.1 masih memiliki hanya @Column(columnDefinition='...')yang Anda menempatkan definisi SQL literal kolom. Yang cukup tidak fleksibel dan memaksa Anda untuk juga mendeklarasikan aspek-aspek lain seperti jenis, hubungan pendek pandangan implementasi JPA tentang masalah itu.

Hibernate, punya ini:

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

Mengidentifikasi nilai DEFAULT untuk diterapkan ke kolom terkait melalui DDL.

Dua catatan untuk itu:

1) Jangan takut menjadi tidak standar. Bekerja sebagai pengembang JBoss, saya telah melihat beberapa proses spesifikasi. Spesifikasi ini pada dasarnya adalah garis dasar bahwa para pemain besar di bidang yang diberikan bersedia berkomitmen untuk mendukung untuk dekade berikutnya. Itu benar untuk keamanan, untuk olahpesan, ORM tidak ada bedanya (walaupun JPA mencakup cukup banyak). Pengalaman saya sebagai pengembang adalah bahwa dalam aplikasi yang kompleks, cepat atau lambat Anda akan memerlukan API yang tidak standar. Dan@ColumnDefault merupakan contoh ketika itu melampaui negatif menggunakan solusi non-standar.

2) Menyenangkan bagaimana semua orang melambaikan inisialisasi @PrePersist atau anggota konstruktor. Tapi itu TIDAK sama. Bagaimana dengan pembaruan SQL massal? Bagaimana dengan pernyataan yang tidak mengatur kolom? DEFAULTmemiliki perannya dan itu tidak dapat diganti dengan menginisialisasi anggota kelas Java.


Versi hibernate-annotations apa yang dapat saya gunakan untuk Wildfly 12, saya mengalami kesalahan dengan versi spesifik ini? Terima kasih sebelumnya!
Fernando Pie

Terima kasih Ondra. Apakah ada solusi lain di 2019?
Alan

1
@Lan tidak dalam stok JPA, sayangnya.
jwenting

13

JPA tidak mendukung itu dan akan berguna jika itu terjadi. Menggunakan columnDefinition khusus untuk DB dan tidak dapat diterima dalam banyak kasus. pengaturan default di kelas tidak cukup ketika Anda mengambil catatan yang memiliki nilai nol (yang biasanya terjadi ketika Anda menjalankan kembali tes DBUnit lama). Apa yang saya lakukan adalah ini:

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 sangat membantu dalam hal itu.


Jangan menyalahgunakan setter! Mereka adalah untuk mengatur parameter di bidang objek. Tidak ada lagi! Lebih baik gunakan konstruktor default untuk nilai default.
Roland

@Roland bagus secara teori, tetapi tidak akan selalu bekerja ketika berhadapan dengan database yang sudah ada yang sudah berisi nilai nol di mana aplikasi Anda ingin tidak memilikinya. Anda harus melakukan konversi database terlebih dahulu di mana Anda membuat kolom bukan-nol dan menetapkan default yang masuk akal pada saat yang sama, dan kemudian berurusan dengan serangan balik dari aplikasi lain yang menganggap itu sebenarnya nullable (yaitu, jika Anda bahkan dapat memodifikasi database).
jwenting

Jika datang ke #JPA dan setter / getter, mereka harus selalu menjadi setter murni / pengambil lain suatu hari aplikasi Anda telah tumbuh dan tumbuh dan menjadi mimpi buruk pemeliharaan. Saran terbaik adalah CIUMAN di sini (aku bukan gay, LOL).
Roland

10

Melihat ketika saya menemukan ini dari Google ketika mencoba untuk memecahkan masalah yang sama, saya hanya akan melempar solusi yang saya buat seandainya seseorang merasa berguna.

Dari sudut pandang saya hanya ada 1 solusi untuk masalah ini - @PrePersist. Jika Anda melakukannya di @PrePersist, Anda harus memeriksa apakah nilainya sudah ditetapkan.


4
+1 - Saya pasti akan memilih untuk @PrePersistusecase OP. @Column(columnDefinition=...)sepertinya tidak elegan.
Piotr Nowicki

@PrePersist tidak akan membantu Anda jika sudah ada data di toko dengan nilai nol. Ini tidak akan secara ajaib melakukan konversi database untuk Anda.
jwenting

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

Saya baru saja menguji masalahnya. Ini bekerja dengan baik. Terima kasih atas petunjuknya.


Tentang komentar:

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

Yang ini tidak menetapkan nilai kolom default di database (tentu saja).


9
Ini tidak berfungsi dengan baik pada tingkat objek (Anda tidak akan mendapatkan nilai default database setelah memasukkan dalam entitas Anda). Default di tingkat Java.
Pascal Thivent

Ini bekerja (terutama jika Anda menentukan insertable = false ke dalam database, tetapi sayangnya versi cache tidak di-refresh (bahkan ketika JPA memilih tabel dan kolom). Setidaknya tidak dengan hibernate: - / tetapi bahkan jika itu akan bekerja tambahan jemput buruk.
Eckes

9

Anda dapat menggunakan java reflect api:

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

Ini biasa:

    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
Saya suka solusi ini, tetapi memiliki satu titik kelemahan, saya pikir itu akan menjadi penting untuk memeriksa apakah kolom tersebut dapat dibatalkan atau tidak sebelum menetapkan nilai default.
Luis Carlos

Jika Anda suka, memasukkan kode dari Field[] fields = object.getClass().getDeclaredFields();dalam for()mungkin juga oke. Dan juga tambahkan finalke parameter / pengecualian yang ditangkap karena Anda tidak ingin objectdimodifikasi secara tidak sengaja. Juga menambahkan cek pada null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Itu memastikan bahwa object.getClass()itu aman untuk memanggil dan tidak memicu NPE. Alasannya adalah untuk menghindari kesalahan oleh programmer yang malas. ;-)
Roland

7

Saya menggunakan columnDefinitiondan itu bekerja dengan sangat baik

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;

6
Ini sepertinya membuat vendor implementasi jpa Anda spesifik.
Udo Diadakan

Itu hanya membuat vendor DDL spesifik. Tetapi Anda memiliki kemungkinan besar hacks DDL khusus vendor. Namun seperti disebutkan di atas nilai (bahkan ketika insertable = false) muncul dalam DB tetapi tidak dalam cache entitas sesi (setidaknya tidak dalam hibernasi).
eckes

7

Anda tidak dapat melakukan ini dengan anotasi kolom. Saya pikir satu-satunya cara adalah menetapkan nilai default ketika sebuah objek dibuat. Mungkin konstruktor default akan menjadi tempat yang tepat untuk melakukan itu.


Ide bagus. Sayangnya tidak ada penjelasan umum atau atribut untuk @Columnsekitar. Dan saya juga kehilangan komentar yang ditetapkan (diambil dari Java doctag).
Roland

5

Dalam kasus saya, saya memodifikasi kode sumber hibernate-core, untuk memperkenalkan anotasi baru @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 );

Nah, ini adalah solusi hibernate-only.


2
Sementara saya sangat menghargai upaya orang-orang yang secara aktif berkontribusi pada proyek open source, saya menurunkan jawaban ini karena JPA adalah spesifikasi Java standar di atas OR / M, OP meminta cara JPA untuk menentukan nilai default dan pekerjaan tambalan Anda hanya untuk Hibernate. Jika ini adalah NHibernate, di mana tidak ada spesifikasi super-partes untuk persistensi (NPA bahkan tidak didukung oleh MS EF), saya akan memutakhirkan patch semacam itu. Yang benar adalah bahwa JPA sangat terbatas dibandingkan dengan persyaratan ORM (satu contoh: tidak ada indeks sekunder). Pokoknya pujian untuk upaya
usr-local-ΕΨΗΕΛΩΝ

Apakah ini tidak membuat masalah pemeliharaan? Orang perlu mengulang perubahan ini setiap kali hibernasi upgrade atau pada setiap instalasi baru?
user1242321

Karena pertanyaannya adalah tentang JPA, dan Hibernate bahkan tidak disebutkan, ini tidak menjawab pertanyaan
Neil Stockton

5
  1. @Column(columnDefinition='...') tidak berfungsi ketika Anda menetapkan batasan default dalam database saat memasukkan data.
  2. Anda perlu membuat insertable = falsedan menghapus columnDefinition='...'dari anotasi, maka database akan secara otomatis memasukkan nilai default dari database.
  3. Misalnya ketika Anda mengatur jenis kelamin varchar adalah laki-laki secara default di database.
  4. Anda hanya perlu menambahkan insertable = falseHibernate / JPA, itu akan berfungsi.


Dan bukan pilihan yang baik, jika Anda ingin mengatur nilainya kadang-kadang.
Shihe Zhang

@ShiheZhang menggunakan false tidak membiarkan saya mengatur nilainya?
fvildoso

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

Dalam kasus saya karena bidang ini adalah LocalDateTime yang saya gunakan ini, direkomendasikan karena independensi vendor


2

Baik anotasi JPA maupun Hibernate tidak mendukung gagasan nilai kolom default. Sebagai solusi untuk batasan ini, setel semua nilai default sesaat sebelum Anda menjalankan Hibernate save()atau update()pada sesi. Ini sedekat mungkin (pendek dari Hibernate pengaturan nilai default) meniru perilaku database yang menetapkan nilai default saat menyimpan baris dalam tabel.

Tidak seperti pengaturan nilai default di kelas model seperti yang disarankan jawaban alternatif ini , pendekatan ini juga memastikan bahwa kriteria kueri yang menggunakan Exampleobjek sebagai prototipe untuk pencarian akan terus bekerja seperti sebelumnya. Ketika Anda menetapkan nilai default atribut nullable (yang memiliki tipe non-primitif) di kelas model, contoh-contoh Hibernate tidak akan lagi mengabaikan kolom terkait di mana sebelumnya akan mengabaikannya karena itu nol.


Dalam solusi sebelumnya, penulis menyebutkan ColumnDefault ("")
nikolai.serdiuk

@ nikolai.serdiuk bahwa anotasi ColumnDefault ditambahkan bertahun-tahun setelah jawaban ini ditulis. Pada 2010 itu benar, tidak ada penjelasan seperti itu (sebenarnya tidak ada penjelasan, hanya konfigurasi xml).
jwenting

1

Ini tidak mungkin di JPA.

Inilah yang dapat Anda lakukan dengan anotasi Kolom: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html


1
Tidak dapat menentukan nilai default tampaknya kekurangan serius dari penjelasan JPA.
Derek Mahar

2
JDO memungkinkannya dalam definisi ORM-nya, jadi JPA harus memasukkan ini ... suatu hari
user383680

Anda pasti dapat melakukannya, dengan atribut columnDefinition, seperti yang dijawab Cameron Pope.
IntelliData

@DerekMahar bukan satu-satunya kekurangan dalam spesifikasi JPA. Ini spesifikasi yang baik, tetapi tidak sempurna
jwenting

1

Jika Anda menggunakan ganda, Anda dapat menggunakan yang berikut:

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

private Double grolsh;

Ya itu spesifik db.


0

Anda bisa menentukan nilai default di perancang database, atau saat Anda membuat tabel. Sebagai contoh di SQL Server Anda dapat mengatur kubah default bidang tanggal ke ( getDate()). Gunakan insertable=falseseperti yang disebutkan dalam definisi kolom Anda. JPA tidak akan menentukan kolom pada sisipan dan database akan menghasilkan nilai untuk Anda.


-2

Anda perlu insertable=falsedi dalam @Columnanotasi Anda . JPA akan mengabaikan kolom itu saat memasukkan dalam database dan nilai default akan digunakan.

Lihat tautan ini: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html


2
Lalu bagaimana Anda akan memasukkan nilai yang diberikan @ runtime ??
Ashish Ratan

Ya itu benar. nullable=falseakan gagal dengan SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Di sini saya lupa mengatur cap waktu "dibuat" dengan openingTime.setOpeningCreated(new Date()). Ini adalah cara yang bagus untuk memiliki konsistensi tetapi itu yang tidak ditanyakan si penanya.
Roland
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.