web-dev-qa-db-ja.com

JPA EntityListenerへのSpring依存関係の注入

Spring依存関係を挿入するJPA EntityListenerにしようとしています。これが私のリスナークラスです。

@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です。

誰でも助けていただけますか?

40
balteo

ステートレスBeanに依存関係を注入するハックは、依存関係を「静的」として定義し、Spterが依存関係を注入できるようにセッターメソッドを作成することです(静的依存関係に割り当てる)。

依存関係を静的として宣言します。

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

29
Juan Jimenez

これは実際には古い質問ですが、別の解決策を見つけました:

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 +ウィービングなしの静的変数よりも優れています。

20

そして、このソリューションはどうですか?

@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/

14
chuckedw

私は、AOPを使用してSpring BeanをEntityリスナーに注入する道を歩み始めました。 1日半の研究とさまざまなことを試した後、私はこれに遭遇しました link

Spring管理BeanをJPA EntityListenerクラスに注入することはできません。これは、JPAリスナーメカニズムがステートレスクラスに基づいている必要があるため、メソッドは事実上静的であり、コンテキストを認識しないためです。 ...実装は実際にインスタンスを作成するのではなく、クラスメソッドを使用するため、AOPの量はあなたを救いません。リスナーを表す「オブジェクト」には何も注入されません。

この時点で、私は再編成し、EclipseLink DescriptorEventAdapter を偶然見つけました。この情報を使用して、記述子アダプターを拡張するリスナークラスを作成しました。

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

少しの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>  

これで、Springがエンティティリスナを作成し、必要な依存関係を注入し、リスナオブジェクトがリッスンするエンティティクラスに自身を登録する状況になりました。

これがお役に立てば幸いです。

13
jhadley

リスナーに@ Componentアノテーションを付けてから、non static setterを作成して、注入されたSpring Beanを割り当てます。

私のコードは次のようになります:

@Component
public class EntityListener {

    private static MyService service;

    @Autowired
    public void setMyService (MyService service) {
        this.service=service;
    }


    @PreUpdate
    public void onPreUpdate() {

        service.doThings()

    }

    @PrePersist
    public void onPersist() {
       ...
    }


}
8
othmane

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

次に、次のようにエンティティリスナーからこれを呼び出します。

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
Naymesh Mistry

これは、このリスナーBeanがSpringの制御下にないためだと思います。 Springはそれをインスタンス化しないのですが、SpringはどのようにしてそのBeanを見つけてインジェクションを行うかを知ることができますか?

私はそれを試したことはありませんが、SpringのConfigurableアノテーションでAspectJ Weaverを使用して、Springがインスタンス化されていないBeanをSpringで制御できるようです。

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

2
Adrian Shum

JPAリスナーの問題は次のとおりです。

  1. それらはSpringによって管理されていません(注入はありません)

  2. それらは作成される(または作成される可能性がある)beforeSpringのApplication Contextの準備ができている(したがって、コンストラクター呼び出しでBeanを注入できない)

問題に対処するための私の回避策:

1)public static Listenerフィールドを持つLISTENERSクラスを作成します。

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の準備ができた直後に、すべてのリスナーを取得してBeanを注入できます。

@Component
public class ListenerInjector {

    @Autowired
    private ApplicationContext context;

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshed() {
       Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
    }

}
1
Taras Shpek

別のオプション:

AplicationContextをアクセス可能にするサービスを作成します。

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の制御下にないため、コンテキストBeanにアクセスできません。複数のオプション(@Configurable(...))を試しましたが、コンテキストに静的にアクセスするクラスを作成する以外は機能しませんでした。すでにそのジレンマでは、これはエレガントなオプションだと思います。

1
leon cio

Spring V5.1(およびHibernate V5.3)以降、Springはこれらのクラスのプロバイダーとして登録されるため、そのまま使用できます。 SpringBeanContainer のドキュメントを参照

0
D.C