相信只要是使用过 MyBatis 开发的同学, 都有使用其中的 Mapper 接口来开发, 因为确实是很方便, 方便到我们只需要编写接口而不需要写实现类, 就能够完成对数据库的 CRUD 操作, 但是不知道大家有没有去思考过, 如果我们真的只有 Mapper 接口的话, 程序又是如何去完成实际的业务的呢? 来看看下面的代码
cn.wolfcode.mybatis.mapper.UserMapper 接口
public interface UserMapper {
void save(User u);
}
UserMapper.xml 映射文件
```java
```java
<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