引言
因此,让我们开始吧! JPA中的
版本注释是什么意思?
简而言之,她负责阻止JPA。 此注释解决了并行事务可能引起的问题之一。
会出现什么问题?
- 在并行运行的两个事务试图更新相同数据的情况下,可能会丢失更新 。
- 当一个事务看到另一个事务尚未保存的更改时,就会发生脏读 。 在这种情况下,由于第二次事务的回滚可能会出现问题,但是已经先读取了数据。
- 当第一个事务已接收到数据,而第二个事务已对其进行更改并成功提交它们,直到第一个事务结束时,就会发生不可重复的读取 。 换句话说,当在同一笔交易中,用于获取例如整个表的相同请求将返回不同的结果。
- 幻像读取与重复读取类似,但返回的行数不同。
简要介绍他们的决定
- READ UNCOMMITED-使用JPA中的Version注释解决(这正是本文)
- READ COMMITED-允许仅读取已提交的更改
- 可重复阅读-这里有点复杂。 我们的交易“看不到”它先前读取的数据的更改,其他交易无法更改属于我们的交易的数据。
- 可序列化-顺序事务执行
随后的每个段落都覆盖了前面的所有内容,换句话说,它可以替代前面指出的解决方案。 因此,SERIALIZABLE具有最高的隔离级别,而READ UNCOMMITED具有最低的隔离级别。
版本号
版本解决了
丢失更新的问题。 现在,我们将确切地看到。
在继续进行代码之前,值得一提的是,有两种类型的锁:
乐观锁和
悲观锁。 不同之处在于,前者受许多交易试图同时更改一个领域的情况的引导,这种情况很少发生,而其他交易则受相反的情况指导。 据此,它们的执行逻辑有所不同。
在
乐观锁中,当提交到数据库时,将在接收数据时和此时比较标记为version的字段的值。 如果已更改,也就是说,其他事务在我们之前,并且可以更改数据,那么在这种情况下,我们的事务将引发错误,您需要重新启动它。
使用
乐观锁时,可以确保访问数据库时具有更高的竞争力,但是在这种情况下,您必须重复没有时间进行更改的事务。
在
悲观的情况下 ,锁定是在所谓的数据修改之前立即施加在据认为会影响的所有行上。
而且,当使用
悲观锁时,由于将其余部分置于备用模式(但这需要时间),因此可以确保在事务执行过程中不会出现矛盾,从而降低了竞争水平。
LockModeType或如何设置锁
可以通过调用EntityManager的look方法来设置锁定。
entityManager.lock(myObject, LockModeType.OPTIMISTIC);
LockModeType定义锁定策略。
LockModeType可以有6种类型(其中2种是
乐观的 ,3种是
悲观的 ):
- 无-无锁
- 乐观的
- OPTIMISTIC_FORCE_INCREMENT
- PESSIMISTIC_READ
- PESSIMISTIC_WRITE
- PESSIMISTIC_FORCE_INCREMENT
创建我们的实体 import lombok.Getter; import lombok.Setter; import javax.persistence.*; @EntityListeners(OperationListenerForMyEntity.class) @Entity public class MyEntity{ @Version private long version; @Id @GeneratedValue @Getter @Setter private Integer id; @Getter @Setter private String value; @Override public String toString() { return "MyEntity{" + "id=" + id + ", version=" + version + ", value='" + value + '\'' + '}'; } }
让我们创建一个将实现所有Callback方法的类。 import javax.persistence.*; public class OperationListenerForMyEntity { @PostLoad public void postLoad(MyEntity obj) { System.out.println("Loaded operation: " + obj); } @PrePersist public void prePersist(MyEntity obj) { System.out.println("Pre-Persistiting operation: " + obj); } @PostPersist public void postPersist(MyEntity obj) { System.out.println("Post-Persist operation: " + obj); } @PreRemove public void preRemove(MyEntity obj) { System.out.println("Pre-Removing operation: " + obj); } @PostRemove public void postRemove(MyEntity obj) { System.out.println("Post-Remove operation: " + obj); } @PreUpdate public void preUpdate(MyEntity obj) { System.out.println("Pre-Updating operation: " + obj); } @PostUpdate public void postUpdate(MyEntity obj) { System.out.println("Post-Update operation: " + obj); } }
Main.java import javax.persistence.*; import java.util.concurrent.*; // , . public class Main { // , .. EntityManagerFactory , . private static EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("ru.easyjava.data.jpa.hibernate"); public static void main(String[] args) { // 10 ( , ). ExecutorService es = Executors.newFixedThreadPool(10); try { // persistFill() - . persistFill(); for(int i=0; i<10; i++){ int finalI = i; es.execute(() -> { // updateEntity(finalI) , java . java - , id, , id , ( , persistFill(), id 500). updateEntity(finalI); }); } es.shutdown(); try { es.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } finally { entityManagerFactory.close(); } } // . private static void updateEntity(int index) { // EntityManager , , . EntityManager em = entityManagerFactory.createEntityManager(); MyEntity myEntity = null; try { em.getTransaction().begin(); // 1. myEntity = em.find(MyEntity.class, 1); // sout, "" . System.out.println("load = "+index); // ( LockModeType.*). em.lock(myEntity, LockModeType.OPTIMISTIC); // Value, , . myEntity.setValue("WoW_" + index); em.getTransaction().commit(); em.close(); System.out.println("--Greeter updated : " + myEntity +" __--__ "+ index); }catch(RollbackException ex){ System.out.println(", =" + myEntity); } } public static void persistFill() { MyEntity myEntity = new MyEntity(); myEntity.setValue("JPA"); EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist(myEntity); em.getTransaction().commit(); em.close(); } }
第一次运行带注释的updateEntity方法 Pre-Persistiting operation: MyEntity{id=null, version=0, value='JPA'} Post-Persist operation: MyEntity{id=531, version=0, value='JPA'} . id find .
LockModeType.OPTIMISTIC
这是一个
乐观的障碍,那么,这是如此合乎逻辑。 如我上面所写,比较版本字段的值;如果不相同,则会引发错误。 检查一下。
结果: Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 3 Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 2 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_2'} Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 9 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 1 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_1'} Post-Update operation: MyEntity{id=531, version=1, value='WoW_1'} --Greeter updated : MyEntity{id=531, version=1, value='WoW_1'} __--__ 1 , =MyEntity{id=531, version=0, value='WoW_2'} , =MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=1, value='WoW_1'} load = 4 Pre-Updating operation: MyEntity{id=531, version=1, value='WoW_4'} Post-Update operation: MyEntity{id=531, version=2, value='WoW_4'} --Greeter updated : MyEntity{id=531, version=2, value='WoW_4'} __--__ 4 , =MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=2, value='WoW_4'} load = 0 Pre-Updating operation: MyEntity{id=531, version=2, value='WoW_0'} Post-Update operation: MyEntity{id=531, version=3, value='WoW_0'} --Greeter updated : MyEntity{id=531, version=3, value='WoW_0'} __--__ 0 Loaded operation: MyEntity{id=531, version=3, value='WoW_0'} load = 6 Pre-Updating operation: MyEntity{id=531, version=3, value='WoW_6'} Post-Update operation: MyEntity{id=531, version=4, value='WoW_6'} Loaded operation: MyEntity{id=531, version=4, value='WoW_6'} load = 5 Pre-Updating operation: MyEntity{id=531, version=4, value='WoW_5'} Post-Update operation: MyEntity{id=531, version=5, value='WoW_5'} --Greeter updated : MyEntity{id=531, version=4, value='WoW_6'} __--__ 6 --Greeter updated : MyEntity{id=531, version=5, value='WoW_5'} __--__ 5 Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 7 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_7'} Post-Update operation: MyEntity{id=531, version=6, value='WoW_7'} Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 8 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_8'} --Greeter updated : MyEntity{id=531, version=6, value='WoW_7'} __--__ 7 , =MyEntity{id=531, version=5, value='WoW_8'}
观察结果:从结果中可以看到,流3、2、9和1是第一个被加载的,并且为它们调用了Pre-Update回调方法。 从结果可以看出,调用Post-Update方法的第一个线程为1,标有Version批注的字段已更改(增加了1)。 因此,所有其余的线程2、3、9都抛出异常。 依此类推。 结果是value = WoW_7,version =6。实际上,最后的Post-Update是在版本7 = 6的线程中进行的。
LockModeType.OPTIMISTIC_FORCE_INCREMENT
它的工作原理与LockModeType.OPTIMISTIC相同,所不同的是,在提交之后,将Version字段的值强制增加1。因此,每次提交后,该字段将增加2(在更新后可以看到的增加+强制增加) 。 问题 怎么了 如果在提交之后我们想要“共轭”相同的数据,并且我们不需要在第一次提交和结束交易之间可能中断的第三方交易。
重要! 如果您尝试将数据更改为相同的数据,则在这种情况下,将不会调用Pre-Update和Post-Update方法。 所有交易可能会崩溃。 例如,与我们并行读取了几个事务,但是由于调用pre和post(更新)方法需要时间,因此尝试将数据更改为相同数据的事务将立即执行。 这将导致剩余交易的错误。
LockModeType.PESSIMISTIC_READ,LockModeType.PESSIMISTIC_WRITE和LockModeType.PESSIMISTIC_FORCE_INCREMENT
由于其余类型的锁的工作看起来相似,因此我将一次性编写所有内容,仅考虑PESSIMISTIC_READ的结果。
LockModeType.PESSIMISTIC_READ-
悲观读取锁定。
LockModeType.PESSIMISTIC_WRITE-
悲观的写锁定(和读取)。
LockModeType.PESSIMISTIC_FORCE_INCREMENT-强制增加Version字段的
悲观写锁定(和读取)。
由于这种锁定,可能会导致长时间等待锁定,进而可能导致错误。
LockModeType.PESSIMISTIC_READ的结果(未完整显示): load = 0 Pre-Updating operation: MyEntity{id=549, version=5, value='WoW_0'} Post-Update operation: MyEntity{id=549, version=6, value='WoW_0'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 8 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_8'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 4 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_4'} ... ERROR: : : 22760 ExclusiveLock " (0,66) 287733 271341"; 20876. 20876 ShareLock " 8812"; 22760.
结果,线程4和8相互阻塞,从而导致无法解决的冲突。 在此之前,没有人干预线程0。 所有线程最多为0的情况类似。