Java JPA/Hibernate实体类和同步的最佳实践是什么?

Java JPA/Hibernate实体类和同步的最佳实践是什么?,java,multithreading,hibernate,jpa,Java,Multithreading,Hibernate,Jpa,我看到的大多数JPA/Hibernate实体bean类的示例似乎都没有显式同步。然而,在构建事务的上下文中,可以对这些对象调用getter/setter。这些方法可以跨多个线程调用(尽管这可能是不寻常和奇怪的) 看起来,如果它是跨多个线程构建的,那么对对象状态的更改可能会丢失,这将是令人伤心的 那么,是否遗漏了同步最佳实践?Hibernate插入指令的代码是否为我提供了适当的同步 例如: @Entity public class Ninja { @Id @GeneratedValue p

我看到的大多数JPA/Hibernate实体bean类的示例似乎都没有显式同步。然而,在构建事务的上下文中,可以对这些对象调用getter/setter。这些方法可以跨多个线程调用(尽管这可能是不寻常和奇怪的)

看起来,如果它是跨多个线程构建的,那么对对象状态的更改可能会丢失,这将是令人伤心的

那么,是否遗漏了同步最佳实践?Hibernate插入指令的代码是否为我提供了适当的同步

例如:

@Entity
public class Ninja {
  @Id @GeneratedValue
  private Long id;

  @Column 
  private String name;

  @Column
  private int throwingStars;

  public Ninja() {}
  public int getThrowingStars() { return throwingStars; } 
  public void addThrowingStar() { throwingStars += 1; }
}
投掷星星的方法需要同步吗?我当然不希望我的忍者失去任何投掷星星。

“而且这些方法可以跨多个线程调用(尽管这可能是不寻常和奇怪的)。”

我只能代表hibernate发言,因为我对JPA没有太多经验。 调用set的对象并不总是调用get的同一个对象。当Hibernate为您加载对象时,它会调用set*方法,然后您(和您的线程)会调用get


同样,当您(即,您的写入线程)修改现有对象并再次保存它时,您需要保护对该对象的访问(以便其他读/写线程不会读取脏数据)。

这两个线程的对象可能不同。假设线程使用会话工厂访问数据库,那么从会话中获取的对象应该被视为“独立的”(我相信hibernate会为每个get()创建“新的”对象,除非它们在会话中)

至于stars问题,当两个人从DB中获取同一行时也是一样的,DB的“ACID”属性将确保每个操作都是原子的,因此如果在线程t1和提交中从忍者中删除一个star,线程t2将读取t1的提交值


或者,您可以要求hibernate锁定T1中相关忍者的行,这样即使T2请求该行,它也必须等待T1提交或中止。

JPA/hibernate实体是POJO。Hibernate和任何JPA提供程序都不会更改运行时语义

因此,如果您对一个简单的POJO有并发问题,那么您的实体也会有并发问题

在我所看到的所有系统中,域模型不是threadsave,实体实例不被多个线程访问


但是,您可以同时拥有相同实体的多个实例。在这种情况下,通过数据库进行同步。这里的模式是乐观锁定和悲观锁定。Hibernate和JPA可以帮助您实现这些模式。

解决问题的最佳实践是乐观锁定:

来自Java持久性API(JPA)规范(第3.4.1章):

乐观锁定是一种 用于确保对 对应于 实体的状态仅在 未更新任何介入交易 该实体状态的数据自 实体状态已被读取。这 确保更新或删除到 这些数据与数据是一致的 数据库的当前状态以及 干预更新不会丢失


您需要在类中添加@Version注释,并在DB表中添加一列。

在我看来,您不应该跨线程共享域对象。事实上,我通常很少在线程之间共享,因为这样的数据必须得到保护。我已经建立了一些大型/高性能系统,从来没有违反过这条规则。如果您需要并行化工作,那么就这样做,但不要通过共享域对象的实例。每个线程都应该从数据库中读取数据,通过改变/创建对象对其进行操作,然后提交/回滚事务。工作传入/传出通常应该是值/只读对象。

那么您希望在后面的案例中看到实体类中的同步?或者您希望看到应用程序协调的更高级别的同步吗?“[…]对对象状态的更改将丢失,这将是悲哀的。”