複数のデータソースを使用した Hibernate Envers と Spring Boot

シェアする

ハイバネート・エンバース プロジェクトは、永続クラスの監査を簡単にできるようにすることを目的としています。 エンティティを監査する手間が完全に取り除かれます。

以下のセクションでは、カスタム リビジョン エンティティを使用して Spring Boot で Envers を構成するための高レベルの手順の概要を説明します。 複数のデータ ソースが関係する場合に Envers を構成する方法を示します。

Envers の使用を開始する

Maven を使用している場合は、pom.xml に Envers の以下の設定を追加します。

org.hibernate 
hibernate-envers

カスタム リビジョン エンティティの作成

多くのシナリオでは、デフォルトのリビジョン エンティティ フィールドでは十分ではないため、カスタム リビジョン エンティティが必要になります。 以下は、カスタム リビジョン エンティティの作成例です。

この例では ID にシーケンスを使用していますが、ID の生成にはさまざまな戦略を使用できます。

@RevisionNumber と @RevisionEntity に注意してください。これらは、Envers がリビジョン エンティティを作成し、データベースに値を保持するために使用します。

package com.example.service.audit.entity @Table(name = "app_user_rev_entity", schema = "application") @Entity @RevisionEntity(UserRevisionListener.class) public class UserRevEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_rev_generator") @SequenceGenerator(name = "user_rev_generator", allocationSize = 10,sequenceName = "app_userrev_seq") @RevisionNumber private int id; @RevisionTimestamp @Temporal(TemporalType.TIMESTAMP) private Date date; @Column(name = "user_name") private String userName; @Column(name = "user_id") private Long userId; // Getters, setters, equals, hashcode ….

UserRevisionListener は、UserRevEntity のすべてのカスタム属性が設定されるクラスです。

以下の例では、Spring Boot プリンシパル ユーザーを使用してユーザー名を取得しています。 同様に、他の属性も設定できます。 このクラスは、Hibernate Envers の RevisionListener インターフェイスを実装する必要があります。

package com.example.service.audit.entity public class UserRevisionListener implements RevisionListener { /** * @see org.hibernate.envers.RevisionListener#newRevision(java.lang.Object) */ @Override public void newRevision(Object userRevision) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User authenticatedUser = (User) authentication.getPrincipal(); UserRevEntity userRevEntity = (UserRevEntity) userRevision; userRevEntity.setUserName(authenticatedUser.getUsername()); userRevEntity.setDate(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime()); }

application.properties を構成する

以下は、Hibernate Envers に固有の構成の一部です。 構成プロパティの詳細については、こちらをご覧ください。 ここ.

spring.jpa.properties.org.hibernate.envers.revision_type_field_name=revision_type spring.jpa.properties.org.hibernate.envers.revision_field_name=revision_id spring.jpa.properties.org.hibernate.envers.modified_flag_suffix=_mod spring.jpa.properties.org.hibernate.envers.audit_strategy=org.hibernate.envers.strategy.ValidityAuditStrategy

Hibernate にカスタム リビジョン エンティティやその他の監査テーブルを作成させたい場合は、 spring.jpa.hibernate.ddl-auto=update を使用できますが、これは運用環境にはお勧めできません。 実稼働環境でテーブルを自分で作成することをお勧めします。

次のセクションは監査戦略についてです。 デフォルトでも十分ですが、ValidityAuditStrategy はより高度な戦略です。

監査対象のエンティティを構成する

以下は、Entity クラスを監査する方法の例です。 必要なのは、 @Audited アノテーションをクラス レベルで追加するか (すべての属性を監査する必要がある場合)、またはアノテーションを個別の属性レベルとして追加することだけです。

@Entity @Table(name = "app_user", schema = "application") public class UserEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator") @SequenceGenerator(name = "user_generator", allocationSize = 1, sequenceName = "application.app_userid_seq") @Column(name = "user_id") private Long userId; @NotNull @Column(name = "first_name") @Audited private String firstName;

複数のデータソースを使用して Envers を構成する

多くのアプリケーションでは、異なるデータを異なるスキーマや場合によっては異なるデータベースに保存する必要があるため、複数のデータ ソースが存在します。 以下のセクションでは、複数のデータ ソースを使用して Envers を構成する手順の概要を説明します。

application.properties ファイルで複数のデータソースを構成します。

application.spring.datasource.url = jdbc:postgresql://xxxx application.spring.datasource.username = xxx application.spring.datasource.password=xxx application.spring.datasource.testWhileIdle = true application.spring.datasource.validationQuery = SELECT 1 application.spring.datasource.schema=application application.spring.datasource.driver-class-name=org.postgresql.Driver example.spring.datasource.url = jdbc:postgresql://xxx example.spring.datasource.username = xxx example.spring.datasource.password=xxx example.spring.datasource.testWhileIdle = true example.spring.datasource.validationQuery = SELECT 1 example.spring.datasource.schema=public example.spring.datasource.driver-class-name=org.postgresql.Driver

