mybatis是目前非常流行的数据库框架。它的轻便灵活的特性使的我们更加方便的操作数据库。所以我们来扒一扒Mybatis的原理。
先扒一张百度上对mybatis结构的描述的图
百度的图
从上可以看出mysql的几个组件Configiration,MappedStatements,Mapper,xml。
先写个demo试验一下。
mybatis版本是
org.mybatis
mybatis
3.4.6
数据源的用的ali的druid。不过数据源不是关键
代码如下
DataSource dataSource = dataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(ApiErrorLogMapper.class);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);
ApiErrorLogMapper apiErrorLogMapper = factory.openSession().getMapper(ApiErrorLogMapper.class);
ApiErrorLog apiErrorLog = apiErrorLogMapper.getByPrimaryKey(1);
代码非常简单,关键代码是
configuration.addMapper(ApiErrorLogMapper.class);
和
sqlSession.getMapper(ApiErrorLogMapper.class);
执行一下,debug调试。得到的流程大致如下
ApiErrorLogMapper实际是通过JDK动态代理获取的MapperProxy对象。所以ApiErrorLogMapper只能是接口,jdk只支持接口的动态代理。
MapperProxy中持有MapperMethod的饮用。MapperMethod又持有SqlSession。在SqlSession中
@Override
public List 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();
}
}
可以看出,MappedStatement也是在Configuration中保存了 Mapper+Method和 MappedStatement的一个StrictMap。
整个Configuration加载Mapper的流程如下。
由此看出,Mybatis核心逻辑就是解析MappedStatement。MappedStatement保存了查询所需要的Sql的来源SqlSource,保存了主键策略,和返回值的ResultMap需要的类型数据。
其核心方法为
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
获取SqlSource的逻辑
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);
}
}
核心的SqlSource为
DynamicSqlSource 处理解析xml中动态sql
RawSqlSource 也是处理Xml 这不过这个sql不处理带有${}替换符号的sql
ProviderSqlSource基于注解的动态SqlSql
综上可以看出mybatis的几种用法
1.基于mapper.xml编写sql。优点点是可以编写任何复杂逻辑,确点是维护sql比较麻烦,并且不友好。
2.基于注解生成sql,这种一般有两种方式,
1).@Select,@Insert 等。这种一个方法一个sql,适用于sql比较简单,而且适用性窄的情况。
2).@InsertProvider ,@SelectProvider 等,这些是动态获取sql的。在自定一的Provider中能获取到ProviderContext对象,对象封装了mapper的类型和method和具体的参数期生成sql的形式入下。这种适合比较复杂的sql。当时结构稍微会多一层。
private SqlSource createSqlSource(Object parameterObject) {
try {
int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1);
String sql;
if (providerMethodParameterTypes.length == 0) {
sql = invokeProviderMethod();
} else if (bindParameterCount == 0) {
sql = invokeProviderMethod(providerContext);
} else if (bindParameterCount == 1 &&
(parameterObject == null || providerMethodParameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) {
sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
} else if (parameterObject instanceof Map) {
@SuppressWarnings("unchecked")
Map params = (Map) parameterObject;
sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames));
} else {
throw new BuilderException("Error invoking SqlProvider method ("
+ providerType.getName() + "." + providerMethod.getName()
+ "). Cannot invoke a method that holds "
+ (bindParameterCount == 1 ? "named argument(@Param)": "multiple arguments")
+ " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object.");
}
Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap());
} catch (BuilderException e) {
throw e;
} catch (Exception e) {
throw new BuilderException("Error invoking SqlProvider method ("
+ providerType.getName() + "." + providerMethod.getName()
+ "). Cause: " + e, e);
}
}
结合Mybatis的用法。如果需要对mybaits进行扩展,如单表操作这种频率很高,生成sql的方式比较相似。可以统一动态生成。类似与Hibernate jpa的工具可以有以下思路。
1.基于SqlProvider,如@SelectProvider这种。犹豫Provider可以获得调用接口,和参数。所以基本能够根据接口获得操作对象表信息和方法信息,进行sql的生成。但是这种没办法还是基于mybaits原生的MappedStatement。向主键策略的就不太容易。
2.修改addMapper的操作。在addMapper的时候加载自己逻辑的MappedStatement 和DynamicSqlSource。这样能够全面进行自定义。但是DynamicSqlSource的生成比较复杂。类似于tk.mapper的原理
3.先修改addMapper的操作。自定义SqlSource。通过自己逻辑创建MappedStatement。这种自定义程度高。而且SqlSourse不用按照DynamicSqlSource这种基于