Spring's @EntityListeners is throwing an exception when using @Postpersist

2k views Asked by At

I have a small test application for testing @EntityListeners functionality. When I run the application I get the following stacktrace

Exception in thread "main" org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy67.save(Unknown Source)
    at com.example.hibernate_envers_test.HibernateEnversTestApplication.main(HibernateEnversTestApplication.java:32)
Caused by: javax.persistence.RollbackException: Error while committing the transaction
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:87)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
    ... 17 more
Caused by: java.lang.NullPointerException
    at com.example.hibernate_envers_test.AccountListener.crudLogger(AccountListener.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.hibernate.jpa.event.internal.jpa.ListenerCallback.performCallback(ListenerCallback.java:35)
    at org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl.callback(CallbackRegistryImpl.java:94)
    at org.hibernate.jpa.event.internal.jpa.CallbackRegistryImpl.postCreate(CallbackRegistryImpl.java:63)
    at org.hibernate.jpa.event.internal.core.JpaPostInsertEventListener.onPostInsert(JpaPostInsertEventListener.java:38)
    at org.hibernate.action.internal.EntityInsertAction.postInsert(EntityInsertAction.java:164)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:131)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
    at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
    ... 18 more

As you can see it complains about not being able to commit a jpa transaction. Followed by a nullpointer exception whenever I try to use my accountHistoryRepo spring jpa repository interface. I have already checked whether the account pojo argument or the repository instance was null, it wasn't the case. When I replace @PostPersist with @PrePersist the TransactionSystemException no longer appears but I still have that mysterious nullpointer exception to deal with. Next to that I prefer to use PostPersist because I don't want to persist a history object when the actual object on which the history object is based wasn't persisted correctly. Can anyone help me? Below is the code of my test application:

AccountListener

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.example.hibernate_envers_test;

import com.example.pojo.Account;
import com.example.pojo.AccountHistory;
import javax.persistence.PostPersist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 *
 * @author maurice
 */
@Component
public class AccountListener {

    @Autowired
    AccountHistoryRepo accountHistoryRepo;


    @PostPersist
    public void crudLogger(Account account){
        System.out.println("is account null?: " + account == null);
                System.out.println("is account null?: " + account.getJabberId());
        System.out.println("is accountHistoryrRepo null?: " + accountHistoryRepo);
        if (accountHistoryRepo.count()>=10) accountHistoryRepo.deleteOldestEntry();
        accountHistoryRepo.save(new AccountHistory(account));
    } 
}

Account pojo

@Entity
@EntityListeners(AccountListener.class)
public class Account implements Serializable {

    @Id
    @GeneratedValue(generator = "ID_GENERATOR")
    private Long accountId;

    private String name;

    private boolean isEnabled;

    private boolean isSmartsuppEnabled;

    private String jabberId;

    private double budgetWarningPercentage;

    private boolean isSentBudgetWarning;

    private double budgetAmount;

    private int departmentLevel;

    private int contactId;

    public Long getAccountId() {
        return accountId;
    }

    public Account setAccountId(Long accountId) {
        this.accountId = accountId;
        return this;
    }

    public String getName() {
        return name;
    }

    public Account setName(String name) {
        this.name = name;
        return this;
    }

    public boolean isIsEnabled() {
        return isEnabled;
    }

    public Account setIsEnabled(boolean isEnabled) {
        this.isEnabled = isEnabled;
        return this;
    }

    public boolean isIsSmartsuppEnabled() {
        return isSmartsuppEnabled;
    }

    public Account setIsSmartsuppEnabled(boolean isSmartsuppEnabled) {
        this.isSmartsuppEnabled = isSmartsuppEnabled;
        return this;
    }

    public String getJabberId() {
        return jabberId;
    }

    public Account setJabberId(String jabberId) {
        this.jabberId = jabberId;
        return this;
    }

    public double getBudgetWarningPercentage() {
        return budgetWarningPercentage;
    }

    public Account setBudgetWarningPercentage(double budgetWarningPercentage) {
        this.budgetWarningPercentage = budgetWarningPercentage;
        return this;
    }

