mybatis源码我已经下载好了,给大家放到了我的gitee上了,里面可能还有一些理解不到位的地方,请大家多多谅解,也请各位大佬给我提出问题,我好及时改正。
https://gitee.com/studyzhai/mybatis-source-code-analysis.git
那么我们看看mybatis是如何执行的吧
// 1. 读取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取Mapper
AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
List<Author> authors = mapper.selectAllAuthors("1");
System.out.println(authors);
// 6. 提交事物
sqlSession.commit();
// 7. 关闭资源
sqlSession.close();
inputStream.close();
首先我们看第一行代码,就是加载资源文件下的一个文件,mybatis-config.xml这也是mybatis的核心配置文件。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://换成自己的数据库"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/AuthorMapper.xml"/>
</mappers>
</configuration>
这是一个很简单的的配置,就配置了一下数据库的连接,和一个mapper那我们看看mybatis还有那些配置吧!地址:https://mybatis.net.cn/configuration.html 大家自行到官网看。我就不具体描述了。
那么我们回到第一行代码,这个没有啥好说的,就是去加载一个xml配置文件,获取一个输入流。
第二行代码,创建SqlSessionFactory工厂
// 调用重载方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 解析mybatis配置文件(mybatis-config.xml)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//通过这个方法去解析的mybati核心配置文件
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 构建SqlSessionFactory工厂
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
1.将读到的流,转化成一个XMLConfigBuilder的对象
2.build方法构建SqlSessionFactory工厂,默认是DefaultSqlSessionFactory
3.parser.parse()方法将核心配置文件,转化成一个大的配置对象,然后交给SqlSessionFactory工厂
配置文件的解析
public Configuration parse() {
// 防止配置文件被多次解析
if (parsed) {
throw new BuilderException("Each MapperConfigParser can only be used once.");
}
parsed = true;
// 解析核心配置文件
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 分别解析root下的每一个根标签
private void parseConfiguration(XNode root) {
try {
// 这些属性就是root标签下可配置的所有属性
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 核心配置文件对应的mappper处理
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
大家看这些标签和mybatis官网给的配置是不是一一对应,看上面那张图。
具体每个标签如何解析的我就不不墨迹了,大家自己看。这样我们就获取了一个SqlSessionFactory工厂,并且把配置文件封装成一个类传给了工厂。
从工厂中获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
大家看这里在上面bulid的时候new 的是一个DefaultSqlSessionFactory工厂,所以这边我们进入DefaultSqlSessionFactory来看一下
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
设置默认的执行器是SIMPLE
/**
*
* @param execType 执行器
* @param level 设置事务隔离级别
* @param autoCommit 数据库是否自动提交
* @return
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 从配置文件种获取使用数据源的环境
final Environment environment = configuration.getEnvironment();
// 工厂设计模式设置事务管理
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
那么就从工厂中获取到了SqlSession,并且设置执行器、事务管理、autoCommit
生成代理对象
AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
// 这个地方是在解析配置文件的时候赋值的
private Set<Class<?>> knownMappers = new HashSet<Class<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
// 通过代理生成代理mappper实体
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 如果这个地方拿不到类类型对于的mapper接口,这个mapper 就没被注册进去 抛出异常结束
if (!knownMappers.contains(type))
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
// 重点代理方法生产代理对象 如果找到了,就去创建对应的代理对象.
return MapperProxy.newMapperProxy(type, sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// 代理对象的生成
/**
* 通过jdk动态代理生成对应的代理对象
*/
@SuppressWarnings("unchecked")
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
// 动态代理所必须使用的类加载器
ClassLoader classLoader = mapperInterface.getClassLoader();
// 这个目前没有看懂为啥是要一个类类型的数组
Class<?>[] interfaces = new Class[]{mapperInterface};
// 构建mapper代理
MapperProxy proxy = new MapperProxy(sqlSession);
// 使用jdk动态代理生成代理对象
/**
* 第一个参数 是一个是类加载器
* 的二个是要被代理的接口类类型
* 第三个是代理对象 也就是MapperProxy
*/
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
关于动态代理的设计模式,我希望大家去学习一下,这个对于我个人而言,是mybais源码中最难的一块了,理解了这个地方其他的就很简单了,那么我们接着往下看,在调用我们的查询方法的时候就会调用
List<Author> authors = mapper.selectAllAuthors("1");
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 获取接口类
final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
// 封装 mapperMethod对象
final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
// 执行sql
final Object result = mapperMethod.execute(args);
if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
哈哈这就差不多是整个mybatis的设计思路,具体的如何执行的查询,大家可以按着我这个思路往下看看,那么今天的初识mybatis源码就到这里了。
最后把自己的用到的表结构给大家