如何将动态EntityManager注入第三方库

时间:2016-10-19 17:34:16

标签: jpa java-ee cdi

我有一个库,其中包含一些我希望在其他项目中重用的功能。我的问题是我的服务需要写入数据库。我希望我的库能够使用注入我服务的项目的数据源。

以下是我服务的最小设置

@Stateless
public class CustomService {
    //to be added in producer
    private EntityManager em;
    private Principal principal;

    //default constructor
    public CustomService() {}
    //custom constructor called in provider
    public CustomService(Principal p, EntityManager e) {
        principal = p;
        em = e;
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    @Transactional
    public CustomJPAObject createObject(...params...) {
       //create JPA Object
       em.persist(customObject);
       em.flush();
       return customObject;
    }

}

我创建了一个自定义注释来覆盖数据源

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD, ElementType.METHOD})
public @interface DynamicDS {
    @Nonbinding String value() default "";

}

我还创建了一个Singleton作为EntityManager Producer

@Singleton
public class CustomEMProducer {
    private Map<String, EntityManagerFactory> emfMap = new HashMap<>();

    @Produces @Dependent @DynamicDS
    public EntityManager produceEntityManager(InjectionPoint injectionPoint) {
        String dataSourceName = null;
        for(Annotation qualifier: injectionPoint.getQualifiers()) {
            if(qualifier instanceof DynamicDS) {
                DynamicDS dds = (DynamicDS) qualifier;
                dataSourceName = dds.value();
                break;
            }
        }
        EntityManagerFactory emf = emfMap.get(dataSourceName);
        if (emf == null) {
            emf = Persistence.createEntityManagerFactory(dataSourceName);
            emfMap.put(dataSourceName, emf);
        }
        return emf.createEntityManager();
    }

    @PostConstruct
    public void cleanup() {
        emfMap.entrySet().stream().forEach(entry -> entry.getValue().close());
    }
}

以下是我的Service Producer的代码

@Stateless
public class CustomServiceProvider {
    @Inject private Principal principal;

    @Produces @Dependent @DynamicDS
    public BackgroundJobService getBackgroundJobService(InjectionPoint injectionPoint) throws EntityManagerNotCreatedException {
        Annotation dsAnnotation = null;
        for(Annotation qualifier: injectionPoint.getQualifiers()) {
            if(qualifier instanceof BackgroundJobDS) {
                dsAnnotation = qualifier;
                break;
            }
        }
        if (dsAnnotation != null) {
            EntityManager em = CDI.current().select(EntityManager.class, dsAnnotation).get();
            CustomService service = new CustomService(principal, em);
            return service;
        }
        throw new EntityManagerNotCreatedException("Could not Produce CustomService");
    }
}

以下是我尝试注入新服务的地方

@Stateless
public class ProjectService {
    @Inject @DynamicDS("project-ds") CustomerService service;

    public CustomObject create(...params...) {
        return service.createObject(...params...);
    }
}

当我部署代码并尝试调用注入的服务时,我收到以下错误:

Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.checkTransactionNeeded(AbstractEntityManagerImpl.java:1171)
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1332)
    ...

看起来所有不同级别的提供程序都阻止了对CustomService.createObject()方法调用的@Transactional传播事务。有没有人深入了解为什么这是实现我注入动态EntityManager的目标的另一种方式?

1 个答案:

答案 0 :(得分:0)

经过多次试验,我无法通过上面的代码动态生成EntityManager。经过大量的研究,我放弃了尝试从第三部分库外传递名称。我想创建以下界面:

public interface CustomEntityManager {
    EntityManager getEntityManager();
}

这意味着在使用第三方服务的项目中我可以创建以下实现来注入EntityManager

public ProjectSpecificEntityManager implements CustomEntityManager {
    @PersistenceContext(unitname = "project-ds")
    private EntityManager em;

    public EntityManager getEntityManager() {
        return em;
    }
}

我必须将CustomService更新为以下

@Stateless
public class CustomService {
   //Ignore warning about no bean eligible because it is intended 
   //that the project that uses this library will provide the
   //implementation
   @SuppressWarnings("cdi-ambiguous-dependency")
   @Inject
   CustomEntityManager cem;

   @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   @Transactional
   public CustomJPAObject createObject(...params...) {
       //create JPA Object
       cem.getEntityManager().persist(customObject);
       return customObject;
   }
}