Spring data Jpa,Mybatis,读写锁,@Lock 使用
Spring data jpa 支持注解式的读写锁(悲观锁),实际上这个东西硬编码也简单,但是基于Jpa 命名方式定义的Sql,只能用注解添加支持读写锁了,
不了解读写锁的可以点这里
mysql读写锁及事务
并且推荐
PESSIMISTIC_READ,
PESSIMISTIC_WRITE,
而不是
READ,
WRITE,
但是官方文档貌似没有更新这个案例,踩了一些坑.
新建一个实体Book.java
/** * User: laizhenwei * Date: 2018-04-18 Time: 9:04 * Description: */ @Entity @Table(name = "test_book") @Alias("Book") @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class Book extends AbstractJbatisIdEntity{ private static final long serialVersionUID = -1L; private String name; private String author; }
BookRepository.java
/** * User: laizhenwei * Date: 2018-04-18 Time: 9:11 * Description: */ public interface BookRepository extends JpaRepository<Book,String> { @Lock(LockModeType.PESSIMISTIC_READ) Book findTop1ByName(String name); }
BookServiceImpl TimeUnit.SECONDS.sleep(20); 是为了让事务延迟提交,好测试save操作需要阻塞到读写释放才能提交
public static final CountDownLatch readCount = new CountDownLatch(1); public static final CountDownLatch saveCount = new CountDownLatch(1); @Transactional(propagation = Propagation.REQUIRES_NEW) @Override public Book save(Book book){ Book book1 = null; try { readCount.await(); book1 = getRepository().save(book); saveCount.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } return book1; } @Transactional(propagation = Propagation.REQUIRES_NEW) @Override public Book findTop1ByName(String name){ Book book = getRepository().findTop1ByName(name); try { readCount.countDown(); TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } return book; }
JunitTest 先添加一条数据,待会要锁这个数据
@Test public void save(){ Book book = new Book(); book.setName("百年孤独"); book.setAuthor("加西亚·马尔克斯"); bookRepository.save(book); }
开启两条线程,一条先加上读锁,然后睡眠一会,另一条线程去修改这个对象的时候,需要阻塞到读事务提交以后才会成功
第二个查询动作不会阻塞,因为读锁只对写操作限制(这里用直接用bookRepository,是为了避免CountDownLatch 再一次阻塞而已)
@Test @Transactional public void findByName() throws InterruptedException { new Thread(()->bookService.findTop1ByName("百年孤独")).start(); BookServiceImpl.readCount.await(); Book book =bookRepository.findTop1ByName("百年孤独"); book.setAuthor("加西亚·马尔克斯5"); new Thread(()->bookService.save(book)).start(); BookServiceImpl.saveCount.await(); }
有个有趣的现象,如果直接运行第二次,会发现不用阻塞,就能save成功,因为数据并没有做任何修改.
再注释掉@Lock跑一次,修改 book.setAuthor("加西亚·马尔克斯5");再保存也不需要等待.
Mybatis下的实现,就是手动编码而已
/** * User: laizhenwei * Date: 2018-04-18 Time: 9:12 */ @Mapper public interface BookMapper extends BaseMapper<Book> { @Select("select * from test_book where name=#{name} limit 1 lock in share mode") Book findTop1ByName(String name); }
Service TimeUnit.SECONDS.sleep(20); 是为了让事务延迟提交,好测试save操作需要阻塞到读写释放才能提交
@Transactional(propagation = Propagation.REQUIRES_NEW) @Override public Book mapperFindTop1ByName(String name){ Book book = getMapper().findTop1ByName(name); try { readCount.countDown(); TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } return book; }
JunitTest (这里用直接用bookRepository,是为了避免CountDownLatch 再一次阻塞而已)
@Test @Transactional public void findByName() throws InterruptedException { new Thread(()->bookService.mapperFindTop1ByName("百年孤独")).start(); BookServiceImpl.readCount.await(); Book book =bookRepository.findTop1ByName("百年孤独"); book.setAuthor("加西亚·马尔克斯3"); new Thread(()->bookService.save(book)).start(); BookServiceImpl.saveCount.await(); }
测试效果与Jpa一样.