前言:
Start:2020.4.25 End2020.11.4 1:24
在看本文章前,需要你对JDK动态代理有所了解,
我的另一篇文件种详细讲解了JDK动态代理源码
地址:https://blog.csdn.net/qq_37391371/article/details/109460734内容:
mybatis源码分析+手写简化版mybatis
[根据目录选择你需要的内容查看]
Mybatis源码
源码流程宏观
Mybatis替代了传统JDBC实现了相同的功能,那么mybatis也一定有经历了以上与数据库交互的必要操作:
-
数据源信息从哪里来?
解析mybatis的主配置文件得到DOM文档对象模型,根节点存入Configuration对象中,其属性储存每一个元素节点,属性节点和文本节点,其属性名与主配置文件中的标签名相同
-
sql语句从哪里来?
解析mybatis的主配置文件拿到mapper映射文件,根据namespace+id生成dao接口的实现类代理对象
-
与数据库交互操作如何实现?
dao接口实现类代理对象属性中装配有数据源,可以连接数据库;其方法是根据mapper映射文件实现的,每个方法有其要执行的sql,并对返回值进行了解析.
源码流程微观
public static void main(String[] args) throws IOException {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//核心代码1 //build(inputStream)方法执行到最后关闭了流
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//核心代码2
SqlSession sqlSession = factory.openSession();
//核心代码3
//方式一:类似原生jdbc的preparedStatement.execute()
Blog blog = session.selectOne("xzq.BlogMapper.selectBlog", 1);
//方式二:利用Dao接口实现类的代理对象(这种方式留给给后面单独探究)
//BlogMapper blogMapper = session.getMapper(BlogMapper.class);
//Blog blog = blogMapper.selectBlog(1);
}
SqlSessionFactoryBuilder.build(is)
Mybatis主配置文件流---------->sqlSessionFactoryBuild.build(流)核心操作--------->返回一个Sql会话工厂
因为build(is)设计到流的操作,可以推测build(is)解决的问题如下:
- 如何解析mybatis主配置文件中的标签内容所包含的信息?
- 解析出信息后又是如何保存在内存中的?
- 信息保存在内存中后又根据这些信息做了什么工作?
- 解析Environment标签和Mapper标志最为重要,因为这两个标签包含的信息涉及到建立数据库连接和执行SQL语句
//核心代码1
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
核心操作是什么?就是对主配置文件进行解析,取出所有配置信息封装进Configuration对象及其属性存入内存
解析数据源:
一、mybatis是如何获取数据库源的
org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parse
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration
》org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement
》org.apache.ibatis.builder.xml.XMLConfigBuilder.dataSourceElement
》org.apache.ibatis.session.Configuration.setEnvironment#######
解析Mapper:
给Dao接口方法指定SQL语句方法有哪几种?
-
mapper映射文件中:namespase-id+SQL语句
-
给dao接口方法上标注解
Mappers文件有几种方式???
源码分析–Mappers标签的处理
package和mapperClass都是加载基于注解的Dao接口
不管是通过拿种方式( )指定mapper(xml映射文件,Dao接口),上面这段代码表明,mybatis都会优先尝试操作mapper.xml映射文件
像mybatis主配置文件又XMLConfiguration类处理一样,mapper映射文件由XMLMapperBuilder处理
二、mybatis是如何获取SQL语句
org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parse
》org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration
》org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement
》org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement
》org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode
>org.apache.ibatis.session.Configuration.addMappedStatement######
sqlSessionfactory.openSession();
//核心代码2
SqlSession sqlSession = factory.openSession();
根据Configuration对象的属性environment,创建一个sql会话(执行器,事务管理器,数据源信息),通过sql会话我们与数据库创建连接进行通信
从数据源中开启一个sql会话
mybatis执行器如下
总结:
openSession()方法中对DOM中的environment元素节点中的信息成分利用.并最终实例化了一个装配有事务管理器和SQL语句执行器的SQL会话(数据源->事务->执行器=sql会话)
Mybatis如何执行Sql语句源码
方式一:SqlSession中的通用模块方法
//核心代码3
//方式一:mybatis封装的通用模板方法,类似原生jdbc的preparedStatement.execute()
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 1);
注意上面代码没有生成Dao接口实现类的代理对象,执行过程类似于原生JDBC的preparedStatement.execut()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u3WOCN53-1604422499008)(C:\Users\80685\AppData\Roaming\Typora\typora-user-images\image-20201031193603213.png)]
三、通过SqlSession中的通用模板方法执行sql的过程
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession()
》org.apache.ibatis.session.Configuration.newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
》org.apache.ibatis.executor.SimpleExecutor
》org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(java.lang.String, java.lang.Object)
》org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(java.lang.String, java.lang.Object)
》org.apache.ibatis.executor.CachingExecutor.query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
》org.apache.ibatis.executor.CachingExecutor.query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
》org.apache.ibatis.executor.BaseExecutor.queryFromDatabase
》org.apache.ibatis.executor.SimpleExecutor.doQuery
》org.apache.ibatis.executor.statement.PreparedStatementHandler.query
》org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets
preparestatement.execute() 最终"关闭"资源,sqlsession.selectOne()底层就是远程jdbc.
以上步骤流程图
方式二:Dao接口实现类的代理对象
//发送二:利用Dao接口实现类的代理对象
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
Blog blog = blogMapper.selectBlog(1);
详细查看一下这个mapperProxy的信息:
先总结一下:
JDK动态代理 Proxy.newInstance(classLoader,interfacesType,invocationHandler)
-
interfaceTypes:BlogMapper.class
-
invocationHandler:mapperProxy
但是目标类是谁?目标了的目标方法肯定是参与执行sql语句的.
哪一个对象有这个功能?没错,就是sqlsession,它就是目标类对象
但是目标对象和代理对象要实现相同的接口,sqlsession实现了BlogMapper接口?
来看看JDK动态代理的步骤
一样的顺序!只不过人家通过了InvocationHandler实现类就是MapperProxy!
Blog blog = blogMapper.selectBlog(1);
总结:
首先方式二与方式一没有很大区别,底层一样!
底层?
其实劳了一圈经过JDK动态代理,最后还是调用sqlSesion.selectOne(sql,param)方法!
org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper
-
org.apache.ibatis.session.
Configuration.getMapper
-
org.apache.ibatis.binding.
MapperRegistry.getMapper
-
org.apache.ibatis.binding.
MapperProxyFactory.newInstance(SqlSession)
-
Proxy.newProxyInstance(mp.getClassLoader(),new Class[]{ mapperInterface },mapperProxy);
[Spring AOP cglib java动态代理]
- org.apache.ibatis.binding.
MapperProxy
.invoke
- org.apache.ibatis.binding.
-
-
-
Mybatis底层是JDK动态代理,设计到JDK动态代理就有如下问题:
- 接口? Dao接口
- 目标类? SqlSession
- InvocationHandler实现类? MapperProxy
- 代理类对象(JDK动态代理帮我们在内存中生成)
手写简易版Mybatis
根据以上源码总结,手写如下简化版Mybatis.
package xzq;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Select {
String[] value();
}
package xzq;
//1.接口
public interface UserMapper {
@Select({"select id,name from user where id = #{id}"})
public User getUserById(int id);
}
package xzq;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// invocationHandler实现类
public class MapperProxy implements InvocationHandler {
SqlSession targetObject;
public MapperProxy(SqlSession targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Select annotation = method.getAnnotation(Select.class);
String sql = annotation.value()[0];
System.out.println("sql语句是:"+sql+"----参数是:"+args[0]);
Object user = method.invoke(targetObject, args[0]);
return user;
}
}
实体类:对应数据库表中一条记录
package xzq;
//实体类
public class User {
public int id;
public String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
测试类:
package xzq;
public class HandWriteMybatisTest {
public static void main(String[] args) {
//1.创建目标类对象
SqlSession sqlSession = new SqlSession();
//2.创建InvocationHandler实现类(同时传入目标类对象) 这个步骤在mybatis中是内部实现的,我们这里也在sqlsession内部实现
//3.获取代理对象
UserMapper UserMapper = (xzq.UserMapper) sqlSession.getMapperProxy(UserMapper.class);
//执行操作
User user = UserMapper.getUserById(1);
System.out.println(user);
}
}