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

+----------+
|   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.


就我现在而言,我有两个选择:


获取托管的BoxType并更新Box上的引用。这个选项
需要对数据库进行额外的查询。


box.setBoxType(entityManager.find(
  BoxType.class, box.getBoxType().getBoxType().name()))
entityManager.persist(box);



使用JDBC和SQL将新的寄存器插入数据库。


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(thisone
是一个很好的例子),但似乎没有一个人专注于同一件事。



编辑:

我确认缓存BoxType会阻止其他SELECT查询。我有
BoxType注释@Cacheable(true)并添加
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
持久性单元。接下来,我发布一个日志部分,显示第一次
某些类型的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

最佳答案

不能完全解决您的问题,但可以解决:

不要使用像实体这样的枚举。将枚举值持久保存在表中并使用该表根本没有任何附加值。您可以简单地使用枚举本身。您可以删除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中未列出的enum一起使用。 。

但是,如果只想使框类型列表可从源代码扩展,则该表是多余的。

09-26 14:30