Spring integration

Our main idea at JaVers is that our library should be very easy to use. So we made JaVers compatible with Spring Framework.

javers-spring module provides the following features:

  • aspects for Repository auto-audit (both SQL and NoSQL),
  • integration with JPA EntityManager for SQL databases.

Dependency

If you are using Spring Boot, simplify JaVers setup with our starters, see Spring Boot integration.

If you are not using Spring Boot, add javers-spring module to your classpath:

compile 'org.javers:javers-spring:3.3.2'

Check Maven Central for other build tools snippets.

Auto-audit aspect

The JaVers auto-audit aspects are based on Spring AOP and frees you from calling javers methods in your data-changing Repositories.

If you’re using Spring Data, annotate your CRUD Repositories with @JaversSpringDataAuditable. For ordinary Repositories, use @JaversAuditable to mark all data-changing methods.

JaVers can audit your data changes automatically — AWESOME!

Below you can see which beans you need to register to use the auto-audit feature.

JaVers instance as a Spring bean

You need to register exactly one JaVers instance in your Application Context. For example, if you’re using MongoDB, setup JaVers as follows:

    @Bean
    public Javers javers() {
        MongoRepository javersMongoRepository = new MongoRepository(mongoDB());

        return JaversBuilder.javers()
                .registerJaversRepository(javersMongoRepository)
                .build();
    }

    @Bean
    public MongoDatabase mongoDB() {
        return new Fongo("test").getDatabase("test");
    }

Aspect beans

JaVers provides two aspects which manage the auto-audit feature:

  • JaversSpringDataAuditableRepositoryAspect for Spring Data CRUD Repositories, enabled by @JaversSpringDataAuditable.
    It defines the pointcut on all save(..) and delete(..) methods within all Spring Data CRUD Repositories annotated with the class-level @JaversSpringDataAuditable annotation.
    @Bean
    public JaversSpringDataAuditableRepositoryAspect javersSpringDataAuditableAspect() {
        return new JaversSpringDataAuditableRepositoryAspect(
                javers(), authorProvider(), commitPropertiesProvider());
    }
  • JaversAuditableAspect for ordinary Repositories, enabled by @JaversAuditable.
    It defines the pointcut on any method annotated with the method-level @JaversAuditable annotation.
    @Bean
    public JaversAuditableAspect javersAuditableAspect() {
        return new JaversAuditableAspect(javers(), authorProvider(), commitPropertiesProvider());
    }

After an advised method is executed, all of its arguments are automatically saved to JaversRepository.
In the case where an argument is an Iterable instance, JaVers iterates over it and saves each element separately.

Aspects require one more bean — AuthorProvider and optionally CommitPropertiesProvider bean.

Note that both aspects are based on Spring @AspectJ.
Remember to enable @AspectJ support by putting the @EnableAspectJAutoProxy annotation in your Spring configuration.

For more info refer to Spring @AspectJ documentation.

AuthorProvider bean

Every JaVers commit (a data change) should be connected to its author, i.e. the user who made the change. Please don’t confuse JaVers commit (a bunch of data changes) with the SQL commit command (finalizing an SQL transaction).

You need to register an implementation of the AuthorProvider interface, which should return a current user name. It’s required by auto-audit aspects. For example:

    @Bean
    public AuthorProvider authorProvider() {
        return new SpringSecurityAuthorProvider();
    }

JaVers comes with SpringSecurityAuthorProvider — suitable implementation when using Spring Security. If you don’t care about commit author, use MockAuthorProvider.

CommitPropertiesProvider bean

Every JaVers commit may have one or more commit properties, useful for querying (see CommitProperty filter example).

In auto-audit, commit properties are supplied by an implementation of the CommitPropertiesProvider interface, for example:

    @Bean
    public CommitPropertiesProvider commitPropertiesProvider() {
        return new CommitPropertiesProvider() {
            @Override
            public Map<String, String> provide() {
                return ImmutableMap.of("key", "ok");
            }
        };
    }

If you don’t use commit properties, simply skip commitPropertiesProvider in the aspect constructors.

That’s the last bean in your Application Context required to run auto-audit aspects. See the full Spring configuration examples for MongoDB and for JPA & Hibernate

