好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

JPA 加锁机制及@Version版本控制方式

JPA的加锁机制有两种,乐观锁和悲观锁。

乐观锁:

乐观锁的特点在于认为数据冲突或者更新丢失等情况是很少发生的.当发生的时候,抛出异常和回滚就足够解决问题.

悲观锁:

悲观锁的逻辑在于认为每次数据操作都很有可能发生冲突,所以一开始就获得记录的锁,再进行记录的操作是解决问题的优先选择.

一 简述悲观锁的用法

悲观锁通常是SQL级别的,通过读写时先拿到锁实现,在SQL语句中就会有体现.

1.1 EntityManager 用法

?

1

2

3

4

return em.createQuery(sql 语句).setLockMode(LockModeType.NONE).getResultList();

//分解写法大概是:

Query query = getSession().createQuery(hql);

query.setLockMode(LockModeType.NONE);

EntityManager 是一个辅助类,createQuery后返回的就是一个Query对象,然后通过

setLockMode设置锁的级别即可.

LockModeType 类型 解释
LockMode.READ 事务的隔离级别是Repeatable Read或Serializable时,请求读取数据库记录时自动获得
LockMode.WRITE 请求插入或更新数据库记录时自动获得
LockMode.OPTIMISTIC 乐观锁
LockMode.OPTIMISTIC_FORCE_INCREMENT 乐观锁,通过version控制
LockMode.PESSIMISTIC_READ 与LockMode.PESSIMISTIC_WRITE相同
LockMode.PESSIMISTIC_WRITE 事务开始即获得数据库的锁
LockMode.PESSIMISTIC_FORCE_INCREMENT 事务开始即设置version
LockMode.NONE 取消任何锁,如事务结束后的所有对象,或执行了Session的update()、

二 乐观锁的详细用法

乐观锁本篇的主要内容

实体类是关键 , 乐观锁常用方法是通过version来控制 ,

数据库对应的表中需要有一个字段(名字随意),字段类型设置成BigInt即可 业务不对该字段进行控制,字段的控制交由系统处理 每一次修改都会导致version递增 当出现同时获得该记录的对象且均需要修改时,当第一个已经提交事务,version字段发生改变,后面提交的事务发现version版本不对,则无法提交,抛出异常

实体类(注意其中的@Version注解)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

@Entity

public class User {

     @Id

     @GeneratedValue

     private Long id;

     private String username;

     private String userdesc;

     @Version

     private Long version;

     public User() {

     }

     public User(String username, String userdesc) {

         this .username = username;

         this .userdesc = userdesc;

     }

     public Long getId() {

         return id;

     }

     public void setId(Long id) {

         this .id = id;

     }

     public String getUsername() {

         return username;

     }

     public void setUsername(String username) {

         this .username = username;

     }

     public String getUserDesc() {

         return userdesc;

     }

     public void setUserDesc(String userdesc) {

         this .userdesc = userdesc;

     }

     public Long getVersion() {

         return version;

     }

     public void setVersion(Long version) {

         this .version = version;

     }

}

controller中通过sleep将线程沉睡,测试事务的提交性

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

@RestController

public class UserController {

     private Logger logger = LoggerFactory.getLogger(getClass());

     @Autowired

     UserService userService;

     @PostMapping ( "/changeone" )

     @Transactional

     public String changeone() {

         User user = userService.findUser( "gang" );

         try {

             logger.info( "修改1 before:user--{}--Versdion:{}" , user.getUserDesc(), user.getVersion());

             Thread.sleep( 25000 );

             user.setUserDesc( "修改1" );

             logger.info( "修改1 :user--{}--version:{}" , user.getUserDesc(), user.getVersion());

         } catch (InterruptedException e) {

             e.printStackTrace();

         } catch (Exception e) {

             logger.info( "eeeeeeeeeeeeee" );

             e.printStackTrace();

         }

         return "true" ;

     }

     @PostMapping ( "/changetwo" )

     @Transactional

     public String changetwo() {

         User user = userService.findUser( "gang" );

         try {

             logger.info( "修改2 before:user--{}--version:{}" , user.getUserDesc(), user.getVersion());

             Thread.sleep( 30000 );

             user.setUserDesc( "修改2" );

             logger.info( "修改2:user--{}--version:{}" , user.getUserDesc(), user.getVersion());

         } catch (InterruptedException e) {

             e.printStackTrace();

         } catch (Exception e) {

             logger.info( "eeeeeeeeeeeeee" );

             e.printStackTrace();

         }

         return "true" ;

     }

     @PostMapping ( "/changethree" )

     @Transactional

     public String changethree() {

         User user = userService.findUser( "gang" );

         logger.info( "修改3 before:user--{}--version:{}" , user.getUserDesc(), user.getVersion());

         user.setUserDesc( "修改3" );

         logger.info( "修改3 :user--{}--version:{}" , user.getUserDesc(), user.getVersion());

         return "true" ;

     }

     @PostMapping ( "/newuser" )

     @Transactional

     public String newuser() {

         logger.info( "save user" );

         User user = new User();

         user.setUserDesc( "第一次创建" );

         user.setUsername( "gang" );

         userService.saveUser(user);

         return "true" ;

     }

}

以及service及repository

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@Service

public class UserService {

     @Autowired

     UserRepository userRepository;

     public User findUser(String username){

         return userRepository.findByUsername(username);

     }

     public void saveUser(User user){

         userRepository.save(user);

     }

}

UserRepository

public interface UserRepository extends JpaRepository<User,Long> {

     User findByUsername(String username);

}

总结

使用很简单,version是自动增长的,唯一的缺点是抛出的异常不易捕获,捕获的方法:

?

1

2

3

4

5

6

7

8

9

10

11

12

@Resource

private UserTransaction rtc;

  try {

         rtc.begin();

         User user = userService.findUser( "gang" );

         user .setDesc( "异常捕获" );

          rtc测试数据mit();

     } catch (OptimisticLockException e) {

         throw new OptimisticLockException ();

     } catch (Exception e) {

         throw new Exception ();

     }

注意其中的 rtc.begin(); 以及 rtc测试数据mit();

不同于@Transaction,这种是手动的提交方法

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://blog.csdn.net/zzg19950824/article/details/85468318

查看更多关于JPA 加锁机制及@Version版本控制方式的详细内容...

  阅读:15次