MyBatis
MyBatis的代理是怎么实现的?
是用JDK的动态代理技术实现的,Proxy.newProxyInstance
在SqlSession类 ctrl + H
一路点下去
Spring
1. AOP的底层实现
在BeanPostProcessor的postProcessAfterInitialization方法中创建代理对象
见AbstractAutowireCapableBeanFactory类:
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
Object result = existingBean;
Object current;
for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) {
BeanPostProcessor processor = (BeanPostProcessor)var4.next();
current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
}
return result;
}
遍历所有的BeanPostProcessor的实现类,在postProcessAfterInitialization创建代理对象后,替换Bean对象。
如果打断点发现,会先创建SkillService对象,然后创建 SkillService的代理对象(CGLib)。
2. AOP配置
- execution(* *(..))
前面的 * : 权限修饰符 返回值
后面的 * : 包名类名方法名 括号里:参数类型
execution(public String com.scc.service.UserService.login(String,String))
2.使用 && 组合切入点表达式时,必须使用字符实体,使用&&
- 切入方法哪个写前面先执行哪个:
<aop:config>
<aop:pointcut id="pc1" expression="within(com.scc.service.impl.UserServiceImpl)"/>
<aop:pointcut id="pc2" expression="within(com.scc.service.SkillService)"/>
<aop:advisor advice-ref="logInterceptor" pointcut-ref="pc1"/>
<aop:advisor advice-ref="logAdvice" pointcut-ref="pc1"/>
</aop:config>
上面先执行 logInterceptor的方法
<aop:config>
<aop:pointcut id="pc1" expression="within(com.scc.service.impl.UserServiceImpl)"/>
<aop:pointcut id="pc2" expression="within(com.scc.service.SkillService)"/>
<aop:advisor advice-ref="logAdvice" pointcut-ref="pc1"/>
<aop:advisor advice-ref="logInterceptor" pointcut-ref="pc1"/>
</aop:config>
上面先执行 logAdvice
3. 事务的隔离级别
如果多个事务同时操作同一份数据,可能引发一下问题
- 脏读:一个事务读取了另一个事务没有提交的数据;
举例:
一个插入语句还没提交,查询语句可以查询出来
把数据库隔离级别改成最低,SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
先执行事务,插入,不提交:
START TRANSACTION;
INSERT INTO skill(name, level) VALUES('Flutter', 2000);
在执行查询:
START TRANSACTION;
SELECT * FROM skill;
COMMIT;
再执行插入语句的提交: COMMIT;
发现可以查询出,未提交的插入的数据;
如果把数据库隔离级别改为:SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; 则查询不出未提交事务的插入数据,必须COMMIT后才能查询出;
- 不可重复读:一个事务范围内两个相同的查询却返回了不同数据;
举例:同一个事务有两个相同的查询语句,执行第一条后,同时另一个事务做了更新操作,执行第二条的时候,查询的结果和第一条不同。
同一个事务有两个相同的查询语句:
START TRANSACTION;
SELECT * FROM skill WHERE id = 1;
SELECT * FROM skill WHERE id = 1;
COMMIT;
先执行 START TRANSACTION;SELECT * FROM skill WHERE id = 1;
再执行另一个事务的更新:
START TRANSACTION;
UPDATE skill SET `level` = 500 WHERE id = 1;
COMMIT;
然后再执行第二条查询:SELECT * FROM skill WHERE id = 1;COMMIT;
查出的结果和第一条相同语句的查询,查出的结果不一致。
如果把数据库隔离级别改为:SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; 则查询的结果一致。
- 幻读:一个事务发现了之前本来确认不存在的数据
幻读的错误理解:
说幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 15 条记录。这其实并不是幻读,既然第一次和第二次读取的不一致,那不还是不可重复读吗,所以这是不可重复读的一种。
幻读的正确理解:
幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
举例:
数据库隔离级别设置为RR:SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
- 事务A,查询id=18的数据,没有的话,插入一条,这是我们期待的业务;
START TRANSACTION;
SELECT * FROM skill WHERE id = 18;
INSERT INTO skill(id,`name`,LEVEL) VALUES(18,'Python',1000);
COMMIT;
首先执行:START TRANSACTION;SELECT * FROM skill WHERE id = 18;
- 这时,事务B,新增一条id=18的记录并提交事务:
START TRANSACTION;
INSERT INTO skill(id,`name`,LEVEL) VALUES(18,'Swift',3000);
COMMIT;
-
事务A,再去查询 id=18 的时候,发现还是没有记录(因为这里是在RR级别下研究(可重复读),所以读到依然没有数据)
SELECT * FROM skill WHERE id = 18;(换成SERIALIZABLE依然没有数据) -
事务A,插入一条 id=18 的数据。
INSERT INTO skill(id,name,LEVEL)VALUES(18,'Python',1000); COMMIT;
最终 事务A 提交事务,发现报错了。这就很奇怪,查的时候明明没有这条记录,但插入的时候 却告诉我 主键冲突,这就好像幻觉一样。这才是所有的幻读。(换成SERIALIZABLE,则不会报错,成功插入)
不可重复读侧重表达 读-读,幻读则是说 读-写,用写来证实读的是鬼影。
如果隔离级别换成:SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE
则 最后一步,则不会报错。