@JaversSpringDataAuditable for Spring Data Repositories

If you’re using Spring Data, just annotate Repositories you want to audit with the class-level @JaversSpringDataAuditable.

For example:

import org.javers.spring.data.JaversSpringDataAuditable
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository

@Repository
@JaversSpringDataAuditable
interface UserRepository extends CrudRepository<User, String> {
}

From now, all objects passed to save() and delete() methods will be automatically versioned by JaVers.

@JaversAuditable for ordinary Repositories

If you’re using ordinary Repositories (non Spring Data), annotate all data-changing methods you want to audit with the method-level @JaversAuditable.

For example:

@Repository
class UserRepository {
    @JaversAuditable
    public void save(User user) {
        ...//
    }

    public User find(String login) {
        ...//
    }
}

In fact, you can use this method-level annotation for advising any bean in your application. It could be a Service, Repository or anything which modifies domain objects.

From now, all objects passed to the annotated methods will be automatically versioned by JaVers.

JPA EntityManager integration

Transaction management is the important issue for applications backed by SQL databases. Generally, all SQL statements executed by JaversSQLRepository should be executed in the context of the current application’s transaction (called Persistence Context in JPA terminology).

Read more about ConnectionProvider and JaVers’ approach to transaction management.

Spring configuration for SQL

First, you need to register exactly one transactional JaVers instance in your Application Context. Simply use TransactionalJaversBuilder instead of standard JaversBuilder.

Second, you need to register a transactional ConnectionProvider. If you’re using JPA with Hibernate, choose JpaHibernateConnectionProvider implementation which is Persistence Context aware and plays along with Spring JpaTransactionManager.

Third, if you are using Hibernate, you need to deal with lazy-loading proxies. Hibernate silently wraps them around your Entities loaded from database. We strongly encourage to get rid of lazy-loading proxies before committing Entities to JaversRepository. It can be easily obtained with HibernateUnproxyObjectAccessHook.

Hibernate unproxy hook

JaVers provides HibernateUnproxyObjectAccessHook which is a way to unproxy and initialize your Hibernate Entities just before processing them by JaVers diff & commit algorithms.

To use HibernateUnproxyObjectAccessHook simply bind it to your JaVers instance using JaversBuilder.withObjectAccessHook() method:

TransactionalJaversBuilder
    .javers()
    .withTxManager(txManager)
    .withObjectAccessHook(new HibernateUnproxyObjectAccessHook()).build()

Feel free to provide your own implementation of object-access hook if you need better control over the unproxing process.

See below for the full Spring configuration example for JPA & Hibernate.

Spring configuration example for JPA & Hibernate

Here is a working example of Spring Application Context with all JaVers beans, JPA, Hibernate, Spring Data and Spring TransactionManager.

package org.javers.spring.example;

import ...

@Configuration
@ComponentScan(basePackages = "org.javers.spring.repository.jpa")
@EnableTransactionManagement
@EnableAspectJAutoProxy
@EnableJpaRepositories(basePackages = "org.javers.spring.repository.jpa")
public class JaversSpringJpaApplicationConfig {

    //.. JaVers setup ..

    /**
     * Creates JaVers instance with [email protected] JaversSqlRepository}
     */
    @Bean
    public Javers javers(PlatformTransactionManager txManager) {
        JaversSqlRepository sqlRepository = SqlRepositoryBuilder
                .sqlRepository()
                .withConnectionProvider(jpaConnectionProvider())
                .withDialect(DialectName.H2)
                .build();

        return TransactionalJaversBuilder
                .javers()
                .withTxManager(txManager)
                .withObjectAccessHook(new HibernateUnproxyObjectAccessHook())
                .registerJaversRepository(sqlRepository)
                .build();
    }

    /**
     * Enables auto-audit aspect for ordinary repositories.<br/>
     *
     * Use [email protected] org.javers.spring.annotation.JaversAuditable}
     * to mark data writing methods that you want to audit.
     */
    @Bean
    public JaversAuditableAspect javersAuditableAspect() {
        return new JaversAuditableAspect(javers(), authorProvider(), commitPropertiesProvider());
    }

