mybatis源码解析
准备工作:
到mybatis官网,学习基本的用法。
http://www.mybatis.org/mybatis-3/zh/statement-builders.html
在eclipse中搭建环境,新建demo项目。
代码
package cn.howso.mybatis;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import cn.howso.mybatis.mapper.UserMapper;
import cn.howso.mybatis.model.User;
public class Index {
public static void main(String[] args) throws IOException {
String resource = "configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
// XMLConfigBuilder xMLConfigBuilder = new XMLConfigBuilder(reader);
// Configuration configuration = xMLConfigBuilder.parse();
// SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
// 根据configuartion.xml创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 打开session
SqlSession session = sqlSessionFactory.openSession();
// 基于statementId的接口
User user = (User) session.selectOne("cn.howso.mybatis.mapper.UserMapper.selectByPrimaryKey", 1L);
System.out.println(user.getName());
// 基于Mapper的接口
UserMapper userMapper = session.getMapper(UserMapper.class);
User user2 = userMapper.selectByPrimaryKey(1L);
System.out.println(user2.getName());
}
}
可以看到客户端主要的接口类
SqlSessionFactoryBuilder=》用于创建SqlSessionFactory
SqlSessionFactory=》用于创建SqlSession
SqlSession=》代表和数据库的会话
UserMapper=》访问数据库的接口
以及主要流程:
1、加载并解析配置
2、根据配置生成sessonFactory
3、从sessionFactory打开session
4、获取mapper
5、执行增删查改
第一部分:框架初始化
接下来
SqlSessionFactoryBuilder
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
类XMLConfigBuilder有个parse方法,根据configuration.xml和环境变量以及properties得到一个Configuration。
XMLConfigBuilder
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
parseConfiguration方法实际上就是根据configuration.xml的内容设置configuration属性的内容。
我们只关注mapperElement方法
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);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
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.");
}
}
}
}
}
configuration.xml部分内容
<mappers>
<mapper resource="cn/howso/mybatis.../UserMapper.xml"/>
</mappers>
可以看出,方法内部读取resource属性配置的文件到流中,构建一个XMLMapperBuilder,然后解析。
XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
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. Cause: " + e, e);
}
}
可以看到,它在处理UserMapper.xml中的各种节点。
我们重点关注它是如何生成statement
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder.parseStatementNode()
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
重点关注SqlSource,它是最后生成sql语句的关键。
注意参数content的类型是XNode。
builderAssistant.addMappedStatement
public MappedStatement addMappedStatement(...
...
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
最后生成了MappedStatement,它代表映射的语句。
并且添加到了configuration中。
前面还有个内容要分析,那就是SqlSource的生成。
它由langDriver创建,langDriver是LanguageDriver类型。
在eclipse中,按ctrl+t
可以看到LanguageDriver是接口。
XMLLanguageDriver用于从XML创建SqlSource,所以支持动态sql。(动态sql可以在sql中写if、forEach、when、where等标签,也可以写${},#{})
RawLanguageDriver用于支持#{}的sql。
XMLLanguageDriver
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
从第二个方法可以看出,可以根据字符串创建动态sql,要求是字符串包含在<script>标签中。
SqlSource有四种
DynamicSqlSource代表动态sql,RawSqlSource封装的sql可以有占位符#{},StaticSqlSource封装静态sql,ProviderSqlSoruce用于注解的provider方式配置的sql。
ProviderSqlSource
private SqlSource createSqlSource(Object parameterObject) {
try {
String sql;
if (providerTakesParameterObject) {
sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject);
} else {
sql = (String) providerMethod.invoke(providerType.newInstance());
}
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
return sqlSourceParser.parse(sql, parameterType, new HashMap<String, Object>());
} catch (Exception e) {
throw new BuilderException("Error invoking SqlProvider method ("
+ providerType.getName() + "." + providerMethod.getName()
+ "). Cause: " + e, e);
}
}
SqlSourceBuilder
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
可以看出provider注解方式支持带占位符#{}的sql,但不支持动态sql。
此外注解Select,Insert,Delete,Update的相关源码,它们使用默认的LanguageDriver(XMLLanguageDriver)
MapperAnnotationBuilder
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
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);
//TODO modified by zhoujiaping,at 2017-05-14 修改源码,支持SqlProvider写带标签的动态sql。
if(ProviderHelper.isScriptSqlProvider(sqlProviderAnnotation, sqlProviderAnnotationType)){
String string = ProviderHelper.create(assistant,method, sqlProviderAnnotation, sqlProviderAnnotationType).getSql();
return buildSqlSourceFromStrings(new String[]{string},parameterType,languageDriver);
}else{
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
}
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
Configuration
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
第二部分:用户接口
因为这部分是给mybatis的用户(也就是我们普通开发程序员)用的,所以书上都会有。
第三部分:接口内部实现
获取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);
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,它是后面的sql执行器。
获取Mapper
@SuppressWarnings("unchecked")
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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
从源码中可以看出用的是jdk动态代理。(jdk动态代理只能代理接口,不能代理类。)
UserMapper的方法如何执行
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
可以看出mapper方法的执行,实际上是生成代理对象的InvocationHandler的invoke方法执行。invoke又委托给了MapperMethod的execute方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
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;
}
可以看到,它根据不同的操作,执行不同的方法。
insert、update、delete、executeForMany、executeForMap、selectOne、executeWithResultHandler、flushStatements。
DefaultSessionFactory
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看到,insert,delete,实际上执行的都是update。而且将具体的执行操作又委托给了executor。
Executor是个接口,它有四个公有的非抽象类。
BatchExecutor用于批量操作,它用jdbc的批量操作,SimpleExecutor每次操作都会发送一条sql。
CachingExecutor
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
从源码中可以看出,它把操作委托给了delegate,也就是剩下的三个Executor实现类。
以SimpleExecutor的update为例
SimpleExecutor
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
SimpleExecutor将操作委托给了StatementHandler。StatementHandler接收的参数stmt,是jdbc的接口。
StatementHandler是个接口,它有四个实现类。
RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
可以看出,用于路由到其他三个实现类。
以PreparedStatementHandler为例
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
到这里,调用了jdbc的PreparedStatement的execute方法。
第四部分mybatis插件原理
同学们自己去分析
第五部分 经验总结
一是mybatis的加载启动流程、执行流程、插件原理。
二是mybatis框架的设计。
前者略,因为阅读源码时就是按照这个目标去的。
后者,还没有分析
源码结构
可以看出mybatis分为很多模块(包)。
注解、绑定、构建、缓存、数据源、异常、执行器、输入输出、jdbc、日志、映射、解析器、插件、反射、脚本、会话、事务、类型。
5.1 annotations
注解是也是一种配置。所以它的核心作用就是替代配置文件。
比如Select、Insert、ResultMap、SelectProvider注解,都是可选的,可以只用xml配置。
5.2、binding
这个包主要用于将绑定Mapper代理对象,Mapper方法。
MapperRegistry:用于保存Mapper的实例,MapperRegistry又被Configuration持有。
Configuration
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
可以看到这里出现了循环依赖,Configuration依赖了MapperRegistry,MapperRegistry的构造函数依赖了Configuration。
MapperMethod:用于执行Mapper的方法。它在MapperProxy的方法中被创建并调用。
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
MapperProxy:Mapper接口的代理对象的InvocationHandler,它用来执行Mapper接口的方法。
mybatis的增删改查接口,一种方式是通过字符串,例如User user = (User)session.selectOne("cn.howso.mybatis.mapper.UserMapper.selectByPrimaryKey", 1L);
另一种是接口方式 UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> list = userMapper.selectAll(u);
吐槽:它推荐面向接口方式,不支持实现类方式,但是又提供SqlProvider注解,让我们提供自己的实现。用SqlProvider,又没有了静态检查机制,通过字符串匹配的方式确定方法。方法不支持重载,不能有同名方法,即使参数列表不同。要实现通用dao非常麻烦。
5.3builder
用于构建Configuration、Mapper、SqlSource、Statement,以及包括一些用于帮助构建的Resolver。
里面有两个子包,xml和annotation。其中的XMLMapperBuilder和MapperAnnotationBuilder分别基于xml和annotation构建Mapper,但是名字取得却不统一。后者很明显可以改为AnnotationMapperBuilder,保持一致。
两个类的作者都是Clinton Begin,可以看到,就算是著名框架的作者,写的代码也有自我不一致的地方。
由于mybatis不是一个ioc框架,所以它的代码实现上,很多地方new实现类。导致代码不方便拓展。比如SqlProvider支持RawSqlSource但不支持DynamicSqlSource,如果想让它支持,就必须修改源代码。
虽然从性能上来说StaticSqlSource性能最好,DynamicSqlSource性能低,但是我们自己可以用缓存解决问题啊。
问题是它不提供这样的拓展点。
5.4 cache
缓存
我把子包impl删除后,annotations、builder、executor等包都编译报错,说明很多包依赖了这个模块的具体实现,而不是只依赖接口。
其中有个decorators包,这个包里面的类,都是实现了装饰器模式的类,用于装饰Cache,对Cache进行增强,有的增加日志功能,有的实现先进先出功能,有的实现将缓存序列化和反序列化的功能。
这个设计模式很值得学习,先设计一个简单功能的类,然后需要增强功能就考虑装饰器模式,用新的类装饰它增强它。
5.5 datasource
数据源,其中包括jndi数据源,连接池数据源,非连接池数据源。
5.6 exceptions
mybatis定义的异常
5.7 io
用于读取配置文件的工具类。
5.8 jdbc
这个包主要用于提供java接口创建sql语句。以及用于执行sql文件的工具。
5.9 logging
日志模块,支持log4j、log4j2、slf4j等。有连接日志、语句日志、结果集日志。
5.10 mapping
映射,包括结果集映射、参数映射、sql映射。
5.11 parsing
解析器,用于解析xml生成内部对象表示形式,解析${}和#{}。
5.12 plugin
插件,用于拦截Executor、StatementHandler、ParameterHandler和ResultSetHandler。
怎么知道可以拦截这四大对象呢?
看Configuration类的源代码。
这里我要吐槽一下这个设计,插件实现用的jdk动态代理,invoke方法参数类型丢失。能够拦截哪些方法通过注解配置。这里完全可以参考servlet过滤器或者springmvc拦截器的实现啊。
5.13 reflection
反射工具。其中最常用的类是MetaObject,可以修改对象的私有属性。
5.14 scripting
解析xml配置的sql。
5.15 session
会话
5.16 transaction
事务
5.17 type
类型处理,包括设置sql语句参数时的类型处理和获取sql语句执行结果的类型处理。
总结:
从整体来说,mybatis框架相对简单,它作了比较细的模块划分,内容也很全(缓存、日志、事务、数据源等)。由于不是ioc容器,在配置一些功能时,难免使用new方式,导致框架灵活性降低。有些设计并不好,例如插件的设计。
在简单性与灵活性之间,mybatis的取舍做的还是挺不错的。
附录:
mybatis插件相关的项目,可以参考https://github.com/zhoujiaping/mybatis-mapper
里面有一个分页插件,一个通用dao插件。