跨 2 个不同 Hibernate 会话的乐观锁定

Optimistic locking across 2 different Hibernate Sessions

我在服务层中写了一个这样的更新方法,我正在使用 Spring-data JpaRepository 进行所有 CRUD 操作。

    public Note updateNote(Note note, Long id) {
        Note current = repository.getOne(id);

        current.setTitle(note.getTitle());
        current.setDetail(note.getDetail());

        return repository.save(current);
    }

我想对该操作进行乐观锁定,所以我在 Note 实体中添加了一个版本字段。

  1. 休眠会话对版本信息有何影响?版本比较实际上是如何进行的?
  2. 我尝试通过使用多线程的一些测试来模拟这种情况,但无法这样做,也无法验证这是否有效。这种方法是正确的还是错误的?如果对象处于分离状态,乐观锁定是否会起作用 状态?或者是否有一些规则规定何时休眠 检查新旧实体之间的版本匹配?
  3. 另外,是否需要显式注解@Lock注解?或者它会默认选择 OPTIMISTIC_READ?

2: 不,你不需要任何 @Lock 来进行乐观锁定, 使用@Version 我们实现了乐观锁定。

1:现在你可以使用线程测试乐观锁场景了,下面我写了一些示例测试代码,你可以根据你的需要更新。

假设我有注释实体:

@Entity
@Data
@ToString
public class Note {
    @Id
    @GeneratedValue
    private Long id;

    @Version
    private int version;

    private String name;
}

我还有用于执行数据库操作的 Note Reposotory

@存储库

public interface NoteRepo extends JpaRepository<Note, Long> {
}

现在我有笔记服务了

@Service
@AllArgsConstructor
public class NoteService {

    private final NoteRepo noteRepo;

    @Transactional
    public Note create() {
        Note note = new Note();
        note.setName("Test Sample");
        note = noteRepo.save(note);
        System.out.println(note);
        return note;
    }

    @Transactional
    public void update(Long id, String name) {
       Note note =  noteRepo.getById(id);
       note.setName(name );
       note = noteRepo.save(note);
    }
}

现在是时候使用集成测试来测试乐观锁定了。

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes =Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestServiceTest {
 @Autowired
    private NoteService noteService;

    @Test
    void assertObjectOptimisticLockingFailure() throws InterruptedException {
        // Create a note, so after this we can perform updation
        Note note = noteService.create();

        //CountDownLatch required for wait test to complete
        CountDownLatch countDownLatch = new CountDownLatch(2);

       // Create 2 NoteThread to perform updation for exising note entity
        NoteThread xNote = new NoteThread("X", note.getId(), countDownLatch);
        NoteThread yNote = new NoteThread("Y", note.getId(), countDownLatch);

        xNote.start();
        yNote.start();

        countDownLatch.await();

        // Validate one of thread have ObjectOptimisticLockingFailureException

        assertTrue(xNote.hasObjectOptimisticLockingFailure() || yNote.hasObjectOptimisticLockingFailure());

    }

    class NoteThread extends Thread {
        private final String name;
        private final Long id;
        private final CountDownLatch countDownLatch;
        private Class exceptionClass;
        
        // verify exception
        boolean hasObjectOptimisticLockingFailure() {
            return this.exceptionClass == ObjectOptimisticLockingFailureException.class;
        }

       // Custome Thread class for performing note update and verify lock exception
        public NoteThread(String name, Long id, CountDownLatch countDownLatch) {
            this.name = name;
            this.id = id;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                noteService.update(id, name);
            } catch (Exception e) {
                exceptionClass = e.getClass();
            }finally {
                countDownLatch.countDown();
            }
        }
    }
}