Mybatis架构层次
Mybatis的层次结构
MyBatis源码分析
先看传统方式开发
-
初始化,加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 此方法会调用到SqlSessionFactoryBuilder的build // build方法会调用XmlConfigBuilder的parse SqlSessionFactroy sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
xml解析,将xml配置解析到Configuration对象中,可以说Configuration就是对应的就是xml文件。
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文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
MappedStatement是什么?
Mapper文件中的select/update/insert/detele,每个标签可以对应一个MappedStatement对象。在上面进行xml解析时,会将mappers标签解析,解析mapper文件,将mapper文件里的每个节点解析为MappedStatement。然后存储在Configuration对象的mappedStatements属性中,mappedStatements是一个HashMap。到此,xml完全解析成了configuration,最后,传入DefaultSqlSessionFactory。
- 执行SQL流程
继上面获得factory后,我们可以开始执行sql。
SqlSession sqlSession = factory.openSession();
// 这里传入了naemspace+id
List<User> list = sqlSession.selectList("com.xujz.mapper.UserMapper.getUserByName")
factory可以获得一个SqlSession,SqlSession是Mybatis中用于和数据库交互的顶层接口,常用的就是DefaultSqlSession。DefaultSqlSession中两个最重要的参数,configuration和executor。Executor也是一个接口,它有三个常用的实现类,默认使用SimpleExecutor。
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
执行sqlSession中的api
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用Executor中的方法处理
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();
}
}
继续分析Executor.query()
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;
}
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;
}
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();
// 传入参数创建StatementHandler对象来执行查询
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象,prepareStatement会调用Param
stmt = prepareStatement(handler, ms.getStatementLog());
// StatementHandler进行stmt执行并处理resultSet
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// getConnection方法,从连接池中获得连接
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置stmt参数
handler.parameterize(stmt);
return stmt;
}
看看parameterize方法
public void parameterize(Statement statement) throws SQLException {
// 调用parameterHandler,和之前的呼应上了
parameterHandler.setParameters((PreparedStatement) statement);
}
看看setParameter方法
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 每一个Mapping都有一个TypeHandler,根据TypeHandler来对preparedStatement进行设置参数
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
到此,一个stmt就已经完全生成好了。
之后将stmt传入statementHandler的query方法。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行stmt
ps.execute();
// 将执行后的结果封装
return resultSetHandler.handleResultSets(ps);
}
Mapper代理方式
我们可以只创建接口,然后让mybatis扫描我们的接口,通过接口可以直接进行访问。
为什么我们是有接口,没有实现方法却可以用?
我们在配置<mappers>
标签时,会配置package
当载入配置文件时,判断解析到接口,会建立此接口对应的MapperProxyFactory对象,存入HashMap中。
sqlSession.getMapper(UserMapper.class)获取到的是什么?
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 {
// 通过动态代理工厂生成实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// JDK的动态代理,mapperProxy是代理类,被代理的接口就是UserMapper
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
返回实例后,调用接口的方法,实际上就是调用代理类的invoke方法。看看代理类的构造方法和代理类的invoke方法。
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
// 可以看到还是用的sqlSession去做底层实现
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
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 (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
重点是mapperMethod.execute(sqlSession, args),根据mapper的标签,进行不同的代码流程。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// 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;
}
MyBatis缓存
mybatis一级缓存
mybatis的一级缓存是和sqlSession绑定的。
清理缓存:
创建缓存:
Executor负责创建缓存,缓存的Key是CacheKey对象。
一级缓存的逻辑:
查询,如果不存在此key的话,查询数据库,将数据库查询后的结果存入缓存中。如果有更新,删除等事务,则把缓存淘汰掉。
二级缓存
二级缓存的原理和一级缓存原理一样,但是二级缓存是基于namespace,namespace就是在mapper文件中定义的,一个mapper文件就会对应一个namespace。所以多个sqlSession可以公用二级缓存。二级缓存默认是关闭的,需要配置开启。查询的结果对象需要实现序列化接口,因为二级缓存不一定存储在内存之中。
Mybatis插件
插件对于mybatis来说就是拦截器,增强了四大核心对象Executor, StatementHandler, ParameterHandler, ResultSetHandler。
Mybatis所允许拦截的方法如下
- 执行器Executor(update,query,commit,rollback等方法)
- SQL语法构建器StatementHandler(prepare,parameterize,batch,update query等方法)
- 参数处理器ParameterHandler(getParameterObject,setParameters方法)
- 结果集处理器ResultSetHandler(handleResultSets,handleOutputParameters等方法)
Mybatis插件原理
在四大对象创建的时候,每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);2.获取到所有的interceptor(插件需要实现的接口),调用interceptor.plugin(target)返回target包装后的对象。3.动态代理,拦截了四大对象的每一个执行。
以ParameterHandler为例子
pluginAll,会遍历所有Interceptor对target进行层层代理。这就是插件的运行原理。