网站运营职业分析,优秀的网页设计案例,北京家装公司十大排名,企业oa管理软件#x1f38d;目录 ⛳ MyBatis 中 Mapper 接口工作原理实例解析#x1f3a8; 一、Mapper 接口是怎么找到实现类的#xff1f;#x1f43e; 二、从一段代码看起#x1f69c; 三、Mapper 接口#x1f3ed; 四、Mapper 接口的动态代理类的生成#x1f381; 五、总结 ⛳ MyBa… 目录 ⛳ MyBatis 中 Mapper 接口工作原理实例解析 一、Mapper 接口是怎么找到实现类的 二、从一段代码看起 三、Mapper 接口 四、Mapper 接口的动态代理类的生成 五、总结 ⛳ MyBatis 中 Mapper 接口工作原理实例解析 本篇文章主要介绍了MyBatis Mapper接口工作源里实例解析文中通过示例代码介绍的非常详细 KeyWords: Mybatis 原理源码Mybatis Mapper 接口实现类代理模式动态代理Java动态代理Proxy.newProxyInstanceMapper 映射Mapper 实现
MyBatis 是一款优秀的持久层框架它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。我们在使用 Mybaits 进行 通常只需要定义几个 Mapper 接口然后在编写一个 xml 文件我们在配置文件中写好 sql , Mybatis 帮我们完成 Mapper 接口道具体实现的调用。以及将结果映射到 model bean 中。
我们在项目中所编写的众多的 Mapper 类只是一个接口(interface )根据 Java 的多态性我们知道可以使用接口接口作为形参进而在运行时确定具体实现的对象是什么。但是对于 Mapper 接口我们并没有编写其实现类Mybatis是如何找到其实现类进而完成具体的 CRUD 方法调用的呢原理何在 一、Mapper 接口是怎么找到实现类的
为了弄清楚 Mapper 接口是如何找到实现类的我们先回忆一下 Mybatis 是怎么使用的根据实际的例子进而一点点的去分析。这里的使用指的是Mybatis 单独使用而不是整合 spring , 因为整合 spring 的话还需要涉及 Mapper dao 装载到 spring 容器的问题spring 帮忙创建数据源配置等问题。
通常我们使用 Mybatis 的主要步骤是
构建 SqlSessionFactory ( 通过 xml 配置文件 , 或者直接编写Java代码)从 SqlSessionFactory 中获取 SqlSession从SqlSession 中获取 Mapper调用 Mapper 的方法 例如blogMapper.selectBlog(int blogId) 二、从一段代码看起
上面我们概括了使用 Mybatis 的4个步骤。这4个步骤看起来很简单但是用代码写出来就很多。我们不妨先记着这4个步骤再去看代码会容易点。
// 1.
DataSource dataSource BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory new JdbcTransactionFactory();
Environment environment new Environment(development, transactionFactory, dataSource);
Configuration configuration new Configuration(environment);
configuration.addMapper(BlogMapper.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(configuration);
// 2.
SqlSession session sqlSessionFactory.openSession();
try {// 3. BlogMapper mapper session.getMapper(BlogMapper.class);// 4.Blog blog mapper.selectBlog(1);
} finally {session.close();
}在这块代码中第 1 部分我们使用了 Java 编码的形式来实现 SqlSessionFactory 也可以使用 xml 。如果使用xml的话上面的第一部分代码就是这样的
String resource org/mybatis/example/mybatis-config.xml; // xml内容就不贴了
InputStream inputStream Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);我们本次的目标是弄清楚 “ Mapper 是如何找到实现类的 ”我们注意上面代码 3 , 4 的位置 // 3. BlogMapper mapper session.getMapper(BlogMapper.class);// 4.Blog blog mapper.selectBlog(1);**这里 mapper 可以调用selectBlog(1) 这个方法说明 mapper 是个对象因为对象才具有方法行为实现啊。**BlogMapper接口是不能实例化的更没有具体方法实现。我们并没有定义一个类让它实现BlogMapper接口而在这里它只是通过调用session.getMapper() 所得到的。由此我们可以推断肯定是session.getMapper() 方法内部产生了BlogMapper的实现类。有什么技术可以根据BlogMapper 接口生成了一个实现类呢想到这里对于有动态代理 使用经验的程序员来说很容易想到这背后肯定是基于动态代理技术具体怎么实现的呢下面我们来根据源码一探究竟。 三、Mapper 接口
从上面的代码中我们知道 BlogMapper 接口的实现类是从session.getMapper中得来的大概是基于动态代理技术实现。我们既然能够从SqlSession中得到BlogMapper接口的那么我们肯定需要先在哪里把它放进去了然后 SqlSession 才能生成我们想要的代理类啊。上面代码中有这么一行
configuration.addMapper(BlogMapper.class);跟着这个 addMapper 方法的代码实现是这样的 public T void addMapper(ClassT type) {mapperRegistry.addMapper(type);}我们看到这里 mapper 实际上被添加到 mapperRegissry 中。继续跟进代码
public class MapperRegistry {private final MapClass?, MapperProxyFactory? knownMappers new HashMapClass?, MapperProxyFactory?();public T void addMapper(ClassT 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 MapperProxyFactoryT(type)); // 注意这里MapperAnnotationBuilder parser new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}
}看到这里我们知道上面所执行的configuration.addMapper(BlogMapper.class); 其实最终被放到了HashMap中其名为knownMappers knowMappers是MapperRegistry 类的一个私有属性它是一个HashMap 。其Key 为当前Class对象value 为一个MapperProxyFactory 实例。
这里我们总结一下 诸如BlogMapper 之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFacory从名字来看好像是一个工厂用来创建Mapper Proxy的工厂。我们继续往下看。 四、Mapper 接口的动态代理类的生成
上面我们已经知道Mapper 接口被到注册到了MapperRegistry中——放在其名为knowMappers 的HashMap属性中我们在调用Mapper接口的方法的时候是这样的
BlogMapper mapper session.getMapper(BlogMapper.class);这里我们跟踪一下session.getMapper() 方法的代码实现这里 SqlSession 是一个接口他有两个实现类一个是DefaultSqlSession,另外一个是SqlSessionManager,这里我们用的是DefaultSqlSession. 为什么是DefaultSqlSession呢因为我们在初始化SqlSessionFactory的时候所调用的SqlSessionFactoryBuilder的build()方法里边配置的就是DefaultSqlSession, 所以我们进入到DefaultSession类中看看它对session.getMapper(BlogMapper.class)是怎么实现的
public class DefaultSqlSession implements SqlSession {private Configuration configuration; Overridepublic T T getMapper(ClassT type) {return configuration.TgetMapper(type, this); //最后会去调用MapperRegistry.getMapper}
}如代码所示这里的 getMapper 调用了 configuration.getMapper , 这一步操作其实最终是调用了MapperRegistry,而此前我们已经知道MapperRegistry是存放了一个HashMap的我们继续跟踪进去看看那么这里的get肯定是从这个hashMap中取数据。我们来看看代码
public class MapperRegistry {private final MapClass?, MapperProxyFactory? knownMappers new HashMapClass?, MapperProxyFactory?();// Mapper 映射public T T getMapper(ClassT type, SqlSession sqlSession) {final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type);try {return mapperProxyFactory.newInstance(sqlSession); // 重点看这里} catch (Exception e) {}}
}我们调用的session.getMapper(BlogMapper.class);最终会到达上面这个方法这个方法根据BlogMapper的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象然后调用这个对象的newInstance()方法。根据这个名字我们就能猜到这个方法是创建了一个对象代码是这样的
public class MapperProxyFactoryT { //映射器代理工厂private final ClassT mapperInterface;private MapMethod, MapperMethod methodCache new ConcurrentHashMapMethod, MapperMethod();public MapperProxyFactory(ClassT mapperInterface) {this.mapperInterface mapperInterface;}// 删除部分代码便于阅读SuppressWarnings(unchecked)protected T newInstance(MapperProxyT mapperProxy) {//使用了JDK自带的动态代理生成映射器代理类的对象return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxyT mapperProxy new MapperProxyT(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}}看到这里就清楚了最终是通过Proxy.newProxyInstance产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现运用了代理模式。具体是使用了JDK动态代理这个Proxy.newProxyInstance方法生成代理类的三个要素是
ClassLoader —— 指定当前接口的加载器即可当前被代理的接口是什么 —— 这里就是 BlogMapper代理类是什么 —— 这里就是 MapperProxy
代理模式中代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码如下
public class MapperProxyT implements InvocationHandler, Serializable {// 实现了InvocationHandlerOverridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//代理以后所有Mapper的方法调用时都会调用这个invoke方法if (Object.class.equals(method.getDeclaringClass())) {try {return method.invoke(this, args); // 注意1} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}final MapperMethod mapperMethod cachedMapperMethod(method); // 使用了缓存//执行CURDreturn mapperMethod.execute(sqlSession, args); // 注意2}
}我们调用的 Blog blog mapper.selectBlog(1); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中if 语句先判断我们想要调用的方法是否来自Object类这里的意思就是如果我们调用toString()方法那么是不需要做代理增强的直接还调用原来的method.invoke()就行了。只有调用selectBlog()之类的方法的时候才执行增强的调用——即mapperMethod.execute(sqlSession, args);这一句代码逻辑。
而mapperMethod.execute(sqlSession, args);这句最终就会执行增删改查了代码如下 public Object execute(SqlSession sqlSession, Object[] args) {Object result;if (SqlCommandType.INSERT command.getType()) { //insert 处理调用SqlSession的insertObject param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.insert(command.getName(), param));} else if (SqlCommandType.UPDATE command.getType()) { // updateObject param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.update(command.getName(), param));} else if (SqlCommandType.DELETE command.getType()) { // deleteObject param method.convertArgsToSqlCommandParam(args);result rowCountResult(sqlSession.delete(command.getName(), param));} else if (SqlCommandType.SELECT command.getType()) {// 删除部分代码 } else {throw new BindingException(Unknown execution method for: command.getName());}// 删除部分代码return result;}再往下一层就是执行JDBC那一套了获取链接执行得到ResultSet解析ResultSet映射成JavaBean。
至此我们已经摸清楚了Blog blog mapper.selectBlog(1); 中BlogMapper接口调用到得到数据库数据过程中Mybaitis 是如何为接口生成实现类的以及在哪里出发了最终的CRUD调用。实际上如果我们在调用Blog blog mapper.selectBlog(1);之前把从slqSession中得到的 mapper 对象打印出来就会看到输出大概是这样的
com.sun.proxy.$Proxy17动态代理没错吧Java动态代理实在是太美妙了。 五、总结
上面我们用层层深入的方式摸清楚了 Mapper接口是如何找到实现类的。我们分析了 Mapper接口是如何注册的Mapper接口是如何产生动态代理对象的Maper接口方法最终是如何执行的。总结起来主要就是这几个点 Mapper 接口在初始SqlSessionFactory 注册的。 Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中 key Mapper class value 创建当前Mapper的工厂。 Mapper 注册之后可以从SqlSession中get SqlSession.getMapper 运用了 JDK动态代理产生了目标Mapper接口的代理对象。 动态代理的 代理类是 MapperProxy 这里边最终完成了增删改查方法的调用。