【问题标题】:How to coordinate J2EE and Java EE database access?如何协调 J2EE 和 Java EE 数据库访问?
【发布时间】:2014-06-30 07:55:19
【问题描述】:

我们有一个相当庞大的应用程序,它始于十年前,并且仍在积极开发中。所以有些部分仍然在 J2EE 1.4 架构中,其他部分使用 Java EE 5/6。

在测试一些新代码时,我意识到通过新旧代码部分传入的信息之间存在数据不一致,其中旧代码直接使用 Hibernate 会话,而新代码使用注入的 EntityManager。这导致了一个问题,一个部分看不到另一部分的新数据,因此也创建了一个数据库记录,导致违反主键约束。

计划完全迁移旧代码以摆脱 J2EE,但与此同时 - 我可以做些什么来协调两个部分之间的数据库访问?无论是通过 JPA 访问还是直接访问,在应用程序服务器中的某个时间点,这两种方式不应该在 Hibernate 层中结合在一起吗?

【问题讨论】:

  • 我建议您引入数据库锁定,而不是会话事务级别隔离,因为它们由不同的连接管理。 @Version 之类的东西可能会有所帮助
  • 嗯,我想知道在这种情况下,答案是否是使用分布式事务而不是锁定。它有点含糊,但我认为问题在于“旧代码”和“新代码”在隔离事务中不必要地工作,实际上应该在同一个事务中工作。
  • @Gimby:澄清一下,新旧部分中传入和处理的信息是分开的用例——我只需要一个共同的持久性上下文,以便两个部分看到相同的数据。
  • 如果你有一个集中的方式在旧代码中获取会话,你可以考虑简单地修改它并从EntityManager(或SessionFactoryEntityManagerFactory中提取休眠会话) )。
  • 您使用的是 JavaEE 标准持久性吗?您的应用程序部署是否包含它自己的 Hibernate 副本?您是否在 Hibernate 配置中使用 JTA 事务管理器?如何管理事务分界?您是否为此目的使用(可能是无状态的)会话 bean?

标签: java database hibernate jakarta-ee jpa


【解决方案1】:

您可以在同一个应用程序中混合使用 Hibernate Session 和 Entity Manager 而不会出现任何问题。 EntityManagerImpl 只是委托调用私有的SessionImpl 实例。

您描述的是事务配置异常。每个数据库事务都是独立运行的(除非您使用 REAN_UNCOMMITED,我猜不是这样),但是一旦您提交它,更改就可以从任何其他事务或连接获得。因此,一旦提交了事务,您应该会看到任何其他 Hibernate 会话、JDBC 连接甚至您的数据库 UI 管理器工具中的所有更改。

你说有一个主键冲突。如果您使用 Hibernate 标识或序列生成器,则不会发生这种情况。对于old hi-lo generator,如果外部连接尝试在同一个表中插入记录,Hibernate 使用旧的 hi/lo 标识符生成器,​​您可能会遇到问题。

如果存在主/主复制异常,也会出现此问题。如果您有多个节点并且没有严格的一致性复制,您最终可能会违反主键约束。

更新

解决方案 1:

当协调新旧代码尝试插入相同的实体时,您可以在 SERIALIZABLE 事务中运行 slect-than-insert 逻辑。 SERIALIZABLE 事务代表游览获取适当的锁,因此您仍然可以拥有默认的 READ_COMMITTED 隔离级别,而只有有问题的 Service 方法被标记为 SERIALIZABLE。

所以旧代码和新代码都有这个逻辑运行一个选择来检查是否已经有一行满足选择约束,只有在没有找到时才插入它。 SERIALIZABLE 隔离级别防止幻读,所以我认为它应该防止违反约束。

解决方案 2:

如果您愿意将此任务委托给 JDBC,如果您当前的数据库支持,您也可以调查MERGE SQL statement。基本上,这是一个在幕后发出更新或插入的 upsert 操作。这个命令更有吸引力,因为即使在 READ_COMMITTED 上你仍然可以运行它。唯一的缺点是不能使用 Hibernate,只有部分数据库支持。

