目录
前言
mybatis的启动流程主要包括sqlSessionFactory,selSession和mapper代理对象的创建,本文将从源码角度分析,在接下来的文章中还会分析在spring参与的情况下启动流程的变化。
一、主程序
本文基于mybatis3.5.5版本,首先基于config文件创建了SqlSessionFactory,然后创建SqlSession,最后获取Mapper的代理对象,执行相关的查询,修改方法,本文主要对获取Mapper代理对象的过程进行分析,关于Mapper方法执行流程会在以后的文章中给出。
public static void main(String[] args) throws Exception{
//1、读取配置文件
String resource = "config/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2、初始化mybatis,创建SqlSessionFactory类实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession(true);
UserMapper userMapper = session.getMapper(UserMapper.class);
}
二、SqlSessionFactory创建
1.SqlSessionFactory的创建过程
SqlSession的创建首先需要对config文件进行解析,在解析的过程中基本就是对mybatis-config.xml文件中的所有元素进行解析,然后生成configure,本文针对其中的Mapper解析过程进行分析。
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
/*
解析Mapper 获取执行的sql语句
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2.mapper.xml加载
mapper可以通过package,resource,url和mapperClass方式进行配置,本文以resource方式为例。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 从resource中获取
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析注解注入的sql语句 @Select
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//从url中获取
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 从class中获取
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
首先判断当前资源是否已经被加载过,如果加载过不做处理,没有加载过,加载文件,解析其中的mapperStatement,设置当前资源为已加载,最后解析mapperk.xml对应的nameSpace。
public void parse() {
//判断当前资源是否加载过,避免重复加载
if (!configuration.isResourceLoaded(resource)) {
//将mapper.xml文件中的语句添加到configure的statements中
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//解析mapper对应的nameSpace
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
3.解析xml文件对应的接口
解析mapper.xml中的nameSpace对应的java接口,这里也有避免重复加载的逻辑。
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {//根据namespace找到对应的java类
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
//避免重复加载
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//加载java类对应的mapperStatement
configuration.addMapper(boundType);
}
}
}
对mapper接口进行解析,首先加载当前mapper接口对应的mapper.xml,到这里可以看到,在解析mapper.xml时会对其对应的mapper接口进行解析,在解析mapper接口的时候也会解析mapper.xml,所以mybatis在这里使用了避免重复加载的逻辑。遍历接口中的每个方法,如果方法的上面带有@Select,@update等相关注解,生成mapperStatement,加入到configure中。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析mapper类
parser.parse();
loadCompleted = true;
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//加载java类对应的xml资源,并进行解析
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
//对方法上的注解中的sql语句进行处理
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
三、SqlSession创建
在获取sqlSession的过程中,首先是创建事务,设置相关属性。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取transaction 事务
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器 executor
/*
mybatis有三种执行器
SIMPLE(默认), REUSE, BATCH
*/
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();
}
}
四、Mapper代理对象创建
最后使用mapperProxyFactory创建mapper接口的代理对象,关于代理对象的详细内容会在以后的文章中给出。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//创建mapper的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
总结
提示本文主要对mybatis的启动流程进行分析,主要是SqlSessionFactory,SqlSession和Mapper代理对象的创建过程,接下来将分析在有spring参与的情况下,相关流程的变化。