Mapper接口用于定义执行SQL语句相关的方法,方法名一般和Mapper XML配置文件中
在介绍Mapper接口之前,我们先来回顾一下如何执行Mapper中定义的方法,可参考下面的代码:
@Test
public void testMybatis () throws IOException {
// 获取配置文件输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 调用openSession()方法创建SqlSession实例
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取UserMapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行Mapper方法,获取执行结果
List<UserEntity> userList = userMapper.listAllUser();
System.out.println(JSON.toJSONString(userList));
}
如上面的代码所示,在创建SqlSession实例后,需要调用SqlSession的getMapper()方法获取一个UserMapper的引用,然后通过该引用调用Mapper接口中定义的方法。
我们知道,接口中定义的方法必须通过某个类实现该接口,然后创建该类的实例,才能通过实例调用方法。所以SqlSession对象的getMapper()方法返回的一定是某个类的实例。具体是哪个类的实例呢?实际上getMapper()方法返回的是一个动态代理对象。
MyBatis中通过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;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 从Object类继承的方法不做处理
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);
}
// 对Mapper接口中定义的方法进行封装,生成MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行方法
return mapperMethod.execute(sqlSession, args);
}
......
}
MapperProxy使用的是JDK内置的动态代理,实现了InvocationHandler接口,invoke()方法中为通用的拦截逻辑,具体内容在介绍Mapper方法调用过程时再做介绍。
使用JDK内置动态代理,通过MapperProxy类实现InvocationHandler接口,定义方法执行拦截逻辑后,还需要调用java.lang.reflect.Proxy类的newProxyInstance()方法创建代理对象。
MyBatis对这一过程做了封装,使用MapperProxyFactory创建Mapper动态代理对象。MapperProxyFactory代码如下:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@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);
}
}
如上面的代码所示,MapperProxyFactory类的工厂方法newInstance()是非静态的。也就是说,使用MapperProxyFactory创建Mapper动态代理对象首先需要创建MapperProxyFactory实例。MapperProxyFactory实例是什么时候创建的呢?
Configuration对象中有一个mapperRegistry属性,具体如下:
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
MyBatis通过mapperRegistry属性注册Mapper接口与MapperProxyFactory对象之间的对应关系。下面是MapperRegistry类的关键代码:
public class MapperRegistry {
// Configuration对象引用
private final Configuration config;
// 用于注册Mapper接口Class对象,和MapperProxyFactory对象对应关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
// 根据Mapper接口Class对象获取Mapper动态代理对象
@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);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
// 根据Mapper接口Class对象,创建MapperProxyFactory对象,并注册到knownMappers属性中
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
......
}
如上面的代码所示,MapperRegistry类有一个knownMappers属性,用于注册Mapper接口对应的Class对象和MapperProxyFactory对象之间的关系。另外,MapperRegistry提供了addMapper()方法,用于向knownMappers属性中注册Mapper接口信息。在addMapper()方法中,为每个Mapper接口对应的Class对象创建一个MapperProxyFactory对象,然后添加到knownMappers属性中。
MapperRegistry还提供了getMapper()方法,能够根据Mapper接口的Class对象获取对应的MapperProxyFactory对象,然后就可以使用MapperProxyFactory对象创建Mapper动态代理对象了。
MyBatis框架在应用启动时会解析所有的Mapper接口,然后调用MapperRegistry对象的addMapper()方法将Mapper接口信息和对应的MapperProxyFactory对象注册到MapperRegistry对象中。
我们首先创建一个Mapper接口,用来模拟
public interface DevMapper {
//表示获取设备信息
DevPo getDev(int id);
}
这个类可以看做MapperProxy,就是实现对接口的动态代理
public class MapperProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("参数为" + args);
//这里看做Mybatis一顿解析最后成功查询出数据并返回
DevPo devPo = new DevPo();
devPo.setId(5);
devPo.setName("zhixuchen");
devPo.setNo("sadas");
return devPo;
}
}
这个类可以看做MapperProxyFactory,生成动态代理类
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
DevMapper devMapper = (DevMapper) Proxy.newProxyInstance(
DevMapper.class.getClassLoader(),
new Class[]{Resources.classForName(DevMapper.class.getName())},
new MapperProxy()
);
DevPo dev = devMapper.getDev(5);
System.out.println(dev.toString());
}
}
输出结果:
参数为[Ljava.lang.Object;@20ad9418
DevPo{id=5, name='zhixuchen', no='sadas'}
学习记录,不断沉淀,终究会成为一个优秀的程序员,加油!
您的点赞、关注与收藏是我分享博客的最大赞赏!
博主博客地址: https://blog.csdn.net/qq_45701514