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;
}
}
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.