I am trying to perform Batch Insert with spring data jpa and Hibernate but the problem is Hibernate only supports batch inserts when the generated value strategy is SEQUENCE but somehow sqlite does not support this strategy so I resort to Identity which works fine but does not support batching with hibernate. Is there any solution or workaround for this.
Entity
#Entity
#Table(name = "party")
public class PartyEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "party_id", nullable = false)
private Long id;
#Column(name = "party_name", unique = true)
#NonNull
private String name;
#Column(name = "party_phone_number", unique = true)
private Long number;
}
SQLite do have this table called 'sqlite_sequence' which I try to use with #SequenceGenerator but hibernate tries to create this table which we cannot as this is a reserved table. And same goes for #TableGenerator.
Annotation for sequence used
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "party_seq")
#SequenceGenerator(name = "party_seq", sequenceName = "sqlite_sequence", allocationSize = 1)
Annotation for table used
#GeneratedValue(strategy = GenerationType.TABLE, generator = "sqlite_generator")
#TableGenerator(name = "sqlite_generator", table = "sqlite_sequence", pkColumnName = "name",
valueColumnName = "seq", pkColumnValue = "party", initialValue = 1, allocationSize = 1)
DDL script for the table
CREATE TABLE party (
party_id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
party_name VARCHAR (256) UNIQUE
NOT NULL,
party_phone_number INTEGER (10) UNIQUE
);
Imagine there is a Work table in Oracle database, the primary key of which is auto-generated sequence. There is another table called External_Reference. They are of One-to-Many relationship, i.e., one work may have many external references.
External_Reference has a foreign key Work_ID to table Work's primary key ID.
With Hibernate, wonder if possible to use saveOrUpdate(object) to save a work saveOrUpdate(aWork), which will automatically save all of its external references?
Work work = new Work("Get started with Hibernate");
Set<ExternalReference> externalRefs = new HashSet<>();
ExternalReference ref1 = new ExternalReference("isbn", "23423454");
ref1.setWork(work); // work.getId() returns null before work being saved.
externalRefs.add(ref1);
ExternalReference ref2 = new ExternalReference("doi", "d2342-345553");
ref2.setWork(work);
externalRefs.add(ref2);
work.setExternalReferences(externalRefs);
// ORA-01400: cannot insert NULL into ("External_Reference"."WORK_ID")
aHibernateSession.saveOrUpdate(work);
The challenge is, before saving the work, you won't be able to know the auto-generated work ID, which means you cannot assign it to the work's external references.
Yes you can save the work without any external references first to get the auto-generated work ID, and then assign the work ID to all of its external references and save them. But I prefer not to do a two-steps thing to save a single work, if possible.
Model classes:
#Entity
#Table(name = "Work")
public class Work implements java.io.Serializable {
#Id
#Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "WORK_SEQ")
#SequenceGenerator(name = "WORK_SEQ", sequenceName = "WORK_SEQ", allocationSize = 1, initialValue = 1)
public BigDecimal getId() {
return this.Id;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "work", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<ExternalReference> getExternalReferences() {
return this.externalReferences;
}
}
#Entity
#Table(name = "External_Reference")
public class ExternalReference implements java.io.Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "WORK_ID", nullable = false, insertable = false, updatable = false)
public Work getWork() {
return this.work;
}
}
Here you need to pass just
ref1.setWorkId(work);
ref2.setWorkId(work);
also, check the hibernate mapping check mapping example
//Work Class is Parent
#Entity
#Table(name = "Work")
public class Work {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "TITTLE", nullable = false)
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "work")
private Set<ExternalReference> externalReference;
public Work() {
}
public Work(String title) {
this.title = title;
}
//setter and getter
}
//ExternalReference Class is Child
#Entity
#Table(name = "External_Reference")
public class ExternalReference {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "Ref_Source", nullable = false)
private String ref_source;
#Column(name = "Ref_Id", nullable = false)
private String ref_id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name ="Work_ID")
private Work work;
public ExternalReference() {
}
public ExternalReference(String ref_source, String ref_id) {
this.ref_source = ref_source;
this.ref_id = ref_id;
}
//setter and getter
}
The workaround I thought of and tested is a manual way (for your reference):
For a new work, manually get the next sequence from the database first, and assign it to the work and all of its external references. Then you can persist the work with all of its external references by just using aHibernateSession.saveOrUpdate(work);
For an existing work, just use its existing ID, and assign it to its new external references if any.
Getting sequences from the database will guarantee that the sequence is unique. It is thread safe.
public BigDecimal getNextSeq() {
Session session = null;
try {
session = sessionFactory.openSession(); // sessionFactory should have been initialised.
Query query = session.createSQLQuery("select WORK_SEQ.nextval as num from dual")
.addScalar("num", StandardBasicTypes.BIG_DECIMAL);
BigDecimal nextSeq = (BigDecimal) query.uniqueResult();
return nextSeq;
} finally {
if (session != null){
session.close();
}
}
}
For this workaround to work, you need to comment out the Work's key generator, as below:
#Entity
#Table(name = "Work")
public class Work implements java.io.Serializable {
#Id
#Column(name = "ID", unique = true, nullable = false, precision = 22, scale = 0)
// #GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "WORK_SEQ")
// #SequenceGenerator(name = "WORK_SEQ", sequenceName = "WORK_SEQ", allocationSize = 1, initialValue = 1)
public BigDecimal getId() {
return this.Id;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "work", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<ExternalReference> getExternalReferences() {
return this.externalReferences;
}
}
This workaround is a manual way, not elegant, but works. Still hoping to find the elegant approach, it must exist somewhere, as this is a quite common use case.
I've tried to use custom id-generator as in Bypass GeneratedValue in Hibernate (merge data not in db?) and it works fine while working with Postgres DB. My code is equals to the code in example.
But while running test with H2 in-memory database I faced the problem, that id is not generated automaticaly.
Without custom generator
#Column(name = "id", nullable = false)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Generated create table script
create table db.schema.entity (id bigserial not null...
With custom generator
#Column(name = "id", nullable = false)
#Id
#GeneratedValue(generator = "idGenerator", strategy = GenerationType.IDENTITY)
#GenericGenerator(name="idGenerator", strategy = "...UseIdOrGenerate")
private Long id;
Generated create table script
create table db.schema.entity (id int8 not null...
As a result tests don't work.
Solved by changing #Column
#Column(name = "id", nullable = false, columnDefinition = "bigserial")
This field "auftragsnummer" always stays null, even though it is annotated with #GeneratedValue:
#Entity
public class Auftrag implements Serializable
{
#Id
#GeneratedValue
private int id;
#Pattern(regexp = AUFTRAGSNUMMER_REGEXP, message = "{validator.auftragsnummer}")
#Length(min = 20, max = 20)
#GenericGenerator(name = "sequence_auftragsnummer", strategy = "de.software.AuftragsnummerGenerator")
#GeneratedValue(generator = "sequence_auftragsnummer")
#Column(unique = true, nullable = false)
private String auftragsnummer;
}
I store it using getHibernateTemplate().persist(t). The referenced generator class implements org.hibernate.id.IdentifierGenerator.
I have no idea why it is ignored.
Environment:
Hibernate 5.0.1
Spring 4.2.1
Java 7
DB2 10
JPA only mandates support for #GeneratedValue on #Id fields.
If your order number doesn't depend on the entity's primary key, then the easiest solution is to use #PrePersist e.g.
#PrePersist
public void onCreate() {
auftragsnummer = ...;
}
I have entity where id generated using sequence
#Id
#SequenceGenerator(name = "ENTITY_SEQ", sequenceName = "ENTITY_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ENTITY_SEQ")
#Column(name = "ID", unique = true, nullable = false)
public Long getId() {
return id;
}
And it work OK. But when i cretate test and set id manualy, sequence rewrite value and set it's own value. Is any posibility to change priority of setting value to ID?