    public boolean isIsSentBudgetWarning() {
        return isSentBudgetWarning;
    }

    public Account setIsSentBudgetWarning(boolean isSentBudgetWarning) {
        this.isSentBudgetWarning = isSentBudgetWarning;
        return this;
    }

    public double getBudgetAmount() {
        return budgetAmount;
    }

accounthistory pojo

@Entity(name = "account_history")
public class AccountHistory implements Serializable {
    @Id
    @GeneratedValue(generator = "ID_GENERATOR")
    private Long accountHistoryId;

    private Long accountId;

    private String name;

    private boolean isEnabled;

    private boolean isSmartsuppEnabled;

    private String jabberId;

    private double budgetWarningPercentage;

    private boolean isSentBudgetWarning;

    private double budgetAmount;

    private int departmentLevel;

    private int contactId;

    public AccountHistory(){}

    public AccountHistory(Account account){
        this.accountId = account.getAccountId();
        this.name = account.getName();
        this.isEnabled = account.isIsEnabled();
        this.isSmartsuppEnabled  = account.isIsSmartsuppEnabled();
        this.jabberId = account.getJabberId();
        this.budgetWarningPercentage = account.getBudgetWarningPercentage();
        this.isSentBudgetWarning = account.isIsSentBudgetWarning();
        this.budgetAmount = account.getBudgetAmount();
        this.departmentLevel = account.getDepartmentLevel();
        this.contactId = account.getContactId();    
    }

    public Long getAccountId() {
        return accountId;
    }

    public AccountHistory setAccountId(Long accountId) {
        this.accountId = accountId;
        return this;
    }

    public String getName() {
        return name;
    }

    public AccountHistory setName(String name) {
        this.name = name;
        return this;
    }

    public boolean isIsEnabled() {
        return isEnabled;
    }

    public AccountHistory setIsEnabled(boolean isEnabled) {
        this.isEnabled = isEnabled;
        return this;
    }

    public boolean isIsSmartsuppEnabled() {
        return isSmartsuppEnabled;
    }

    public AccountHistory setIsSmartsuppEnabled(boolean isSmartsuppEnabled) {
        this.isSmartsuppEnabled = isSmartsuppEnabled;
        return this;
    }

    public String getJabberId() {
        return jabberId;
    }

    public AccountHistory setJabberId(String jabberId) {
        this.jabberId = jabberId;
        return this;
    }

    public double getBudgetWarningPercentage() {
        return budgetWarningPercentage;
    }

    public AccountHistory setBudgetWarningPercentage(double budgetWarningPercentage) {
        this.budgetWarningPercentage = budgetWarningPercentage;
        return this;
    }

    public boolean isIsSentBudgetWarning() {
        return isSentBudgetWarning;
    }

    public AccountHistory setIsSentBudgetWarning(boolean isSentBudgetWarning) {
        this.isSentBudgetWarning = isSentBudgetWarning;
        return this;
    }

    public double getBudgetAmount() {
        return budgetAmount;
    }

    public AccountHistory setBudgetAmount(double budgetAmount) {
        this.budgetAmount = budgetAmount;
        return this;
    }

    public int getDepartmentLevel() {
        return departmentLevel;
    }

    public AccountHistory setDepartmentLevel(int departmentLevel) {
        this.departmentLevel = departmentLevel;
        return this;
    }

    public int getContactId() {
        return contactId;
    }

    public AccountHistory setContactId(int contactId) {
        this.contactId = contactId;
        return this;
    }
}



    public Account setBudgetAmount(double budgetAmount) {
        this.budgetAmount = budgetAmount;
        return this;
    }

    public int getDepartmentLevel() {
        return departmentLevel;
    }

    public Account setDepartmentLevel(int departmentLevel) {
        this.departmentLevel = departmentLevel;
        return this;
    }

    public int getContactId() {
        return contactId;
    }

    public Account setContactId(int contactId) {
        this.contactId = contactId;
        return this;
    }
}

AccountHistoryRepo

