在 Hibernate 环境中添加自定义修订时出错
Error when adding custom revision in Hibernate envers
当我添加自定义修订实体时,我开始收到错误消息:
2020-12-13 00:22:29.418 ERROR 80983 --- [ost-startStop-1] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDetailsServiceImpl': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Cannot create inner bean '(inner bean)#4384acd' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#4384acd': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/resource/beans/spi/ManagedBeanRegistry
我的版本:
package ...;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import javax.persistence.Entity;
@Entity
@RevisionEntity(MyRevisionListener.class)
public class MyRevision extends DefaultRevisionEntity {
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
MyRevisionListener:
package ...;
// import de.xxxxx.carorderprocess.models.User;
import org.hibernate.envers.RevisionListener;
// import org.springframework.security.core.Authentication;
// import org.springframework.security.core.context.SecurityContext;
// import org.springframework.security.core.context.SecurityContextHolder;
// import java.util.Optional;
public class MyRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
/* String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.orElse("Unknown-User"); */
MyRevision audit = (MyRevision) revisionEntity;
audit.setUsername("dd");
}
}
网络安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
UserDetailsServiceImpl:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(user);
}
}
用户资料库:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.xxxxxxx</groupId>
<artifactId>carorderprocess</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>carorderprocess</name>
<description>Demo project for Spring Boot</description>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.4.25.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
您的代码看起来不错。但这可能不足以确定根本原因。
查看异常很明显应用程序失败了,因为它无法找到 bean 依赖项
你可以尝试关注
首先在 build.gradle 或 pom.xml 中检查库导入。通常,除了 Spring Boot Data JPA 和 Hibernate Envers
之外,您不需要任何其他 Hibernate 库
尝试 removing/disabling Hibernate Envers 审核代码和库依赖项,看看是否可以启动您的应用程序并 运行。这将帮助您确定错误是由于 Hibernate Envers 还是您的应用程序代码有其他问题。
如果上述方法无效,请提供更多信息
- 你用的是哪个版本的 Spring Boot
- 你导入了哪些库(build.gradle 或 maven pom 文件)
- 您的项目中还有哪些其他配置 - 您是否有任何其他 JPA 配置文件或任何其他与 Hibernate 或 JPA 相关的自定义配置
- 主应用上有哪些注解class
- 存储库的目录结构class,以及进行组件扫描的目录(如果您覆盖了它)
我认为您的问题可能与 pom.xml
中的不同依赖项有关。
请首先删除 spring-data-envers
依赖项,除非您正在查询您的审计表,否则您不需要它。即使在那种情况下,如果需要,您也可以单独使用 Envers 来获取该信息。
请注意,如 Sunit 回答的评论中所述,您将需要删除属性 repositoryFactoryBeanClass
,它不能再取值 EnversRevisionRepositoryFactoryBean
。但是您可能仍然需要包含 @EnableJpaRepositories
注释。
虽然我最初表示您可以让 Spring 引导管理您的版本,但由于 spring-boot-starter-parent
之一,框架正在为您提供类似于 [=] 的 hibernate-xxx
版本19=].
但是,正如您所指出的,您需要使用方法 forRevisionsOfEntityWithChanges
来查询您的审计实体。正如您在 java 文档中看到的那样,该方法是在 AuditQueryCreator
版本 5.3
.
中引入的
因此,您需要提供以下依赖项:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.3.20.Final</version>
</dependency>
但除此之外你还需要提供hibernate-entitymanager
和hibernate-core
的兼容版本:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.20.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.20.Final</version>
</dependency>
根据我对以上所有评论的了解,您的要求是
- 使用 Envers 审计
- 并使用方法
forRevisionsOfEntityWithChanges
获取所有修订的列表以及其中的更改内容
请从这些开始
- 删除
spring-data-envers
库的依赖项。
- 只保留库
hibernate-envers
- 版本 5.4.23.Final
也适用于我
- 从
@EnableJpaRepositories
注释中删除 repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class
- 所有存储库 classes 应该只从
JpaRespository
而不是 RevisionRepository
扩展。你不需要 RevisionRepository
您现在应该可以启动您的应用程序 运行。
现在回到问题,如何使用 forRevisionsOfEntityWithChanges
方法获取所有修订。
像这样创建 AuditConfiguration class,以创建 AuditReader
bean
@Configuration
public class AuditConfiguration {
private final EntityManagerFactory entityManagerFactory;
AuditConfiguration(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
@Bean
AuditReader auditReader() {
return AuditReaderFactory.get(entityManagerFactory.createEntityManager());
}
}
在您的 AuditRevisionEntity
class 中,添加以下注释。没有这个 class 的序列化将无法工作。例如
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class AuditRevisionEntity extends DefaultRevisionEntity {
在您的实体 class 中将选项 withModifiedFlag = true
添加到 @Audited
注释。没有这个,您将无法获得包含所有更改的实体修订版。例如
@Audited(withModifiedFlag = true)
public class Customer {
为此实体审核 table 和字段 *_mod 修改您的数据库 table。例如,如果您有一个 customer
table 字段 name
、age
、address
列,则添加列 name_mod
、age_mod
, address_mod
到 customer_audit
table
最后,在您的服务方法中添加以下代码以获取带有更改的审核修订
@Autowired
private AuditReader auditReader;
public List<?> getRevisions(Long id) {
AuditQuery auditQuery = auditReader.createQuery()
.forRevisionsOfEntityWithChanges(Customer.class, true)
.add(AuditEntity.id().eq(id));
return auditQuery.getResultList();
}
我会在今天的某个时候尝试 post Github 中的相同代码,以便您可以查看工作代码。
当我添加自定义修订实体时,我开始收到错误消息:
2020-12-13 00:22:29.418 ERROR 80983 --- [ost-startStop-1] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'webSecurityConfig': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userDetailsServiceImpl': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Cannot create inner bean '(inner bean)#4384acd' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#4384acd': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/resource/beans/spi/ManagedBeanRegistry
我的版本:
package ...;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import javax.persistence.Entity;
@Entity
@RevisionEntity(MyRevisionListener.class)
public class MyRevision extends DefaultRevisionEntity {
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
MyRevisionListener:
package ...;
// import de.xxxxx.carorderprocess.models.User;
import org.hibernate.envers.RevisionListener;
// import org.springframework.security.core.Authentication;
// import org.springframework.security.core.context.SecurityContext;
// import org.springframework.security.core.context.SecurityContextHolder;
// import java.util.Optional;
public class MyRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
/* String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.orElse("Unknown-User"); */
MyRevision audit = (MyRevision) revisionEntity;
audit.setUsername("dd");
}
}
网络安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
UserDetailsServiceImpl:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(user);
}
}
用户资料库:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.xxxxxxx</groupId>
<artifactId>carorderprocess</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>carorderprocess</name>
<description>Demo project for Spring Boot</description>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.4.25.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
您的代码看起来不错。但这可能不足以确定根本原因。
查看异常很明显应用程序失败了,因为它无法找到 bean 依赖项
你可以尝试关注
首先在 build.gradle 或 pom.xml 中检查库导入。通常,除了 Spring Boot Data JPA 和 Hibernate Envers
之外,您不需要任何其他 Hibernate 库尝试 removing/disabling Hibernate Envers 审核代码和库依赖项,看看是否可以启动您的应用程序并 运行。这将帮助您确定错误是由于 Hibernate Envers 还是您的应用程序代码有其他问题。
如果上述方法无效,请提供更多信息
- 你用的是哪个版本的 Spring Boot
- 你导入了哪些库(build.gradle 或 maven pom 文件)
- 您的项目中还有哪些其他配置 - 您是否有任何其他 JPA 配置文件或任何其他与 Hibernate 或 JPA 相关的自定义配置
- 主应用上有哪些注解class
- 存储库的目录结构class,以及进行组件扫描的目录(如果您覆盖了它)
我认为您的问题可能与 pom.xml
中的不同依赖项有关。
请首先删除 spring-data-envers
依赖项,除非您正在查询您的审计表,否则您不需要它。即使在那种情况下,如果需要,您也可以单独使用 Envers 来获取该信息。
请注意,如 Sunit 回答的评论中所述,您将需要删除属性 repositoryFactoryBeanClass
,它不能再取值 EnversRevisionRepositoryFactoryBean
。但是您可能仍然需要包含 @EnableJpaRepositories
注释。
虽然我最初表示您可以让 Spring 引导管理您的版本,但由于 spring-boot-starter-parent
之一,框架正在为您提供类似于 [=] 的 hibernate-xxx
版本19=].
但是,正如您所指出的,您需要使用方法 forRevisionsOfEntityWithChanges
来查询您的审计实体。正如您在 java 文档中看到的那样,该方法是在 AuditQueryCreator
版本 5.3
.
因此,您需要提供以下依赖项:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.3.20.Final</version>
</dependency>
但除此之外你还需要提供hibernate-entitymanager
和hibernate-core
的兼容版本:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.3.20.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.20.Final</version>
</dependency>
根据我对以上所有评论的了解,您的要求是
- 使用 Envers 审计
- 并使用方法
forRevisionsOfEntityWithChanges
获取所有修订的列表以及其中的更改内容
请从这些开始
- 删除
spring-data-envers
库的依赖项。 - 只保留库
hibernate-envers
- 版本5.4.23.Final
也适用于我 - 从
@EnableJpaRepositories
注释中删除repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class
- 所有存储库 classes 应该只从
JpaRespository
而不是RevisionRepository
扩展。你不需要RevisionRepository
您现在应该可以启动您的应用程序 运行。
现在回到问题,如何使用 forRevisionsOfEntityWithChanges
方法获取所有修订。
像这样创建 AuditConfiguration class,以创建
AuditReader
bean@Configuration public class AuditConfiguration { private final EntityManagerFactory entityManagerFactory; AuditConfiguration(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } @Bean AuditReader auditReader() { return AuditReaderFactory.get(entityManagerFactory.createEntityManager()); }
}
在您的
AuditRevisionEntity
class 中,添加以下注释。没有这个 class 的序列化将无法工作。例如@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) public class AuditRevisionEntity extends DefaultRevisionEntity {
在您的实体 class 中将选项
withModifiedFlag = true
添加到@Audited
注释。没有这个,您将无法获得包含所有更改的实体修订版。例如@Audited(withModifiedFlag = true) public class Customer {
为此实体审核 table 和字段 *_mod 修改您的数据库 table。例如,如果您有一个
customer
table 字段name
、age
、address
列,则添加列name_mod
、age_mod
,address_mod
到customer_audit
table最后,在您的服务方法中添加以下代码以获取带有更改的审核修订
@Autowired private AuditReader auditReader; public List<?> getRevisions(Long id) { AuditQuery auditQuery = auditReader.createQuery() .forRevisionsOfEntityWithChanges(Customer.class, true) .add(AuditEntity.id().eq(id)); return auditQuery.getResultList();
}
我会在今天的某个时候尝试 post Github 中的相同代码,以便您可以查看工作代码。