In Hibernate ORM & Spring Boot, durante la creazione di SessionFactory
, è possibile eseguire istruzioni SQL DML per creare tabelle e / o inserire dati tramite file come import.sql
definito in javax.persistence.hibernate.hbm2ddl.import_files
proprietà. Come ho capito, l'utilizzo di questa funzione è particolarmente utile per l'inserimento di dati referenziali (ad es. Codici ISO come Paesi) sulla creazione dello schema ( create
, create-drop
o update
). L'uso della multi-tenancy di Hibernate significa anche che se i nuovi tenant vengono creati dinamicamente, verrà eseguito import.sql.
esempio import.sql:
INSERT INTO address_types (address_type_id, address_type) VALUES
(1, 'Home'), (2, 'Business') ON CONFLICT DO NOTHING;
AddressType.java:
@Entity
@Table(name = "address_types")
public class AddressType {
@Id
@SequenceGenerator(name = "address_types_id_seq", sequenceName = "address_types_id_seq")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "address_types_id_seq")
@Column(name = "address_type_id")
private int id;
@Column(name = "address_type", nullable = false, unique = true)
private String type;
// getters and setters ...
}
Tuttavia, ho notato che ci sono un paio di limitazioni nell'utilizzo di semplici istruzioni DML:
-
Se si utilizzano sequenze rispetto a un tasto di autoincremento del database (tramite
GenerationType.IDENTITY
) sulla colonna della chiave primaria, è facile inserire i dati referenziali e dimenticare di impostare il nuovo valore di sequenza o di specificare il numero di sequenza iniziale corretto nel Annotazione JPA. Questo errore può portare aDataIntegrityViolationException
s se si inseriscono dati più tardi all'interno dell'applicazione:org.springframework.dao.DataIntegrityViolationException: could not execute batch; SQL [/* insert com.mypackage.model.AddressType */ insert into address_types (address_type_id, address_type) values (?, ?)]; constraint [address_types_pkey]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute batch
La soluzione per questa limitazione sarebbe quella di impostare con cura il
initialValue
corretto nell'entità JPA:@SequenceGenerator(initialValue=2, name = "address_types_id_seq", sequenceName = "address_types_id_seq")'
O per eseguire l'istruzione DML SQL
SETVAL
:SELECT SETVAL(address_types_id_seq, (SELECT MAX(address_type_id) FROM address_types))
Entrambe le soluzioni di cui sopra sembrano non proprio ideali per me.
Usando la prima correzione, devi tenere attentamente traccia del numero di entità inserite o lasciare uno spazio di sequenza extra (ad esempio, imposta
initialValue
su un numero maggiore).Con la seconda correzione, mentre è valido SQL DML, non sembra funzionare quando lo metto nel mio import.sql. Inoltre, con gli schemi multi-tenancy, potrebbe esserci qualche confusione su quale schema è in esecuzione l'istruzione SQL DML. È lo schema pubblico / dbo o è lo schema del tenant?
Nel complesso, entrambi gli approcci sembrano soggetti a errori.
-
Il refactoring dei nomi di entità JPA significa che anche import.sql deve essere aggiornato manualmente.
In alternativa, ho pensato perché non creare semplicemente i dati referenziali direttamente come entità Hibernate, persistendo quando l'applicazione si avvia per la prima volta? Uno di questi articoli che ho trovato suggerito esattamente che usando un Spring ApplicationListener
:
@Component
public class ExampleLoader implements ApplicationListener<ContextRefreshedEvent>, Ordered {
public int getOrder() {
return 1;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//generate data
}
}
Ci sono dei motivi contro questo approccio? O c'è una buona pratica per quanto riguarda l'impostazione dei dati referenziali?