example - jpa auditing spring boot
Inyectando una dependencia de Spring en un JPA EntityListener (7)
¿Y qué hay con esta solución?
@MappedSuperclass
@EntityListeners(AbstractEntityListener.class)
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "creation_date")
private Date creationDate;
@Column(name = "modification_date")
private Date modificationDate;
}
Entonces el oyente ...
@Component
public class AbstractEntityListener {
@Autowired
private DateTimeService dateTimeService;
@PreUpdate
public void preUpdate(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
abstractEntity.setModificationDate(this.dateTimeService.getCurrentDate());
}
@PrePersist
public void prePersist(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
Date currentDate = this.dateTimeService.getCurrentDate();
abstractEntity.setCreationDate(currentDate);
abstractEntity.setModificationDate(currentDate);
}
}
Y el ayudante ...
/**
* Helper class which is able to autowire a specified class. It holds a static reference to the {@link org
* .springframework.context.ApplicationContext}.
*/
public final class AutowireHelper implements ApplicationContextAware {
private static final AutowireHelper INSTANCE = new AutowireHelper();
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
/**
* Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
* are null.
*
* @param classToAutowire the instance of the class which holds @Autowire annotations
* @param beansToAutowireInClass the beans which have the @Autowire annotation in the specified {#classToAutowire}
*/
public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
for (Object bean : beansToAutowireInClass) {
if (bean == null) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
}
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
/**
* @return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
}
Funciona para mi.
Fuente: http://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/
Intento inyectar una dependencia de Spring en un JPA EntityListener . Aquí está mi clase de oyente:
@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class PliListener {
@Autowired
private EvenementPliRepository evenementPliRepository;
@PostPersist
void onPostPersist(Pli pli) {
EvenementPli ev = new EvenementPli();
ev.setPli(pli);
ev.setDateCreation(new Date());
ev.setType(TypeEvenement.creation);
ev.setMessage("Création d''un pli");
System.out.println("evenementPliRepository: " + evenementPliRepository);
evenementPliRepository.save(ev);
}
}
Aquí está mi clase de Entidad:
@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
Sin embargo, mi dependencia (es decir, evenementPliRepository
) siempre es nula .
Alguien puede ayudarme porfavor?
Comencé a seguir el camino de usar AOP para inyectar un bean de primavera en un oyente de Entity. Después de un día y medio de investigación y probando diferentes cosas, encontré este link que decía:
No es posible inyectar beans administrados por resorte en una clase JPA EntityListener. Esto se debe a que el mecanismo del oyente JPA debe basarse en una clase sin estado, por lo que los métodos son efectivamente estáticos y no tienen en cuenta el contexto. ... Ninguna cantidad de AOP te salvará, nada se inyecta al ''objeto'' que representa al oyente, porque las implementaciones en realidad no crean instancias, sino que usan el método de clase.
En este punto, me reagrupé y tropecé con EclipseLink DescriptorEventAdapter . Usando esta información, creé una clase de oyente que extendía el Descriptor Adapter.
public class EntityListener extends DescriptorEventAdapter {
private String injectedValue;
public void setInjectedValue(String value){
this.injectedValue = value;
}
@Override
public void aboutToInsert(DescriptorEvent event) {
// Do what you need here
}
}
Para usar la clase, podría haber usado la anotación @EntityListeners en mi clase de entidad. Desafortunadamente, este método no le permitió a Spring controlar la creación de mi oyente y, como resultado, no permitió la inyección de la dependencia. En cambio, agregué la siguiente función ''init'' a mi clase:
public void init() {
JpaEntityManager entityManager = null;
try {
// Create an entity manager for use in this function
entityManager = (JpaEntityManager) entityManagerFactory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(<EntityClass>.class);
// Add this class as a listener to the class descriptor
desc.getEventManager().addListener(this);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
Agregue una pequeña configuración Spring XML
<!-- Define listener object -->
<bean id="entityListener" class="EntityListener " init-method="init">
<property name="injectedValue" value="Hello World"/>
<property name="entityManagerFactory" ref="emf"/>
</bean>
Ahora tenemos una situación en la que Spring crea un oyente de entidad, lo inyecta con las dependencias necesarias, y el objeto oyente se registra con la clase de entidad a la que intenta escuchar.
Espero que esto ayude.
Creo que es porque este bean listener no está bajo el control de Spring. Spring no lo está creando, ¿cómo puede Spring saber cómo encontrar ese grano y hacer la inyección?
No lo he intentado, pero parece que puedes utilizar AspectJ Weaver con la anotación Configurable de Spring para tener Spring control beans no instanciados por Spring.
Esta es una vieja pregunta, pero encontré una solución alternativa:
public class MyEntityListener {
@Autowired
private ApplicationEventPublisher publisher;
@PostPersist
public void postPersist(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnCreatedEvent<>(this, target));
}
@PostUpdate
public void postUpdate(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnUpdatedEvent<>(this, target));
}
@PostRemove
public void postDelete(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnDeletedEvent<>(this, target));
}
}
Probablemente no sea el mejor, pero mejor que las variables estáticas sin AOP + weaving.
Otra opción:
Crea un servicio para hacer que AplicationContext sea accesible:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import lombok.Setter;
@Service
class ContextWrapper {
@Setter
private static ApplicationContext context;
@Autowired
public ContextWrapper(ApplicationContext ac) {
setContext(ac);
}
public static ApplicationContext getContext() {
return context;
}
}
Úselo:
...
public class AuditListener {
private static final String AUDIT_REPOSITORY = "AuditRepository";
@PrePersist
public void beforePersist(Object object){
//TODO:
}
@PreUpdate
public void beforeUpdate(Object object){
//TODO:
}
@PreRemove
public void beforeDelete(Object object) {
getRepo().save(getAuditElement("DEL",object));
}
private Audit getAuditElement(String Operation,Object object){
Audit audit = new Audit();
audit.setActor("test");
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
audit.setDate(timestamp);
return audit;
}
private AuditRepository getRepo(){
return ContextWrapper.getContext().getBean(AUDIT_REPOSITORY, AuditRepository.class);
}
}
Esta clase se crea como un oyente de jpa:
...
@Entity
@EntityListeners(AuditListener.class)
@NamedQuery(name="Customer.findAll", query="SELECT c FROM Customer c")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
...
Como el oyente no está bajo el control de Spring, no puede acceder al bean de contexto. He intentado varias opciones (@Configurable (...)) y ninguna ha funcionado excepto para crear una clase que tenga acceso estático al contexto. Ya en ese dilema, creo que esta es una opción elegante.
Probé el enfoque sugerido en https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/ y funcionó. No muy limpio, pero hace el trabajo. La clase AutowireHelper ligeramente modificada para mí se veía así:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class AutowireHelper implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
public static void autowire(Object classToAutowire) {
AutowireHelper.applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
Luego llamé esto desde el oyente de la entidad de esta manera:
public class MyEntityAccessListener {
@Autowired
private MyService myService;
@PostLoad
public void postLoad(Object target) {
AutowireHelper.autowire(this);
myService.doThings();
...
}
public void setMyService(MyService myService) {
this.myService = myService;
}
}
Un truco para inyectar dependencias en beans sin estado, es definir la dependencia como "estática", crear un método setter para que Spring pueda inyectar la dependencia (asignándola a la dependencia estática).
Declara la dependencia como estática.
static private EvenementPliRepository evenementPliRepository;
Crea un método para que Spring pueda inyectarlo.
@Autowired
public void init(EvenementPliRepository evenementPliRepository)
{
MyListenerClass.evenementPliRepository = evenementPliRepository;
logger.info("Initializing with dependency ["+ evenementPliRepository +"]");
}
Más detalles en: http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html