hibernate:替换对象(onetoone)

42fyovps  于 2021-07-03  发布在  Java
关注(0)|答案(2)|浏览(364)

我有一个母公司 Stock 它有一个子实体 StockDetails 一对一的关系。我不知道如何正确设置和替换 Stock.details 现场。
以下是我的实体类(@getter/@setter来自lombok):

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "stocks")
public class Stock
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  private String isin;
  private String symbol;
  private String name;

  @OneToMany(mappedBy = "stock", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
  private List<StockChartPoint> chart;

  @OneToOne(mappedBy = "stock", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
  private StockDetails details;

  public void setDetails(StockDetails d)
  {
    details = d;
    details.setStock(this);
  }

}

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "stock_details")
public class StockDetails
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  @OneToOne
  @JoinColumn(name = "stock")
  private Stock stock;
}

当details表为空时,在db中插入一个新值(和相应的行)就可以正常工作,我可以看到hibernate只记录一个intert语句。我的代码如下所示:

dbService.transactional(session ->
{
  var stocks = getAllStocks();
  var s = stocks.get(0);

  StockDetails n = new StockDetails();
  s.setDetails(n);
  session.save(s);
});

这个 DbService.transactional 方法( TransactionExecutor 只是一个功能接口):

public void transactional(TransactionExecutor executor)
{
  Transaction t = null;
  try
  {
    t = session.beginTransaction();
    executor.execute(session);
    session.flush();
    t.commit();
  }
  catch (HibernateException ex)
  {
    if (t != null)
      t.rollback();
  }
}

但当明细表中已有一行时,将删除旧行并插入新行。结果是,每次更新值时,details表的pk都在增加。有没有我找不到的模式来解决这个问题?我也可以只更新现有的 Stock.details 但是这会导致把所有的字段从对象a复制到对象b,我想有一个更聪明的方法。我试着用 EntityManager , merge()/saveOrUpdate()/persist() 而不是 save() 以及在新对象中操作id,但这导致什么也不更改或引发异常。
然后我遇到了另一个问题,我不知道是否与第一个问题有关:执行 dbService.transactional(...) 块两次它的行为不同:现在两行被添加到数据库中。sql日志如下所示:

...
Hibernate: insert into stock_details (stock) values (?)
Hibernate: delete from stock_details where id=?
-> insert/delete produces by the first run
Hibernate: select stock0_.id as id1_3_, stock0_.isin as isin2_3_, stock0_.name as name3_3_, 
stock0_.symbol as symbol4_3_ from stocks stock0_
Hibernate: insert into stock_details (stock) values (?)
-> Just insert, no delete

如果需要更多的信息,请告诉我。
mysql.mysql-connector-java>8.0.22
org.hibernate.hibernate-core>5.4.25.final
org.hibernate.hibernate-validator>5.4.3.final

o4tp2gmn

o4tp2gmn1#

但当明细表中已有一行时,旧行将被删除。。。
…这是我们所期待的 orphanRemoval = true …并插入新行
…这显然也是意料之中的,因为您正在覆盖现有的 StockDetailss 一个全新的例子 StockDetails .
如果要更新现有 StockDetails ,而不是创建新的 StockDetails 实体,你需要,好吧,用java代码来做。
我也可以只更新现有的stock.details字段,但这会导致将所有字段从对象a复制到对象b。。。
这将是最不容易出错的方法
……但我想有更聪明的方法
你可以这样做:

StockDetails n = new StockDetails();
n.setId(s.getStockDetails().getId());
... //configure the remaining properties
n.setStock(s);
entityManager.merge(n);
tuwxkamq

tuwxkamq2#

你可能已经猜到了,你实际上是在创建一个新的 StockDetails 把旧的换掉。如果要更新现有 StockDetails 您真的需要获取它并逐字段更新它(正如您所说的那样)。就像:

StockDetails sd = s.getStockDetails();
sd.setSomeFiled(updateValue);
// ... copying field by field

为了防止逐字段复制,您可以获得 StockDetails 并将可能的编辑直接放入数据库中,这样就不需要复制每个字段。
但是,如果您需要将某些dto中的值写入实体,则可以使用一些库来减轻复制的痛苦。
我只想提一个 ModelMapper . 复制过程如下:

ModelMapper mm = new ModelMapper();
StockDetails sd = s.getStocDetails();
mm.map(stockDetailsDto, sd);

哪里 stockDetailsDto 是某个dto对象,其中包含要更新到实体的字段。
如果你的 StockDetails 包含所有其他字段,甚至包括未更改的字段 em.merge 就像克里兹的回答中所说的,这是最简单的。
但是如果只得到更新的字段,那么其他字段将设置为null。有时id是不可设置的,然后合并是不可能的。

相关问题