mybatis源码解析Mapper动态代理的实现
mybatis是通过XML配置的方来定制化 SQL、存储过程以及高级映射等。
用mybatis框架的执行步骤:
主要的代码
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlsessionfactor = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sqlsessionfactor.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
1.Resources
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
第一步的是进行io操作,对xml的操作一个io操作。
这行代码的主要的作用就是将路径下的资源读取到流中
是Mybatis的一个查询数据库数据的代码,通过mapper找到配置文件的sql语句,执行sql语句获取数据。
通过getResourceAsStream 读取资源文件 ,在读取的过程中调用classLoaderWrapper中的方法,然后通过对
classLoader[]数组的遍历 ,然后进行判断类加载器中所读的流是否为null,如果不为null则返回InputStream对象
第一:
Resources 资源工具类
主要作用: 把路径下的资源文件读取到流中
通过getResourceAsStream 读取资源文件
在读取的过程中 调用 classLoaderWrapper中的方法
然后通过对classLoader[]数组的遍历 然后进行判断 类加载器中所读的流是否为null
如果不为null 则返回InputStream对象
2. SqlSessionFactory 与 SqlSession
SqlSessionFactory sqlsessionfactor = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sqlsessionfactor.openSession();
这边是以 SqlSessionFactoryBuilder 去创建 SqlSessionFactory,从表面上来看,咱们都是通过SqlSession去执行sql语句
那么如何怎么获取SqlSession呢?
(1)我们就先从SqlSessionFactoryBuilder入手, 咱们先看看源码是怎么实现的:
public class SqlSessionFactoryBuilder {
//Reader读取mybatis配置文件,传入构造方法
//除了Reader外,其实还有对应的inputStream作为参数的构造方法,
//这也体现了mybatis配置的灵活性
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
//mybatis配置文件 + properties, 此时mybatis配置文件中可以不配置properties,也能使用${}形式
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//通过XMLConfigBuilder解析mybatis配置,然后创建SqlSessionFactory对象
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//下面看看这个方法的源码
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
这边是以SqlSessionFactoryBuilder 通过XMLConfigBuilder 去解析我们传入的mybatis的配置文件。
(2)当我们获取到SqlSessionFactory之后,就可以通过SqlSessionFactory去获取SqlSession对象。源码如下:
/**
* 通常一系列openSession方法最终都会调用本方法
* @param execType
* @param level
* @param autoCommit
* @return
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//之前说了,从表面上来看,咱们是用sqlSession在执行sql语句, 实际呢,其实是通过excutor执行, excutor是对于Statement的封装
final Executor executor = configuration.newExecutor(tx, execType);
//关键看这儿,创建了一个DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} 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对象了。接下来就是是执行sql语句了。
SqlSession拿到了,就可以调用SqlSession中一系列的select..., insert..., update..., delete...方法进行CRUD操作了。
二、
SqlsessionFactory是mybatis的 核心对象,获取核心对象的方式
SqlsessionFactoryBuilder构建 SqlsessionFactory 实例
通过Xpath解析的方式去解析mybatis-config.xml 文件 解析的文件内容套接到configuration中 而这个configuration 相
当于 mybatis-config.xml 中的配置文件所对应的类。返回DefaultSqlSessionFactory对象。
3.openSession
openSession的最终调用在org.apache.ibatis.session.defaults包下的DefaultSqlsessionFactory中。
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
该方法下有三个参数 第一个执行器的类型 第二个 事务的隔离级别 第三个 是否自动提交
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, autoCommit);
} 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对象 通过DefaultSqlSessionFactory对象调用它里面opensession方法返回DefaultSQLSession对象
调用的是DefaultSqlSessionFactory类中opensessionFromDataSource
该方法下有三个参数 第一个执行器的类型 第二个 事务的隔离级别 第三个 是否自动提交
声明一个事务的对象,通过配置文件去读取环境标签的信息,然后通过环境去获取事务工厂对象,通过dataSource的配置获取事务的对象,再根据事务执行器的类型 去创建事务执行器 Executor(相当于Statement)通过执行器 事务自动提交以及配置文件对象 返回 DefaultSqlSession对象。
4.MapperProxy
在mybatis中,通过MapperProxy动态代理我们的dao,
当执行自己写的dao里面的方法的时候,其实是对应的mapperProxy在代理。那么,咱们就看看怎么获取MapperProxy对象吧:
(1)通过SqlSession从Configuration中获取。源码如下
/**
* 什么都不做,直接去configuration中找, 哥就是这么任性
*/
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
(2)SqlSession把包袱甩给了Configuration, 接下来就看看Configuration。源码如下
/**
* 烫手的山芋,俺不要,你找mapperRegistry去要
* @param type
* @param sqlSession
* @return
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
(3)Configuration不要这烫手的山芋,接着甩给了MapperRegistry, 那咱看看MapperRegistry。 源码如下:
/**
* 烂活净让我来做了,没法了,下面没人了,我不做谁来做
* @param type
* @param sqlSession
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//能偷懒的就偷懒,俺把粗活交给MapperProxyFactory去做
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//关键在这儿
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
(4)MapperProxyFactory,最终交给它去做了。咱们看看源码:
/**
* 别人虐我千百遍,我待别人如初恋
* @param mapperProxy
* @return
*/
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//动态代理我们写的dao接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
四、通过sqlsession获取相应Mapper
第一层通过调用sqlsession中getMapper方法
第二层通过调用配置中的getMapper方法
第三层通过映射的注册器中的getMapper方法来进行获取相应的Mapper对象
其中使用了MapperProxyFactory 工厂对象来获取 MapperProxy 在里面使用反射和动态代理的方式来获取最终的mapper对象。
通过以上的动态代理,咱们就可以方便地使用dao接口啦:
UserMapper mapper = session.getMapper(UserMapper.class);
User insertUser = new User();
总结:
- 首先会读取mybatis-config.xml配置文件及Mapper.xml映射文件。
- 从以上配置文件中解析各个标签中的信息,并将信息注册到Configuration实例中,同时还会初始化默认的参数。
- 通过DefaultSqlSessionFactoryBuilder#build(Configuration config)实例化DefaultSqlSessionFactory,并将Configuration传递给它。并通过Configuration中的连接池、事务隔离级别,以及executor和autoCommit等参数实例化SqlSession接口的实现类DefaultSqlSession。
- DefaultSqlSession具体实现了SqlSession中定义的接口。并通过自己持有的Executor接口,委托具体的Executor去执行sql语句完成具体的CRUD操作。