注解型 SQL
众所周知, mybatis 支持两种编写 SQL 的方式 : xml 配置文件和注解, 就像这样
@Results(id = "stu", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "tel", property = "tel"),
@Result(column = "address", property = "address")
})
@Select("select * from student where id = #{id}")
Student getStudentWithAnnotation(Integer id);
如果使用这种方式, 耦合性太强了, 且不能在全局配置文件中引入 mapper.xml , 使用 package, 或者 class
<mappers>
<mapper class="com.gp.ibatis.dao.StudentDao"/>
<package name="com.gp.ibatis.dao"/>-->
</mappers>
前面我们知道, 再解析 <mappers>
标签的时候, 为 <package>
单独处理, 回过头去看
// 直接 <package> 的指定路径拿到, 调用了 MapperRegistry 的 addMappers(String, Class) 方法
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 使用 VFS 这个 IO 工具类, 得到想要的 Class 对象, 放在 ResolverUtil 对象中
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 使用 Set 去重
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
------------------------------------------
// 接着看他的 find 方法
public ResolverUtil<T> find(Test test, String packageName) {
// 绝对路径
String path = getPackagePath(packageName);
try {
// 拿到此路径下的所有文件, VFS 用于处理 IO
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
// 文件匹配, 只要后缀名为 .class 的文件
if (child.endsWith(".class")) {
addIfMatching(test, child);
}
}
}
catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
-----------------------------
// 接着看 addIfMatching 方法
protected void addIfMatching(Test test, String fqn) {
try {
// 将路径 / 替换成 . (变成全类名的方式)
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
// 使用类加载器将这个全类名加载到 JVM 中 (并未初始化)
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
// 放到此集合中
matches.add((Class<T>) type);
}
}
catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a "
+ t.getClass().getName() + " with message: " + t.getMessage());
}
}
接着看 MapperRegistry 的 addMapper(Class) 方法
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<>(type));
// 必须注意的是 : 此类型必须在解析器执行前添加到 Configuration 对象中
// 如果此类型已知, 则不会处理
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解方法
parser.parse();
loadCompleted = true;
}
finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
// 继续查看 MapperAnnotationBuilder 的 parse 方法
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 生成一个 xml 配置文件名 (并没有存放在磁盘)
// 然后转换成字节流 (类似于解析全局配置文件)
loadXmlResource();
// 下面类似于解析 mapper.xml 配置文件
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
// 如果接口的方法存在 mybatis 的注解, 解析此方法
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
// 封装成 MappedStatement 对象放入 Configuration 对象中
parseStatement(method);
}
catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
自此, Mapper 的方法被封装成一个个 MappedStatement 对象, 存放在 Configuration 对象中, 只要拿到这个 MappedStatement 对象, mybatis 就能拿到各种信息, 比如 SQL 语句, StatementType , 参数 …