JPA EntityManager 在使用 Guice 的 PrivateModule 时不工作
JPA EntityManager not working when using Guice's PrivateModule
我有一个使用 JPA、Hibernate 和 Guice 的持久性设置的服务(如果它有用,我没有使用 Spring)。这是我的代码的第一个 工作 版本:
public class BookDao {
@Inject
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
@Transactional
public void persist(Book book) {
getEntityManager().persist(book);
}
}
public class MyAppModule extends AbstractModule {
@Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
final JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
但是现在我需要配置多个持久化单元。我正在遵循此 mailing list 中的建议,根据他们的建议,我应该将我的模块逻辑移至私有模块。我按照建议做了,并创建了相同代码的第二个版本,更改在下面评论:
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ FIELD, PARAMETER, METHOD })
public @interface ProductionDataSource {} // defined this new annotation
public class BookDao {
@Inject
@ProductionDataSource // added the annotation here
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
@Transactional
public void persist(Book book) throws Exception {
getEntityManager().persist(book);
}
}
public class MyAppModule extends PrivateModule { // module is now private
@Override
protected void configure() {
initializePersistence();
// expose the annotated entity manager
Provider<EntityManager> entityManagerProvider = binder().getProvider(EntityManager.class);
bind(EntityManager.class).annotatedWith(ProductionDataSource.class).toProvider(entityManagerProvider);
expose(EntityManager.class).annotatedWith(ProductionDataSource.class);
}
private void initializePersistence() {
JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
新注释的 EntityManager
被 Guice 正确注入并且是非空的,但这是有趣的部分:我的一些单元测试开始失败,例如:
class BookDaoTest {
private Injector injector;
private BookDao testee;
@BeforeEach
public void setup() {
injector = Guice.createInjector(new MyAppModule());
injector.injectMembers(this);
testee = injector.getInstance(BookDao.class);
}
@Test
public void testPersistBook() throws Exception {
// given
Book newBook = new Book();
assertNull(newBook.getId());
// when
newBook = testee.persist(newBook);
// then
assertNotNull(newBook.getId()); // works in the first version, fails in the second
}
}
在我的代码的第一个版本中,上面的最后一行只是 works:实体被持久化并有一个新的 id。但是,在我的代码的第二个版本中(使用 PrivateModule
并从中公开带注释的 EntityManager
) persist()
操作不再起作用,实体没有 ID。可能是什么问题呢?我没有在我的环境中进行任何其他配置更改,也没有在日志中看到错误消息。如果您需要更多详细信息,请告诉我。
原来是@Transactional
注解的问题。在我的代码的第一个版本中,Guice 自动添加拦截器来管理事务。通过调试,我发现 在 执行我的 persist(Book book)
方法之前,Guice 从 com.google.inject.internal.InterceptorStackCallback
包中调用了以下方法:
public Object intercept(Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
在我的代码的第二个版本中,当我从私有模块公开持久化单元时,不再调用上述拦截器,使我的持久化操作没有事务处理。这是 known issue 并且是设计使然。
作为一种解决方法,我不得不手动实现事务,这使我的代码更加冗长。我还必须更改注入实体管理器的方式。这个解决方案对我有用:
public class BookDao {
@Inject
@Named(PROD_PERSISTENCE_UNIT_NAME)
private EntityManagerFactory entityManagerFactory;
private EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
public void persist(Book book) throws Exception {
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.persist(book);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
throw e;
} finally {
em.close();
}
}
}
public class MyAppModule extends PrivateModule {
public static final String PROD_PERSISTENCE_UNIT_NAME = "prod";
@Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
// persistence unit set to prod DB
final JpaPersistModule jpaPersistModule = new JpaPersistModule(PROD_PERSISTENCE_UNIT_NAME);
// connection properties set to suitable prod values
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
// expose bindings to entity manager annotated as "prod"
bind(JPAInitializer.class).asEagerSingleton();
bind(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).to(PersistService.class).asEagerSingleton();
expose(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManagerFactory.class));
expose(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManager.class));
expose(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(UnitOfWork.class));
expose(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
}
}
作为一个教训,要非常注意注释和其他在后台修改代码的“魔术”,发现错误变得非常困难。
我有一个使用 JPA、Hibernate 和 Guice 的持久性设置的服务(如果它有用,我没有使用 Spring)。这是我的代码的第一个 工作 版本:
public class BookDao {
@Inject
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
@Transactional
public void persist(Book book) {
getEntityManager().persist(book);
}
}
public class MyAppModule extends AbstractModule {
@Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
final JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
但是现在我需要配置多个持久化单元。我正在遵循此 mailing list 中的建议,根据他们的建议,我应该将我的模块逻辑移至私有模块。我按照建议做了,并创建了相同代码的第二个版本,更改在下面评论:
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ FIELD, PARAMETER, METHOD })
public @interface ProductionDataSource {} // defined this new annotation
public class BookDao {
@Inject
@ProductionDataSource // added the annotation here
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
@Transactional
public void persist(Book book) throws Exception {
getEntityManager().persist(book);
}
}
public class MyAppModule extends PrivateModule { // module is now private
@Override
protected void configure() {
initializePersistence();
// expose the annotated entity manager
Provider<EntityManager> entityManagerProvider = binder().getProvider(EntityManager.class);
bind(EntityManager.class).annotatedWith(ProductionDataSource.class).toProvider(entityManagerProvider);
expose(EntityManager.class).annotatedWith(ProductionDataSource.class);
}
private void initializePersistence() {
JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
新注释的 EntityManager
被 Guice 正确注入并且是非空的,但这是有趣的部分:我的一些单元测试开始失败,例如:
class BookDaoTest {
private Injector injector;
private BookDao testee;
@BeforeEach
public void setup() {
injector = Guice.createInjector(new MyAppModule());
injector.injectMembers(this);
testee = injector.getInstance(BookDao.class);
}
@Test
public void testPersistBook() throws Exception {
// given
Book newBook = new Book();
assertNull(newBook.getId());
// when
newBook = testee.persist(newBook);
// then
assertNotNull(newBook.getId()); // works in the first version, fails in the second
}
}
在我的代码的第一个版本中,上面的最后一行只是 works:实体被持久化并有一个新的 id。但是,在我的代码的第二个版本中(使用 PrivateModule
并从中公开带注释的 EntityManager
) persist()
操作不再起作用,实体没有 ID。可能是什么问题呢?我没有在我的环境中进行任何其他配置更改,也没有在日志中看到错误消息。如果您需要更多详细信息,请告诉我。
原来是@Transactional
注解的问题。在我的代码的第一个版本中,Guice 自动添加拦截器来管理事务。通过调试,我发现 在 执行我的 persist(Book book)
方法之前,Guice 从 com.google.inject.internal.InterceptorStackCallback
包中调用了以下方法:
public Object intercept(Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
在我的代码的第二个版本中,当我从私有模块公开持久化单元时,不再调用上述拦截器,使我的持久化操作没有事务处理。这是 known issue 并且是设计使然。
作为一种解决方法,我不得不手动实现事务,这使我的代码更加冗长。我还必须更改注入实体管理器的方式。这个解决方案对我有用:
public class BookDao {
@Inject
@Named(PROD_PERSISTENCE_UNIT_NAME)
private EntityManagerFactory entityManagerFactory;
private EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
public void persist(Book book) throws Exception {
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.persist(book);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
throw e;
} finally {
em.close();
}
}
}
public class MyAppModule extends PrivateModule {
public static final String PROD_PERSISTENCE_UNIT_NAME = "prod";
@Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
// persistence unit set to prod DB
final JpaPersistModule jpaPersistModule = new JpaPersistModule(PROD_PERSISTENCE_UNIT_NAME);
// connection properties set to suitable prod values
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
// expose bindings to entity manager annotated as "prod"
bind(JPAInitializer.class).asEagerSingleton();
bind(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).to(PersistService.class).asEagerSingleton();
expose(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManagerFactory.class));
expose(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManager.class));
expose(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(UnitOfWork.class));
expose(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
}
}
作为一个教训,要非常注意注释和其他在后台修改代码的“魔术”,发现错误变得非常困难。