1

When I add custom revision entity, I start getting error:

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

MyRevision:

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");

    }
}

WebSecurityConfig:

@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);
    }

}

UserRepository:

@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>
farahm
  • 1,326
  • 6
  • 32
  • 70

3 Answers3

2

I think your problem could be related with the different dependencies in your pom.xml.

Please, first, remove the spring-data-envers dependency, unless you are querying your audit tables you do not need it. Even in that case, you can use Envers on its own to obtain that information if required.

Be aware that, as indicated in the comments of the answer from Sunit, you will need to remove the attribute repositoryFactoryBeanClass, it could not longer take the value EnversRevisionRepositoryFactoryBean. But you probably still need to include the @EnableJpaRepositories annotation.

Although I initially indicated that you can let Spring Boot manage your versions, due to the one of spring-boot-starter-parent, the framework is providing you versions of hibernate-xxx similar to 5.2.17.Final.

But, as you indicated, you need to use the method forRevisionsOfEntityWithChanges for querying your audit entities. As you can see in the java docs, that method was introduced in AuditQueryCreator in version 5.3.

As a consequence, you need to provide the following dependency:

<dependency> 
  <groupId>org.hibernate</groupId> 
  <artifactId>hibernate-envers</artifactId> 
  <version>5.3.20.Final</version> 
</dependency>

