Подтвердить что ты не робот

Bean инъекция внутри JPA @Entity

Можно ли вставить beans в JPA @Entity с помощью инъекции зависимостей Spring?

Я попытался @Autowire ServletContext, но, когда сервер действительно начал успешно, я получил исключение NullPointerException при попытке получить доступ к свойству bean.

@Autowired
@Transient
ServletContext servletContext;
4b9b3361

Ответ 1

Вы можете вставлять зависимости в объекты, не управляемые контейнером Spring, используя @Configurable, как описано здесь: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-atconfigurable.

Как вы уже поняли, если не использовать @Configurable и соответствующую конфигурацию ткачества AspectJ, Spring не встраивает зависимости в объекты, созданные с помощью оператора new. Фактически, он не вводит зависимости в объекты, если вы не извлекли их из ApplicationContext по той простой причине, что он просто не знает об их существовании. Даже если вы аннотируете свою сущность с помощью @Component, экземпляр этого объекта все равно будет выполняться с помощью операции new, либо вами, либо средой, такой как Hibernate. Помните, что аннотации - это просто метаданные: если никто не интерпретирует эти метаданные, он не добавляет никакого поведения или не влияет на запущенную программу.

Все сказанное я настоятельно рекомендую не вводить a ServletContext в сущность. Объекты являются частью вашей модели домена и должны быть отделены от любого механизма доставки, такого как уровень веб-доставки на основе сервлета. Как вы будете использовать этот объект, когда он будет доступен клиенту командной строки или что-то еще, не связанное с ServletContext? Вы должны извлечь необходимые данные из этого ServletContext и передать его с помощью традиционных аргументов метода для своей сущности. Благодаря этому подходу вы получите гораздо лучший дизайн.

Ответ 2

Да, конечно, вы можете. Вам просто нужно убедиться, что сущность также зарегистрирована как Spring управляемая bean декларативно, используя теги <bean> (в некотором spring -context.xml) или аннотации, как показано ниже.

Используя аннотации, вы можете либо пометить свои объекты с помощью @Component (или более определенного стереотипа @Repository, который позволяет автоматически переводить исключения для DAO и может или не может мешать JPA).

@Entity
@Component
public class MyJAPEntity {

  @Autowired
  @Transient
  ServletContext servletContext;
  ...
}

После того, как вы сделали это для своих объектов, вам нужно настроить свой пакет (или некоторый пакет предков) для проверки на Spring, чтобы объекты получались как beans, и их зависимости получались автоматически.

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

EDIT: (что, наконец, сработало и почему)

  • Создание статического значения ServletContext. (удалить @Autowired)

    @Transient
    private static ServletContext servletContext;
    

Поскольку JPA создает отдельный экземпляр объекта, т.е. не использует Spring bean Spring, он должен использоваться для совместного использования контекста.

  • Добавление метода @PostConstruct init().

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

Это срабатывает init() после того, как Entity был создан, и, ссылаясь на ServletContext внутри, он принудительно вставляет статическое свойство, если оно уже не было введено.

  • Перемещение @Autowired в метод экземпляра, но установка статического поля внутри.

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

Цитата моего последнего комментария ниже, чтобы ответить, почему мы должны использовать эти махинации:

Там нет красивого способа делать то, что вам нужно, поскольку JPA не использует контейнер Spring для создания экземпляров своих объектов. Подумайте о JPA как отдельном контейнере ORM, который создает и управляет жизненным циклом сущностей (полностью отделяется от Spring) и делает DI только на основе связей с объектами.

Ответ 3

Спустя долгое время я наткнулся на этот SO ответ, который заставил меня подумать об элегантном решении:

  • Добавьте в свои сущности все поля @Transient @Autowired, которые вам нужны.
  • Создайте @Repository DAO с этим полем для автоподключения: @Autowired private AutowireCapableBeanFactory autowirer;
  • Из вашего DAO, после извлечения сущности из БД, вызовите этот код автоподключения: String beanName = fetchedEntity.getClass().getSimpleName(); autowirer.autowireBean(fetchedEntity); fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);

Затем ваша сущность сможет получить доступ к полям с автоподстановкой, как может любой @Component.