写在前面
mybatis作为现在最流行的半自动orm框架。我们做Java企业开发几乎都用过,比起jpa个人更喜欢mybatis一些,一是他更灵活,复杂的sql可以写在xml,二是随着mybatis-Plus与springBoot的使用,使用起来越来越简单。之前一直想读一下mybatis源码,这里不得不吐槽一下mybatis源码,注释真的太少了。所以这次只有借助网上文章与debug硬着头皮读。但其实读下来过后发现比spring简单多了。这里自己稍微总结一下,也想与想了解mybatis源码的同学一起讨论。下面说的都是自己的总结,可能存在不对或者表达不清楚的地方,欢迎大家留言讨论。
从Jdbc说起
mybatis其实就是对Jdbc的一层封装。大家可以回想一下我们在刚刚学java时,使用Jdbc进行数据库操作。代码与下面类似
List<User> result = new ArrayList<>();
//1.加载驱动程序
17 Class.forName("com.mysql.jdbc.Driver");
18 //2.获得数据库的连接
19 Connection conn = DriverManager.getConnection(URL, NAME, PASSWORD);
20 //3.通过数据库的连接操作数据库,实现增删改查
21 Statement stmt = conn.createStatement();
22 ResultSet rs = stmt.executeQuery("select name,age from user");//选择import java.sql.ResultSet;
23 while(rs.next()){
24 String name = rs.getString("user_name");
Int age = rs.getInt("age");
User user= new User(name,age);
result.add(user);
25 }
return result;
对于自己写的demo这样操作是可以的。但是对于一个商业项目或者大型项目来说,如果你所有db操作都这样操作会存在哪些问题呢?
- 冗余代码,你所有进行操作的地方,都需要进行这4步操作。
- 代码污染。sql在代码里面遍地就是并且 对于结果集处理会占据大量的代码。因为从ResultSet到javaBean的映射较麻烦,针对不同的java类型要编写不同的typeHandler。
- 使用缓存麻烦。试想在上述的例子中。如果大量的sql都是是“select name,age from user”。是的。你还得配套写一个缓存组件。 其实上述这些缺点。Mybatis都帮我们解决了。这就是我们为什么要使用一个成熟的orm框架来帮我快捷的开发,让我们只用专注于sql编写。
mybatis的执行流程
上面说了mybatis是对jdbc的封装(封装解决了上述描述的以及其他问题)。它代码底层在具体执行操作的时候,依然使用的是Jdbc的操作。那它的整体流程其实跟jdbc大差不差。但它毕竟一是进行了功能增强(缓存,结果集处理),二是保证代码质量(毕竟apache项目)。所以它整个流程要复杂一些。 使用了mybatis的同学都知道。如果不借助spring。只是使用mybatis进行操作,代码如下:
SqlSessionFactory factory= new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession(true); // 自动提交事务
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
StudentEntity studentEntity = studentMapper.getStudentById(1);
代码也非常简单。
- 构建SqlSessionFactory对象
- 拿到SqlSession,进而拿到mapper
- 进行方法调用
- 拿到最终查询对象 但它在代码内部,其实要复杂一些。
第一步是配置解析,这一步的重点就是根据配置文件生成Configuration。Configuration 是mybatis的大管家,大家这里有个印象就行,后续会专门讲一下Configuration这个类。它里面包含mybatis的重要组件,配置(也就是我们在配置文件中配置的属性。例如mapper扫描路径,是否开启驼峰命名,官网中配置里面的参数都会加载到该类中:mybatis.org/mybatis-3/z… )都在里面。并且源码里面许多执行都是通过Configuration.getXXX().do。个人理解有点像springMvc的dispatcherSerlvet + Spring中的ApplicationContext。大家只需记住2点就行。
- 它是大管家,里面包含了重要组件与配置
- mybatis初始化的时候会初始化它
第二步是创建会话,主要是获得了一个SqlSession对象。其实sqlSession内部是通过一个叫做Executor来进行sql调用的,这其实就是设计模式中的门面模式,对于用户来说,我们只需要通过sqlSession来进行db操作,但其实内部是通过Executor来调用。Executor是mybatis中非常重要一个组件,他其实就是执行器,我们面试常见的一级缓存,二级缓存都在这里面。并且个人认为这块代码写的非常漂亮,后续也会单独讲。
第三步是获取Mapper对象。内部使用的是动态代理, 当然也可以直接使用sqlSession进行查询
List<StudentEntity> o = sqlSession.selectOne("mapper.StudentMapper");
动态代理的好处,官方文档已经说明了:"有很多优势,首先它不依赖于字符串字面值,会更安全一点;其次,如果你的 IDE 有代码补全功能,那么代码补全可以帮你快速选择到映射好的 SQL 语句。"
第四步是执行方法。执行方法又细分为准备参数与执行方法。也就是将我们sql中的?替换为具体的值,然后调用jdbc的execute方法
第五步就是拼装返回结果。这块代码是Mybatis中最复杂的地方。ResultSetHandler 只有一个实现类DefaultResultSetHandler。所有拼装的代码都在这个类里面。
重要组件/类
流程中的类
- Configuration 大管家,涵盖所有的配置与重要组件
- SqlSession
- 可以直接进行sql操作,但是其实内部是executor执行
- 也可以获取动态代理过后mapper对象
- Executor
- 真正执行sql语句的对象。里面还包括了一级缓存与二级缓存
- Statement 这个就是jdbc的Statement,我们最最常用的PreparedStatement就是继承于它。负责进行db查询
- ResultSetHandler 负责将查询的结果做结果集映射,所有代码都在唯一实现类DefaultResultSetHandler
- StatementHandler 这个类可以看做另一个门面。他里面包括了Statement与ResultSetHandler。将上述查询与结果集拼装合成在自己的方法里面。对外来说都是StatementHandler进行查询并返回对应的结果集。
其实通过上述这几个类就可以看出mybatis整个粗略的执行流程是非常简单的。
其余重要的类
- MetaObject 这个是个人觉得读mybatis源码最大的收获。它其实就是一个反射的工具类。并且功能强大,后续有需要可以直接借鉴(bai piao)
- SqlSource与BoundSql
- 我们xml中的那些sql解析为SqlSource。而SqlSource只有一个方法就是getBoundSql,BoundSql就是我们要执行的sql的包装类。里面包含了我们的sql,与我们的参数。
后续
后续会以单独讲述mapper动态代理,Configuration,executor,ResultSetHandler以及MetaObject