【问题标题】:EntityManager.merge() does not create table recordEntityManager.merge() 不创建表记录
【发布时间】:2011-07-07 05:36:57
【问题描述】:

以下代码无法可靠地创建新记录。

TransferRecord transfer = new TransferRecord();
transfer.setTransferId(metaData.getTransferId());
transfer.setUserName(metaData.getUserId().getUserName());
transfer.setCancelled(false);
em.merge(transfer);
em.flush();

转账记录代码如下:

/***
 * Contains a record of an ongoing transfer.
 * @author anchapma
 *
 */
@Entity
@Table(name = "TransferRecord")
public class TransferRecord implements Serializable
{
    /**
     * Generated static unique ID.
     */
    private static final long serialVersionUID = -8131586518447878482L;

    /***
     * The id of the transfer.
     */
    private String transferId;

    /***
     * The name of the user who ownes the transfer.
     */
    private String username;

    /***
     * Has this transfer been cancelled?
     */
    private boolean cancelled;

    /***
     * Constructor.
     */
    public TransferRecord()
    {
        this.transferId = "";
        this.username = "";
        this.cancelled = false;
    }

    /***
     * Gets the transfer ID.
     * 
     * @return The transfer ID.
     */
    @Id
    @Column(name = "TransferID", unique = true, nullable = false, length = 50)
    public String getTransferId()
    {
        return this.transferId;
    }

    /***
     * Sets the transfer ID.
     * @param transferId The new transfer ID.
     */
    public void setTransferId(String transferId)
    {
        this.transferId = transferId;
    }

    /***
     * Gets the username.
     * 
     * @return The username.
     */
    @Column(name = "UserName", nullable = false, length = 50)
    public String getUserName()
    {
        return this.username;
    }

    /***
     * Sets the username.
     * @param username The new username.
     */
    public void setUserName(String username)
    {
        this.username = username;
    }

    /***
     * Gets whether or not the transfer has been cancelled.
     * 
     * @return True if the transfer has been cancelled.
     */
    @Column(name = "Cancelled", nullable = false, length = 50)
    public boolean getCancelled()
    {
        return this.cancelled;
    }

    /***
     * Sets whether or not the transfer has been cancelled.
     * @param cancelled True if the transfer has been cancelled.
     */
    public void setCancelled(boolean cancelled)
    {
        this.cancelled = cancelled;
    }
}

我认为正在发生的事情是记录在延迟后被添加到数据库中。我的代码使用 TransferRecord 记录的存在或不存在作为标志。因此,它需要数据立即显示在表中。

我的假设可能是正确的吗?如果是这样,有没有办法强制 em.flush() 调用等到它写出记录后再返回?

【问题讨论】:

  • 您的某些陈述相当模糊。例如,“因此需要数据立即显示在表格中”。从交易的角度来看,意义不大。 em.flush() 将导致持久化上下文被刷新到数据库;不同事务中这些更改的可见性取决于事务隔离级别。因此,说明您希望的行为是值得的。另外,“有没有办法强制 em.flush() 调用等到它写出记录后再返回?”也没有任何意义,因为它是一个阻塞调用。
  • 您使用的是哪种交易方式,检查“是否存在 TransferRecord 记录”的代码在哪里运行?
  • 代码在一个 bean 中运行,该 bean 被另一个 bean 调用。调用 bean 是无状态的,并且会执行许多秒。在 bean 的操作结束之前,数据不会显示给其他数据库用户。

标签: java hibernate jpa


【解决方案1】:

对问题的随附评论指出 -

“调用的 bean 是无状态的,并且执行了很多秒。在 bean 的操作结束之前,数据不会显示给其他数据库用户。”

此描述的行为与刷新与 EntityManager 关联的持久性上下文无关。它与事务关联的事务隔离级别有很大关系。与无状态会话 bean (SLSB) 关联的事务实际上只在从 bean 方法返回时将数据提交到数据库,因为每个 SLSB 方法可能与REQUIRED 的默认事务属性相关联(它使用现有事务或启动一个新的);最终的行为是事务通过 SLSB 方法中的回滚或通过从该方法返回时的提交终止。

此行为会影响其他客户端和事务执行的读取,因为结果结果取决于当前相关事务的隔离级别,而该隔离级别又取决于数据库连接池上指定的隔离级别。对于大多数数据库和关联的连接池,事务隔离级别恰好是READ COMMITTED,因此其他事务只能读取相关事务提交的数据(即在 SLSB 方法返回时)。在大多数情况下,这是可取的行为,因为您不希望读取未提交的数据(并且可能稍后回滚),从而导致脏读的可能性。

如果您确实打算执行脏读,则必须将 JDBC 连接池配置为允许脏读,或者换句话说,将池的事务隔离级别设置为 READ UNCOMMITTED。配置更改会因容器而异,并且还会受到数据库对READ UNCOMMITTED 隔离级别的支持;例如,Oracle 不允许您将隔离级别设置为 READ UNCOMMITTED,并且可以将其设置为它支持的下一个更高的隔离级别 (READ COMMITTED)。

如果您想避免混淆事务隔离级别,请考虑将方法调用拆分为多个事务。您可以通过在单独的 SLSB 中创建一个新的业务方法来实现这一点,该方法需要在每次调用时创建一个新事务 (REQUIRES_NEW)。伪代码示例如下:

@Stateless
@Local(X.class)
public class SLSBX implements X {

    @EJB Y y;

    @TransactionAttribute(TransactionAttributeType.REQUIRED) // start a new transaction to do work
    public void sampleMethod() {
         // suspends the existing transaction on invoking Y.anotherSampleMethod()
         Y.anotherSampleMethod();
         // continue doing work in the current transaction. Work done in anotherSampleMethod() would have either committed or rolled back.
    }
}

@Stateless
@Local(Y.class)
public class SLSBY implements Y {
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) // Suspends the existing transaction and creates a new transaction that terminates on method return
    public void anotherSampleMethod() {
        // do some stuff
    }
}

当然,仅当交易的业务性质允许采用这种方法时,才推荐这种方法。通常,必须将作为业务交易一部分的所有业务活动范围纳入实际交易。

【讨论】:

    猜你喜欢
    • 2019-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多