一、Mapper案例 相信只要是使用过MyBatis开发的同学,都有使用其中的Mapper接口来开发,因为确实是很方便,方便到我们只需要编写接口而不需要写实现类 ,就能够完成对数据库的CRUD操作。
但是不知道大家有没有去思考过,如果我们真的只有Mapper接口的话,程序又是如何去完成实际的业务的呢?来看看下面的代码
1. UserMapper cn.wolfcode.mybatis.mapper.UserMapper接口
public interface UserMapper { void save (User u) ; }
2. UserMapper.xml映射文件 <mapper namespace ="cn.wolfcode.mybatis.mapper.UserMapper" > <insert id ="save" > INSERT INTO user (id, username, password) VALUES (NULL, #{username}, #{password}) </insert > </mapper >
3. 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
这里的实现原理就是我们前面所讲过的 动态代理 ,接下来我们看一波源码。
二、Mapper中的动态代理 通过debug断点调试,我们可以依次看到下面的代码
1. 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方法可以获取到一个代理对象 ,继续往下看
2. 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的动态代理为我们生成实现类。
相信大家和我一样,还想继续了解一下,在这个代理类中具体为我们做了什么,那好,我们继续
3. MapperProxy 如果大家了解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);
4. mapperMethod.execute MapperMethod: 根据insert还是update,还是delete等等……来执行sqlSession的方法
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中根据执行的方法 (Insert、delete等等)返回值决定调用SqlSession中的对应方法执行SQL