Mybatis 原理分析
对于入门程序的流程分析
使用过程
读配置文件
读取配置文件时绝对路径和相对路径(web工程部署后没有src路径)都有一定问题,实际开发中一般有两种方法
- 使用类加载器,它只能读取类路径的配置文件
- 使用SerbletContext对象的getRealPath()
创建SqlSessionFactory工厂,使用了建造者模式(Builder Pattern)
使用工厂生产SqlSession对象,使用了工厂模式(Factory Pattern)
使用SqlSession创建Dao接口的代理对象,使用了代理模式(Proxy Pattern)
使用代理对象执行方法
释放资源
底层调用jdbc的流程,即自定义dao中selectList()方法的执行流程,也是代理对象增强的逻辑
- 注册驱动,获取Connection对象(需要数据库信息)
- 通过SqlMapConfig.xml的数据库信息,解析xml文件用到的是dom4j技术
- 获取预处理对象PreparedStatement(需要sql语句)
- 通过SqlMapConfig.xml中的mapper标签定位UserDao.xml,映射配置文件中有sql语句
- 执行查询,得到结果集ResultSet
- 遍历结果集用于封装
- 根据UserDao.xml中的resultType反射获得User对象,User对象的属性名和表中列名一致,可以一一封装进user对象中,再把user对象封装进list中
- 所以,要想让selectList()执行,需要提供两个信息,连接信息和映射信息,映射信息包括sql语句和封装结果的实体类的全限定类名,所以映射信息可以用map存储
创建代理对象流程
根据dao接口的字节码创建dao的代理对象
public <T> T getMapper(Class<T> daoInterfaceClass){
/*
类加载器,使用和被代理对象相同的类加载器
接口数组,和被代理对象实现相同的接口
增强逻辑,自己提供,此处是一个InvocationHandler接口,需要写一个该接口的实现类,在其中调用selectList()方法
*/
Proxy.newProxyInstance(类加载器, 接口数组,增强逻辑);
}
原理分析:模拟Mybatis各组件实现入门程序
分析用到的类
public void test(){
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSession session = factory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
List<User> users = userDao.findAll();
for(User user: users) System.out.println(user);
session.close();
in.close();
}
解决读取配置信息的问题
读取xml文件具体实现可以使用dom4j和xpath技术,坐标名dom4j和jaxen
//模拟Mybatis的配置类
public class Configuration{
private String driver;
private String url;
private String username;
private String password;
/*
mappers中的数据结构形如
-------------------------------------------------------------------------
key | value
-----------------------------------|-------------------------------------
"com.whteway.dao.UserDao.findAll()"| queryString:"select * from user"
| resultType:"com.whteway.domain.User"
-----------------------------------|-------------------------------------
"com.whteway.dao.StuDao.findAll()" | queryString:"select * from stu"
| resultType:"com.whteway.domain.Stu"
-----------------------------------|-------------------------------------
"package.daoInterface.method()" | queryString: SQL语句
| resultType: 实体类的全限定类名
*/
private Map<String Mapper> mappers;
//to generate getters and setters exclude setMappers;
public void setMappers(Map<String, Mapper> mappers){
this.mappers.putAll(mappers); //追加而不是覆盖
}
}
//用于封装映射配置文件的信息,SQL语句和封装实体类的全限定类名
//SqlMapConfig.xml的mappers标签中的每一个mapper标签对应一个Mapper对象,
//也就是说第一个映射配置文件对应一个Mapper对象
public class Mapper{
private String queryString;
private String resultType;
//to generate getters and setters;
}
//读取xml文件的工具类
public class XMLConfigBuilder{
//读取SqlMapConfig.xml文件并调用方法读取映射配置文件或加注解的类,将配置信息存入Configuration中并返回
public static Configuration loadConfiguration(InputStream is){
Configuration config = new Configuration();
//1.读取SqlMapConfig文件,将数据库连接信息存入config
/*2.根据SqlMapConfig文件中的mappers,属性循环读取每一个映射配置文件
如果用的是resource属性
mappers = loadMapperResource(resource.value);
如果用的是class属性
mappers = loadMapperAnnotation(class.value);
使用追加的方式将mappers存入config.mappers中
config.setMappers(mappers);
*/
return config;
}
//读取映射配置文件
public static Map<String, Mapper> laodMapperResource(String daoClassPath) throws Exception{
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
/*
1 循环读取每一个映射配置文件,如UserDao.xml
2 读取mappers标签的namespace
3 循环mappers标签中的每一个子标签(对应一个方法)
4 读取id,组成namespace.id形式的字符串,作为key
5 读取sql语句存入Mapper中的queryString
6 读取resultType存入Mapper中的resultType
7 将Mapper作为value,与key组成键值对,存入mappers中
*/
return mappers;
}
//读取加注解的类
public static Map<String, Mapper> laodMapperAnnotation(String daoClassPath) throws Exception{
Map<String, Mapper> mappers = new HashMap<String, Mapper>();
/*
1.得到dao接口的字节码对象
2.循环每一个方法(对应一个Mapper)
3. 判断是否有select注解,如果有
4. 获取各种数据封装到一个Mapper中
包名,类名,@Select的value值,返回类型的泛型的实际类型,方法名
5. 把Mapper存入mappers中
*/
return mappers;
}
}
Resources
//将文件名转换为输入流
public class Resources{
//根据传入的参数,返回一个输入流
//1.Resources.class 获取当前类的字节码
//2.getClassLoader() 获取这个字节码的类加载器
//3.getResourceAsStream(filePath) 根据类加载器读取文件配置
public static InputStream getResourceAsStream(String filePath){
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
}
SqlSessionFactoryBuilder 读取配置文件将配置信息存入配置类中的代码在此类中调用
public class SqlSessionFactoryBuilder{
//根据参数的字节输入流来构建一个SqlSessionFactory工厂
public SqlSessionFactory build(InputStream config){
Configuration cfg = XmlConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(config);
}
}
SqlSessionFactory
//接口
public interface SqlSessionFactory{
public SqlSession openSession();
}
//实现类
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration cfg;
//实例化此类必须传入配置信息
public DefaultSqlSessionFactory(Configuration cfg){
this.cfg = cfg;
}
//创建一个新的操作数据库的对象
@Override
public SqlSession openSession(){
return new DeaultSqlSession(cfg);
}
}
SqlSession jdbc代码调用全在此类和此类的代理对象中
//接口,和数据库交互的核心类
public interface SqlSession{
//创建代理对象,参数是dao接口的字节码
public <T> T getMapper(Class<T> daoInterfaceClass);
//释放资源
public void close();
}
//实现类
public class DefaultSqlSession implements SqlSession{
private Configuration cfg;
private Connection conn;
//实例化此类必须传入配置信息
public DefaultSqlSession(Configuration cfg){
this.cfg = cfg;
conn = DataSourceUtil.getConnection(cfg);
//使用传统JDBC方式获取连接,创建数据源,建议封装到一个工具类中,源码中叫executor
try{
Class.forName(cfg.getDriver());
conn = DriverManager.getConnection(cfg.getUrl(), cfg.getUserName(), cfg.getPassword())
}catch(Exception e){
throw new RuntimeException(e);
}
} @Override
public <T> T getMapper(Class<T> daoInterfaceClass){
return Proxy.newProxyInstance(daoInterfaceClass.getClassLoader()), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers()));
} //关闭Connection
public void close(){
if(conn != null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
//作为创建代理对象时的第三个参数,用于对方法进行增强,此处的增强只需要调用selectList()方法
public class MapperProxy implements InvocationHandler{
//接收参数
private Map<String, Mappers> mappers;
private Connection conn;
public MapperProxy(Map<String, Mapper> mappers, Connection conn){
this.mappers = mappers;
this.conn = conn;
}
//定义增强逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
//获取方法名
String methodName = method().getName();
//获取方法所在的类的名称
String className = method.getDeclaringClass().getName();
//组合key
String key = className + "." + methodName();
//获取mappers中的mapper对象
Mapper mapper = mappers.get(key);
//判断参数是否合法
if(mapper == null)
throw new IllegalArgumentException("传入参数有误");
//使用传统JDBC方式执行SQL语句并封装到实体类列表中返回,建议作为selectList()方法封装到工具类中
selectList();
return null;
}
}
session.selectList()源码分析
//UserDaoImpl.java
public List<User> findAll(){
session.selectList("com.whteway.dao.UserDao.findAll");
}
//DefaultSqlSession.java
public <E> List<E> selectList(String statement){
this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter){
this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds){
try{
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapColeection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e){
} finally {
ErrorContext.instance().reset();
}
}
//CachingExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException{
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException{
...
return delegate.<E> query(ms, parameterObect, rowBounds, resultHandler, key, boundSql);
}
//BaseExecutor.java...
//经过一系列的调用,会走到PreparedStatementHandler类中的一个query方法
//PreparedStatementHandler.java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException{
//出现JDBC代码
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
//DefaultResultSetHandler.java
public List<Object> handleResultSets(Statement stmt) throws SQLException{
//封装结果集的代码
}
Mybatis 原理分析的更多相关文章
- MyBatis原理分析
MyBatis原理分析 参考博客: 深入理解mybatis原理: http://blog.csdn.net/luanlouis/article/details/40422941 一 . JDBC的 ...
- Mybatis原理分析之一:从JDBC到Mybatis
1.引言 本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架.再而论述Mybatis作为一个数据持久层框架本身有待改进之处. 2 ...
- Mybatis原理分析一 从JDBC到Mybaits
1.引言 本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架.再而论述Mybatis作为一个数据持久层框架本身有待改进之处. 2 ...
- MyBatis原理分析之四:一次SQL查询的源码分析
上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: ) { ); ) { throw ne ...
- MyBatis原理分析之三:初始化(配置文件读取和解析)
1. 准备工作 编写测试代码(具体请参考<Mybatis入门示例>),设置断点,以Debug模式运行,具体代码如下: )ExecutorType:执行类型,ExecutorType主要有三 ...
- Mybatis原理分析之二:框架整体设计
1.引言 本文主要讲解Mybatis的整体程序设计,理清楚框架的主要脉络.后面文章我们再详细讲解各个组件. 2.整体设计 2.1 总体流程 (1)加载配置并初始化 触发条件:加载配置文件 ...
- MyBatis原理系列
原理分析之一:从JDBC到Mybatis 原理分析之二:框架整体设计 原理分析之三:初始化(配置文件读取和解析) 原理分析之四:一次SQL查询的源码分析
- MyBatis原理总结(代码实现流程)
我们在实际开发中,越简单越好,所以都是采用不写Dao实现类的方式.不管是使用xml还是直接配置. 但是MyBatis是支持写Dao实现类的 注意sqlSession是这里面的一个灵魂,有很多执行api ...
- MyBatis框架及原理分析
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 MyBatis的主要设计目的就 ...
随机推荐
- 重新编译kubeadm,修改默认证书时间
参考 kubeadm alpha certs renew Kubeadm1.14 证书调整 kubeadm 部署的 kubernetes 集群,默认的证书有效时间是1年,需要每年手工更新. 1. 重新 ...
- maven中,dependency 中的 classifier属性
classifier元素用来帮助定义构件输出的一些附属构件.附属构件与主构件对应,比如主构件是 kimi-app-2.0.0.jar 该项目可能还会通过使用一些插件生成 如 kimi-app-2.0. ...
- UGUI:技能冷却效果
版权申明: 本文原创首发于以下网站: 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123 优梦创客的官方博客:https://91make.top ...
- Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)
题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...
- JMeter【第五篇】关联:5种方法
前几天在Q群里看到群友发的最近10年性能测试工具使用率的统计,最近的2018年,jmeter+loadrunner占了93%的使用率,说明这两个是主流,其中,jmeter的使用率逐年提升,现在已经超过 ...
- 小学四则运算口算练习app
目标: 第一次尝试做APP,这次做的东西不是很麻烦,做出一个口算练习的加减乘除的页面,使用者做题,设有答案页,进行核对! 核心部分是出题页面的程序,还有答案页的程序.不设置登录注册页面.冲刺时间:一周 ...
- Windbg的快捷键
窗口切换 可以使用以下键盘快捷方式窗口之间进行切换. 项 效果 CTRL+TAB 调试信息窗口之间切换. 通过重复使用此密钥,你可以扫描通过的所有窗口,而不考虑是否浮动. 停靠本身,或选项卡式停靠窗口 ...
- GLIBC中的库函数fflush究竟做了什么?
目录 目录 1 1. 库函数fflush原型 1 2. FILE结构体 1 3. fflush函数实现 2 4. fclose函数实现 4 附1:强弱函数名 5 附2:属性__visibility__ ...
- Linux下进程间通信方式——信号量(Semaphore)
1.信号量 信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据 ...
- CF888G XOR-MST trie,贪心
CF888G XOR-MST 链接 CF888G 思路 trie上贪心,先左右两边连边,再用一条边的代价连起左右两颗树.因为内部的边一定比跨两棵树的边权笑,显然是对的. 代码自己瞎yy的.启发式合并 ...