在我们工作中一般都是spring + mybatis 组合使用,试想,如果单独使用Mybatis Api如何使用呢?
@Test
public void testUserFind() throws IOException {
//1.读取mybatis全局配置文件,创建sqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.创建sqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
//3.获取mapper 代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
//4.执行查询
User user = mapper.findUserById(1);
System.out.println(user);
} finally {
session.close();
}
}
以上就是原生Mybatis的api使用过程。从这里我们可以得知几个重要的对象:
SqlSessionFactoryBuilder : 创建 SqlSessionFactory
SqlSessionFactory : 用户创建sqlSession
SqlSession: 操作数据库的api门面和获取mapper等功能。
接下来我们就一步一步来分析这几个步骤都做了什么事。全部缕清楚后就明白Mybatis的运行原理
第一步:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
我们主要看 build 方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//调用parse()进行解析处理返回ConfigBuiler,再由build()方法创建sqlSessionFactory
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.
}
}
}
//1.构建 XMLConfigBuilder 对象
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
// 2. 调用parse()方法查询解析
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// configuration是mybatis-config.xml顶级标签,
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//2.1
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.xml
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//3.创建 SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
主要做了三件事:
1.根据配置文件流创建 XMLConfigBuilder 对象
2.然后调用parse()方法对 mybatis-config.xml 文件一级标签进行解析,如:<settings>,<typeAliases>等
3.调用build方法创建 SqlSessionFactory
在解析方法中我们主要看下mappers 相关的解析。
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");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//每一个mapper.xml由一个XMLMapperBuilder 对象来进行解析处理
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析mapper.xml
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
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<?> 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.");
}
}
}
}
}
可以看到这里又创建 XMLMapperBuilder 来负责解析我们的xxxMapper.xml。 看下是如果解析的:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//我们看下这里
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//定义的结果集解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//解析我们定义的增、删、改、查标标签内容
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
在 configurationElement() 方法中可以看到不同标签交给了不同方法去处理解析,我们主要看下 select | insert | update | delete 的解析:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// mapper.xml中定义的每一个 增、册、改、查 statement 交给每个XMLStatementBuilder 对象来处理
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//进行解析处理
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
这里又创建一个 XMLStatementBuilder 对象来专门处理,每上select | update 等标签称为 Statement 。具体解析xml的代码就不在这里贴出来了,要我们要关注的是解析完mapper.xml做了什么事儿:
public void parseStatementNode() {
//获取标签里定义的各属性值
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
.....
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//将结果增加到MappedStatement容器中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout, String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {
.....
// 构建一个mappedStatement
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
//添加到Configuration 的map结合中,id为key,对象为value
configuration.addMappedStatement(statement);
return statement;
}
把解析出来的属性值封装成 MappedStatement 对象,然后注册到Configuration的MappedStatement集合容器中(Map<statementId, MappedStatement>)。
其他的解析也是类似,解析-封装-注册到Configuration对象中.
我们再回到上面的一段代码:
XMLMapperBuilder.parse()方法:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//看这里
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
我们看下bindMapperForNamespace()方法:
// MapperRegistry 类
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//根据namespace获取到我们定义mapper.java 的字节码
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!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);
//注册mapper
configuration.addMapper(boundType);
}
}
}
}
//注册每一个mapper对应代理工厂
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//MapperProxyFactory 就是用来创建mapper接口的代理对象.
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//使用反射解析mapper.java上定义的注解信息
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
根据xxxMapper.xml中的namespace 来获取xxxMapper.java的字解码,然后也注册 到Configuration的集合容器中。
至此我们的 SqlSessionFactory 对象就创建完成了,里面持有Configuration 对象。
小结:
不管是对xml解析还是xxxMapper.java绑定,最终都把结果注册到Configuration对应的容器集合中。主要目的就是初始化基础信息,为后续操作便使用。
第二步:
SqlSession session = sqlSessionFactory.openSession();
根据sqlSeseesion工厂(DefaultSqlSessionFactory)创建一个sqlSession对象:
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);
//创建Executor
final Executor executor = configuration.newExecutor(tx, execType);
//创建sqlSession对象
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();
}
}
根据 mybatis-config.xml 配置的环境创建事物,然后根据事物对象和executor类型去创建一个Executor对象。我们看下executor的有哪几种类型
我们看 Executor 接口定义就能明白,其实 Executor 才是真正去和数据库打交道的,他被SqlSeesion包装代理。
CachingExecutor:当我们在配置文件中开启二级缓存时会使用CachingExecutor对象,他是典型的装饰模式,里面包装了BaseExecutor对象。在操作前后进行缓存的相关操作。
SimpleExecutor 、ReuseExecutor、BatchExecutor的主要区别就是Jdbc的Statement使用上不同,ReuseExecutor 类中使用map集合维护了每一个Statement,可重用对象,SimpleExecutor则是每次执行sql都使用Jdbc的api创建一个新的 Statement 对象 。 BatchExecutor 是批量去执行update 操作的Statement。
最后根据configuration 、executor 去创建一sqlSession对象(DefaultSqlSession)返回。
第三步
UserMapper mapper = session.getMapper(UserMapper.class);
根据类型获取 mapper对象。这里获取到的肯定是一个动态代理生成的对象,因为我们只定义UserMapper接口,并没有实现类。
//DefaultSqlSession 类
@Override
public <T> T getMapper(Class<T> type) {
//从configuration获取Mapper
return configuration.getMapper(type, this);
}
//Configuration 类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从mapperRegistry类中获取。还记得我们第一步的注册嘛,就是mapper代理工厂注册这个类容器中
return mapperRegistry.getMapper(type, sqlSession);
}
//MapperRegistry 类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根据字节码类型获取mapper代理工厂,来创建代理对象
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);
}
}
根据mapper.class获取对象的mapperProxyFactory 对象 ,来创建mapper的代理对象。
我们看是他是如果创建代理对象的:
// MapperProxyFactory 类
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
// MapperProxyFactory 类
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用 jdk的动态代理来创建的
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
根据mapper接口创建代理mapper对象,那么在我们调用mapper接口方法时就会执行invoker方法,所以我们看下 InvocationHandler的 invoke() 方法逻辑:
// MapperProxy 类
@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 if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//获取到方法对应的处理器
final MapperMethod mapperMethod = cachedMapperMethod(method);
//只想方法逻辑
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod 类
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
可以看到 MapperMethod.execute()方法获取到参数然后又回到sqlSession api的调用。
第四步
User user = mapper.findUserById(1);
由第三步可以知道,我们调用mapper的方法时会进入到InvocationHandler的 invoke() 方法,也就是MapperMethod.execute()里面,在这里会调用sqlSession对应的api。因为我们这里是查询操作且是seleceOne,所以最终会执行sqlSession.selectOne()方法:
//command.getName获取到就是当前方法的StatementId --> 类全名.findUserById
result = sqlSession.selectOne(command.getName(), param);
// DefaultSqlSession 类
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//调用selectList方法,取第一条记录
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
// DefaultSqlSession 类
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据statement签名获取到对应的对象(第步一解析好的对象)
MappedStatement ms = configuration.getMappedStatement(statement);
//调用executor.query 执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement对应就是我们第一步解 <select id="findUserById">..</select> 的结果对象。
然后调用Executor.query 方法执行sql的查询处理:
// BaseExecutor 类,如果开启二级缓存会先进入到CachingExecutor 类的query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取到sql
BoundSql boundSql = ms.getBoundSql(parameter);
//一级缓存的key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// BaseExecutor 类
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//先尝试一级缓存获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//数据库获取
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// BaseExecutor 类
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
//SimpleExecutor 类
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//这里会创建参数处理器和结果处理器,用于处理我方法参数到sql参数的处理,返回记录到对象的映射处理
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// PreparedStatementHandler 类
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//最终调用jdbc ps.execute()执行查询
ps.execute();
//使用反射完成行记录到对象的映射处理
return resultSetHandler.handleResultSets(ps);
}
到这里就完成了整个selectOne的查询,将结果交给ResultHandler 进行处理,返回结果数据的集合。