在 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;

UserDetailsS​​erviceImpl:

@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 依赖项

你可以尝试关注

  1. 首先在 build.gradle 或 pom.xml 中检查库导入。通常,除了 Spring Boot Data JPA 和 Hibernate Envers

    之外,您不需要任何其他 Hibernate 库
  2. 尝试 removing/disabling Hibernate Envers 审核代码和库依赖项,看看是否可以启动您的应用程序并 运行。这将帮助您确定错误是由于 Hibernate Envers 还是您的应用程序代码有其他问题。

如果上述方法无效,请提供更多信息

  1. 你用的是哪个版本的 Spring Boot
  2. 你导入了哪些库(build.gradle 或 maven pom 文件)
  3. 您的项目中还有哪些其他配置 - 您是否有任何其他 JPA 配置文件或任何其他与 Hibernate 或 JPA 相关的自定义配置
  4. 主应用上有哪些注解class
  5. 存储库的目录结构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-entitymanagerhibernate-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 字段 nameageaddress 列,则添加列 name_modage_mod , address_modcustomer_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 中的相同代码,以便您可以查看工作代码。