Mybatis解析
概述
可以直接从github上下载Mybatis源码,这个源码特别好,因为里面包含很多测试代码,下载下来安装依赖之后就可以直接运行。在学习的过程中,结合网上各路资料,debug源码,对学习很有帮助。
之前刚好写了一篇有关于 Java代理 的总结,发现这这里刚好就用上了。
1、介绍了Mybatis部分工作流程:加载配置、代理生成、SQL解析、SQL执行、返回包装结果集。并不涉及到于缓存、插件、日志等一些其它特性,有机会可以补充。
2、介绍了Mybatis-Spring的工作流程。Mybatis可以作为一个单独的框架使用,但在实际应用中,一般是配合Spring一起使用。但是在Spring应用中,怎么可以更方便的使用Mybatis这是一个问题。通过社区提供的Mybatis-Spring插件,可以通过注入的方式使用Mapper,大大的提高了开发效率。
3、结合Hap源码,分析通用Mapper的实现。了解通用Mapper的实现,有利于对Hap中一些API的掌握,提高问题的排查效率。
网上流程的一张Mybatis工作原理图:
image.png
启动
从xml配置文件中读取配置,然后通过SqlSessionFactoryBuilder构建SqlSessionFactory实例(建造者模式)
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSessionFactory是Mybatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory是创建SqlSession的工厂,每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心,同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,在应用执行期间都存在。
当Mybatis成功加载配置文件之后,即执行了 new SqlSessionFactoryBuilder().build(configuration)语句之后(其实调用的 return new DefaultSqlSessionFactory(config)),就已经配置了 Configuration 对象中的一部分内容。 此时的 sqlSessionFactory 对象如下
image.png
SqlSessionFactory提供的方法如下:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
image.png
建立sqlSession
在Mybatis中通过sqlSession提供的API与数据库进行交互。通过sqlSessionFactory获取sqlSession:
// 其实是新建了一个 DefaultSqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession是Mybatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection。SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句.每个线程都应该有它自己的SqlSession实例.SqlSession的实例不能被共享,同时SqlSession也是线程不安全的,绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中.也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Servlet当中的HttpSession对象中.使用完SqlSeesion之后关闭Session很重要,应该确保使用finally块来关闭它。
@Override
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对象 关联了Executor 对象和 Configuration 对象。SqlSession 中提供了一系列的方法:
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
/**
* Clears local session cache
*/
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
getMapper
此方法用于获取一个mapper代理对象。我们知道,我们在应用中定义的都是mapper接口,但是却可以直接注入,这其实都是因为Mybatis为这些接口创建了代理对象。
BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);
// DefaultSqlSession
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
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);
}
}
MapperProxyFactory一看名字我们就知道肯定是一个工厂类,就是为了生成MapperProxy。mapperInterface就是Mapper接口,methodCache就是对Mapper接口中的方法和方法的封装类(MapperMethod)的映射,MapperMethod处理的事情主要就是:处理Mapper接口中方法的注解,参数,和返回值。然后就是2个newInstance,看名字就知道是工厂方法,一个是protected,一个是public,public方法有一个参数SqlSession,SqlSession处理的其实就是执行一次SQL的过程。其实public的newInstance就是new了一个MapperProxy,然后调用了protected的newInstance。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@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);
}
}
看到木有,标准的JDK动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
MapperRegistry中的knownMappers 缓存了所有Mapper接口与MapperProxyFactory的关系。
那么,当我们调用代理对象方法的时候,其内部是怎么执行的呢?这里需要关注一下MapperProxy这个类,它实现了InvocationHandler接口,也就是当我们mapper接口方法的时候,实际上是执行了MapperProxy的invoke方法,其方法如下:
@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 (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 方法,而 execute 方法内部则是调用了sqlSession的方法。
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 = OptionalUtil.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;
}
SqlSession的getMapper就是通过JDK动态代理创建了一个代理类对象,而调用接口方法的时候,实际上执行的是代理对象的方法。即都会执行MapperProxy的invoke方法,然后MapperMethod中的execute方法调用sqlSession中的方法执行真正的sql。所以如果想知道sql的具体执行流程,需要研究sqlSession中的各个方法。
Executor
通过sqlSession调用select、insert等方法的时候,其内部是怎么执行的?
sqlSession.selectOne(String statement, Object parameter);
// 以下三个方法都在 DefaultSqlSession 中
private final Executor executor;
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>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;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
// BaseExecutor 的 query 方法。可以看到query方法先从缓存中获取结果,如果缓存中没有值,就从数据库获取,这里主要关注从数据库获取结果:queryFromDatabase
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@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;
}
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类 是Mybatis执行Mapper语句时默认使用的Executor。以下是 SimpleExecutor类 的doQuery方法实现,发现最后其实是通过 JDBC Statement 来执行SQL的。
@Override
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
// SimpleStatementHandler 类的 query 方法
protected final ResultSetHandler resultSetHandler;
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
// DefaultResultSetHandler 的 handleResultSets 方法。
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
所以,以selectOne为例子,其执行流程如下:
image.png
当然其中还有很多细节:比如通过 BoundSql获取动态执行SQL;通过 handleResultSets 包装结果集等,不在本文解读的范围。
观察了sqlSession中的其它几个方法,可以发现,Mybatis中都是通过 Executor 去执行SQL的,在Executor执行SQL的流程中,先看缓存中是否有值,如果没有则从数据库中获取结果。在从数据库中获取结果的过程中,通过BoundSql获取到动态执行的SQL,然后交给JDBC 的 Statement 对象执行execute方法,最后通过DefaultResultSetHandler的handleResultSets方法包装结果集返回。
有关于Executor 接口的 UML图如下:
image.png
StatementHandler 接口的UML图如下:
image.png
ResultSetHandler 接口的UML图如下:
image.png
加载Mapper
目前可以知道的是:Mapper接口中的方法和configuration中的mappedStatements元素是一一关联的,那么这一部分内容在Mybatis中的执行流程是什么样的?构建器。在Mybatis中可以通过两种构造器将Mapper接口中的方法转化为MappedStatement对象。一个是MapperAnnotationBuilder,通过注解的方式构建MappedStatement;还有一个是XMLMapperBuilder,将xml中定义的内容转化为MappedStatement。这里主要介绍MapperAnnotationBuilder。
MapperAnnotationBuilder的作用就是解析指定的mapper接口对应的Class对象中,包含的所有mybatis框架中定义的注解,并生成Cache、ResultMap、MappedStatement三种类型对象。在创建一个的时候,会关联一个MapperBuilderAssistant对象,这是一个构建助理,实际上,真正将接口中的方法转换成MappedStatement对象就是通过MapperBuilderAssistant对象完成的。
configuration.addMapper(BoundAuthorMapper.class);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
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 {
knownMappers.put(type, new MapperProxyFactory<T>(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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
// 是否谈桥接方法,反射生成的方法是桥接方法,Java的泛型时创建的方法不是桥接方法
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
通过调用MapperAnnotationBuilder的parse方法进行构建。该方法内部会先通过loadXmlResource方法对XML文件构建,然后再通过注解的方式进行构建,主要包括以下注解:
private static final Set<Class<? extends Annotation>> SQL_ANNOTATION_TYPES = new HashSet<>();
private static final Set<Class<? extends Annotation>> SQL_PROVIDER_ANNOTATION_TYPES = new HashSet<>();
static {
SQL_ANNOTATION_TYPES.add(Select.class);
SQL_ANNOTATION_TYPES.add(Insert.class);
SQL_ANNOTATION_TYPES.add(Update.class);
SQL_ANNOTATION_TYPES.add(Delete.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
}
@Select、@Insert、@Update、@Delete、@SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
parseStatement方法才是真正通过注解的方式构建。从代码中可以发现,如果再接口方法中没有使用以上注解,Mybatis将不会执行构建操作。
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
......
}
至此,大体上可以知道Mybatis工作的流程,但在我们实际使用过程中,单独使用Mybatis的机会很少,一般都是和Spring配合使用。但是不难发现,在使用Spring+Mybatis中的应用中,我们根本不需要单独配置一个个Mapper接口,并且还可以通过注入的方式使用Mapper接口,这样大大的简化了我们的开发工作。那么,这是如何是是实现的?
Mybatis-Spring
Mybatis只是一个独立的框架,并没有提供与Spring整合的方法,为了在Spring中方便的使用Mybatis,社区提供了一个Mybatis-Spring这个工具。
上面已经说过了:在Mybatis的所有操作都是基于一个SqlSession的,而SqlSession是由SqlSessionFactory来产生的,SqlSessionFactory又是由SqlSessionFactoryBuilder来生成的。
但是Mybatis-Spring是基于SqlSessionFactoryBean这个类的,这是Mybatis-Spring为我们封装的一个类,不过在这个类里面还是通过SqlSessionFactoryBuilder来建立对应的SqlSessionFactory,进而获取到对应的SqlSession,我们可以通过对SqlSessionFactoryBean指定一些属性来提供Mybatis的一些配置信息。
所以在SSM项目中经常可以在配置文件中看到以下的配置信息:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/hap35"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:/**/*Mapper.xml"/>
<property name="plugins">
<array>
<bean class="com.hand.hap.core.interceptor.RequestContextInterceptor"/>
<bean class="com.hand.hap.core.interceptor.MultiLanguageInterceptor"/>
<bean class="com.hand.hap.core.interceptor.SecurityTokenInterceptor"/>
<bean class="com.hand.hap.core.interceptor.OvnInterceptor"/>
<bean class="com.hand.hap.core.interceptor.AuditInterceptor"/>
<bean class="com.github.pagehelper.PageHelper"/>
<bean class="com.hand.hap.core.interceptor.CacheJoinInterceptor">
<property name="cacheJoinType" ref="cacheJoinType"></property>
</bean>
</array>
</property>
<property name="configLocation" value="classpath:mybatis-configuration.xml"/>
</bean>
在定义SqlSessionFactoryBean的时候,dataSource属性是必须指定的,它表示用于连接数据库的数据源。当然,我们也可以指定一些其他的属性,下面简单列举几个:
-
mapperLocations
它表示我们的Mapper文件存放的位置,当我们的Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。 -
configLocation
用于指定Mybatis的配置文件位置。如果指定了该属性,那么会以该配置文件的内容作为配置信息构建对应的SqlSessionFactoryBuilder,但是后续属性指定的内容会覆盖该配置文件里面指定的对应内容。 -
plugins
数组类型,用来指定Mybatis的Interceptor。
MapperFactoryBean
接下来就是在Spring的applicationContext文件中定义我们想要的Mapper对象对应的MapperFactoryBean了。通过MapperFactoryBean可以获取到我们想要的Mapper对象。MapperFactoryBean实现了Spring的FactoryBean接口,所以MapperFactoryBean是通过FactoryBean接口中定义的getObject方法来获取对应的Mapper对象的。在定义一个MapperFactoryBean的时候有两个属性需要我们注入,一个是Mybatis-Spring用来生成实现了SqlSession接口的SqlSessionTemplate对象的sqlSessionFactory;另一个就是我们所要返回的对应的Mapper接口了。
定义好相应Mapper接口对应的MapperFactoryBean之后,我们就可以把我们对应的Mapper接口注入到由Spring管理的bean对象中了,比如Service bean对象。这样当我们需要使用到相应的Mapper接口时,MapperFactoryBean会从它的getObject方法中获取对应的Mapper接口,而getObject内部还是通过我们注入的属性调用SqlSession接口的getMapper(Mapper接口)方法来返回对应的Mapper接口的。这样就通过把SqlSessionFactory和相应的Mapper接口交给Spring管理实现了Mybatis跟Spring的整合。
// MapperFactoryBean
<bean id="blogMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface"
value="com.tiantian.mybatis.mapper.BlogMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
MapperScannerConfigurer
利用上面的方法进行整合的时候,我们有一个Mapper就需要定义一个对应的MapperFactoryBean,当我们的Mapper比较少的时候,这样做也还可以,但是当我们的Mapper相当多时我们再这样定义一个个Mapper对应的MapperFactoryBean就显得速度比较慢了。为此Mybatis-Spring为我们提供了一个叫做MapperScannerConfigurer的类,通过这个类Mybatis-Spring会自动为我们注册Mapper对应的MapperFactoryBean对象。
如果我们需要使用MapperScannerConfigurer来帮我们自动扫描和注册Mapper接口的话我们需要在Spring的applicationContext配置文件中定义一个MapperScannerConfigurer对应的bean。对于MapperScannerConfigurer而言有一个属性是我们必须指定的,那就是basePackage。basePackage是用来指定Mapper接口文件所在的基包的,在这个基包或其所有子包下面的Mapper接口都将被搜索到。多个基包之间可以使用逗号或者分号进行分隔。最简单的MapperScannerConfigurer定义就是只指定一个basePackage属性。
<bean id="mapperScannerConfigurer" class="com.hand.hap.mybatis.spring.MapperScannerConfigurer">
<property name="basePackage" value="*.**.mapper"/>
<property name="processPropertyPlaceHolders" value="true"/>
<property name="propertiesMap">
<map>
<entry key="mappers" value="com.hand.hap.mybatis.common.Mapper"/>
<entry key="IDENTITY" value="${mybatis.identity}"/>
<entry key="dataBaseType" value="${db.type}"/>
<entry key="seqFormat" value="{3}_s.nextVal"/>
<entry key="enableMethodAnnotation" value="true"/>
</map>
</property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
这样MapperScannerConfigurer就会扫描指定基包下面的所有接口,并把它们注册为一个个MapperFactoryBean对象。
-
sqlSessionFactory
这个属性已经废弃。当我们使用了多个数据源的时候我们就需要通过sqlSessionFactory来指定在注册MapperFactoryBean的时候需要使用的SqlSessionFactory,因为在没有指定sqlSessionFactory的时候,会以Autowired的方式自动注入一个。换言之当我们只使用一个数据源的时候,即只定义了一个SqlSessionFactory的时候我们就可以不给MapperScannerConfigurer指定SqlSessionFactory。 -
sqlSessionFactoryBeanName:它的功能跟sqlSessionFactory是一样的,只是它指定的是定义好的SqlSessionFactory对应的bean名称。
-
sqlSessionTemplate
这个属性已经废弃。它的功能也是相当于sqlSessionFactory的,因为就像前面说的那样,MapperFactoryBean最终还是使用的SqlSession的getMapper方法取的对应的Mapper对象。当定义有多个SqlSessionTemplate的时候才需要指定它。对于一个MapperFactoryBean来说SqlSessionFactory和SqlSessionTemplate只需要其中一个就可以了,当两者都指定了的时候,SqlSessionFactory会被忽略。 -
sqlSessionTemplateBeanName
指定需要使用的sqlSessionTemplate对应的bean名称。
SqlSessionTemplate
除了上述整合之后直接使用Mapper接口之外,Mybatis-Spring还为我们提供了一种直接使用SqlSession的方式。Mybatis-Spring为我们提供了一个实现了SqlSession接口的SqlSessionTemplate类,它是线程安全的,可以被多个Dao同时使用。同时它还跟Spring的事务进行了关联,确保当前被使用的SqlSession是一个已经和Spring的事务进行绑定了的。而且它还可以自己管理Session的提交和关闭。当使用了Spring的事务管理机制后,SqlSession还可以跟着Spring的事务一起提交和回滚。
使用SqlSessionTemplate时我们可以在Spring的applicationContext配置文件中如下定义:
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
这样我们就可以通过Spring的依赖注入在Dao中直接使用SqlSessionTemplate来编程了,这个时候我们的Dao可能是这个样子:
package com.tiantian.mybatis.dao;
import java.util.List;
import javax.annotation.Resource;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
import com.tiantian.mybatis.model.Blog;
@Repository
publicclass BlogDaoImpl implements BlogDao {
private SqlSessionTemplate sqlSessionTemplate;
publicvoid deleteBlog(int id) {
sqlSessionTemplate.delete("com.tiantian.mybatis.mapper.BlogMapper.deleteBlog", id);
}
public Blog find(int id) {
returnsqlSessionTemplate.selectOne("com.tiantian.mybatis.mapper.BlogMapper.selectBlog", id);
}
public List<Blog> find() {
returnthis.sqlSessionTemplate.selectList("com.tiantian.mybatis.mapper.BlogMapper.selectAll");
}
publicvoid insertBlog(Blog blog) {
this.sqlSessionTemplate.insert("com.tiantian.mybatis.mapper.BlogMapper.insertBlog", blog);
}
publicvoid updateBlog(Blog blog) {
this.sqlSessionTemplate.update("com.tiantian.mybatis.mapper.BlogMapper.updateBlog", blog);
}
public SqlSessionTemplate getSqlSessionTemplate() {
returnsqlSessionTemplate;
}
@Resource
publicvoid setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
}
通用Mapper
这一小节主要通过结合Hap中的源码来分析通用Mapper的实现。首先可以明确需要用到MyBatis中用到的几个注解,其实在上面也已经提到过了:@DeleteProvider、@InsertProvider、@UpdateProvider、@SelectProvider。有关于这几个注解的具体作用,可以去网上查一下资料,其实在上面也已经介绍过了,Mybatis的MapperAnnotationBuilder组件会使用这几个注解构建MappedStatement对象。这里举一个小例子,看看Hap是怎么巧妙的通过这几个注解实现通用Mapper的。
/**
* 通用Mapper接口,查询
*
* @param <T> 不能为空
* @author liuzh
*/
public interface SelectCountMapper<T> {
/**
* 根据实体中的属性查询总数,查询条件使用等号
*
* @param record
* @return
*/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
int selectCount(T record);
}
public class BaseSelectProvider extends MapperTemplate {
public BaseSelectProvider() {}
public BaseSelectProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
......
public String selectCount(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectCount(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
}
首先看看在Hap中重写的MapperScannerConfigurer类,可以把这个当作是入口:
public class MapperScannerConfigurer extends org.mybatis.spring.mapper.MapperScannerConfigurer {
private MapperHelper mapperHelper = new MapperHelper();
private Map<String,String> propertiesMap;
public void setMarkerInterface(Class<?> superClass) {
super.setMarkerInterface(superClass);
if (Marker.class.isAssignableFrom(superClass)) {
mapperHelper.registerMapper(superClass);
}
}
public MapperHelper getMapperHelper() {
return mapperHelper;
}
public void setMapperHelper(MapperHelper mapperHelper) {
this.mapperHelper = mapperHelper;
}
/**
* 属性注入
*
* @param properties
*/
public void setProperties(Properties properties) {
mapperHelper.setProperties(properties);
}
public void setPropertiesMap(Map<String, String> propertiesMap) {
if (propertiesMap.get("ORDER") == null) {
if ("JDBC".equalsIgnoreCase(propertiesMap.get("IDENTITY"))) {
propertiesMap.put("ORDER", "AFTER");
} else {
propertiesMap.put("ORDER", "BEFORE");
}
}
this.propertiesMap = propertiesMap;
//Properties properties = new Properties();
//propertiesMap.forEach((k, v) -> {
// properties.put(k, v);
//});
//setProperties(properties);
}
/**
* 注册完成后,对MapperFactoryBean的类进行特殊处理
*
* @param registry
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
Properties config = new Properties();
Properties p = new Properties();
try {
config.load(getClass().getResourceAsStream("/config.properties"));
if (propertiesMap.get("ORDER") == null) {
if ("JDBC".equalsIgnoreCase(propertiesMap.get("IDENTITY"))) {
p.put("ORDER", "AFTER");
} else {
p.put("ORDER", "BEFORE");
}
}
propertiesMap.forEach((k,v)->{
if (v.startsWith("${") && v.endsWith("}")) {
p.put(k, config.getProperty(v.substring(2, v.length() - 1), v));
} else {
p.put(k, v);
}
});
setProperties(p);
} catch (IOException e) {
throw new RuntimeException(e);
}
super.postProcessBeanDefinitionRegistry(registry);
//如果没有注册过接口,就注册默认的Mapper接口
this.mapperHelper.ifEmptyRegisterDefaultInterface();
String[] names = registry.getBeanDefinitionNames();
GenericBeanDefinition definition;
for (String name : names) {
BeanDefinition beanDefinition = registry.getBeanDefinition(name);
if (beanDefinition instanceof GenericBeanDefinition) {
definition = (GenericBeanDefinition) beanDefinition;
if (StringUtil.isNotEmpty(definition.getBeanClassName())
&& definition.getBeanClassName().equals("org.mybatis.spring.mapper.MapperFactoryBean")) {
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("mapperHelper", this.mapperHelper);
}
}
}
}
}
org.mybatis.spring.mapper.MapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor接口是在bean定义之后,实例化之前执行的接口,Spring 框架会根据配置,过滤出 BeanDefinitionRegistryPostProcessor 类型的 Bean 定义,并通过 Spring 框架生成其对应的 Bean 对象,Spring 容器会在实例化开发人员所定义的 Bean 前先调用该 processor 的 postProcessBeanDefinitionRegistry(...) 方法。所以在项目启动的时候,会执行Hap中重写的postProcessBeanDefinitionRegistry方法。
首先加载config.properties配置文件,用于构建一个Properties对象,然后调用setProperties方法,这个方法很关键,因为里面涉及到Mapper的加载过程。
/**
* 属性注入
*
* @param properties
*/
public void setProperties(Properties properties) {
mapperHelper.setProperties(properties);
}
/**
* 配置属性
*
* @param properties
*/
public void setProperties(Properties properties) {
config.setProperties(properties);
//注册通用接口
String mapper = null;
if (properties != null) {
mapper = properties.getProperty("mappers");
}
if (StringUtil.isNotEmpty(mapper)) {
String[] mappers = mapper.split(",");
for (String mapperClass : mappers) {
if (mapperClass.length() > 0) {
registerMapper(mapperClass);
}
}
}
}
/**
* 注册通用Mapper接口
*
* @param mapperClass
*/
public void registerMapper(String mapperClass) {
try {
registerMapper(Class.forName(mapperClass));
} catch (ClassNotFoundException e) {
throw new RuntimeException("注册通用Mapper[" + mapperClass + "]失败,找不到该通用Mapper!");
}
}
/**
* 注册通用Mapper接口
*
* spilledyear
* 递归 注册 Mapper 的所有父类,registerClass 和 registerMapper 的作用是什么
*
* @param mapperClass
*/
public void registerMapper(Class<?> mapperClass) {
if (!registerMapper.containsKey(mapperClass)) {
registerClass.add(mapperClass);
// fromMapperClass 的作用是通用Mapper接口获取对应的MapperTemplate
registerMapper.put(mapperClass, fromMapperClass(mapperClass));
}
//自动注册继承的接口
Class<?>[] interfaces = mapperClass.getInterfaces();
if (interfaces != null && interfaces.length > 0) {
for (Class<?> anInterface : interfaces) {
registerMapper(anInterface);
}
}
}
// com.hand.hap.mybatis.common.Mapper
public interface Mapper<T> extends
BaseMapper<T>,
ExampleMapper<T>,
RowBoundsMapper<T>,
Marker {
}
MapperFactoryBean的继承关系如下:
image.png
public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass());
public DaoSupport() {
}
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
......
}
所以,在初始化实例之后,会调用MapperFactoryBean的checkDaoConfig方法:
public class MapperFactoryBean<T> extends org.mybatis.spring.mapper.MapperFactoryBean<T> {
private MapperHelper mapperHelper;
public MapperFactoryBean() {
}
public MapperFactoryBean(Class<T> mapperInterface) {
super(mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
//通用Mapper
if (mapperHelper.isExtendCommonMapper(getObjectType())) {
mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
}
}
public void setMapperHelper(MapperHelper mapperHelper) {
this.mapperHelper = mapperHelper;
}
}
如果是通用Mappper,就会调用mapperHelper的processConfiguration方法重新设置SqlSource。也就是根据各个provider动态生成SQL,然后创建一个SqlSource,然后根据Mybatis中的MappedStatement重新设置SqlSource。这样就达到了通用SQl的效果。
/**
* 配置指定的接口
*
* @param configuration
* @param mapperInterface
*/
public void processConfiguration(Configuration configuration, Class<?> mapperInterface) {
String prefix;
if (mapperInterface != null) {
prefix = mapperInterface.getCanonicalName();
} else {
prefix = "";
}
for (Object object : new ArrayList<Object>(configuration.getMappedStatements())) {
if (object instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) object;
if (ms.getId().startsWith(prefix) && isMapperMethod(ms.getId())) {
if (ms.getSqlSource() instanceof ProviderSqlSource) {
setSqlSource(ms);
}
}
}
}
}
configuration.getMappedStatements()的作用是获取所有的MappedStatement对象,一个MappedStatement对象对应Mapper中的一个接口。上面代码的意思就是当找到复核要求的MappedStatement对象时,就重新设置SqlSource。
/**
* 重新设置SqlSource
* <p/>
* 执行该方法前必须使用isMapperMethod判断,否则msIdCache会空
* msIdCache 里面缓存了 MappedStatement对象的 key
*
* @param ms
*/
public void setSqlSource(MappedStatement ms) {
MapperTemplate mapperTemplate = msIdCache.get(ms.getId());
try {
if (mapperTemplate != null) {
mapperTemplate.setSqlSource(ms);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
而重新设置SqlSource的具体逻辑则是交给MapperTemplate的setSqlSource方法
/**
* 重新设置SqlSource
*
* @param ms
* @throws java.lang.reflect.InvocationTargetException
* @throws IllegalAccessException
*/
public void setSqlSource(MappedStatement ms) throws Exception {
if (this.mapperClass == getMapperClass(ms.getId())) {
throw new RuntimeException("请不要配置或扫描通用Mapper接口类:" + this.mapperClass);
}
Method method = methodMap.get(getMethodName(ms));
try {
//第一种,直接操作ms,不需要返回值
if (method.getReturnType() == Void.TYPE) {
method.invoke(this, ms);
}
//第二种,返回SqlNode
else if (SqlNode.class.isAssignableFrom(method.getReturnType())) {
SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
setSqlSource(ms, dynamicSqlSource);
}
//第三种,返回xml形式的sql字符串
else if (String.class.equals(method.getReturnType())) {
String xmlSql = (String) method.invoke(this, ms);
SqlSource sqlSource = createSqlSource(ms, xmlSql);
//替换原有的SqlSource
setSqlSource(ms, sqlSource);
} else {
throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void,SqlNode,String三种!");
}
//cache
checkCache(ms);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException() != null ? e.getTargetException() : e);
}
}
/**
* 重新设置SqlSource,同时判断如果是Jdbc3KeyGenerator,就设置为MultipleJdbc3KeyGenerator
*
* @param ms
* @param sqlSource
*/
protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) {
MetaObject msObject = SystemMetaObject.forObject(ms);
msObject.setValue("sqlSource", sqlSource);
//如果是Jdbc3KeyGenerator,就设置为MultipleJdbc3KeyGenerator
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (keyGenerator instanceof Jdbc3KeyGenerator) {
msObject.setValue("keyGenerator", new MultipleJdbc3KeyGenerator());
}
}
这里根据各个Provider中方法的返回类型执行不同的逻辑,这里以 BaseSelectProvider 中的 selectCount 方法为例:
public class BaseSelectProvider extends MapperTemplate {
/**
* 查询总数
*
* @param ms
* @return
*/
public String selectCount(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectCount(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
return sql.toString();
}
}
其实就是返回一个字符串SQL,至于SQL是怎么拼接出来的,这里不做过多介绍,有兴趣的可以追一下代码,并不复杂。返回字符串之后就重新创建一个SqlSource,然后将新的SqlSource设置给MappedStatement对象。
在我们的Hap应用中,我们自定义的Mapper接口一般都会继承com.hand.hap.mybatis.common.Mapper接口,所以就间接拥有了com.hand.hap.mybatis.common.Mapper接口中的所有方法。但是这些继承的方法在一开始的时候其实是没有任何意义的,只有在执行了MapperFactoryBean的checkDaoConfig方法之后,才会动态生成SQL,然后创建新的SqlSource对象,并一次覆盖MappedStatement对象中的SqlSource对象,这时候这些继承过来的方法来变得有意义。
可能这样一遍看下来,大家会觉得有点模糊。这里还有很多可以值得学些的东西,比如:EntityHelper、MapperHelper、SqlHelper、MapperTemplate 以及Hap中定义的一些注解,在动态生成SQL的时候,其实主要就是应用到了这几个对象。阅读这些代码其实很有用:可以在开发Hap应用的时候更加的熟练;在出现 问题的时候更好的排查;对于非常常用的框架,很有必要更多的了解一些其底层实现,这样在阅读其它应用源码的时候,其实很有帮助。