But in addition you also need to provide a compatible version of both hibernate-entitymanager and 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>
jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • Now I am getting at startup: `org.springframework.data.repository.history.RevisionRepository.findRevision(java.lang.Object,java.lang.Number)! No property findRevision found for type Customer!` – farahm Dec 15 '20 at 23:24
  • Hi farahm. And did you have any repository in which you define `findRevision`? Also, be aware that, as indicated in the comments of the answer from Sunit an in my answer, you will need to remove the attribute repositoryFactoryBeanClass, it could not longer take the value `EnversRevisionRepositoryFactoryBean`. But you probably still need to include the `@EnableJpaRepositories` annotation. Are you extending `RevisionRepository` in any place of your code? – jccampanero Dec 15 '20 at 23:30
  • No, I dont have any repository in which I define `findRevision`. About your second point, yes I only have now: `@SpringBootApplication @EnableJpaRepositories public class CarorderprocessApplication extends SpringBootServletInitializer {` – farahm Dec 15 '20 at 23:31
  • Yes I extend: `@Repository public interface CustomerRepository extends RevisionRepository, JpaRepository {` – farahm Dec 15 '20 at 23:33
  • Ok. Please, change it. Extend instead `Repository` or `JpaRepository`, for instance. Please, can you try? – jccampanero Dec 15 '20 at 23:34
  • You mean: `public interface TajneedRepository extends RevisionRepository, Repository{` ? – farahm Dec 15 '20 at 23:36
  • No, just: `public interface TajneedRepository extends JpaRepository`. You do not need `RevisionRepository` – jccampanero Dec 15 '20 at 23:37
  • Also, you can safely remove the `@Repository` annotation from your interface. You need to indicate Spring Data where to find your repositories, using for instance the attribute `basePackages` of the `@EnableJpaRepositories` annotation. Please, see the relevant [docs](https://docs.spring.io/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/repository/config/EnableJpaRepositories.html). – jccampanero Dec 15 '20 at 23:39
  • OK, the application is running now. But the problem is, that I need that specific version of Envers, because I want to call function `forRevisionsOfEntityWithChanges`, which is only available in that particular version. But when I add version to dependency, I again get the error from the beginning: `nested exception is java.lang.NoClassDefFoundError: org/hibernate/resource/beans/spi/ManagedBeanRegistry` – farahm Dec 15 '20 at 23:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/226014/discussion-between-farahm-and-jccampanero). – farahm Dec 15 '20 at 23:46
  • 1
    farahm, I must leave now, but I exemplified in my answer how you can use Envers directly to query your audit information. I hope it helps. We can join the chat if necessary tomorrow. – jccampanero Dec 15 '20 at 23:56
  • OK thanks, lets do the chat tomorrow, please – farahm Dec 15 '20 at 23:58
  • I have tried your `MyRevisionRepositoryImpl`, but this solution does not provide me information what fields have been changed in each revision. Thats why I need `forRevisionsOfEntityWithChanges` – farahm Dec 16 '20 at 00:14
  • 1
    @farahm I was able to get my local code working with Hibernate Envers only (version 5.4.23.Final) and I was also able to use `forRevisionsOfEntityWithChanges` to get all revision history with changes... Since comment section is small, i will try to post details in the answer – Sunit Chatterjee Dec 16 '20 at 03:29
  • Hi @farahm. I updated my answer, if you need this method, you can change the proposed repository implementation and use `forRevisionsOfEntityWithChanges` instead. Do you have any question? Do you want to chat? – jccampanero Dec 16 '20 at 11:15
  • @jccampanero Yes, lets have a chat please – farahm Dec 16 '20 at 11:21
  • Hi @jccampanero, I still have a small problem, that the second element of the query return object does not contain the information such as `id` or `timestamp`. But this information is saved in the revisions table – farahm Dec 16 '20 at 15:53
  • Hi @farahm. Sorry for the late reply. Do you mean the object at index `1` in the object array returned per every revision? It should provide you an instance of `MyEntity`. Is it not giving you that information? – jccampanero Dec 16 '20 at 18:04
  • Yes, it is returning instance of `MyEntity`, but `id=0, timestamp=0, username=null`. But in `revinfo` table I have the information: `id|timestamp |username | --|-------------|-----------------------| 6|1608132477781|user123|` – farahm Dec 16 '20 at 18:06
  • It is very strange, indeed, your revision entity configuration looks fine. Please, can you share the code you are using to process the revision information when you are obtaining these results? – jccampanero Dec 16 '20 at 18:11
  • I have shared a screenshot in the chat – farahm Dec 16 '20 at 18:17
  • Hi @jccampanero, I have asked another question related to this. Maybe you can help: https://stackoverflow.com/questions/65333022/hibernate-envers-forrevisionsofentitywithchanges-only-returning-last-modified-co – farahm Dec 17 '20 at 00:24
2

From what I understood from all the comments above, your requirement is

  • to use Envers Auditing
  • and use method forRevisionsOfEntityWithChanges to get list of all revisions with what changed in them

Please start by doing these

  • Remove dependency of spring-data-envers library.
  • Just keep library hibernate-envers - version 5.4.23.Final also worked for me
  • Remove repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class from @EnableJpaRepositories annotation
  • All Repository classes should only extend from JpaRespository and NOT from RevisionRepository. You dont need RevisionRepository

You should be able to get your application up and running now.

Now coming back to the question, how to get all revisions with changes using forRevisionsOfEntityWithChanges method.

  • Create an AuditConfiguration class like this, to create the AuditReader bean

     @Configuration
     public class AuditConfiguration {
    
     private final EntityManagerFactory entityManagerFactory;
    
     AuditConfiguration(EntityManagerFactory entityManagerFactory) {
         this.entityManagerFactory = entityManagerFactory;
     }
    
     @Bean
     AuditReader auditReader() {
         return AuditReaderFactory.get(entityManagerFactory.createEntityManager());
     }
    

    }

  • In your AuditRevisionEntity class, add following annotation. Without this the serialization of this class wont work. e.g

    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    public class AuditRevisionEntity extends DefaultRevisionEntity {
    
  • In your entity class add option withModifiedFlag = true to @Audited annotation. Without this you cannot get entity revisions with all changes. e.g

     @Audited(withModifiedFlag = true)
     public class Customer {
    
  • Modify your database table for this entity audit table and fields *_mod. e.g if you have a customer table with fields name, age, address columns, then add columns name_mod, age_mod, address_mod to the customer_audit table

  • Last, add following code in your service method to get audit revisions with changes

     @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();
    

    }

I will try to post the same code in Github sometime today, so that you can take a look at working code.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • As soon as I specify the version for `hibernate-envers` in pom.xml, I start getting the error again (the one mentioned in the question) – farahm Dec 16 '20 at 10:56
  • @farahm I tried with both (specifying and not specifying versions for enver). If i didn't specify the version, then with `Spring Boot Version 2.2.3`, gradle was pulling `envers 5.4.10.Final`. However now I have upgraded to `Spring Boot 2.4.1` and now gradle is pulling `envers 5.4.25.Final`. – Sunit Chatterjee Dec 16 '20 at 14:23
  • 1
    You can try the code without specifying `hibernate-envers` version too. If it still does not work, you can compare with the code that I have working currently and see if there is any mismatch between our codes that is causing the issue. I have checked in my code at - https://github.com/chatterjeesunit/spring-boot-app/tree/v4.0 (checkout `tag v4.0`) – Sunit Chatterjee Dec 16 '20 at 14:26
  • 1
    Hi Sunit. jccampanero has found out the problem as you can see in his answer. I also needed specific `hibernate-entitymanager` and `hibernate-core` dependencies in pom – farahm Dec 16 '20 at 14:36
1

Your code looks fine. But it may not be sufficient to identify the root cause.

Looking at the exception it is clear that application is failing since it is not able to find bean dependency

Could you try following

  1. Check your library imports first in your build.gradle or pom.xml. Generally you should not require any other Hibernate library other than Spring Boot Data JPA and Hibernate Envers

  2. Try removing/disabling the Hibernate Envers audit code and library dependencies and see if can you get your application up and running. This will help you identify if error is due to Hibernate Envers or if your application code has other issues.

If above does not works, then please provide more information

  1. Which version of Spring Boot are you on
  2. What libraries have you imported (build.gradle or maven pom file)
  3. What other Configurations you have in your project - do you have any other JPA configuration file or any other custom configuration related to Hibernate or JPA
  4. What annotations are on the main application class
  5. Directory structure of your Repository class, and the directory on which you do component scan (in case you have overridden it)
  • The stuff after "If above does not works, then please provide more information" is suited in a comment. You just need 1 rep to start commenting! :) – Sabito stands with Ukraine Dec 13 '20 at 14:15
  • Yes when I remove `MyRevision` and `MyRevisionListener ` the application runs correctly. I have attached pom.xml file to question – farahm Dec 13 '20 at 15:09
  • Update: After removing` org.springframework.data spring-data-envers ` I get error with: `@EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class) public class CarorderprocessApplication extends SpringBootServletInitializer {` – farahm Dec 13 '20 at 15:28
  • 1
    @farahm 1. You should not need `spring-data-envers` library. Hibernate Envers (`hibernate-envers`) is enough for you to get started with Hibernate envers auditing. 2. Also once you remove `spring-data-envers`, you may need to remove this from `@EnableJpaRepositories` - `repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class` (since this class is present within the spring data envers - `org.springframework.data.envers.repository.support` – Sunit Chatterjee Dec 14 '20 at 15:28
  • 1
    Try removing both Spring Data Envers library and also the `repositoryFactoryBeanClass` configuration inside `@EnableJpaRepositories` – Sunit Chatterjee Dec 14 '20 at 15:30
  • Ok I will try and let you know – farahm Dec 14 '20 at 15:48
  • @farahm Are you still getting the original error `java.lang.NoClassDefFoundError: org/hibernate/resource/beans/spi/ManagedBeanRegistry` OR are you now getting different error now? – Sunit Chatterjee Dec 14 '20 at 16:18
  • @SunitChatterjee I have added a bounty on this question, if you can help further... – farahm Dec 14 '20 at 23:31