@Repository
public interface AccountHistoryRepo extends JpaRepository<AccountHistory,Long> {
    @Query("DELETE FROM account_history p WHERE p.accountHistoryId = (SELECT MIN(p.accountHistoryId) FROM account_history p)")
    public void deleteOldestEntry();
}

AccountRepo

@Repository
public interface AccountRepository extends JpaRepository<Account,Long> {

}

main class

@SpringBootApplication
public class HibernateEnversTestApplication implements ApplicationContextAware  {

    private static ApplicationContext ac;

//    @Autowired
//    AccountRepository accountRepo;
    public static void main(String[] args) {
        SpringApplication.run(HibernateEnversTestApplication.class, args);
                AccountRepository accountRepo = ac.getBean(AccountRepository.class);
                //Account account = accountRepo.findOne(1003L);
                Account account = new Account();
                account.setBudgetAmount(6666.4)
                        .setBudgetWarningPercentage(66666.2)
                        .setContactId(3333)
                        .setDepartmentLevel(44444)
                        .setIsEnabled(true)
                        .setIsSentBudgetWarning(false)
                        .setIsSmartsuppEnabled(true)
                        .setJabberId("en dit is aanpassing 4")
                        .setName("henk");
                accountRepo.save(account);
    }

            @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ac = applicationContext;
    }
}

root config

@Configuration
@PropertySources(value = {@PropertySource("classpath:application.properties")})
@ComponentScan//(basePackages={"repository"})
@EnableJpaRepositories//(basePackages={"repository"})
@EnableTransactionManagement
public class RootConfig {

  @Value("${spring.datasource.url}")
  private String dataBaseUrl;
  @Value("${spring.datasource.driver}")
  private String driver;
  @Value("${spring.datasource.username}")
  private String userName;
  @Value("${spring.datasource.password}")
  private String password;
  @Value("${spring.datasource.strategy}")
  private String strategy;

@Bean(destroyMethod = "close")
public DataSource seedDataSource(){
    HikariConfig hikariConfig = new HikariConfig();
    hikariConfig.setDriverClassName(driver);
    hikariConfig.setJdbcUrl(dataBaseUrl); 
    hikariConfig.setUsername(userName);
    hikariConfig.setPassword(password);

    hikariConfig.setMaximumPoolSize(5);
    hikariConfig.setConnectionTestQuery("SELECT 1");
    hikariConfig.setPoolName("springHikariCP");

    hikariConfig.addDataSourceProperty("dataSource.cachePrepStmts", "true");
    hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSize", "250");
    hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSqlLimit", "2048");
    hikariConfig.addDataSourceProperty("dataSource.useServerPrepStmts", "true");

    HikariDataSource dataSource = new HikariDataSource(hikariConfig);

    return dataSource;
}    

  @Bean
  JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
  }  

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, 
                                                                JpaVendorAdapter jpaVendorAdapter){
  LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
  Properties props = new Properties();
  props.put("org.hibernate.envers.audit_strategy","org.hibernate.envers.strategy.ValidityAuditStrategy");
  props.put("org.hibernate.envers.audit_strategy_validity_store_revend_timestamp", "true");
  props.put("org.hibernate.envers.track_entities_changed_in_revision","true");
  props.put("org.hibernate.envers.global_with_modified_flag","true");
  props.put("org.hibernate.envers.store_data_at_delete", "true");
  props.put("hibernate.listeners.envers.autoRegister", "true");

  entityManagerFactory.setDataSource(dataSource);
  entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
  entityManagerFactory.setPackagesToScan("com.example.pojo");
  entityManagerFactory.setJpaProperties(props);
  return entityManagerFactory;
}

@Bean
public JpaVendorAdapter jpaVendorAdapter(){
    HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
    adapter.setDatabase(Database.MYSQL);
    adapter.setShowSql(false);
    adapter.setGenerateDdl(true);
    adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
    return adapter;
}  

}
1

There are 1 answers

4
Strelok On BEST ANSWER

Even though you made the entity listener a @Component, what JPA uses is actually just an instance of the class and is not a spring managed bean. That's why you're getting a NullPointerException. The accountHistoryRepo will always be null.