Apakah mungkin menggunakan urutan DB untuk beberapa kolom yang bukan pengenal / bukan bagian dari pengenal komposit ?
Saya menggunakan hibernate sebagai penyedia jpa, dan saya memiliki tabel yang memiliki beberapa kolom yang menghasilkan nilai (menggunakan urutan), meskipun mereka bukan bagian dari pengenal.
Yang saya inginkan adalah menggunakan urutan untuk membuat nilai baru untuk entitas, di mana kolom urutan BUKAN (bagian dari) kunci utama:
@Entity
@Table(name = "MyTable")
public class MyEntity {
//...
@Id //... etc
public Long getId() {
return id;
}
//note NO @Id here! but this doesn't work...
@GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
@SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
@Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
public Long getMySequencedValue(){
return myVal;
}
}
Lalu ketika saya melakukan ini:
em.persist(new MyEntity());
id akan dibuat, tetapi mySequenceVal
properti juga akan dibuat oleh penyedia JPA saya.
Hanya untuk memperjelas: Saya ingin Hibernate menghasilkan nilai mySequencedValue
properti. Saya tahu Hibernate dapat menangani nilai yang dihasilkan database, tetapi saya tidak ingin menggunakan pemicu atau hal lain selain Hibernate itu sendiri untuk menghasilkan nilai properti saya. Jika Hibernate dapat menghasilkan nilai untuk kunci utama, mengapa tidak dapat menghasilkan untuk properti sederhana?
@GeneratedValue
pada bidang yang bukan id. HarapSaya menemukan itu
@Column(columnDefinition="serial")
berfungsi sempurna tetapi hanya untuk PostgreSQL. Bagi saya ini adalah solusi yang tepat, karena entitas kedua adalah pilihan yang "jelek".Panggilan ke
saveAndFlush
entitas juga diperlukan, dansave
tidak akan cukup untuk mengisi nilai dari DB.sumber
columnDefinition=
Bit pada dasarnya memberitahu Hiberate untuk tidak mencoba membuat definisi kolom dan sebagai gantinya menggunakan teks yang Anda berikan. Pada dasarnya, DDL Anda untuk kolom secara harfiah hanya berupa name + columnDefinition. Dalam hal ini (PostgreSQL),mycolumn serial
adalah kolom yang valid dalam sebuah tabel.@Column(columnDefinition = "integer auto_increment")
@Column(insertable = false, updatable = false, columnDefinition="serial")
mencegah hibernasi mencoba memasukkan nilai null atau memperbarui bidang. Anda kemudian perlu membuat kueri ulang db untuk mendapatkan id yang dihasilkan setelah penyisipan jika Anda perlu langsung menggunakannya.Saya tahu ini adalah pertanyaan yang sangat lama, tetapi ini pertama kali ditunjukkan pada hasil dan jpa telah banyak berubah sejak pertanyaan itu.
Cara yang benar untuk melakukannya sekarang adalah dengan
@Generated
anotasi. Anda dapat menentukan urutan, mengatur default di kolom ke urutan itu dan kemudian memetakan kolom sebagai:@Generated(GenerationTime.INSERT) @Column(name = "column_name", insertable = false)
sumber
Hibernate pasti mendukung ini. Dari dokumen:
"Properti yang dihasilkan adalah properti yang nilainya dihasilkan oleh database. Biasanya, aplikasi Hibernasi diperlukan untuk menyegarkan objek yang berisi properti apa pun yang nilainya dihasilkan oleh database. Menandai properti yang dihasilkan, bagaimanapun, memungkinkan aplikasi mendelegasikan tanggung jawab ini ke Hibernasi. Pada dasarnya, setiap kali Hibernate mengeluarkan SQL INSERT atau UPDATE untuk entitas yang telah menentukan properti yang dihasilkan, segera mengeluarkan pilihan setelah itu untuk mengambil nilai yang dihasilkan. "
Untuk properti yang dibuat hanya dengan penyisipan, pemetaan properti Anda (.hbm.xml) akan terlihat seperti:
<property name="foo" generated="insert"/>
Untuk properti yang dihasilkan saat menyisipkan dan memperbarui pemetaan properti Anda (.hbm.xml) akan terlihat seperti:
<property name="foo" generated="always"/>
Sayangnya, saya tidak tahu JPA, jadi saya tidak tahu apakah fitur ini diekspos melalui JPA (saya kira mungkin tidak)
Alternatifnya, Anda harus bisa mengecualikan properti dari penyisipan dan pembaruan, dan kemudian "secara manual" memanggil session.refresh (obj); setelah Anda memasukkan / memperbaruinya untuk memuat nilai yang dihasilkan dari database.
Beginilah cara Anda mengecualikan properti agar tidak digunakan dalam pernyataan sisipkan dan perbarui:
<property name="foo" update="false" insert="false"/>
Sekali lagi, saya tidak tahu apakah JPA mengekspos fitur Hibernate ini, tetapi Hibernate mendukungnya.
sumber
Sebagai tindak lanjut, inilah cara saya membuatnya berfungsi:
@Override public Long getNextExternalId() { BigDecimal seq = (BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0); return seq.longValue(); }
sumber
SQLQuery sqlQuery = getSession().createSQLQuery("select NAMED_SEQ.nextval seq from dual"); sqlQuery.addScalar("seq", LongType.INSTANCE); return (Long) sqlQuery.uniqueResult();
Saya memperbaiki pembuatan UUID (atau urutan) dengan Hibernate menggunakan
@PrePersist
anotasi:@PrePersist public void initializeUUID() { if (uuid == null) { uuid = UUID.randomUUID().toString(); } }
sumber
Meskipun ini adalah utas lama, saya ingin membagikan solusi saya dan semoga mendapatkan umpan balik tentang ini. Berhati-hatilah karena saya hanya menguji solusi ini dengan database lokal saya di beberapa kasus pengujian JUnit. Jadi ini bukan fitur produktif sejauh ini.
Saya memecahkan masalah itu untuk saya dengan memperkenalkan anotasi khusus yang disebut Urutan tanpa properti. Itu hanya penanda untuk bidang yang harus diberi nilai dari urutan yang bertambah.
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sequence { }
Menggunakan anotasi ini saya menandai entitas saya.
public class Area extends BaseEntity implements ClientAware, IssuerAware { @Column(name = "areaNumber", updatable = false) @Sequence private Integer areaNumber; .... }
Untuk menjaga agar database tetap independen, saya memperkenalkan entitas yang disebut SequenceNumber yang memegang urutan nilai saat ini dan ukuran kenaikan. Saya memilih className sebagai kunci unik sehingga setiap kelas entitas akan mendapatkan urutannya sendiri.
@Entity @Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) }) public class SequenceNumber { @Id @Column(name = "className", updatable = false) private String className; @Column(name = "nextValue") private Integer nextValue = 1; @Column(name = "incrementValue") private Integer incrementValue = 10; ... some getters and setters .... }
Langkah terakhir dan yang paling sulit adalah PreInsertListener yang menangani penetapan nomor urut. Perhatikan bahwa saya menggunakan pegas sebagai wadah kacang.
@Component public class SequenceListener implements PreInsertEventListener { private static final long serialVersionUID = 7946581162328559098L; private final static Logger log = Logger.getLogger(SequenceListener.class); @Autowired private SessionFactoryImplementor sessionFactoryImpl; private final Map<String, CacheEntry> cache = new HashMap<>(); @PostConstruct public void selfRegister() { // As you might expect, an EventListenerRegistry is the place with which event listeners are registered // It is a service so we look it up using the service registry final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class); // add the listener to the end of the listener chain eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this); } @Override public boolean onPreInsert(PreInsertEvent p_event) { updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames()); return false; } private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames) { try { List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class); if (!fields.isEmpty()) { if (log.isDebugEnabled()) { log.debug("Intercepted custom sequence entity."); } for (Field field : fields) { Integer value = getSequenceNumber(p_entity.getClass().getName()); field.setAccessible(true); field.set(p_entity, value); setPropertyState(p_state, p_propertyNames, field.getName(), value); if (log.isDebugEnabled()) { LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value }); } } } } catch (Exception e) { log.error("Failed to set sequence property.", e); } } private Integer getSequenceNumber(String p_className) { synchronized (cache) { CacheEntry current = cache.get(p_className); // not in cache yet => load from database if ((current == null) || current.isEmpty()) { boolean insert = false; StatelessSession session = sessionFactoryImpl.openStatelessSession(); session.beginTransaction(); SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className); // not in database yet => create new sequence if (sequenceNumber == null) { sequenceNumber = new SequenceNumber(); sequenceNumber.setClassName(p_className); insert = true; } current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue()); cache.put(p_className, current); sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue()); if (insert) { session.insert(sequenceNumber); } else { session.update(sequenceNumber); } session.getTransaction().commit(); session.close(); } return current.next(); } } private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState) { for (int i = 0; i < propertyNames.length; i++) { if (propertyName.equals(propertyNames[i])) { propertyStates[i] = propertyState; return; } } } private static class CacheEntry { private int current; private final int limit; public CacheEntry(final int p_limit, final int p_current) { current = p_current; limit = p_limit; } public Integer next() { return current++; } public boolean isEmpty() { return current >= limit; } } }
Seperti yang Anda lihat dari kode di atas, pendengar menggunakan satu instance SequenceNumber per kelas entitas dan menyimpan beberapa nomor urut yang ditentukan oleh incrementValue dari entitas SequenceNumber. Jika kehabisan nomor urut, ia memuat entitas SequenceNumber untuk kelas target dan mencadangkan nilai incrementValue untuk panggilan berikutnya. Dengan cara ini saya tidak perlu meminta database setiap kali nilai urutan diperlukan. Perhatikan StatelessSession yang sedang dibuka untuk memesan rangkaian nomor urut berikutnya. Anda tidak dapat menggunakan sesi yang sama dengan entitas target yang saat ini dipertahankan karena ini akan mengarah ke ConcurrentModificationException di EntityPersister.
Semoga ini bisa membantu seseorang.
sumber
Jika Anda menggunakan postgresql
Dan saya menggunakan di boot musim semi 1.5.6
@Column(columnDefinition = "serial") @Generated(GenerationTime.INSERT) private Integer orderID;
sumber
seq_order
dan membuat referensi dari lapangan,nextval('seq_order'::regclass)
Saya menjalankan dalam situasi yang sama seperti Anda dan saya juga tidak menemukan jawaban yang serius apakah pada dasarnya memungkinkan untuk menghasilkan properti non-id dengan JPA atau tidak.
Solusi saya adalah memanggil urutan dengan permintaan JPA asli untuk mengatur properti dengan tangan sebelum bertahan.
Ini tidak memuaskan tetapi berfungsi sebagai solusi untuk saat ini.
Mario
sumber
Saya telah menemukan catatan khusus ini di sesi 9.1.9 Anotasi GeneratedValue dari spesifikasi JPA: "[43] Aplikasi portabel tidak boleh menggunakan anotasi GeneratedValue pada bidang atau properti lain yang persisten." Jadi, saya berasumsi bahwa tidak mungkin menghasilkan nilai secara otomatis untuk nilai kunci non primer setidaknya menggunakan JPA saja.
sumber
Sepertinya utas sudah tua, saya hanya ingin menambahkan solusi saya di sini (Menggunakan AspectJ - AOP di musim semi).
Solusinya adalah membuat anotasi kustom
@InjectSequenceValue
sebagai berikut.@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectSequenceValue { String sequencename(); }
Sekarang Anda dapat membuat anotasi bidang apa pun dalam entitas, sehingga nilai bidang yang mendasarinya (Panjang / Bilangan bulat) akan dimasukkan saat runtime menggunakan nilai berikutnya dari urutan tersebut.
Beri anotasi seperti ini.
//serialNumber will be injected dynamically, with the next value of the serialnum_sequence. @InjectSequenceValue(sequencename = "serialnum_sequence") Long serialNumber;
Sejauh ini kita telah menandai field yang kita butuhkan untuk menginjeksi nilai sequence. Jadi kita akan melihat bagaimana menginjeksi nilai sequence ke field yang ditandai, ini dilakukan dengan membuat point cut di AspectJ.
Kami akan memicu injeksi sebelum
save/persist
metode ini dijalankan. Ini dilakukan di kelas di bawah ini.@Aspect @Configuration public class AspectDefinition { @Autowired JdbcTemplate jdbcTemplate; //@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save()) @Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA. public void generateSequence(JoinPoint joinPoint){ Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save for (Object arg :aragumentList ) { if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class Field[] fields = arg.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields field.setAccessible(true); try { if (field.get(arg) == null){ // Setting the next value String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename(); long nextval=getNextValue(sequenceName); System.out.println("Next value :"+nextval); //TODO remove sout. field.set(arg, nextval); } } catch (Exception e) { e.printStackTrace(); } } } } } } /** * This method fetches the next value from sequence * @param sequence * @return */ public long getNextValue(String sequence){ long sequenceNextVal=0L; SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL"); while (sqlRowSet.next()){ sequenceNextVal=sqlRowSet.getLong("value"); } return sequenceNextVal; } }
Sekarang Anda dapat membuat anotasi Entitas apa pun seperti di bawah ini.
@Entity @Table(name = "T_USER") public class UserEntity { @Id @SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq") @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq") Long id; String userName; String password; @InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving. Long serialNumber; String name; }
sumber
"Saya tidak ingin menggunakan pemicu atau hal lain selain Hibernasi itu sendiri untuk menghasilkan nilai properti saya"
Dalam kasus tersebut, bagaimana dengan membuat implementasi UserType yang menghasilkan nilai yang diperlukan, dan mengonfigurasi metadata untuk menggunakan UserType tersebut untuk persistensi properti mySequenceVal?
sumber
Ini tidak sama dengan menggunakan urutan. Saat menggunakan urutan, Anda tidak memasukkan atau memperbarui apa pun. Anda hanya mengambil nilai urutan berikutnya. Sepertinya hibernasi tidak mendukungnya.
sumber
Jika Anda memiliki kolom dengan tipe UNIQUEIDENTIFIER dan pembuatan default diperlukan pada penyisipan tetapi kolom bukan PK
@Generated(GenerationTime.INSERT) @Column(nullable = false , columnDefinition="UNIQUEIDENTIFIER") private String uuidValue;
Dalam db Anda akan memiliki
CREATE TABLE operation.Table1 ( Id INT IDENTITY (1,1) NOT NULL, UuidValue UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL)
Dalam hal ini Anda tidak akan menentukan generator untuk nilai yang Anda butuhkan (Ini akan otomatis berkat
columnDefinition="UNIQUEIDENTIFIER"
). Hal yang sama dapat Anda coba untuk jenis kolom lainnyasumber
Saya telah menemukan solusi untuk ini di database MySql menggunakan @PostConstruct dan JdbcTemplate dalam aplikasi Spring. Ini mungkin bisa dilakukan dengan database lain tetapi kasus penggunaan yang akan saya sajikan didasarkan pada pengalaman saya dengan MySql, karena menggunakan auto_increment.
Pertama, saya telah mencoba mendefinisikan kolom sebagai auto_increment menggunakan properti ColumnDefinition dari anotasi @Column, tetapi itu tidak berfungsi karena kolom harus menjadi kunci agar menjadi penambahan otomatis, tetapi tampaknya kolom tidak akan didefinisikan sebagai indeks sampai setelah itu didefinisikan, menyebabkan kebuntuan.
Di sinilah saya mendapatkan ide untuk membuat kolom tanpa definisi auto_increment, dan menambahkannya setelahnya database dibuat. Hal ini dimungkinkan menggunakan anotasi @PostConstruct, yang menyebabkan metode dipanggil tepat setelah aplikasi menginisialisasi kacang, digabungkan dengan metode update JdbcTemplate.
Kodenya adalah sebagai berikut:
Dalam Entitas Saya:
@Entity @Table(name = "MyTable", indexes = { @Index(name = "my_index", columnList = "mySequencedValue") }) public class MyEntity { //... @Column(columnDefinition = "integer unsigned", nullable = false, updatable = false, insertable = false) private Long mySequencedValue; //... }
Di kelas PostConstructComponent:
@Component public class PostConstructComponent { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void makeMyEntityMySequencedValueAutoIncremental() { jdbcTemplate.update("alter table MyTable modify mySequencedValue int unsigned auto_increment"); } }
sumber
Saya ingin memberikan alternatif di samping solusi yang diterima @Morten Berg, yang bekerja lebih baik untuk saya.
Pendekatan ini memungkinkan untuk menentukan bidang dengan jenis yang sebenarnya diinginkan
Number
-Long
dalam kasus penggunaan saya - alih-alihGeneralSequenceNumber
. Ini dapat berguna, misalnya untuk serialisasi JSON (de-).Sisi negatifnya adalah ia membutuhkan sedikit lebih banyak overhead database.
Pertama, kita membutuhkan
ActualEntity
di mana kita ingin auto-incrementgenerated
typeLong
:// ... @Entity public class ActualEntity { @Id // ... Long id; @Column(unique = true, updatable = false, nullable = false) Long generated; // ... }
Selanjutnya, kita membutuhkan entitas pembantu
Generated
. Saya meletakkannya package-private di sebelahActualEntity
, untuk menjadikannya sebagai detail implementasi dari paket:@Entity class Generated { @Id @GeneratedValue(strategy = SEQUENCE, generator = "seq") @SequenceGenerator(name = "seq", initialValue = 1, allocationSize = 1) Long id; }
Akhirnya, kita membutuhkan tempat untuk menghubungkan sebelum kita menyimpan file
ActualEntity
. Di sana, kami membuat dan mempertahankan sebuahGenerated
instance. Ini kemudian menyediakan urutan database yang dihasilkanid
dari tipeLong
. Kami memanfaatkan nilai ini dengan menuliskannya keActualEntity.generated
.Dalam kasus penggunaan saya, saya menerapkan ini menggunakan REST Data Musim Semi
@RepositoryEventHandler
, yang dipanggil tepat sebelumActualEntity
get dipertahankan. Ini harus menunjukkan prinsip:@Component @RepositoryEventHandler public class ActualEntityHandler { @Autowired EntityManager entityManager; @Transactional @HandleBeforeCreate public void generate(ActualEntity entity) { Generated generated = new Generated(); entityManager.persist(generated); entity.setGlobalId(generated.getId()); entityManager.remove(generated); } }
Saya tidak mengujinya dalam aplikasi kehidupan nyata, jadi nikmati dengan hati-hati.
sumber
Saya berjuang dengan ini hari ini, bisa menyelesaikannya menggunakan ini
@Generated(GenerationTime.INSERT) @Column(name = "internal_id", columnDefinition = "serial", updatable = false) private int internalId;
sumber
Saya pernah berada dalam situasi seperti Anda (urutan JPA / Hibernate untuk bidang non @Id) dan saya akhirnya membuat pemicu dalam skema db saya yang menambahkan nomor urut unik pada penyisipan. Saya tidak pernah membuatnya bekerja dengan JPA / Hibernate
sumber
Setelah menghabiskan berjam-jam, ini dengan rapi membantu saya memecahkan masalah saya:
Untuk Oracle 12c:
Untuk H2:
Buat juga:
@Column(insertable = false)
sumber