【讨论】:

  • 你是对的,一旦它提交,但我认为问题在于,两个不同的 ORM 组件中的每一个都检查具有给定 id 的现有记录同时,由于没有找到任何记录,因此尝试插入新记录,这会导致其中一个违反 PK。
  • 你用的是什么标识符生成器?
  • 我在实体(Java EE)中使用它:@GenericGenerator(name="abc", strategy="sequence", parameters = {@Parameter(name="sequence", value="xyz")}) @GeneratedValue(strategy = GenerationType.AUTO, generator = "abc")
  • 这在.hbm.xml 映射文件(J2EE)中:<id name="id" type="long"> <column name="id" /> <generator class="sequence"> <param name="sequence">xyz</param> </generator> </id>
  • 因为你使用GenericGenerator,你实际上可以指向实际的Hibernate标识符生成器。 “序列”生成器总是调用数据库以获取新的身份值,因此除非您有多个节点并且复制失败,否则永远不会发生冲突。数据库序列可确保值的唯一性,因此对您不利的不是 Hibernate。
【解决方案2】:

如果您分别为旧代码实例化 SessionFactory 和为新代码实例化 EntityManagerFactory,这可能会导致一级缓存中的值不同。如果在单个 Http 请求期间,您更改了旧代码中的值,但没有立即提交,则该值将在会话缓存中更改,但在提交之前将无法用于新代码。独立于任何可以保护 持久 值的事务或数据库锁定,两个不同的 Hibernate 会话的混合可能会给内存值带来奇怪的东西。

我承认注入的 EntityManager 仍然使用 Hibernate。恕我直言,最强大的解决方案是为PersistenceUnit 获取EntityManagerFactory 并将其转换为休眠EntityManagerFactoryImpl。然后就可以直接访问底层的SessionFactory

SessionFactory sessionFactory = entityManagerFactory.getSessionFactory();

然后您可以在旧代码中安全地使用此 SessionFactory,因为现在它在您的应用程序中是唯一的,并且在新旧代码之间共享。

您仍然需要处理会话创建关闭和事务管理的问题。我想它已经用旧代码实现了。在不了解更多信息的情况下,我认为您应该将其移植到 JPA,因为我很确定如果存在 EntityManagersessionFactory.getCurrentSession() 将提供其底层 Session,但我不能肯定相反的任何事情。

【讨论】:

  • 我从所有 cmets 那里得到了一些很好的见解,但我对这个提案的评价最高,并将了解如何使用它。正如我所说,未来的计划是完全摆脱 J2EE 部分并直接使用 Hibernate,但同时它可能会有所帮助。
【解决方案3】:

当我有一个枚举查找值列表时,我遇到了类似的问题,其中两段代码将检查列表中是否存在给定值,如果不存在,代码将创建数据库中的新条目。当他们俩遇到相同的不存在的值时,他们都会尝试创建一个新的值,并且其中一个会回滚其事务(丢弃我们在事务中完成的一堆其他工作)。

我们的解决方案是在立即提交的单独事务中创建这些查找值;如果该事务成功,那么我们知道我们可以使用该对象,如果它失败,那么我们知道我们只需要执行get 来检索另一个进程保存的那个。一旦我们有了一个我们知道可以在会话中安全使用的查找对象,我们就可以愉快地进行其余的数据库修改,而不会冒事务被回滚的风险。

从您的描述中很难知道您的数据模型是否适合类似的方法,在这种方法中您至少会立即提交实体的初始版本,然后一旦您确定正在使用一个持久对象,您可以执行您知道需要执行的其余 DB 修改。但是,如果您能找到一种方法来实现这一点,它将避免在不同代码段之间共享 Session 的需要(并且即使旧代码和新代码在不同的 JVM 中运行也可以工作)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-18
    • 2018-02-11
    • 2022-01-21
    • 2016-06-30
    相关资源
    最近更新 更多