构建sessionFactory
以下解析的是mybatis的一个构建解析流程,在创建会话工厂和拿到具体会话到拿到具体代理对象的一个过程,其实该流程当中最为总要的是创建Configuration.class对象和解析的一个过程。最终所有的核心配置都会被封装成该对象。本文是自己在阅读源码当中的一些领悟整理成文字分享给大家,有什么讲解分析错误的地方希望大家指出。
这里是我写的一个入口程序,由该案例来深入的解析执行流程。
public static SqlSession getSqlSession() throws IOException {
//获得核心文件配置
InputStream resourceAsStream = Resources.getResourceAsStream("sqlUserMapper.xml");
//获取session工厂对象,默认创建的是DefaultSqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//获得session会话对象,这里默认创建的是DefaultSqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
在获取到我们的核心配置文件之后,我们会构建一个sessionFactory工厂对象来获取我们的session来进行操作。执行SqlSessionFactoryBuilder().build(resourceAsStream)方法创建一个session工厂。
/**
* 最终调用这个build进行一个对象创建
* @param inputStream 这里解析的是我们的核心配置文件
* @param environment
* @param properties
* @return
*/
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//构造解析配置文件parser.parse()该方法进行一个配置文件的具体解析与构造,默认创建的是DefaultSqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
}
...忽略不核心代码
}
这里我们将对象构造成了XMLConfigBuilder类我们关注下它的构造方法。这里进行了核心配置类的一个初始化Configuration.class,后期我们将围绕该类进行一个全面讲解,Configuration.class是整个mybatis的核心类。里面封装了mybatis的所有数据配置。
/**
* 这里对流构造构造成了XPathParser对象
* @param parser
* @param environment
* @param props
*/
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//在父类的构造方法创建了Configuration对象,该对象是整个mybatis的核心对象
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
上面是我讲解到的初始化XMLConfigBuilder.class做的一些事情,这里主要讲解到了Configuration的一个初始化,其实Configuration.class初始化为我们做了很多事情,一些组件的初始化都是在这个时候进行构建的。这里不做详细的讲解。这里我们关注:build(parser.parse())该方法。执行parser.parse()方法会返回一个已经绑定了整个配置文件配置的Configuration.class。全局配置文件。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析配置文件,这里解析的就是我们配置文件配置的每个标签,绑定到Configuration.class当中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 解析配置文件
* @param root
*/
private void parseConfiguration(XNode root) {
try {
//这里我们的properties配置文件的一个解析,里面配置了我们对mybatis的一些数据源配置
propertiesElement(root.evalNode("properties"));
//这个配置文件配置着我们需要开启一些格外的配置,例如二级缓存,还有延迟加载和log工厂等配置,这里配置mybatis的运行规则
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//这里要说明下,在我们使用mybatis的时候都会使用自定义的一个日志框架来记录,前提是否开启这个配置,这个地方就是开启定义的日志配置
loadCustomLogImpl(settings);
//定义别名解析别名,这里主要解析我们定义的别名映射解析该标签
typeAliasesElement(root.evalNode("typeAliases"));
//解析我们配置的拦截器,这里拦截器可以配置多个
pluginElement(root.evalNode("plugins"));
//这里配置的是对象工厂,我们可以自己定义一些对象的实现工厂主要争对实体的一些作用
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
/**
* 设置默认的配置,其中最为重要的就是一些缓存配置,这里mybatis为我们默认开启了二级缓存
* configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
* 这里的配置改变mybatis的运行规则
*/
settingsElement(settings);
// 解析我们配置的数据库的链接驱动,创建对象的数据源
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//这里配置我们的类型处理器,可以配置多个
typeHandlerElement(root.evalNode("typeHandlers"));
//这里就是扫描我们具体的映射sql的配置文件或映射mapper
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这里我们看build(parser.parse())方法为我们创建一个sessionFactory工厂对象来获取相应的一个会话。这里创建的是DefaultSqlSessionFactory工厂。
public SqlSessionFactory build(Configuration config) {
//这里我们默认创建的是DefaultSqlSessionFactory会话工厂
return new DefaultSqlSessionFactory(config);
}
构建session会话
上面讲解到了构建并得到sessionFactory会话工厂对象,得到工厂对象的可以为我们得到相应的会话对象,来执行操作。构建会话也是一个繁琐的一个解析过程。
SqlSession sqlSession = sqlSessionFactory.openSession();
//这里其实是使用了一个重载的,其实在创建会话的时候可以传递参数,根据具体的参数来创建具体的会话对象
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* 这里就是创建相应的一个会话,默认是DefaultSqlSession
* @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();
//获取事务工厂,这里使用的是JdbcTransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//这里默认创建的是JdbcTransaction
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();
}
}
这里我们可以关注这个方法,该方法是为我们创建相应的执行器。后续执行代理操作都需要该执行器为我们执行任务。Executor.class
Executor executor = configuration.newExecutor(tx, execType);该方法是为我们得到相应的执行器,来执行响应操作。源码分析如下:
/** * 这里使用的是简单工厂面试,创建具体的执行器 * @param transaction * @param executorType * @return */ public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; //批处理执行器,用于将多个SQL一次性输出到数据库 if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); //可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。 // 内部的实现是通过一个HashMap来维护Statement对象的 } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } //这里我们默认是开启缓存的,XMLConfigBuilder.class的settingsElement(settings)方法当中mybatis给我们设置了一些默认的熟悉,其中就包括缓存的开启 if (cacheEnabled) { //这里进行了一个装饰,内部还是使用的是我们创建的执行器,只是在我们执行之前会进行查询缓存 executor = new CachingExecutor(executor); } //这里就是我们为什么会被拦截的一个关键步骤,如果我们配置了拦截器,就会给我们的执行器进行一个代理,返回一个代理的执行器 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
得到相应的执行器,构建了默认的会话器DefaultSqlSession
获取mapper代理对象
在上述获取到sessionFactory工厂为我们创建了DefaultSqlSession默认的一个会话器,得到了session会话器我们可以得到我们相应的代理类,让Executor为什么执行相应的操作。以上讲解如有错误点希望大家能够指出,谢谢大家。
以下我们根据会话器来获取我们的一个代理类,这里主要关注点就是:
mapperProxyFactory.newInstance(sqlSession);该方法使用代理工厂为我们创建一个代理类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/**
* 获取代理类
* @param type Mapper interface class 需要代理的对象
* @param <T>
* @return
*/
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获取代理工厂,这里每一个映射类都有一个代理工厂,这里可以观看在构建Configuration的时候进行一个创建
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);
}
}
这里我讲解核心的代理工厂,为我们创建出代理对象,这里可以关注下MapperProxy.class类,该类为动态代理的核心类,实现于InvocationHandler.class接口,这里使用的是Java的动态代理,代理类在执行方法的时候会进行一个调用。
public class MapperProxyFactory<T> {
//这里就是扫描到我们需要被代理的类
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
//这里是创建代理对象,使用jdk的动态代理
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
当前获取到的就是代理对象,这里就是执行的是我们的MapperProxy.class的invoke方法进行一个代理的过程。当前MapperProxy.class内部维护一个内部类PlainMethodInvoker.class类对象,在调用。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
在调用method.invoke(this, args)方法时会调用内部维护的PlainMethodInvoker.class的incoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
走到这一步就开始进行一个准备执行方法级别的数据查询了。以上有什么讲解分析不对的地方希望大家能够指出,谢谢各位大佬的意见。