一、mybatis操作数据库基本流程
1、读取配置文件信息
InputStream resource = Resources.getResourceAsStream("sqlMapConfig.xml");
2、获取连接工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resource);
3、打开连接
SqlSession session = sessionFactory.openSession();
4、执行查询,更新,插入,删除等操作
List<User> users = session.selectList("com.letsiot.dao.UserMapper.findById","4");
User one = session.selectOne("com.letsiot.dao.UserMapper.findById", "3");
session.insert("com.letsiot.dao.UserMapper.save",user);
session.update("com.letsiot.dao.UserMapper.updateById",user);
5、如果是保存、修改、删除等操作需要提交事务
session.commit();
6、关闭连接
session.close();
二、执行流程简单分析
1、读取配置文件
InputStream resource = Resources.getResourceAsStream("sqlMapConfig.xml");
经过应用类加载器加载把配置文件以刘的形式加载到内存。需要建立服务器与磁盘之间的socket连接。
2、获取连接工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resource);
通过构建一个XML解析器解析配置文件,获取配置信息以及sql映射文件,把第一步获取的文件流解析到Configuration内,传递给工厂构建器
3、打开一个连接
SqlSession session = sessionFactory.openSession();
指定session执行sql方式,事务隔离级别,是否自动提交等信息。
4、执行查询
执行查询又分为:
4.1执行参数设置
参数类型处理(参数区分为collection,list,array和其他)
offset,limit偏移处理(默认offset为0,limit为Integer.MAX_VALUE)
结果类型处理器设置,默认没有处理器
4.2结果查询
是否从缓存中获取还是从数据库查询
4.3 mybatis的PreparedStatement执行查询操作,动态调用method.invoke()执行,最终会调用到本地方法
4.4 mybatis的ResultSetHandler使用反射技术对结果集映射
三、mybatis解析动态sql分析
1、mybatis在通过SQLSessionFactoryBuilder构建SqlSessionFactory的时候会解析配置文件,首先解析配置文件根节点信息到XNode中。
1.1 解析sqlMapConfig.cml文件
1.2 从根节点开始已解析,解析成XNode对象。
1.3 逐个解析配置项,找到mapper节点。
1.4 根据mapper节点类型使用解析
配置package,最终将使用会使用MapperAnnotationBuilder解析器,否则将使用XMLMapperBuilder解析。
1.5 以XMLMapperBuilder为例分析
顺便可以看到sql映射文件必须要有namespace。
mybatis会解析cache-ref,cache标签,这两个标签时为了创建二级缓存使用。
接着mybatis会寻找parameterMap,resultMap,sql片段,select,delete,update,insert等。
1.6 解析select,delete,update,insert标签时会解析动态sql
跟踪XMLStatementBuilder的parseStatementNode方法,
mybatis会把标签内容解析到SqlSource中。
接下来mybatis会真正开始已解析配置文件,根据select标签中具体id,parameter,动态标签if,where等构造出不同的节点存入SqlSource中。
select * from user where 1=1
< if test=“id!=null and id!=’’”>and id=#{id}< /if>
< if test=“username!=null and username!=’’”>and username=#{username}< /if >
解析结果如下:
SqlSource实现类如下:
最终把SqlSource是否批处理,参数类型,返回类型等设置到configuration中。至此,mybatis解析工作才算完成。
四、SQLSession接口生成代理对象(动态代理)
代码:
UserMapper是一个接口,没有实现类,session.getMapper(User.class);会生成UserMapper接口的代理对象。
调用MapperProxyFactory创建一个对象。跟进newInstance(sqlSesion)方法内部。
可以看到mybatis使用的是jdk的动态代理创建的代理对象。
同时可以看到mapperProxyFactory来自于knownMappers的map中,在解析配置文件时候放入。
可以看到解析完sql映射文件后都会把对应的Mapper接口和相关信息存入knownMappers。而且在加载解析失败后会把对应的Mapper从knownMappers中移除。
五、mybatis缓存
缓存是一般ORM框架都会提供的功能,目的是提高查询效率和较少数据库压力。mybatis相关的类都在cache包里,有一个默认实现的类PerpetualCache,使用HashMap结构。
1.一级缓存
1.1 mybatis一级缓存是SqlSession级别的缓存,默认开启。Mybatis开启一次和数据库的对话,会创建出一个SqlSession对象表示一次会话。Mybatis每一次查询都会从本地缓存中获取,如果不存在,就会从数据库查询,并把结果存入本地缓存。可以跟踪代码查看。
1.2 测试用例如下:
1.3 查询会使用DefaultSqlSession的selectList()方法,进而使用Executor的query()方法。
1.4 可以看到先创建缓存key,再调用查询方法。
1.5 此处会先从缓存中查找,如果找到就返回结果,找不到就调用queryFromDatabase()从数据库查询,查找的key就是上一步使用hash算法构建的key值。
1.6 跟踪queryFromDatabase()方法处理过程。
queryFromDatabase()就是从数据库查询,并把结果保存到缓存中。
1.7 如果是update,insert,delete操作则会清空缓存
BaseExecutor的update()方法可以看到,更新之前会先清除本地缓存,跟进insert和delete内部可以发现,最终都是update方法。
1.8 mybatis一级缓存小结
- 对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果。
- 如果没命中,回去数据库查询,并把结果以key,value形式存到Cache中。
- update,insert,delete操作会清空一级缓存。
- 如果一级缓存是statement级别的,那么每次查询结束都会清掉一级缓存,每次都会从数据库查询。
设置方式如下:
问题:
-
一级缓存不能跨会话共享,不同的会话对于相同的数据可能会有不同的缓存。
解决方案: -
将一级缓存设置为statement级别,这样就没有使用缓存。
-
使用中间件,缓存不同session的数据。
-
使用mybatis的二级缓存。
2.二级缓存
2.1配置
需要在对应的mapper.xml文件中开启。
对应的pojo类需要实现序列化接口。
可以看到查询时候会先从二级缓存中获取,如果二级缓存中没有回调用query方法,query方法又会先从一级缓存中获取,如果没有查到才会查询数据库。
2.2 二级缓存什么时候创建
解析配置文件时候已经分析过,会在解析sqlMapConfig.xml配置文件时解析到mapper节点时会解析二级缓存相关标签。
最终会把二级缓存标签解析放到Configuration对象中。
2.3 insert,update,delete操作会清除二级缓存
insert,delete,update操作最终都会调用CacheingExecutor的update()方法,update方法都会先执行刷新缓存操作。