坚持拥有关系的实体而不首先获得关系的另一方

时间:2014-01-27 12:21:20

标签: java jpa

从实体拥有与他人关系的模型开始 实体。第二个实体限制可以在属性上设置的值 它映射了这种关系。让我用一个例子更好地说明这一点。

+----------+
|   Box    |              +------------+
+----------+              |    BoxType |
|PK Box    |              +------------+
|FK BoxType|  * -----> 1  | PK BoxType |
+----------+              +------------+

这些是相应的实体(实际上,实际的实现有所不同 从这个例子中可以看出,我认为这对我来说是必要的 我想要的是什么。

@Entity @Table(name = "Box") public class Box {
  @Id @Column(name = "Box") private Integer box;
  @ManyToOne @JoinColumn(
    name = "BoxType",
    referencedColumnName = "BoxType"
  ) private BoxType boxType;
  // getters, setters
}

@Entity @Table(name = "BoxType") public class BoxType {
  public static enum BoxTypeEnum {
    SMALL, AVERAGE, BIG;
  }
  @Id @Column(name = "BoxType") private String boxType;
  public BoxTypeEnum getBoxType() {
    return BoxTypeEnum.valueOf(boxType);
  }
  public void setBoxType(BoxTypeEnum boxType) {
    this.boxType = boxType.name();
  }
}

SMALLAVERAGEBIG已保存在BoxType中 应用程序运行前的表。让我们假设OpenJPA用于 持久性提供者。如果尝试持久化Box实例 返回异常,显示boxType属性保持不受管理 BoxType对象。

<openjpa-2.3.0-nonfinal-1540826-r422266:1542644 nonfatal user error>
org.apache.openjpa.persistence.InvalidStateException:
Encountered unmanaged object "BoxType@bdb3dc" in life cycle state
unmanaged while cascading persistence via field "Box.boxType" during
flush. However, this field does not allow cascade persist. You cannot
flush unmanaged objects or graphs that have persistent associations to
unmanaged objects.
Suggested actions:

  a) Set the cascade attribute for this field to CascadeType.PERSIST or
  CascadeType.ALL (JPA annotations) or "persist" or "all" (JPA
  orm.xml),
  b) enable cascade-persist globally,
  c) manually persist the related field value prior to flushing.
  d) if the reference belongs to another context, allow reference to it
  by setting StoreContext.setAllowReferenceToSiblingContext().

如果关系标记为级联,则相反,例外情况是 返回,显示应用程序正在尝试的BoxType实例 数据库中已存在持久性。

@ManyToOne @JoinColumn(
  name = "BoxType",
  referencedColumnName = "BoxType",
  cascade = CascadeType.PERSIST
) private BoxType boxType;

例外。

<openjpa-2.3.0-nonfinal-1540826-r422266:1542644 nonfatal store error>
org.apache.openjpa.persistence.EntityExistsException:
An object of type "BoxType" with oid "SMALL" already exists in this
context; another cannot be persisted.

就我而言,我有两种选择:

  1. 获取托管BoxType并更新Box上的参考。这个选项 需要对数据库进行额外查询。
  2. box.setBoxType(entityManager.find(
      BoxType.class, box.getBoxType().getBoxType().name()))
    entityManager.persist(box);
    
    1. 使用JDBC和SQL在数据库中插入新寄存器。
    2. PeparedStatement preparedStatement =
        connection.prepareStatement(
          "INSERT INTO Box(Box, BoxType) VALUES (?, ?);");
      preparedStatement.setInt(1, box.getBox());
      preparedStatement.setString(2, box.getBoxType().getBoxType().name());
      preparedStatement.execute();
      

      目前我总是实施第一个选项,除此之外 效率低下。我总是将类似BoxType的实体标记为@Cacheable跳跃它 将阻止应用程序进行额外查询。

      我的问题是:¿有没有办法在不使用JPA的情况下持久保存实体 得到关系的另一面?,¿有没有其他方法 在这种情况下坚持一个实体?,¿@Cacheable确实阻止了 额外的查询?,我错过了什么吗?

      StackOverflow上有相关问题(this one 是一个很好的例子)但是他们似乎都没有关注同一个问题。


      编辑:

      我确认缓存BoxType会阻止其他SELECT查询。我有 带BoxType的注释@Cacheable(true)并添加 <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>到 持久性单位。接下来我发布一个日志部分,显示第一次a 某些类型的Box持久存在相应的BoxType实例 合并,而第二次只执行INSERT

      84485  boxPU  TRACE  [http-bio-8080-exec-3] openjpa.jdbc.SQL - <t
      31743778, conn 30174353> executing prepstmnt 604724 
      SELECT t0.BOX_TYPE 
          FROM BOX_TYPE t0 
          WHERE t0.BOX_TYPE = ? 
      [params=?]
      84765  boxPU  TRACE  [http-bio-8080-exec-3] openjpa.jdbc.SQL - <t
      31743778, conn 30174353> [280 ms] spent
      84846  boxPU  TRACE  [http-bio-8080-exec-3] openjpa.jdbc.SQL - <t
      31743778, conn 30174353> executing prepstmnt 1210409 
      INSERT INTO BOX (BOX, BOX_TYPE) 
          VALUES (?, ?) 
      [params=?, ?]
      84874  boxPU  TRACE  [http-bio-8080-exec-3] openjpa.jdbc.SQL - <t
      31743778, conn 30174353> [28 ms] spent
      165033  boxPU  TRACE  [http-bio-8080-exec-5] openjpa.jdbc.SQL - <t
      20406142, conn 3709916> executing prepstmnt 25081694 
      INSERT INTO BOX (BOX, BOX_TYPE) 
          VALUES (?, ?) 
      [params=?, ?]
      165034  boxPU  TRACE  [http-bio-8080-exec-5] openjpa.jdbc.SQL - <t
      20406142, conn 3709916> [1 ms] spent
      

1 个答案:

答案 0 :(得分:1)

不是你的问题的答案,但可能会解决它:

不要使用类似实体的枚举。在表中持久化枚举值并使用此表没有任何附加值。你可以简单地使用枚举本身。您可以删除BoxType课程,只保留BoxTypeEnum,然后您可以将其重命名为BoxType。你的Box类看起来像这样:

@Enumerated(EnumType.STRING)
private BoxType boxType;

或不重命名:

@Enumerated(EnumType.STRING)
private BoxTypeEnum boxType;

编辑:如果您希望保持实体不变,并且能够保留新的Box,则必须首先合并BoxType实例并将合并后的实例附加到框中。由于它已经设置了id,因此持久性提供程序假定它是一个已经存在的实例。这就是持续级联失败的原因。你可能会这样解决它:

BoxType mergedAverageBoxType = em.merge(averageBoxType);  
box.setBoxType(mergedAverageBoxType);
em.persist(box);

然而,请再次考虑BoxType类的设计。如果要通过将框架类型映射到表格来使外部框架类型可扩展,请不要使用enum,因为您不能拥有BoxType id的{​​{1}}实例。列于enum

但是,如果您只想从源代码中创建可扩展的框类型列表,则表格是多余的。