Mybatis中Mapper的实现原理解析

作者:钟昕灵,叩丁狼教育高级讲师

相信只要是使用过MyBatis开发的同学,都有使用其中的Mapper接口来开发,因为确实是很方便,方便到我们只需要编写接口而不需要写实现类,就能够完成对数据库的CRUD操作,但是不知道大家有没有去思考过,如果我们真的只有Mapper接口的话,程序又是如何去完成实际的业务的呢?来看看下面的代码

cn.wolfcode.mybatis.mapper.UserMapper接口

public interface UserMapper {
 void save(User u);
}

UserMapper.xml映射文件

<mapper namespace="cn.wolfcode.mybatis.mapper.UserMapper">
 <insert id="save">
 INSERT INTO user (id, username, password) VALUES (NULL, #{username}, #{password})
 </insert>
</mapper>

UserServiceImpl业务方法

public void save(User u) throws IOException {
 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
 SqlSession session = factory.openSession();
 UserMapper mapper = session.getMapper(UserMapper.class);
 mapper.save(u);
 session.commit();
 session.close();
 }

从上面的代码中可以看出,我们只需要给MyBatis提供Mapper接口和与之匹配的映射文件,就能够让MyBatis按照我们的需求执行到对应的SQL

这里的实现原理就是我们前面所讲过的 动态代理,接下来我们看一波源码

通过debug断点调试,我们可以依次看到下面的代码

DefaultSqlSession:

public <T> T getMapper(Class<T> type) {
 return this.configuration.getMapper(type, this);
 }

Configuration:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 return this.mapperRegistry.getMapper(type, sqlSession);
 }

MapperRegistry:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
 if(mapperProxyFactory == null) {
 throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 } else {
 try {
 return mapperProxyFactory.newInstance(sqlSession);
 } catch (Exception var5) {
 throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
 }
 }
 }

该方法中最关键代码:mapperProxyFactory.newInstance(sqlSession);

MapperProxyFactory是一个创建MapperProxy的工厂类,调用其中的newInstance方法可以获取到一个代理对象,继续往下看

MapperProxyFactory:

protected T newInstance(MapperProxy<T> mapperProxy) {
 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
 }
 public T newInstance(SqlSession sqlSession) {
 MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
 return this.newInstance(mapperProxy);
 }

在该类中可以看到, Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);

最终由JDK的动态代理,动态的为我们在内存中创建了一个代理对象

到此,我们已经看到了一部分真相,就是我们为mybatis提供Mapper接口,而mybatis使用JDK的动态代理为我们生成实现类

相信大家和我一样,还想继续了解一下,在这个代理类中具体为我们做了什么,那好,我们继续

如果大家了解JDK的动态代理的话,那么就应该知道我们现在最关心的应该是InvocationHandler的实现,从上面的代码中可以看到,它叫做MapperProxy

MapperProxy:

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;
 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
 this.sqlSession = sqlSession;
 this.mapperInterface = mapperInterface;
 this.methodCache = methodCache;
 }
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 if(Object.class.equals(method.getDeclaringClass())) {
 return method.invoke(this, args);
 } else {
 MapperMethod mapperMethod = this.cachedMapperMethod(method);
 return mapperMethod.execute(this.sqlSession, args);
 }
 }
 private MapperMethod cachedMapperMethod(Method method) {
 MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
 if(mapperMethod == null) {
 mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
 this.methodCache.put(method, mapperMethod);
 }
 return mapperMethod;
 }
}

在invoke方法中可以看到,如果我们调用的是Object中的方法,不做任何处理,直接调用,否则执行:

mapperMethod.execute(this.sqlSession, args);

MapperMethod:

public Object execute(SqlSession sqlSession, Object[] args) {
 Object param;
 Object result;
 if(SqlCommandType.INSERT == this.command.getType()) {
 param = this.method.convertArgsToSqlCommandParam(args);
 result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
 } else if(SqlCommandType.UPDATE == this.command.getType()) {
 param = this.method.convertArgsToSqlCommandParam(args);
 result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
 } else if(SqlCommandType.DELETE == this.command.getType()) {
 param = this.method.convertArgsToSqlCommandParam(args);
 result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
 } else {
 if(SqlCommandType.SELECT != this.command.getType()) {
 throw new BindingException("Unknown execution method for: " + this.command.getName());
 }
 if(this.method.returnsVoid() && this.method.hasResultHandler()) {
 this.executeWithResultHandler(sqlSession, args);
 result = null;
 } else if(this.method.returnsMany()) {
 result = this.executeForMany(sqlSession, args);
 } else if(this.method.returnsMap()) {
 result = this.executeForMap(sqlSession, args);
 } else {
 param = this.method.convertArgsToSqlCommandParam(args);
 result = sqlSession.selectOne(this.command.getName(), param);
 }
 }
 if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
 throw new BindingException("Mapper method '" + this.command.getName() 
 + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
 } else {
 return result;
 }
 }

主要将SQL分为两类执行,DML和SQL

如果是DML,调用SQLSession中对应的方法执行,并且使用rowCountResult方法根据方法的返回值和受影响的行数做处理

如果是查询,则要根据方法的返回值的类型来执行不同的方法

如果Collection系的集合获取数组来接收,使用selectList方法执行查询

如果使用Map集合,调用selectMap方法执行查询

否则,调用selectOne执行查询

相信,源码看到这里,大家心里应该很清楚MyBatis中Mapper接口的使用原理了

最后总结一下:

MapperProxyFactory中,使用JDK的动态代理生成Mapper接口的代理代理类

由动态处理器MapperProxy中调用MapperMethod中的方法处理执行SQL

最后,在MapperMethod中根据执行的方法返回值决定调用SqlSession中的对应方法执行SQL

举报
评论 0