framework - spring mvc español
¿Cómo se usa Spring Data JPA fuera de Spring Container? (2)
Estoy tratando de conectar manualmente los objetos JPA de Spring Data para que pueda generar proxies DAO (también conocidos como Repositorios), sin utilizar un contenedor Spring bean.
Inevitablemente, se me preguntará por qué quiero hacer esto: es porque nuestro proyecto ya está usando Google Guice (y en la interfaz de usuario usando Gin con GWT), y no queremos mantener otra configuración de contenedor IoC, ni acceder a ella. todas las dependencias resultantes. Sé que podríamos usar la SpringIntegration
de SpringIntegration
de Guice, pero este sería el último recurso.
Parece que todo está disponible para cablear los objetos manualmente, pero como no está bien documentado, estoy teniendo un momento difícil.
De acuerdo con la guía del usuario de Spring Data, es posible usar fábricas de repositorios independientes . Desafortunadamente, el ejemplo muestra RepositoryFactorySupport
que es una clase abstracta. Después de buscar, logré encontrar JpaRepositoryFactory
JpaRepositoryFactory
realidad funciona bastante bien, excepto que no crea automáticamente transacciones. Las transacciones se deben administrar manualmente, o no se mantendrá nada en la base de datos:
entityManager.getTransaction().begin();
repositoryInstance.save(someJpaObject);
entityManager.getTransaction().commit();
El problema resultó ser que @Transactional
anotaciones @Transactional
no se usan automáticamente y necesitan la ayuda de un TransactionInterceptor
Afortunadamente, el JpaRepositoryFactory
puede tomar una devolución de llamada para agregar más consejos AOP al proxy Repository generado antes de regresar:
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()));
}
});
Aquí es donde las cosas no funcionan tan bien. Al pasar por el depurador en el código, TransactionInterceptor
está efectivamente creando una transacción, pero en el EntityManager
equivocado. Spring administra el EntityManager
activo al observar el hilo que se está ejecutando actualmente. El TransactionInterceptor
hace esto y ve que no hay ningún EntityManager
activo vinculado al hilo, y decide crear uno nuevo.
Sin embargo, este nuevo EntityManager
no es la misma instancia que se creó y pasó al constructor JpaRepositoryFactory
, que requiere un EntityManager
. La pregunta es, ¿cómo hago que TransactionInterceptor
y JpaRepositoryFactory
usen el mismo EntityManager
?
Actualizar:
Mientras escribía esto, descubrí cómo resolver el problema, pero aún puede no ser la solución ideal. Publicaré esta solución como una respuesta separada. Me complacería escuchar cualquier sugerencia sobre una mejor manera de utilizar Spring Data JPA de forma independiente de cómo lo he resuelto.
El principio general detrás del diseño de JpaRepositoryFactory
y la integración Spring Spring JpaRepositoryFactory
bean es el siguiente:
Estamos asumiendo que ejecuta su aplicación dentro de un entorno administrado de tiempo de ejecución JPA, sin importar cuál.
Esa es la razón por la que confiamos en EntityManager
inyectado en lugar de en EntityManagerFactory
. Por definición, el EntityManager
no es seguro para subprocesos. Por lo tanto, si se tratara directamente con una EntityManagerFactory
tendríamos que reescribir todo el código de administración de recursos que un entorno de tiempo de ejecución administrado (como Spring o EJB) le proporcionaría.
Para integrarse con la gestión de transacciones de Spring, SharedEntityManagerCreator
de Spring que realmente hace la magia de enlace de recursos de transacciones que ha implementado manualmente. Entonces, probablemente quiera usar ese para crear instancias de EntityManager
desde su EntityManagerFactory
. Si desea activar directamente la transaccionalidad en los beans del repositorio (para que una llamada a p.ej. repo.save(…)
cree una transacción si ninguna está ya activa) eche un vistazo a la implementación de TransactionalRepositoryProxyPostProcessor
en Spring Data Commons. Activa las transacciones cuando los repositorios Spring Data se usan directamente (p. Ej., Para repo.save(…)
) y personaliza levemente la búsqueda de configuración de transacción para preferir las interfaces sobre las clases de implementación para permitir que las interfaces del repositorio anulen la configuración de transacción definida en SimpleJpaRepository
.
Lo resolví vinculando manualmente EntityManager
y EntityManagerFactory
al subproceso que se ejecuta, antes de crear repositorios con JpaRepositoryFactory
. Esto se logra utilizando el método 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());
}
Sin embargo, debe haber una mejor manera. Parece extraño que RepositoryFactory se diseñó para usar EnitiyManager
lugar de EntityManagerFactory
. Esperaría, que primero vería si un EntityManger
está vinculado al hilo y luego crea uno nuevo y lo vincula, o usa uno existente.
Básicamente, me gustaría inyectar los servidores proxy del repositorio, y espero que en cada llamada creen internamente un nuevo EntityManager
, para que las llamadas sean seguras para hilos.