    /**
     * Enables auto-audit aspect for Spring Data repositories. <br/>
     *
     * Use [email protected] org.javers.spring.annotation.JaversSpringDataAuditable}
     * to annotate CrudRepositories you want to audit.
     */
    @Bean
    public JaversSpringDataAuditableRepositoryAspect javersSpringDataAuditableAspect() {
        return new JaversSpringDataAuditableRepositoryAspect(javers(), authorProvider(),
            commitPropertiesProvider());
    }

    /**
     * Required by auto-audit aspect. <br/><br/>
     *
     * Creates [email protected] SpringSecurityAuthorProvider} instance,
     * suitable when using Spring Security
     */
    @Bean
    public AuthorProvider authorProvider() {
        return new SpringSecurityAuthorProvider();
    }

    /**
     * Optional for auto-audit aspect.
     */
    @Bean
    public CommitPropertiesProvider commitPropertiesProvider() {
        return new CommitPropertiesProvider() {
            @Override
            public Map<String, String> provide() {
                return ImmutableMap.of("key", "ok");
            }
        };
    }

    /**
     * Integrates [email protected] JaversSqlRepository} with Spring [email protected] JpaTransactionManager}
     */
    @Bean
    public ConnectionProvider jpaConnectionProvider() {
        return new JpaHibernateConnectionProvider();
    }
    //.. EOF JaVers setup ..


    //.. Spring-JPA-Hibernate setup ..
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan("org.javers.spring.model");

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties());

        return em;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);

        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
        return new PersistenceExceptionTranslationPostProcessor();
    }

    @Bean
    public DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
        return dataSource;
    }

    Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "create");
        properties.setProperty("hibernate.connection.autocommit", "false");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        return properties;
    }
    //.. EOF Spring-JPA-Hibernate setup ..
}

Spring configuration example for MongoDB

Here is a working example of Spring Application Context with JaVers instance, JaVers auto-audit aspect and Spring Data MongoDB.

package org.javers.spring.example;

import ...

@Configuration
@ComponentScan(basePackages = "org.javers.spring.repository.mongo")
@EnableAspectJAutoProxy
@EnableMongoRepositories(basePackages = "org.javers.spring.repository.mongo")
public class JaversSpringMongoApplicationConfig {

    /**
     * Creates JaVers instance backed by [email protected] MongoRepository}
     */
    @Bean
    public Javers javers() {
        MongoRepository javersMongoRepository = new MongoRepository(mongoDB());

        return JaversBuilder.javers()
                .registerJaversRepository(javersMongoRepository)
                .build();
    }

    /**
     * MongoDB setup
     */
    @Bean
    public MongoDatabase mongoDB() {
        return new Fongo("test").getDatabase("test");
    }

    /**
     * Enables auto-audit aspect for ordinary repositories.<br/>
     *
     * Use [email protected] org.javers.spring.annotation.JaversAuditable}
     * to mark data writing methods that you want to audit.
     */
    @Bean
    public JaversAuditableAspect javersAuditableAspect() {
        return new JaversAuditableAspect(javers(), authorProvider(), commitPropertiesProvider());
    }

    /**
     * Enables auto-audit aspect for Spring Data repositories. <br/>
     *
     * Use [email protected] org.javers.spring.annotation.JaversSpringDataAuditable}
     * to annotate CrudRepositories you want to audit.
     */
    @Bean
    public JaversSpringDataAuditableRepositoryAspect javersSpringDataAuditableAspect() {
        return new JaversSpringDataAuditableRepositoryAspect(javers(), authorProvider(),
            commitPropertiesProvider());
    }

    /**
     * Required by auto-audit aspect. <br/><br/>
     *
     * Creates [email protected] SpringSecurityAuthorProvider} instance,
     * suitable when using Spring Security
     */
    @Bean
    public AuthorProvider authorProvider() {
        return new SpringSecurityAuthorProvider();
    }

    /**
     * Optional for auto-audit aspect.
     */
    @Bean
    public CommitPropertiesProvider commitPropertiesProvider() {
        return new CommitPropertiesProvider() {
            @Override
            public Map<String, String> provide() {
                return ImmutableMap.of("key", "ok");
            }
        };
    }
}