Listener de eventos de Hibernate con servicios de Spring

Escrito por el .
blog-stack java planeta-codigo programacion
Comentarios

Java

En el anterior artículo explicaba como crear un listener que reciba eventos de Hibernate pero quizá necesitemos en el listener hacer uso de un servicio de Spring si el proceso de la acción necesita aprovecharse de la funcionalidad proporcionada en los servicios. En este artículo mostraré como crear un listener de Hibernate que use un servicio de Spring, es decir, un listener de Hibernate con la posibilidad de inyectar servicios de Spring.

Para hacer la integración de los listeners con Spring debemos sustituir el Interceptor por un servicio que haga lo mismo pero al inicio del contenedor de Spring con la anotación @PostConstruct. Para ello creamos una clase con el siguiente contenido:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate;

import javax.annotation.PostConstruct;

import org.hibernate.SessionFactory;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.internal.SessionFactoryImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class HibernateConfigurer {

    @Autowired
    private SessionFactory sessionFactory;

    @Autowired
    private ProductoEventAdapter productoEventAdapter;
    
    @PostConstruct
    public void registerListeners() {
        SessionFactoryImpl sfi = (SessionFactoryImpl) sessionFactory;
        EventListenerRegistry elr = sfi.getServiceRegistry().getService(EventListenerRegistry.class);

        elr.setListeners(EventType.PRE_INSERT, productoEventAdapter);
        elr.setListeners(EventType.PRE_UPDATE, productoEventAdapter);
        elr.setListeners(EventType.PRE_DELETE, productoEventAdapter);
        elr.setListeners(EventType.POST_INSERT, productoEventAdapter);
        elr.setListeners(EventType.POST_UPDATE, productoEventAdapter);
        elr.setListeners(EventType.POST_DELETE, productoEventAdapter);
    }
}

Configurando Spring con anotaciones y código Java, como es recomendable en vez de xml, la configuración del ejemplo es la siguiente y un archivo xml casi testimonial de Spring. En esta configuraicón vemos el servicio ProductoEventAdapter que usaremos para recibir los eventos y el servicio DummyService que se inyectará en el anterior:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package es.com.blogspot.elblogdepicodev.plugintapestry.services.spring;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.ResourceTransactionManager;

import es.com.blogspot.elblogdepicodev.plugintapestry.services.dao.ProductoDAO;
import es.com.blogspot.elblogdepicodev.plugintapestry.services.dao.ProductoDAOImpl;
import es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate.ProductoEventAdapter;

@Configuration
@ComponentScan({ "es.com.blogspot.elblogdepicodev.plugintapestry" })
@EnableTransactionManagement
public class AppConfiguration {

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.h2.Driver");
        ds.setUrl("jdbc:h2:mem:test");
        ds.setUsername("sa");
        ds.setPassword("sa");
        return ds;
    }

    @Bean
    public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
        LocalSessionFactoryBean sf = new LocalSessionFactoryBean();
        sf.setDataSource(dataSource);
        sf.setPackagesToScan("es.com.blogspot.elblogdepicodev.plugintapestry.entities");
        sf.setHibernateProperties(getHibernateProperties());
        return sf;
    }

    @Bean
    public ResourceTransactionManager transactionManager(SessionFactory sessionFactory) {
        HibernateTransactionManager tm = new HibernateTransactionManager();
        tm.setSessionFactory(sessionFactory);
        return tm;
    }

    @Bean
    public ProductoEventAdapter productoEventAdapter() {
        return new ProductoEventAdapter();
    }

    @Bean
    public ProductoDAO productoDAO(SessionFactory sessionFactory) {
        return new ProductoDAOImpl(sessionFactory);
    }

    @Bean
    public DummyService dummyService() {
        return new DummyService();
    }

    private Properties getHibernateProperties() {
        Map<String, Object> m = new HashMap<>();
        m.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        //m.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
     m.put("hibernate.hbm2ddl.auto", "create");
        // Debug
     m.put("hibernate.generate_statistics", true);
        m.put("hibernate.show_sql", true);

        Properties properties = new Properties();
        properties.putAll(m);
        return properties;
    }
}
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <context:component-scan base-package="es.com.blogspot.elblogdepicodev.plugintapestry" />
</beans>

En el listener haremos uso de un servicio de Spring que podemos inyectar usando la anotación @Autorwire tal y como hacemos normalmente usando el contenedor de depednecias de Spring. La implementación con respecto a usar un listener con solo Hibernate varía ligeramente para adaptarse a los cambios de usar un servicio.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate;

import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostDeleteEventListener;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.event.spi.PreDeleteEvent;
import org.hibernate.event.spi.PreDeleteEventListener;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreInsertEventListener;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;

public class HibernateEventAdapter implements PreInsertEventListener, PostInsertEventListener, PreUpdateEventListener, PostUpdateEventListener, PreDeleteEventListener,
        PostDeleteEventListener {

    private static final long serialVersionUID = 1L;

    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return false;
    }

    @Override
    public void onPostDelete(PostDeleteEvent event) {
    }

    @Override
    public boolean onPreDelete(PreDeleteEvent event) {
        return false;
    }

    @Override
    public void onPostUpdate(PostUpdateEvent event) {
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        return false;
    }

    @Override
    public void onPostInsert(PostInsertEvent event) {
    }

    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        return false;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate;

import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PreDeleteEvent;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreUpdateEvent;
import org.springframework.beans.factory.annotation.Autowired;

import es.com.blogspot.elblogdepicodev.plugintapestry.services.spring.DummyService;

public class ProductoEventAdapter extends HibernateEventAdapter {

    private static final long serialVersionUID = 1L;

    @Autowired
    private DummyService dummy;
    
    public void setDummy(DummyService dummy) {
        this.dummy = dummy;      
    }
    
    @Override
    public void onPostDelete(PostDeleteEvent event) {
        dummy.process("postDelete", event.getEntity());
    }

    @Override
    public boolean onPreDelete(PreDeleteEvent event) {
        dummy.process("preDelete", event.getEntity());
        return false;
    }

    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        dummy.process("postUpdate", event.getEntity());
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        dummy.process("preUpdate", event.getEntity());
        return false;
    }

    @Override
    public void onPostInsert(PostInsertEvent event) {
        dummy.process("postInsert", event.getEntity());
    }

    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        dummy.process("preInsert", event.getEntity());
        return false;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package es.com.blogspot.elblogdepicodev.plugintapestry.services.spring;

import es.com.blogspot.elblogdepicodev.plugintapestry.entities.Producto;

public class DummyService {

    public void process(String action, Object entity) {
        if (entity instanceof Producto) {
            Producto p = (Producto) entity;
            System.out.println(String.format("Action: %s, Id: %d", action, p.getId()));
        }
    }
}

En este ejemplo el resultado que veríamos en la consola sería el siguiente con las trazas Action: preInsert, Id: null y Action: postInsert, Id: 1 antes y después de ejecutarse la sentencia SQL:

Usando los listeners de Hibernate con Spring no necesitamos el archivo que creábamos antes en /META-INF/services/org.hibernate.integrator.spi.Integrator. Esto es un ejemplo de prueba de concepto pero perfectamente podría ser aplicado a una necesidad real. En el ejemplo PlugIn Tapestry que hice para un libro sobre el framework de desarrollo web Apache Tapestry puede verse el código completo y funcional de esta implementación.