Spring Boot が使用するデータ ソースを識別して読み込むために必要な構成は次のとおりです。

@Configuration @EnableTransactionManagement @EnableJpaRepositories(entityManagerFactoryRef = "exampleEntityManagerFactory", transactionManagerRef = "exampleTransactionManager", basePackages = { "com.example.service.example.entity" }) public class ExampleDatabaseConfig { @Bean(name = "exampleDataSource") @ConfigurationProperties(prefix = "example.spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "exampleEntityManagerFactory") public LocalContainerEntityManagerFactoryBean exampleEntityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("exampleDataSource") DataSource dataSource) { return builder.dataSource(dataSource).packages("com.example.service.audit.entity").persistenceUnit("example").build(); } @Bean(name = "exampleTransactionManager") public PlatformTransactionManager exampleTransactionManager( @Qualifier("exampleEntityManagerFactory") EntityManagerFactory exampleEntityManagerFactory) { return new JpaTransactionManager(exampleEntityManagerFactory); } } @Configuration @EnableTransactionManagement @EnableJpaRepositories(entityManagerFactoryRef = "applicationEntityManagerFactory", transactionManagerRef = "applicationTransactionManager", basePackages = { "com.application.service.example.entity " }) public class ApplicationDatabaseConfig { @Primary @Bean(name = "applicationDataSource") @ConfigurationProperties(prefix = "application.spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean(name = "applicationEntityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("applicationDataSource") DataSource dataSource) { return builder.dataSource(dataSource) .packages("com.example.service.audit.entity") .persistenceUnit("application").build(); } @Primary @Bean(name = "applicationTransactionManager") public PlatformTransactionManager transactionManager( @Qualifier("applicationEntityManagerFactory") EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); } }

上記の両方の構成で、覚えておくべき重要な点は、Envers Custom Revision Entity クラスが実装されているパッケージを追加することです。この場合、エンティティ スキャン用に両方のデータソース構成ファイルに「com.example.service.audit.entity」を追加します。エンヴェール。

構成ファイルの XNUMX つにパッケージを追加しなかった場合、監査対象のエンティティを永続化しようとすると、突然以下のエラーが表示され始める可能性があります。 コードは変更されていない可能性がありますが、障害が発生し始める可能性があります。

これは、起動時に最初にデータ ソースがロードされる ClassLoader によって異なります。 エンティティ スキャン パッケージが欠落しているデータ ソースが最後にロードされた場合、以下のエラーが表示されます。

これにより、問題のデバッグに多くの時間が無駄になり、Hibernate Envers で使用される汎用シーケンスである欠落した hibernate_sequence を作成するという誤解を招く可能性があります。

Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106) at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79) at org.hibernate.id.enhanced.SequenceStructure$1.getNextValue(SequenceStructure.java:96) at org.hibernate.id.enhanced.NoopOptimizer.generate(NoopOptimizer.java:40) at org.hibernate.id.enhanced.SequenceStyleGenerator.generate(SequenceStyleGenerator.java:412) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:101) at org.hibernate.jpa.event.internal.core.JpaSaveEventListener.saveWithGeneratedId(JpaSaveEventListener.java:56) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192) at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177) at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73) at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:679) at org.hibernate.internal.SessionImpl.save(SessionImpl.java:671) at org.hibernate.envers.internal.revisioninfo.DefaultRevisionInfoGenerator.saveRevisionData(DefaultRevisionInfoGenerator.java:75) at org.hibernate.envers.internal.synchronization.AuditProcess.getCurrentRevisionData(AuditProcess.java:119) at org.hibernate.envers.internal.synchronization.AuditProcess.executeInSession(AuditProcess.java:96) ... 141 common frames omitted Caused by: org.postgresql.util.PSQLException: ERROR: relation "hibernate_sequence" does not exist Position: 17

まとめ

Hibernate Envers は、Hibernate によって提供される成熟した監査モジュールです。 高度に構成可能であり、監査フレームワークを構築する労力を節約します。

ただし、複数のデータ ソースを使用する場合は、両方のデータ ソースでエンティティ スキャン パッケージを構成することを忘れないでください。そうしないと、特に作業中のコードが突然失敗し始めた場合に、誤解を招き、問題のデバッグに多くの時間を費やすことになります。

私たちの投稿を読んでください」フロントエンドブラウザのデバッグブラウザの問題をトラブルシューティングするためのツールとテクニックについては、「」を参照してください。

提言

ニュースレターを購読する

eコマース専門家向けの製品アップデート、ウェビナー、ニュースについて学びましょう。