Konfiguration des O/R-Mappings
Diese Seite ist ein Teil der IsyFact-Standards. Alle Inhalte der Seite, insbesondere Texte und Grafiken, sind urheberrechtlich geschützt. Alle urheberrechtlichen Nutzungs- und Verwertungsrechte liegen beim Bundesverwaltungsamt.
Die Nutzung ist unter den Lizenzbedingungen der Creative Commons Namensnennung 4.0 International gestattet.
In den folgenden Abschnitten werden konkrete Vorgaben gemacht, welche Konfigurationen für die Umsetzung der Persistenzschicht verwendet werden sollen.
1. Konfiguration von JPA über Spring Beans durchführen
Die für die Verwendung von JPA benötigten Beans werden von Spring Boot beim Start der Anwendung automatisch instanziiert.
Teile dieser automatischen Konfiguration können bei Bedarf überschrieben werden.
Soll z. B. für die Entwicklung eine andere Datenbank verwendet werden, kann die automatisch konfigurierte DataSource
-Bean durch eine andere überschrieben werden.
Das Gleiche gilt für die Anbindung einer zweiten Datenbank, siehe dazu Nutzung und Anbindung einer zweiten Datenbank.
2. Konfiguration des EntityManagers
Der EntityManager wird von Spring Boot automatisch konfiguriert.
Eine zusätzliche Konfiguration kann über application.properties
erfolgen.
Grundsätzlich können nach dem Schema spring.jpa.properties.<Schlüssel>=<Wert>
beliebige native Properties für Hibernate gesetzt werden (Listing 1).
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=<Database Dialect>
spring.jpa.properties.hibernate.connection.isolation=4
spring.jpa.properties.hibernate.connection.useUnicode=true
spring.jpa.properties.hibernate.connection.characterEncoding=utf-8
spring.jpa.properties.hibernate.jdbc.batch_size=0
spring.jpa.properties.hibernate.jdbc.use_streams_for_binary=true
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.properties.hibernate.default_schema=<Default Schema>
spring.jpa.properties.hibernate.ejb.metamodel.generation=enabled
# Folgender Parameter ist optional, da er dem Standard entspricht
spring.jpa.properties.hibernate.transaction.coordinator_class=jdbc
3. Konfiguration der Datasource
Als Datasource-Implementierung muss die Implementierung aus de.bund.bva.isyfact.persistence.datasource.IsyDataSource
genutzt werden.
Bei der Verwendung von isy-persistence
wird automatisch eine Bean mit dem Namen appDataSource
erzeugt.
Diese prüft die Version des Datenbankschemas (siehe Abschnitt Prüfen der Schema-Version) und dient als Wrapper für die eigentliche Spring DataSource
.
Die Autokonfiguration der IsyFact 3 Version von isy-persistence
verwendet Spring Boot Mechanismen, um die DataSource
zu konfigurieren.
Unter dem Präfix isy.persistence.datasource
können alle Properties konfiguriert werden, welche in Spring unter dem Präfix spring.datasource
bekannt sind (vgl. Common Application Properties).
Die Konfiguration bietet damit die Flexibilität, Datenbanken verschiedener Anbieter zu nutzen, sofern diese von Spring Boot unterstützt sind.
Die Nutzung der Dieses Problem kann durch Setzen der Property |
3.1. Standardmäßig Lazy Loading verwenden
Standardmäßig verwendet Hibernate für alle 1:n und n:m Assoziationen ein Lazy Loading über dynamische Proxies und für n:1 oder 1:1 Assoziationen wird Eager Loading eingesetzt. Standardmäßig soll für alle Assoziationen Lazy Loading verwendet werden, wobei Bytecode-Manipulationen für Lazy Loading nicht verwendet werden sollen.
Um Lazy Loading auch für 1:1 Assoziationen einzuschalten, wird das fetch
-Attribut auf FetchType.LAZY
gesetzt.
Damit das Lazy Loading über Proxies funktioniert, muss die Assoziation nicht optional sein, d. h., dass Feld darf nicht null
sein.
@OneToOne(optional = false, fetch = FetchType.LAZY)
private SomeEntity someEntity;
Ist ein 1:1 assoziiertes Feld optional und kann den Wert null
annehmen, kann Lazy Loading nur über Bytecode-Manipulation realisiert werden.
Für n:1 Assoziationen wird genauso verfahren und das fetch
-Attribut auf FetchType.LAZY
gesetzt.
Es ist erlaubt und erwünscht, dieses Verhalten für Assoziationen zu überschreiben, bei denen Eager Loading Sinn ergibt.
Hierfür ist das Attribut fetch
der jeweiligen Mapping-Annotation wie folgt zu setzen:
@OneToMany(fetch = FetchType.EAGER)
3.2. Standardmäßig optimistisches Locking verwenden
Standardmäßig ist für Hibernate ein optimistisches Locking zu verwenden: Objekte werden bei dieser Locking-Strategie nicht per „select for update“ gesperrt. Stattdessen wird am Ende der Transaktion geprüft, ob lokal veränderte Objekte parallel in der Datenbank geändert wurden. Ist dies der Fall, wird eine Ausnahme geworfen.
Dieser Vorgehensweise liegt die Annahme zugrunde, dass konkurrierende schreibende Zugriffe in einer Geschäftsanwendung nicht oder höchstens in Ausnahmefällen vorkommen. Sollte dies nicht zutreffen, muss explizites Locking verwendet werden (vgl. Abschnitt Explizites Locking). In der Anwendung ist keine explizite Fehlerbehandlung (etwa durch das Mergen der Daten) zu implementieren. Die geworfene Ausnahme ist (gewrappt) an den Aufrufer weiterzugeben.
Um zu erkennen, ob sich das Objekt in der Datenbank verändert hat, empfiehlt Hibernate die Verwendung eines numerischen Versions-Felds in jeder Datenbank-Tabelle.
Dazu wird in den Entitäten eine numerische Property mit der Annotation @Version
gekennzeichnet.
@Version
public int getVersion() {
return version;
}
Dieses Feld wird einzig von Hibernate verwaltet. Es ist weder zu lesen noch zu schreiben.
3.3. Nutzung und Anbindung einer zweiten Datenbank
Einige Anwendungsfälle machen es notwendig, eine zweite Datenbank zu nutzen. Das ist beispielsweise notwendig, wenn Daten aus einem Altsystem über die Datenbank für andere Systeme bereitgestellt werden und diese Daten in eine IsyFact-Anwendung über einen Batch importiert werden sollen. Der Batch muss dann sowohl auf die Datenbank der IsyFact-Anwendung, als auch auf die Datenbank des Altsystems zugreifen.
Die Anbindung einer zweiten Datenbank erfolgt analog zur Anbindung der primären Datenbank über Spring und die Nutzung von JPA. Dabei erfolgt der Zugriff auf die zweite Datenbank getrennt über einen weiteren Entity Manager und eine weitere Data Source.
Wird eine zweite Datenbank verwendet, müssen die Beans für EntityManagerFactory
und den TransactionManager
auch für die primäre Datenbank manuell konfiguriert werden Listing 2.
Als DataSource
wird hier die von isy-persistence
automatisch konfigurierte appDataSource
verwendet.
@Configuration
@EnableJpaRepositories(basePackages = "de.beispiel.zweidatasources.persistence", entityManagerFactoryRef = "entityManagerFactoryApp", transactionManagerRef = "transactionManagerApp")
public class PersistenceConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryApp(@Qualifier("appDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPackagesToScan("de.beispiel.zweidatasource.persistence");
em.setDataSource(dataSource);
em.setJpaDialect(new HibernateJpaDialect());
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabase(...); (1)
vendorAdapter.setShowSql(false);
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
@Bean
public PlatformTransactionManager transactionManagerApp(@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
1 | Korrekten Hersteller setzen. |
Für die zweite Datenbankanbindung wird eine weitere Konfiguration angelegt Listing 3.
Dafür können die von isy-persistence erweiterten DatabaseProperties
verwendet werden, um durch das Verpacken der DataSource in eine IsyDataSource
die Schema-validierung zu nutzen.
@Configuration
@EnableJpaRepositories(basePackages = "de.beispiel.zweidatasources.persistencesec", entityManagerFactoryRef = "entityManagerFactorySec", transactionManagerRef = "transactionManagerSec")
public class Persistence2Config {
@Bean("secondaryDatabaseProperties")
@ConfigurationProperties("isy.persistence.datasource2")
DatabaseProperties secondaryDatabaseProperties() {
return new DatabaseProperties();
}
@Bean("secondaryDataSource")
@ConfigurationProperties(prefix = "isy.persistence.datasource2.config")
public DataSource secondaryDataSource(@Qualifier("secondaryDatabaseProperties") DataSourceProperties secondaryDatabaseProperties) {
return secondaryDatabaseProperties.initializeDataSourceBuilder().build();
}
@Bean("secondaryAppDataSource")
@DependsOnDatabaseInitialization
public IsyDataSource secondaryAppDataSource(
@Qualifier("secondaryDataSource") DataSource secondaryDataSource,
@Qualifier("secondaryDatabaseProperties") DatabaseProperties secondaryDatabaseProperties
) {
IsyDataSource isyDataSource = new IsyDataSource();
isyDataSource.setSchemaVersion(secondaryDatabaseProperties.getSchemaVersion());
isyDataSource.setInvalidSchemaVersionAction(secondaryDatabaseProperties.getSchemaInvalidVersionAction());
isyDataSource.setTargetDataSource(secondaryDataSource);
return isyDataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactorySec(
@Qualifier("secondaryAppDataSource") DataSource secondaryAppDataSource
) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPackagesToScan("de.beispiel.zweidatasource.persistencesec");
em.setDataSource(secondaryAppDataSource);
em.setJpaDialect(new HibernateJpaDialect());
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabase(...); (1)
vendorAdapter.setShowSql(false);
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
@Bean
public PlatformTransactionManager transactionManagerSec(@Qualifier("entityManagerFactorySec") EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
1 | Korrekten Hersteller setzen. |
Die Datei application.properties
wird um den neuen Konfigurationsparameter datasource.second.url
für die zweite Datenbankverbindung erweitert.
3.4. Konfiguration der ID und Sequenz
Primärschlüssel werden in JPA mittels der @Id
und @GeneratedValue
Annotation markiert.
Der GenerationType
der @GeneratedValue
Annotation muss in jedem Fall AUTO
sein.
Als Generator kommt idealerweise ein @SequenceGenerator
zum Einsatz, der eine Datenbanksequenz benutzt.
Es muss unbedingt darauf geachtet werden, das Inkrement (INCREMENT BY
) der zur ID-Generierung genutzten Datenbanksequenz auf denselben Wert einzustellen, der auch beim SequenceGenerator
mit allocationSize
angegeben ist.
Ein Konfigurationsbeispiel kann folgendermaßen aussehen:
@Id
@GeneratedValue(strategy=GenerationType.AUTO, generator="my_seq")
@SequenceGenerator(name="my_seq",sequenceName="MY_SEQ", allocationSize=50)