(一)事务:Spring 的 Controller 访问特性

@Controller

被该符号标注的类为 Spring 的控制层类

Spring 的 Bean 默认是单例的
> Spring 的 Bean 默认是单例的,这意味着 @Controller 标注的控制层类也是单例的。那么试想一下,如果该类中有一个全局变量,就可以在历次访问中进行信息通信了。

这里顺带介绍一下 Spring Bean 的作用域配置。
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 每次请求新建一个实例
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) 所有请求共享一个单例
@Scope(WebApplicationContext.SCOPE_REQUEST) web请求,一次请求新建一个实例
@Scope(WebApplicationContext.SCOPE_SESSION) web请求,一个 session 生命周期内共享一个实例
@Scope(WebApplicationContext.SCOPE_GLOBAL_SESSION) web请求,所有的 session 生命周期内共享一个实例

各 Controller 访问互不影响,但是有特例

对于 Controller 的访问 ,Spring 使用ThreadLocal实现了各访问之间互不干扰,可以认为一次访问就使用了一个新的线程。
但是在实际实验过程中发现,使用同一台主机的同一个浏览器的多个窗口,同时访问同一个 Controller 的时候会出现访问延迟的现象。详述如下文

延迟现象出现的条件、现象和规律

条件:当使用同一台主机的同一种浏览器的多个窗口,同时访问同一个url地址。
现象:对于几乎同时发起的两次访问出现了两种现象。1、第二次访问总是在第一次访问结束之后才开始,二者是串行关系;2、当第一次访问用时20秒(人为线程休眠)以上时,第二次访问会在第一次没有结束的时候就开始运行,二者是并发关系(这里的并发不是多线程的并发,而是各自运行,互不干扰的意思)。
规律:同一台主机的同一个浏览器连续多次访问同一个url会造成多次访问的关系变为串行,除非前面的访问超时,后面的访问才可能在前面的访问没有结束就开始。

避免出现延迟访问的条件

不区分访问的url,访问来源于不同的主机
不区分访问的url和主机,访问来源于不同的浏览器
不区分访问的主机和浏览器,访问的 url 不同

运用多线程简化并发访问 Controller 的问题,仅仅出于方便测试的目的

由于同一台主机同一个浏览器多次同时访问一个 url 会出现延迟问题,这样在一些测试场景上无法满足我们的需要,比如想要测试两次访问之间事务配置的效果。虽然从两个浏览器访问也可以避免出现延迟现象,但是手动操作毕竟存在时间误差,所以,我们可以采取多线程的方式,让一次(Controller 请求)访问变成多个(业务请求)访问。
具体来说,Controller 层 调用 Service 层,那么在Controller 层中使用线程调用 Service 层即可避免两次 Controller 延迟的问题,而且可以在减少操作时间的情况下增加并发数量。
下面给出关键代码。

Controller
	@Autowired
	private TestTableOneService testTableOneService;
	
	/*** 初始化一个数量为2的线程池*/
	ExecutorService pool=new ThreadPoolExecutor(2, 2,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());
            
	/*** 使用线程池测试* @return*/
	@RequestMapping("/test")
	@ResponseBody
	public Map<String,Object> gettest(){
		Map map=new HashMap<String,Object>();
		
		//线程一-事务配置为读未提交-单独事务
		//service 内部逻辑:读-休眠5秒等待另一个线程提交-再读
		Thread readTwice=new Thread(){
			@Override
			public void run() {
				super.run();
				testTableOneService.readTwiceByPrimaryKey(1);
			}	
		};
		
		//线程二-事务配置为读已提交-单独事务
		//service 内部逻辑:休眠一秒等待线程一第一次读取参数,修改参数并提交事务
		Thread updateOnce=new Thread(){
			@Override
			public void run() {
				super.run();
				TestTableOne testTableOne=new TestTableOne();
				testTableOne.setId(1);
				testTableOne.setComment(System.currentTimeMillis()+"");
				testTableOneService.updateByPrimaryKey(testTableOne);
			}	
		};
		pool.execute(readTwice);
		pool.execute(updateOnce);
		
		return map;
	}
Service
@Transactional(readOnly=true,propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_UNCOMMITTED)
	@Override
	public TestTableOne readTwiceByPrimaryKey(Integer id) {
		TestTableOne t=null;
		t=testTableOneMapper.selectByPrimaryKey(id);
		System.out.println("第一次搜索:"+t.getComment()+";"+System.currentTimeMillis());
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("线程一休眠结束:"+t.getComment()+";"+System.currentTimeMillis());
		t=testTableOneMapper.selectByPrimaryKey(id);
		System.out.println("第二次搜索:"+t.getComment()+";"+System.currentTimeMillis());
		return null;
	}
	@Transactional(readOnly=false,propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
	@Override
	public int updateByPrimaryKey(TestTableOne testTableOne) {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("线程二休眠结束:"+System.currentTimeMillis()+";即将修改为"+testTableOne.getComment());
		int i=testTableOneMapper.updateByPrimaryKey(testTableOne);
		System.out.println("线程二修改结束:"+System.currentTimeMillis());
		return i;
	}
测试结果

线程一一个事务内读两次
线程二在线程一之间提交了一个修改
线程一和线程二分属两个事务
线程一配置为读未提交,但是第二次读没有读取到线程二已提交到新数据
结论是: Mybatis 默认配置下 “不可重复读”不可复现
第一次读取123,修改为1544331870504,第二次读取123

第一次搜索:123;1544331870511
线程二休眠结束:1544331871511;即将修改为1544331870504
线程二修改结束:1544331871513
线程一休眠结束:123;1544331875516
第二次搜索:123;1544331875517
默认情况,Mybatis 无法出现"不可重复读"问题

实验代码如下,但是在事务一配置为读未提交这种级别的情况下,事务一内两次读取并不能读取到两次读取之间另一个独立事务到修改操作到结果。
这是因为 Mybatis 在事务开启到状态下,会默认开启本地缓存导致到,这样同一个事务内,同一个搜索语句与搜索条件下默认会共享同一个搜索结果
关于Mybatis 到缓存,请参考博主到另一篇文章 (三)事务:Mybatis的本地缓存和二级缓存

再现"不可重复读"

修改 Mybatis 的一个配置

<setting name="localCacheScope" value="SESSION" />

修改为

 <setting name="localCacheScope" value="STATEMENT" />  

再次运行上面的代码,可的结果
第一次读取1544331870504,修改为1544332406152,第二次读取1544332406152

第一次搜索:1544331870504;1544332406297
线程二休眠结束:1544332407191;即将修改为1544332406152
线程二修改结束:1544332407195
线程一休眠结束:1544331870504;1544332411298
第二次搜索:1544332406152;1544332411301
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值