복붙노트

[SPRING] JPA EntityListener에 Spring 의존성 삽입하기

SPRING

JPA EntityListener에 Spring 의존성 삽입하기

JPA EntityListener에 Spring 종속성을 주입하려고합니다. 다음은 리스너 클래스입니다.

@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);
    }


}

다음은 Entity 클래스입니다.

@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...

그러나 내 의존성 (예 : evenementPliRepository)은 항상 null입니다.

아무도 도와 줄 수 있습니까?

해결법

  1. ==============================

    1.상태없는 빈에 의존성을 주입하는 해킹은 의존성을 "정적"으로 정의하고, Spring이 의존성을 주입 (정적 종속성에 할당) 할 수 있도록 setter 메소드를 생성하는 것입니다.

    상태없는 빈에 의존성을 주입하는 해킹은 의존성을 "정적"으로 정의하고, Spring이 의존성을 주입 (정적 종속성에 할당) 할 수 있도록 setter 메소드를 생성하는 것입니다.

    종속성을 정적으로 선언하십시오.

    static private EvenementPliRepository evenementPliRepository;
    

    Spring이 그것을 삽입 할 수 있도록 메소드를 생성하십시오.

    @Autowired
    public void init(EvenementPliRepository evenementPliRepository) 
    {
        MyListenerClass.evenementPliRepository = evenementPliRepository;
        logger.info("Initializing with dependency ["+ evenementPliRepository +"]"); 
    }
    

    자세한 내용은 http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html을 참조하십시오.

  2. ==============================

    2.이것은 실제로 오래된 질문이지만 대체 솔루션을 발견했습니다.

    이것은 실제로 오래된 질문이지만 대체 솔루션을 발견했습니다.

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

    아마도 가장 좋은 것은 아니지만 AOP + 직조가없는 정적 변수보다 낫습니다.

  3. ==============================

    3.그리고이 솔루션은 어떻습니까?

    그리고이 솔루션은 어떻습니까?

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

    그런 다음 리스너 ...

    @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);
        }
    }
    

    그리고 도우미는 ...

        /**
         * 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;
            }
    
        }
    

    나를 위해 일합니다.

    출처: http://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/

  4. ==============================

    4.AOP를 사용하여 Entity Listener에 스프링 빈을 주입하는 길을 시작했습니다. 연구 1 일 반 동안 다른 일을 시도한 후에 다음과 같은 링크를 발견했습니다.

    AOP를 사용하여 Entity Listener에 스프링 빈을 주입하는 길을 시작했습니다. 연구 1 일 반 동안 다른 일을 시도한 후에 다음과 같은 링크를 발견했습니다.

    이 시점에서 나는 EclipseLink DescriptorEventAdapter를 재편성하여 우연히 발견했다. 이 정보를 사용하여 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
        }
    }
    

    클래스를 사용하기 위해 @EntityListeners 주석을 내 엔티티 클래스에 사용할 수있었습니다. 불행히도,이 방법은 Spring이 내 리스너의 생성을 제어하지 못하게하고 결과적으로 의존성 삽입을 허용하지 않을 것입니다. 대신 내 클래스에 다음 'init'함수를 추가했습니다.

    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();
            }
        }
    }
    

    약간의 스프링 XML 설정 추가하기

    <!-- Define listener object -->
    <bean id="entityListener" class="EntityListener " init-method="init">
        <property name="injectedValue" value="Hello World"/>
        <property name="entityManagerFactory" ref="emf"/>
    </bean>  
    

    이제 우리는 Spring이 엔티티 리스너를 생성하고 필요한 모든 의존성을 주입하며 리스너 객체가 리스닝하려는 엔티티 클래스에 자신을 등록하는 상황을 보았습니다.

    이게 도움이 되길 바란다.

  5. ==============================

    5.나는 https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/에서 제안 된 접근법을 시험하고 일했다. 매우 깨끗하지는 않지만 일을합니다. 나를 위해 약간 수정 된 AutowireHelper 클래스는 다음과 같습니다.

    나는 https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/에서 제안 된 접근법을 시험하고 일했다. 매우 깨끗하지는 않지만 일을합니다. 나를 위해 약간 수정 된 AutowireHelper 클래스는 다음과 같습니다.

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

    그런 다음 엔티티 리스너에서 this를 다음과 같이 호출합니다.

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

    6.나는이 청자 콩이 봄 통제하에 있지 않기 때문에 그것이라고 믿는다. Spring은 인스턴스화하지 않습니다. Spring은 어떻게 bean을 찾아서 injection을 할 수 있습니까?

    나는이 청자 콩이 봄 통제하에 있지 않기 때문에 그것이라고 믿는다. Spring은 인스턴스화하지 않습니다. Spring은 어떻게 bean을 찾아서 injection을 할 수 있습니까?

    나는 그걸 시도하지 않았지만 AspectJ Weaver와 Spring의 Configurable annotation을 사용하여 스프링 컨트롤이 아닌 Spring 인스턴스화 된 빈을 가질 수 있다고 생각한다.

    http://static.springsource.org/spring/docs/3.1.2.RELEASE/spring-framework-reference/html/aop.html#aop-using-aspectj

  7. ==============================

    7.다른 옵션 :

    다른 옵션 :

    ApplicationContext에 액세스 할 수있게 해주는 서비스를 만듭니다.

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

    그걸 써:

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

    이 클래스는 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;
    ...
    

    리스너는 Spring의 통제하에 있지 않기 때문에 컨텍스트 빈에 접근 할 수 없다. 여러 옵션 (@Configurable (...))을 시도했지만 none은 컨텍스트에 대한 정적 액세스 클래스를 만드는 것 외에는 작동하지 않았습니다. 이미 그 딜레마에 나는 이것이 우아한 선택이라고 생각합니다.

  8. ==============================

    8.JPA Listeners의 문제점은 다음과 같습니다.

    JPA Listeners의 문제점은 다음과 같습니다.

    문제를 해결할 수있는 해결 방법은 다음과 같습니다.

    1) public static LISTENERS 필드가있는 Listener 클래스를 만듭니다.

    public abstract class Listener {
        // for encapsulation purposes we have private modifiable and public non-modifiable lists
        private static final List<Listener> PRIVATE_LISTENERS = new ArrayList<>();
        public static final List<Listener> LISTENERS = Collections.unmodifiableList(PRIVATE_LISTENERS);
    
        protected Listener() {
            PRIVATE_LISTENERS.add(this);
        }
    }
    

    2) Listener.LISTENERS에 추가 할 모든 JPA 리스너는이 클래스를 확장해야합니다.

    public class MyListener extends Listener {
    
        @PrePersist
        public void onPersist() {
            ...
        }
    
        ...
    }
    

    3) 이제 Spring의 Application Context가 준비된 직후에 모든 리스너를 얻고 콩을 주입 할 수 있습니다

    @Component
    public class ListenerInjector {
    
        @Autowired
        private ApplicationContext context;
    
        @EventListener(ContextRefreshedEvent.class)
        public void contextRefreshed() {
           Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
        }
    
    }
    
  9. from https://stackoverflow.com/questions/12155632/injecting-a-spring-dependency-into-a-jpa-entitylistener by cc-by-sa and MIT license