Как вы используете Spring Data JPA вне Spring-контейнера? - PullRequest
44 голосов
/ 03 февраля 2012

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

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

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

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

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

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

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

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

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 автономно, чем то, как я его решаю.

Ответы [ 2 ]

24 голосов
/ 05 февраля 2012

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

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

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

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

12 голосов
/ 03 февраля 2012

Я решил эту проблему, вручную связав 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, так что вызовы являются потокобезопасными.

...