web-dev-qa-db-fra.com

Injection de haricots dans un JPA @Entity

Est-il possible d'injecter des haricots dans un JPA @Entity à l'aide de l'injection de dépendance de Spring?

J'ai essayé de @Autowire ServletContext mais, bien que le serveur ait démarré avec succès, j'ai reçu une NullPointerException lorsque j'essayais d'accéder à la propriété du bean.

@Autowired
@Transient
ServletContext servletContext;
34
theblang

Vous pouvez injecter des dépendances dans des objets non gérés par le conteneur Spring à l'aide de @Configurable, comme expliqué ici: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html# aop-atconfigurable .

Comme vous vous en êtes rendu compte, à moins d'utiliser @Configurable et la configuration de tissage AspectJ appropriée, Spring n'injecte pas de dépendances dans des objets créés à l'aide de l'opérateur new. En fait, il n'injecte pas de dépendances dans des objets sauf si vous les avez récupérées à partir de la variable ApplicationContext, pour la simple raison qu'il ne sait tout simplement pas qu'il existe. Même si vous annotez votre entité avec @Component, l'instanciation de cette entité sera toujours effectuée par une opération new, par vous-même ou par un framework tel que Hibernate. N'oubliez pas que les annotations ne sont que des métadonnées: si personne n'interprète ces métadonnées, cela n'ajoute aucun comportement ni n'a d'impact sur un programme en cours d'exécution.

Cela dit, je vous déconseille fortement d’injecter une ServletContext dans une entité. Les entités font partie de votre modèle de domaine et doivent être dissociées de tout mécanisme de diffusion, tel qu'une couche de diffusion Web basée sur Servlet. Comment utiliserez-vous cette entité lorsqu'elle est accédée par un client en ligne de commande ou par quelque chose d'autre n'impliquant pas un ServletContext? Vous devez extraire les données nécessaires de ce ServletContext et les transmettre à votre entité par le biais des arguments de méthodes traditionnelles. Vous obtiendrez un bien meilleur design grâce à cette approche.

36
Spiff

Oui bien sûr, vous pouvez. Vous devez simplement vous assurer que l'entité est également enregistrée en tant que bean géré par Spring, soit de manière déclarative à l'aide de balises <bean> (dans un fichier spring-context.xml), ou via des annotations, comme indiqué ci-dessous.

À l'aide d'annotations, vous pouvez marquer vos entités avec @Component (ou un stéréotype plus spécifique @Repository qui permet la traduction automatique des exceptions pour les DAO et qui peut ou non interférer avec JPA). 

@Entity
@Component
public class MyJAPEntity {

  @Autowired
  @Transient
  ServletContext servletContext;
  ...
}

Une fois que vous avez fait cela pour vos entités, vous devez configurer leur package (ou un package ancêtre) afin qu'il soit analysé par Spring afin que les entités soient collectées sous forme de beans et que leurs dépendances soient connectées automatiquement.

<beans ... xmlns:context="..." >
  ...
  <context:component-scan base-package="pkg.of.your.jpa.entities" />
<beans>

EDIT: (ce qui a finalement fonctionné et pourquoi)

  • Rendre la ServletContextstatic. (supprime @Autowired)

    @Transient
    private static ServletContext servletContext;
    

Étant donné que JPA crée une instance d'entité distincte, c'est-à-dire n'utilisant pas le bean géré Spring, il est nécessaire que le context soit partagé.

  • Ajout d'une méthode @PostConstructinit().

    @PostConstruct
    public void init() {
        log.info("Initializing ServletContext as [" +
                    MyJPAEntity.servletContext + "]");
    }
    

Cela déclenche init() une fois que l'entité a été instanciée et en référençant ServletContext à l'intérieur, il force l'injection sur la propriété static s'il n'a pas déjà été injecté.

  • Déplacer @Autowired vers une méthode instance mais définir le champ static à l'intérieur.

    @Autowired
    public void setServletContext(ServletContext servletContext) {
        MyJPAEntity.servletContext = servletContext;
    }
    

Citant mon dernier commentaire ci-dessous pour répondre à la question de savoir pourquoi devons-nous employer ces manigances:

Il n'y a pas vraiment de moyen de faire ce que vous voulez, car JPA n'utilise pas le conteneur Spring pour instancier ses entités. Pensez à JPA en tant que conteneur ORM distinct qui instancie et gère le cycle de vie des entités (complètement distinct de Spring) et effectue une ID basée sur les relations d'entité uniquement.

18
Ravi Thapliyal

Après un long moment, je suis tombé sur cette SO réponse qui m'a fait penser à une solution élégante:

  • Ajoutez à vos entités tous les champs @Transient @Autowired dont vous avez besoin
  • Créez un DAO @Repository avec ce champ auto-câblé: @Autowired private AutowireCapableBeanFactory autowirer;
  • Après avoir récupéré l'entité à partir de la base de données, appelez ce code de transfert automatique à partir de votre DAO: String beanName = fetchedEntity.getClass().getSimpleName(); autowirer.autowireBean(fetchedEntity); fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);

Votre entité pourra alors accéder aux champs câblés automatiquement comme n'importe quel @Component.

0
xtian