JPA动态持久化单元名称

16

我需要一种动态指定EJB持久化单元的方法。

简单示例:

我有一个应用程序,使用多个数据库作为数据存储库。每个数据存储都具有相同的结构。根据连接到应用程序的客户端,我需要访问特定数据存储中的数据。

因此,我想使用相同的EJB,以便不会重复业务逻辑,但是根据客户端选择正确的持久化单元。

到目前为止,我只直接注入了实体管理器,并硬编码了持久化单元名称。是否有一种方法可以将所需的持久化单元动态地注入到EJB中的实体管理器中?另外,可以在运行时动态添加持久化单元吗?我目前必须在persistence.xml文件中指定持久化单元。理想情况下,当系统正在运行时,我想在服务器jdbc/db1,jdbc/db2等上创建池。然后仅将这些添加到中央客户端数据库并将其链接到客户端,以便客户端连接时,它将检查池的名称,并在调用EJB以获取持久化单元时使用它。

我对Java EE开发还非常新。任何帮助将不胜感激。

4个回答

9
在当前的JPA版本中,很遗憾无法动态创建持久性单元。如果这个功能对您很重要,您可以考虑在JPA问题跟踪器上为其创建一个JIRA问题:http://java.net/jira/browse/JPA_SPEC 使用 @PersistenceContext 注释也不能动态选择特定的持久单元。这实际上是分片的领域,Hibernate曾试图解决这个问题,但后来突然取消了。请参见http://www.hibernate.org/subprojects/shards.html 然而,有一些事情可以做到类似的效果。
其中一种方法是创建一个无状态的EJB / CDI工厂bean,您可以使用它注入所有实体管理器。这样做的成本很小,因为那些bean将被池化,而且实体管理器在第一次创建时不太昂贵。
如果您还想根据某些条件进行注入,则必须从上下文中使用此条件或在注入点指定该条件(但如果您那样做,您可以直接注入正确的实体管理器)。
一个启动示例:
@Stateless
@TransactionAttribute(SUPPORTS)
public class ShardingEntityManagerFactory {

    @Resource
    private SessionContext sessionContext;

    @PersistenceContext(unitName = "pu1")
    private EntityManager entityManager1;

    @PersistenceContext(unitName = "pu2")
    private EntityManager entityManager2;

    @Produces @TransactionScoped @ShardedPersistenceContext
    public EntityManager getEntityManager() {
        if (sessionContext.isCallerInRole("FOO")) {
            return entityManager1;
        } else {
            return entityManager2;
        }
    }
}

接下来在你的Beans中:

@Stateless
public class SomeBean {

    @Inject @ShardedPersistenceContext
    private EntityManager entityManager;

    // ...
}

注意,您需要在这里使用Seam Persistence来使用@TransactionScoped注释。忘记透明地注入实体管理器并代之以注入ShardingEntityManagerFactory并手动获取正确的实体管理器可能更容易,但会稍微冗长一些。

这几乎是我所需要的,但我仍然需要预先定义每个持久化单元到SharedEntityManagerFactory中。 我希望能够动态添加它们,以便不需要进行代码更改和重新部署。我只需将持久化单元详细信息保存在主数据库中,并在需要时将其链接到客户端。但这已经比将每个数据存储实现为单独的Web服务要好得多。开销少得多。 Web服务的附加好处是能够将URL存储在数据库中并动态使用它。我必须权衡各种选择。 - likenoother

3

我认为这与多租户技术并不相距,但是在多租户技术中,一个单一的数据库被分成相等的份额并在多个租户之间共享,而在这里,一个单一的租户想要多个数据库看起来像是一个单一的数据库。 - Arjan Tijms
@ArjanTijms是正确的。我想要的类似于多租户,但不是使用一个数据库,而是想使用多个数据库。也许值得将每个数据库分别部署到单独的EJB中,然后通过Web服务连接它们。我不喜欢这种开销,但至少它给了我在需要时支持不同系统上的数据库的自由。将客户端存储在不同的机器上。不确定是否可以使用远程接口实现? - likenoother
@ArjanTijms 我该如何查找特定 EJB 模块的部署?我只注入过它们。我了解本地和远程接口,但据我所知,它们与特定的 EJB 部署紧密耦合。 - likenoother
我想我已经弄清楚了如何通过JDNI查找EJB。感谢@ArjanTijms的建议。将尝试在服务器上查找已部署的EJB应用程序的JDNI。 - likenoother
@(Arjan Tijms)。是的,但这种自动分配的名称不允许同一Bean类/包名称具有不同的明确名称,而这是实现在每个Facade Bean(针对每个不同的数据存储)中硬编码不同@PersistenceContext值的模式所必需的,并且前端代码可以根据JNDI查找动态查找正确的Facade Bean来使用,而不是注入。 - Marcus Junius Brutus
显示剩余3条评论

2

您可以使用同一个持久化单元来实现此功能。只需在调用createEntityManagerFactory()时提供一个属性映射,其中包含您想要使用的url /数据源即可。


好主意,但在Java EE环境中行不通吧?您可以注入一个em工厂,但create方法仅适用于Java SE环境。 - Mike Braun

1

仅是一种想法,不确定是否有帮助: 您可以打开一个 inputStream 来读取 persistence.xml 文件并替换这些行:

<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/agendasccv2?zeroDateTimeBehavior=convertToNull"/>
<property name="javax.persistence.jdbc.password" value="btxbtxbtx"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>

然后展示一个连接配置屏幕,用户登录并根据其在该文件上的权限设置连接,然后启动主应用程序。

不确定这是否有帮助,这只是一个想法。取决于您的应用程序和业务逻辑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接