Mybatis编写sql有两种方式,即通过xml和注解,我个人比较喜欢xml配置,但是注解还是要了解下的。并且Mybatis中xml优先于注解加载,也就是如果DAO接口中的方法有对应的xml配置,再加入注解会抛异常,如果两个都没配置,在调用DAO方法时再抛异常。
源码分析
一、XML中sql转MappedStatement
Mybatis会把编写的sql语句信息封装成一个MappedStatement对象,加载xml中sql信息从XMLMapperBuilder#buildStatementFromContext()
开始,一路调用到MapperBuilderAssistant#addMappedStatement
完成添加,而加载注解最终也会通过MapperBuilderAssistant类。其中 this.configuration.addMappedStatement(statement);
就是最终添加到配置类中的map集合中,先不管。
二、注解转MappedStatement
那么开始加载注解从什么地方开始呢?
同样是从XMLMapperBuilder类中,在bindMapperForNamespace()方法下开始,完成XML加载之后被调用。
加载注解通过代码跟踪一直在MapperRegistry下的addMapper中。此时参数是DAO的class,首先判断是不是接口,以及是否被添加过了。其次,开始new 一个MapperAnnotationBuilder对象开始处理接口中方法上的注解。
public void addMapper(Class type) {
//是不是接口
if (type.isInterface()) {
//是否已被添加
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//添加
this.knownMappers.put(type, new MapperProxyFactory(type));
//开始注解解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
MapperAnnotationBuilder的parseStatement方法中处理每一个DAO层方法上的注解,其中getSqlSourceFromAnnotations返回一个SqlSource对象,为空则方法上不存在注解信息,什么也不做,不为空最终生成各种需要的信息调用MapperBuilderAssistant下的addMappedStatement方法试图添加到Configuration中的mappedStatements集合中。
void parseStatement(Method method) {
Class> parameterTypeClass = this.getParameterType(method);
LanguageDriver languageDriver = this.getLanguageDriver(method);
SqlSource sqlSource = this.getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = (Options)method.getAnnotation(Options.class);
String mappedStatementId = this.type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = this.getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
String keyProperty = "id";
String keyColumn = null;
//-----此处省略不知道多少行代码
那为什么要说试图添加呢?因为最终存放MappedStatement的是一个Map,Mybatis自己实现了个静态内部类StrictMap继承HashMap,在put中判断了有没有相同的key,如果相同则抛出异常,也就是间接让你xml和注解二选一。
三、sqlProviderAnnotation
Mybatis还有另外4个注解,也就是SelectProvider、InsertProvider、UpdateProvider、DeleteProvider,
从getSqlSourceFromAnnotations
中可以看出,原来的Insert、Select、Update、Delete和SelectProvider、InsertProvider、UpdateProvider、DeleteProvider只能二选一。
private SqlSource getSqlSourceFromAnnotations(Method method, Class> parameterType, LanguageDriver languageDriver) {
try {
//获取方法上Insert、Select、Update、Delete中其中一个注解
Class extends Annotation> sqlAnnotationType = this.getSqlAnnotationType(method);
//获取SelectProvider、InsertProvider、UpdateProvider、DeleteProvider中其中一个注解
Class extends Annotation> sqlProviderAnnotationType = this.getSqlProviderAnnotationType(method);
Annotation sqlProviderAnnotation;
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
//如果两种注解都存在,则抛出异常
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
} else {
sqlProviderAnnotation = method.getAnnotation(sqlAnnotationType);
//获取值,也就是sql语句
String[] strings = (String[])((String[])sqlProviderAnnotation.getClass().getMethod("value").invoke(sqlProviderAnnotation));
//生成SqlSource对象
return this.buildSqlSourceFromStrings(strings, parameterType, languageDriver);
}
} else if (sqlProviderAnnotationType != null) {
sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
//ProviderSqlSource实现了SqlSource
return new ProviderSqlSource(this.assistant.getConfiguration(), sqlProviderAnnotation, this.type, method);
} else {
return null;
}
} catch (Exception var8) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + var8, var8);
}
}
四、SQL ProviderAnnotation的使用
public interface IUserDao {
@ResultMap({"BaseResultMap"})
@SelectProvider(type = SelectSql.class,method = "createSelectSql")
List select();
class SelectSql{
public String createSelectSql(){
return new SQL(){{
SELECT("*");
FROM("tb_user");
}}.toString();
}
}
}
测试代码
public static void main( String[] args )
{
String resource = "mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession2 = build.openSession();
IUserDao mapper = sqlSession2.getMapper(IUserDao.class);
System.out.println(mapper.select());
} catch (IOException e) {
e.printStackTrace();
}
}
end....