先看一段示例代码
static SqlSession javaStart(){
// dataSource 自己创建一个,用什么都行,比如Druid
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
// 事务功能也可以自己定义的,这里用 JdbcTransactionFactory ,因为Mybatis默认就是这个
TransactionFactory transactionFactory = new JdbcTransactionFactory();
// 先创建环境
Environment environment = new Environment("development", transactionFactory, dataSource);
// 创建 configuration
Configuration configuration = new Configuration(environment);
// ======= 以上内容,在`第二章`,也有对应的xml方式的实现,基本都一样,下面开始才是xml和注解方式的差异=========
// 将mapper添加到configuration的容器中,这个方法的描述在`5.2.2`中有涉及,最终会走到 MapperAnnotationBuilder.parse(),解析注解的Mapper,细节请看章节`8.1`
configuration.addMapper(BlogMapper.class);
configuration.addMapper(UserMapper.class);
// 构建SqlSessionFactory,章节`2.1`中有描述
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 创建一个 SqlSession
return sqlSessionFactory.openSession();
}
8.1 mapper解析
public void parse() {
// Class 类名转成字符串,前面会有一个前缀:"interface "
String resource = type.toString();
// 判断是否重复加载
if (!configuration.isResourceLoaded(resource)) {
// 先加载对应类名的xml资源,这也就是为什么,我们在开发时,只写了一个接口类XXXMapper.java,在调用该接口类时,就能执行到同名XXXMapper.xml文件中同名的mapper语句
loadXmlResource();
// 解析完成,把资源添加到 configuration 里的 set集合,证明已加载过,判断重复时有用
configuration.addLoadedResource(resource);
// 设置当前类名为命名空间
assistant.setCurrentNamespace(type.getName());
// 解析注解的缓存
parseCache();
// 解析注解的缓存引用
parseCacheRef();
// 通过反射技术,遍历该接口类的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
/* 这里有一个brige(桥接)方法的判断,什么是桥接方法呢?jvm中有描述,就是修饰符标记为:
#define JVM_ACC_BRIDGE 0x0040 (bridge method generated by compiler )
意思是桥接方法由编译器生成,在JVM中什么情况下会生成桥接代码呢?其实就是通过方法签名(方法+参数)无法唯一确定一个函数时,编译器就会默认再生成一个同名的桥接方法。一般在子类继承父类方法时,改变了返回值;还有一种是参数类型是泛型,被擦除了类型后。这里以第一种情况举例,代码看`图8-1`,生成的字节码看`图8-2`
*/
if (!method.isBridge()) {
// 解析出sql语句,看`8.1.1`
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
private void loadXmlResource() {
// 先判断是否重复加载
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 查找资源
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
// 创建 XMLMapperBuilder 对象,看`3.1`有介绍,并解析xml
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
// xml解析,看`3.2`有介绍
xmlParser.parse();
}
}
}
private void parseCache() {
// 类型上有 CacheNamespace 的注解
CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
if (cacheDomain != null) {
// 下面这些参数,是不是很眼熟,看看前面章节`3.2.2.2`
Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
Properties props = convertToProperties(cacheDomain.properties());
assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
}
}
private void parseCacheRef() {
// 类型上有 CacheNamespaceRef 的注解
CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
if (cacheDomainRef != null) {
// 下面这些参数,是不是很眼熟,看看前面章节`3.2.2.1`
Class<?> refType = cacheDomainRef.value();
String refName = cacheDomainRef.name();
if (refType == void.class && refName.isEmpty()) {
throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
}
if (refType != void.class && !refName.isEmpty()) {
throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
}
String namespace = (refType != void.class) ? refType.getName() : refName;
try {
assistant.useCacheRef(namespace);
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
}
}
}
图8-1
图8-2
8.1.1 parseStatement
void parseStatement(Method method) {
// 该方法对应的参数类型
Class<?> parameterTypeClass = getParameterType(method);
// 这里还是用的 XMLLanguageDriver
LanguageDriver languageDriver = getLanguageDriver(method);
// 从注解中解析出Sql并封装成SqlSource,看章节`8.1.2`
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
// Options 注解
Options options = method.getAnnotation(Options.class);
// 映射语句id
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
// 设置默认类型
StatementType statementType = StatementType.PREPARED;
// resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
ResultSetType resultSetType = configuration.getDefaultResultSetType();
// sql类型:select|update|insert|delete
SqlCommandType sqlCommandType = getSqlCommandType(method);
// 判断是否是select
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect; // 刷新缓存标志,只有非select语句才有
boolean useCache = isSelect; // 是否缓存标志,只有select语句才有
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// insert/update时,需要考虑SelectKey的情况
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
// 参考章节`3.2.2.7`
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 {
// select/delete时,不需要考虑selectkey的情况
keyGenerator = NoKeyGenerator.INSTANCE;
}
// Option注解中,设置了一些基础参数值,比如超时时间、刷新缓存标志、使用缓存标志、语句类型、读取数据库数据量大小等,下面就是对这些参数的设置,这些参数,前面讲解xml方式时,都有讲,不做赘述了
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();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
// 处理注解的 ResultMap
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// 创建 MappedStatement 并添加到 configuration 中,看章节`3.2.2.6`,这里有一点要说明一下,如果xml中和注解中都有同名的sql,会抛出已包含异常,搜索关键字:“already contains value for”
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);
}
}
8.1.2 getSqlSourceFromAnnotations
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
// 拿到注解类型:select|insert|update|delete
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
// sql提供者类型,顾名思意,也就是把一个接口一分为二,sql有提供者提供,接口负责传参,类型主要有4种:SelectProvider|InsertProvider|UpdateProvider|DeleteProvider
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
// sqlAnnotationType 和 sqlProviderAnnotationType 两者只能出现一个
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);
// 这一步就是取出注解上的值,这里是指在注解中写的sql语句
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);
}
// 没有注解,那直接返回null
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
final StringBuilder sql = new StringBuilder();
for (String fragment : strings) {
sql.append(fragment);
sql.append(" ");
}
// 这一步可以参考章节`3.2.2.6`
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
}
至此,xml和注解方式的都讲完了,后续章节再讲一个插件,Mybatis源码就算完结了。