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

Как вы используете Spring Data JPA вне контейнера Spring?

Я пытаюсь подключить объекты Spring Data JPA вручную, чтобы я мог создавать прокси DAO (aka Repositories) - без использования контейнера Spring bean.

Неизбежно, меня спросят, почему я хочу это сделать: это потому, что в нашем проекте уже используется Google Guice (и в пользовательском интерфейсе с использованием Gin с GWT), и мы не хотим поддерживать другую конфигурацию контейнера IoC, или тянуть все результирующие зависимости. Я знаю, что мы могли бы использовать Guice SpringIntegration, но это было бы последним средством.

Кажется, что все доступно для проводки объектов вручную, но поскольку оно не задокументировано, у меня трудное время.

В соответствии с руководством пользователя Spring Data, использование хранилищ заводов автономно. К сожалению, на примере показан RepositoryFactorySupport, который является абстрактным классом. После некоторых поисков мне удалось найти JpaRepositoryFactory

JpaRepositoryFactory действительно работает достаточно хорошо, за исключением того, что он автоматически не создает транзакции. Транзакции должны управляться вручную или ничто не будет сохраняться в базе данных:

entityManager.getTransaction().begin();
repositoryInstance.save(someJpaObject);
entityManager.getTransaction().commit();

Проблема заключалась в том, что аннотации @Transactional не используются автоматически и нуждаются в помощи a TransactionInterceptor

К счастью, JpaRepositoryFactory может выполнить обратный вызов для добавления дополнительных советов AOP к сгенерированному прокси-серверу Repository перед возвратом:

final JpaTransactionManager xactManager = new JpaTransactionManager(emf);
final JpaRepositoryFactory factory = new JpaRepositoryFactory(emf.createEntityManager());

factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
    @Override
    public void postProcess(ProxyFactory factory) {
        factory.addAdvice(new TransactionInterceptor(xactManager, new AnnotationTransactionAttributeSource()));
    }
});

Здесь все не так хорошо работает. Пройдя через отладчик в коде, TransactionInterceptor действительно создает транзакцию, но не с ошибкой EntityManager. Spring управляет активным EntityManager, просматривая текущий исполняемый поток. TransactionInterceptor делает это и видит, что нет активного EntityManager, связанного с потоком, и решает создать новый.

Однако этот новый EntityManager не тот экземпляр, который был создан и передан в конструктор JpaRepositoryFactory, для которого требуется EntityManager. Вопрос в том, как сделать TransactionInterceptor и JpaRepositoryFactory использовать те же EntityManager?

Обновление:

При написании этого вопроса я выяснил, как решить проблему, но это все еще может быть не идеальным решением. Я отправлю это решение в виде отдельного ответа. Я был бы рад услышать любые предложения по более эффективному использованию Spring Data JPA отдельно, чем то, как я его разрешаю.

4b9b3361

Ответ 1

Общий принцип построения JpaRepositoryFactory и соответствующего Spring интеграции JpaRepositoryFactory bean заключается в следующем:

Мы предполагаем, что вы запускаете свое приложение в управляемой среде выполнения JPA, не заботясь о том, какой из них.

В этой причине мы полагаемся на инъекцию EntityManager, а не на EntityManagerFactory. По определению EntityManager не является потокобезопасным. Поэтому, если бы мы имели дело с EntityManagerFactory напрямую, нам пришлось бы переписать весь код управления ресурсами, чтобы обеспечила вам управляемая среда выполнения (как и Spring или EJB).

Чтобы интегрироваться с управлением транзакциями Spring, мы используем Spring SharedEntityManagerCreator, который фактически использует магию привязки ресурсов транзакций, которую вы внедрили вручную. Поэтому вы, вероятно, захотите использовать этот способ для создания экземпляров EntityManager из вашего EntityManagerFactory. Если вы хотите напрямую активировать транзакцию в репозитории beans (так что вызов, например, repo.save(…) создает транзакцию, если ни один из них уже не активен), посмотрите на реализацию TransactionalRepositoryProxyPostProcessor в Spring Data Commons. Он фактически активирует транзакции, когда Spring Репозитории данных используются напрямую (например, для repo.save(…)) и слегка настраивают поиск конфигурации транзакций, чтобы предпочесть интерфейсы по классам реализации, чтобы позволить интерфейсам репозитория переопределять конфигурацию транзакций, определенную в SimpleJpaRepository.

Ответ 2

Я решил это, вручную привязав EntityManager и EntityManagerFactory к исполняющему потоку, прежде чем создавать репозитории с помощью JpaRepositoryFactory. Это выполняется с помощью метода TransactionSynchronizationManager.bindResource:

emf = Persistence.createEntityManagerFactory("com.foo.model", properties);
em = emf.createEntityManager();

// Create your transaction manager and RespositoryFactory
final JpaTransactionManager xactManager = new JpaTransactionManager(emf);
final JpaRepositoryFactory factory = new JpaRepositoryFactory(em);

// Make sure calls to the repository instance are intercepted for annotated transactions
factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
    @Override
    public void postProcess(ProxyFactory factory) {
        factory.addAdvice(new TransactionInterceptor(xactManager, new MatchAlwaysTransactionAttributeSource()));
    }
});

// Create your repository proxy instance
FooRepository repository = factory.getRepository(FooRepository.class);

// Bind the same EntityManger used to create the Repository to the thread
TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));

try{
    repository.save(someInstance); // Done in a transaction using 1 EntityManger
} finally {
    // Make sure to unbind when done with the repository instance
    TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
}

Однако должен быть лучший способ. Кажется странным, что RepositoryFactory был разработан для использования EnitiyManager вместо EntityManagerFactory. Я бы ожидал, что сначала он посмотрит, привязан ли EntityManger к потоку, а затем либо создает новый, либо связывает его, либо использует существующий.

В принципе, я хотел бы ввести прокси-серверы репозитория и ожидать, что при каждом вызове они внутренне создадут новый EntityManager, так что вызовы будут потокобезопасными.