问题. 通常一个mapper.xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
一、SqlSource类
1、Mybatis会把每个sql语句封装成一个SqlSource对象,根据xml文件或者注解的配置,创建sql 语句对应的SqlSource对象。
2、MappedStatement类
每一个sql 语句就对应一个MappedStatement对象,这里面有两个属性很重要。
id :全限定类名+方法名组成的ID。
sqlSource:当前sql语句对应的SqlSource对象
3、MapperProxyFactory 类用来创建接口的代理对象
每个DAO接口类对应一个MapperProxyFactory ,MapperProxyFactory 可以通过jdk 的动态代理生成接口的代理对象。
4、MapperRegistry 类,用来注册接口类对应的MapperProxyFactory
MapperRegistry 类里面有一个名为knownMappers的HashMap, 这个map 用来存放接口类的MapperProxyFactory 。键为:接口类对象,值为:接口类对象对应的MapperProxyFactory 。
5、MapperProxy 类
这个类实现了InvocationHandler 接口,在生成接口的动态代理对象时,这类就是代理对象里面的
InvocationHandler。
6、Configuration类
mybatis 里面的全局配置文件对象,Configuration类里面有一个mappedStatements属性,是用来存放所有的MappedStatement对象,mappedStatements 是一个StrictMap。这个Map的键为:接口的全限定名 + 方法名,值为:MappedStatement,即sql 语句对应的MappedStatement对象。所以可以根据全限定接口名+ 方法名 确定一个唯一的MappedStatement对象。
总结:mybatis 里面 sql 语句执行流程及xml 文件 或者SQL注解 和DAO 文件的映射关系
1、生成SqlSessionFactory对象
默认实现是DefaultSqlSessionFactory
2、获取SqlSession对象
通过调用DefaultSqlSessionFactory的openSession()方法来获取SqlSession对象,
默认返回的是一个DefaultSqlSession实例对象。
3、获取MapperProxy对象
UserDao userMapper = sqlSession.getMapper(UserDao.class);
// sqlSession.getMapper 方法
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// Configuration类的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry类的getMapper
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);
}
}
通过上述代码可以看出sqlSession.getMapper() 最终会调用MapperRegistry类的getMapper()方法,拿到Mapper 对象对应的mapperProxyFactory, 然后通过mapperProxyFactory.newInstance() 创建Mapper 的代理对象。生成的userMapper 实际上是一个动态代理对象。
User user = userMapper.findUserById(10); // 当代理对象调用方法时,由动态代理的原理可知,会调用到handler 里面的invoke 方法。
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);
}
// mapperMethod.execute() 方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
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 if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
// sqlSession.update(command.getName(), param)
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();
}
}
//statement 为权限定类名加方法名,通过statement拿到sql 语句对应的MappedStatement。这样DAO 文件和 xml文件或者sql 注解通过全限定类名+方法名对应起来。
总结:DAO 文件的工作原理其实是通过jdk底层的动态代理技术实现的,由于权限定类名+ 方法名唯一确定一个 sql 语句的MappedStatement。所有DAO文件的方法不能重载,也就是说,方法名不能相同。