1

we are building one application where we need to log Entity updates in to History table. I am trying to achieve this by hibernate interceptor, and we could able to mange to get all the changes but having difficulties in inserting them into audit table.

My JPA configuration

public class JPAConfiguration {
 ----
 @Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() throws SQLException {
    LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setPackagesToScan(new String[] {"com.yyy.persist"});

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    vendorAdapter.setShowSql(true);
    // thsi is required in order to enable Query DSL
    vendorAdapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
    factoryBean.setJpaVendorAdapter(vendorAdapter);
   // factoryBean.setMappingResources(mappingResources);
    // adding hibernate interceptor

    Properties jpaProperties = new Properties();
    jpaProperties.setProperty("hibernate.ejb.interceptor", "com.yyy.admin.service.AuditInterceptor");
    factoryBean.setJpaProperties(jpaProperties);
    return factoryBean;
}

My Interceptor

public class AuditInterceptor extends EmptyInterceptor {
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
        String[] propertyNames, Type[] types) {

     if ( entity instanceof Auditable ) {
    // updates++;

    for (int i = 0; i < propertyNames.length; i++) {

        if ((currentState[i] == null && previousState[i] != null)
                || (currentState[i] != null && previousState[i] == null) || (currentState[i] != null
                        && previousState[i] != null && !currentState[i].equals(previousState[i]))) {

            AuditLog audit = new AuditLog();
            audit.setAction("UPDATE");
            audit.setFieldChanged(propertyNames[i]);
            audit.setOldvalue(previousState[i] != null ? previousState[i].toString() : "");
            audit.setNewvalue(currentState[i] != null ? currentState[i].toString() : "");
            audit.setTimeStamp(new Date());
            audit.setUsername(userName);

            entities.add(audit);
        }
    }

    // iterate elements on the report build a entity
 }
    return false;
}

public void afterTransactionCompletion(Transaction tx) {

    if (tx.wasCommitted()) {
if (entities != null) {
            for (AuditLog e : entities) {
                System.out.println(e);
                //.save(e);
            }
            entities = new ArrayList<AuditLog>();
        }
    }
     }

}

in method afterTransactionCompletion I need to write all audit entities into DB, Autowire not working as this is not spring managed bean, is there any way to get DB session in this method so that I can perform inserts .?

Nuthan Kumar
  • 483
  • 5
  • 22

2 Answers2

6

The typical solution to inject Spring Beans into non-spring managed class is thru static resource holder. For example you have a class called StaticServiceHolder and annotate is with @Component then create static fields for the spring bean you want to inject thru setter. Like:

@Component
public class StaticServiceHolder
{
    public static AuditService auditService;

    @Autowired
    public void setAuditService(AuditService auditService)
    {
        StaticServiceHolder.auditService = auditService;
    }
}

Or Even easier if you have a lot of these stuff need to be injected, then you can Autowire the ApplicationContext. This way you can get whatever bean you need.

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        ApplicationContextHolder.applicationContext = ctx;
    }
}

....
//in your hibernate interceptor
YourAuditService auditService = ApplicationContextHolder.applicationContext.getBean(YourAuditService.class);
auditService.saveAuditLog();

Either way, you should be able to persist your stuff in DB as long as the service you are using is Transactional. Hope this work for you.

For Transaction manager setup:

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

    return transactionManager;
}
Andy Birchall
  • 311
  • 1
  • 3
  • 12
LeOn - Han Li
  • 9,388
  • 1
  • 65
  • 59
  • Thanks for your reply. Second approach with Application context is not working in my case .. not sure what wrong i am doing there .. Trying with first appraoch – Nuthan Kumar Aug 11 '16 at 02:54
  • 1
    I could able to inject repo using static holder .. some how data is not getting saved into DB. I made my spring JPA repo transnational still no luck .. any thoughts ..? – Nuthan Kumar Aug 11 '16 at 04:44
  • hmmm, that is really another topic i think. It is related to you `TransactionManager` setup. let me try to help you.... I updated the answer at the end for a typical TransactionManger java config. As long as a transaction starts, the persist should work. – LeOn - Han Li Aug 11 '16 at 19:58
  • And typically you do not make the repo/dao layer transactional but the service layer so that a transaction could cover multiple db calls and success/fail together. But here for testing purpose, it is fine I guess. – LeOn - Han Li Aug 11 '16 at 20:01
  • Hi Leon , first problem solved .. still not able to save the record .. as you mentioned I will start new problem and make this as solved. Thanks for your help. – Nuthan Kumar Aug 12 '16 at 15:38
  • Sure, as I mentioned above, if you are using Spring managing your transaction, double check your Spring transaction manager setup. If you use Hibernate to control transaction, then make sure you commit the transaction at the end of your dao/repo. – LeOn - Han Li Aug 12 '16 at 15:51
1

I know this is a little late, but maybe helpful for others. By using the spring bean instead of class name as value for "hibernate.ejb.interceptor", hibernate takes the spring bean instead of instantiating a new class.

More to find here: Autowired to hibernate Interceptor

Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
GisMarsch
  • 11
  • 1
  • 2