MyBatis 3源码解析
Posted xpz-python
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis 3源码解析相关的知识,希望对你有一定的参考价值。
MapperProxy
到了这了大家可能还有一个疑问,我调用的是DAO接口中的方法,和上面这些好像没关系。别急接下来我们就来看看二者是怎么联系起来的
在mybatis和Spring集合使用中,使用DAO时我们一般使用@Autowired注入,但是大家有没有一个疑问,DAO是一个接口,接口是不能创建对象的,这个是怎么完成的呢?
Mybatis获取如何获取Mapper?
先来看第一个疑惑:使用mybatis操作数据库时,方法是这样的:
SqlSessionFactory sessionFactory = null;
String resource = "mybatis-conf.xml";
sessionFactory = new SqlSessionFactoryBuilder().build(Resources
.getResourceAsReader(resource));
SqlSession sqlSession = sessionFactory.openSession();
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("jdk proxy mapper : "+mapper);
UserPO userPo = mapper.getUserByUsername("zhangsan");
如何获取SqlSession上面我们已经了解了,这里重点关注session.getMapper(UserMapper.class)这一句
上图描述了getMapper()这个方法的过程,我们来看MapperRegistry中的这个方法:
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
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);
}
}
konwMapper会在创建MapperRegistry对象是初始化,key是我们定义的Dao的class对象,value是根据各Dao接口的class对象创建的MapperProxyFactory、接下来我们看MapperProxyFactory:
public class MapperProxyFactory<T> {
//我们的Dao接口的class对象
private final Class<T> mapperInterface;
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
到了这一步我们明白了,这是通过动态代理创建Dao接口的动态类的对象,而对接口所有方法的调用,最后都会回到调用mapperProxy的invoke方法上(这就是JDK动态代理)。我们去看看mapperProxy对象的invoke方法:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断你调用的是否是已实现的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
if判断我们调用的方法是否是对象中的,我们调用的都是接口的方法,所以直接走mapperMethod.execute()。mapperMethod标识我们调用接口中的那个方法:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
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接上了,最后这些操作都会归为SqlSession中的update、selectList、select操作了。而这其实我们已经知道了,最后SqlSession其实是交给Statement执行SQL命令了。
mybatis-spring如何获取Mapper?
下面来解决另外一个疑惑@Autowired注入的是个什么鬼?
注入的其实就是动态代理创建的接口实现类型的对象,不过mybatis-spring把sqlSession.getMapper()这个动作交个了MapperFactoryBean。而这个实在Spring扫描dao层的时候,为每一个接口分别创建一个MapperFactoryBean:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
mapperInterface就是dao的class对象,因为实现了FactoryBean接口,因此通过@Autowired获取对象时,实际是调用getObject方法获取对象,而这里有回到了sqlSession.getMapper()。到此终于和上面接上了
以上是关于MyBatis 3源码解析的主要内容,如果未能解决你的问题,请参考以下文章