框架学习记录1
Spring框架中的单例bean是线程安全的吗?
首先Spring的bean是单例的,默认就是singleton
- singleton:bean在每个Spring IOC容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
bean不是线程安全的,因为id为传入的参数,临时变量。所以count不是线程安全的。可以通过多例或者加锁解决。
不过userService无状态,不会被修改。所以userService是线程安全的。
对AOP的理解
AOP是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等
我们当时在后台管理系统中,就是使用aop来记录了系统的操作日志
主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库
Spring中事务失效的场景
1.你自己捕获了异常
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解决:在catch块添加throw new RuntimeException(e)抛出
2.抛出检查异常
原因:Spring 默认只会回滚非检查异常,也就是运行时异常。(文件找不到FileNotFoundException 就属于检查异常)
解决:配置rollbackFor属性 @Transactional(roolbackFor = Exception.class)
3.非public方法导致的事务失效
原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
解决:将Transactional注解的函数改为public函数
4.一个带事务的方法被不带事务的方法调用
不带事务的方法调用该类中带事务的方法,不会回滚。因为spring的回滚是用过代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接通过this.xxx()
调用,而不生成代理事务,所以事务不起作用。常见解决方法,拆类。
问题原因:JDK的动态代理。只有被动态代理直接调用时才会产生事务。在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象。而这里的this是EmployeeService
真实对象而不是代理对象。
解决方法:
1.在方法A上开启事务,方法B不用事务或默认事务,并在方法A的catch中throw new RuntimeException();
(在没指定rollbackFor时,默认回滚的异常为RuntimeException
),这样使用的就是方法A的事务。(一定要throw new RuntimeException();
否则异常被捕捉处理,同样不会回滚。)如下:
@Transactional() //开启事务
public void save(){
try {
this.saveEmployee(); //这里this调用会使事务失效,数据会被保存
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}
2.方法A上可以不开启事务,方法B上开启事务,并在方法A中将this调用改成动态代理调用(AopContext.currentProxy()
),如下:
public void save(){
try {
EmployeeService proxy =(EmployeeService) AopContext.currentProxy();
proxy.saveEmployee();
}catch (Exception e){
e.printStackTrace();
}
}
Spring的bean的生命周期
我感觉有意思的就是他在初始化前面和初始化后面都有对应的函数,一个前置处理器,一个后置处理器。
Spring的循环引用
A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:缓存早期的bean对象(生命周期还没走完)
③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
先实例A对象,实例不完,把A扔到三级缓存里。然后实例B对象,B需要注入A,通过三级缓存的对象工厂获取A,并且把A存入二级缓存。B得到A了,B创建成功,B被存入一级缓存。然后A可以注入B了,A创建成功,A被存入一级缓存。清除二级缓存里的A对象。
如果是构造方法出现了循环依赖怎么解决?
由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建
SpringMVC的执行流程
- 用户发送出请求到前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping(处理器映射器)
- HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter(处理器适配器)
- HandlerAdapter经过适配调用具体的处理器(Handler/Controller)
- 方法上添加了@ResponseBody
- 通过HttpMessageConverter来返回结果转换为JSON并响应
SpringBoot自动配置原理
在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
其中@EnableAutoConfiguration
是实现自动化配置的核心注解。
该注解通过@Import
注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。
一般条件判断会有像@ConditionalOnClass
这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。
Spring框架的常见注解
Spring的常见注解
SpringMVC的常见注解
SpringBoot的常见注解
Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :
- @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
- @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
- @ComponentScan:Spring组件扫描
Mybatis的执行流程
①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理
③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法
④操作数据库的接口,Executor执行器,同时负责查询缓存的维护
⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息
⑥输入参数映射
⑦输出结果映射
Mybatis的延迟加载
延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载
在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的
延迟加载的底层原理
延迟加载在底层主要使用的CGLIB动态代理完成的
第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper
第二是, 当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询
第三是,获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了
Mybatis的一级、二级缓存
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储
二级缓存默认是关闭的
开启方式,两步:
1,全局配置文件 mybatis-config.xml
<settings><setting name="cacheEnabled" value="true"/></settings>
2,映射文件 UserMapper.xml
使用
注意事项:
1,对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear
2,二级缓存需要缓存的数据实现Serializable接口
3,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
Mybatis执行流程
1.加载我们的配置文件(mybatis-config.xml)和全局映射文件(UserMapper.xml).加载解析的相关信息在Configuration 对象中。
- SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
工厂模式对外提供sqlSessionFactory对象,建造者模式解决复杂对象的创建。
工厂应该是单例的。
- SqlSession sqlSession = factory.openSession();
后面通过sqlSession的一些API去做业务处理。
Mybatis中的缓存
通过装饰器模式扩展缓存的功能。
基于最普通的Cache接口,有一个普通简单的PerpetualCache实现类。我们还有LruCache,LoggingCache,SoftCache,WeakCache.这些比较复杂的装饰类。
public calss synchronizedCache implements Cache {
private final Cache delegate; //默认实现就是PerpetualCache
}
先走二级缓存再走一级缓存。
一级缓存是session级别的,二级缓存是sqlSessionFactory级别的,也就是进程级别。
MyBatis的涉及到的设计模式
缓存模块:装饰器模式
日志模块:适配器模式适配不同的适配器实现,interface Log,当然也体现了策略模式。代理模式
反射模块:工厂模式,装饰器模式Wrapper
sqlSesssion:建造者模式
代理模式:将 sqlSession.selectList(“com.boge.mapper.UserMapper.selectUser”)拆分为
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.selectUserById(1);
分页
在PageHelper包下,MysqlParser类中
在getPageSql函数中,sqlBuilder.append(“ limit ?,?”);
sqlSession的线程安全问题
是由Spring解决的。DefaultSqlSession是线程非安全的。
在Spring中提供了一个SqlSessionTemplate,其中每个方法都通过SqlSessionProxy来操作,这是一个动态代理对象。然后在动态代理对象中通过方法级别的DefaultSqlSession来实现相关的数据库的操作。
MyBatis的架构
接口层:sqlSession,面向开发者
核心层:具体的操作,配置解析,参数映射,sql解析,sql执行,结果集映射,插件
基础模块:支撑核心层完成一些功能,反射模块,日志模块,缓存模块,类型转换模块,数据源模块,事务模块,资源加载模块
JDBC差在哪
1.sql直接写在代码里,维护成本太高。
2.JDBC没有像Mybatis一样搞数据库连接池
3.像sql中传递参数也很麻烦,因为sql语句中where条件参数不一定,占位符和参数要一一对应。
4.对于结果集的映射也很麻烦,主要是sql本身的变化会导致解析的难度。
当实体的字段和表的字段不一致咋办?
1.在对应的sql语句中通过别名的方式
2.通过自定义resultMap标签,可以设置属性和字段的映射关系。
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="userName" column="user_name"></result>
<result property="password" column="password"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</resultMap>
<select id="testMohu" resultMap="userMap">
select id,user_name,password,age,sex from t_user where user_name like concat('%',#{mohu},'%')
</select>
MyBatis的多个传参
@Param 注解传参法(推荐)
#{}里面的名称对应的是注解 @Param 括号里面修饰的名称。
Map 传参法(推荐)
#{}里面的名称对应的是 Map 里面的 key 名称。
public void selectUser(Map<String,Object> map);
Java Bean 传参法(推荐)
最常见的,public void selecytUser(User user); #{}的名称就是自定义对象的属性名称。