一文搞定 Spring事务( 三 )

4、Spring事务隔离级别

Spring面试之中隔离级别的面试问题是最为常见的,也是一个核心的基础所在,但是所谓的隔离级别一定要记住 , 是在并发环境访问下才会存在的问题 。数据库是一个项目应用中的公共存储资源,所以在实际的项目开发过程中,很有可能会有两个不同的线程(每个线程拥有各自的数据库事务),要进行同一条数据的读取以及更新操作 。

一文搞定 Spring事务

文章插图
下面就通过代码的形式 一步步的揭开他的庐山真面目 。
  • 对于事务,
private class BookRowMapper implements RowMapper<Book> {// 对象映射关系@Overridepublic Book mapRow(ResultSet rs, int rowNum) throws SQLException {Book book = new Book();book.setBid(rs.getInt(1));book.setTitle(rs.getString(2));book.setAuthor(rs.getString(3));book.setPrice(rs.getDouble(4));return book;}}@Testpublic void testInsertIsolation() throws InterruptedException { // 测试事务的隔离级别String query = "select bid,title,author,price from yootk.book where bid = ?"; // 查询String update = "update yootk.book set title = ?, author =? where bid =?"; // 根据id修改BookRowMapper bookRowMapper = new BookRowMapper(); // 对Book对象的映射DefaultTransactionDefinition definition =new DefaultTransactionDefinition(); // 创建默认事务对象Thread threadA = new Thread(() -> {TransactionStatus statusA = this.transactionManager.getTransaction(definition); //开始事务Book book = this.jdbcTemplate.queryForObject(query, bookRowMapper, 1); // 查询bid = 1的数据String name = Thread.currentThread().getName();// 获取线程名称System.out.println(11111 + "??????");LOGGER.info("{}【查询结果】:{}", name, book);try {TimeUnit.SECONDS.sleep(2); //等待两秒 让线程B修改之后再查询} catch (InterruptedException e) {throw new RuntimeException(e);}book = jdbcTemplate.queryForObject(query, bookRowMapper, 1); // 再次查询LOGGER.info("{}【查询结果】:{}", name, book);}, "事务线程-A");Thread threadB = new Thread(() -> {TransactionStatus statusB =transactionManager.getTransaction(definition); // 开启事务String name = Thread.currentThread().getName();// 获取线程名称int i = 0;try {i = jdbcTemplate.update(update, "Netty", "李老师", 1);LOGGER.info("{} 执行结果:{}", name, i);transactionManager.commit(statusB); // 提交事务} catch (DataAccessException e) {transactionManager.rollback(statusB); // 回滚事务throw new RuntimeException(e);}}, "事务线程-B");threadB.start();// 启动线程threadA.start();threadA.join();// 等待相互执行完成threadB.join();}执行结果
事务线程-A【查询结果】:Book(bid=1, title=Netty, author=李老师, price=99.9)事务线程-B 执行结果:1事务线程-A【查询结果】:Book(bid=1, title=Netty, author=李老师, price=99.9)
查看执行结果可知,我们线程B执行的是更新操作,但是更新成功后,在事务A进行查询时,本应是我们更新后的数据 , 这才对呀 。所以这个事务出现了事务不同步的问题 。
为了保证并发状态下的数据读取的正确性,就需要通过事务的隔离级别来进行控制,实际上控制的就是脏读、幻读以及不可重复读的问题了 。
4.1、脏读
脏读(Dirty reads):事务A在读取数据时,读取到了事务B未提交的数据,由于事务B有可能被回滚,所以该数据有可能是一个无效数据

一文搞定 Spring事务

文章插图
4.2、不可重复读
不可重复读(Non-repeatable Reads):事务A对一个数据的两次读取返回了不同的数据内容,有可能在两次读取之间事务B对该数据进行了修改,一般此类操作出现在数据修改操作之中;

一文搞定 Spring事务

文章插图
4.3、幻读
幻读(Phantom Reads):事务A在进行数据两次查询时产生了不一致的结果 , 有可能是事务B在事务A第二次查询之前增加或删除了数据内容所造成的.

一文搞定 Spring事务

文章插图
Spring最大的优势是在于将所有的配置过程都进行了标准化的定义,于是在TransactionDefintion接口里面就提供了数据库隔离级别的定义常量 。

一文搞定 Spring事务

文章插图
从正常的设计角度来讲,在进行Spring事务控制的时候,不要轻易的去随意修改隔离级别(需要记住这几个隔离级别的概念),因为一般都使用默认的隔离级别 , 由数据库自己来实现的控制 。

推荐阅读