前言
前两天写了一个 手写Spring ioc 框架,“撸撸”Spring 源码我们今天来整整Mybatis。
mybaits 在 ORM 框架中,可算是半壁江山了,由于它是轻量级,半自动加载,灵活性和易拓展性。深受广大公司的喜爱,所以我们程序开发也离不开 mybatis 。
很多朋友对 mybatis 源码没什么了解,或者想看但是不知道怎么看的苦恼吗?
归根结底,我们还是需要知道为什么会有 mybatis ,mybatis 解决了什么问题? 想要知道 mybatis 解决了什么问题,就要知道传统的 JDBC 操作存在哪些痛点才促使 mybatis 的诞生。 我们带着这些疑问,再来一步步学习吧。
内容稍微有点长!耐心阅读!
另外本人整理收藏了20年多家公司面试知识点整理 ,以及各种Java核心知识点免费分享给大家,下方只是部分截图
想要资料的话也可以点击直接进入:暗号:csdn,免费获取。
传统JDBC的弊端
所以我们先来来看下原始 JDBC 的操作
我们知道最原始的数据库操作。分为以下几步
- 获取 connection 连接
- 获取 preparedStatement
- 参数替代占位符
- 获取执行结果 resultSet
- 解析封装 resultSet 到对象中返回。
如下是原始 JDBC 的查询代码,存在哪些问题?
public static void main(String[] args) {
String dirver="com.mysql.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
String userName="root";
String password="123456";
Connection connection=null;
List<User> userList=new ArrayList<>();
try {
Class.forName(dirver);
connection= DriverManager.getConnection(url,userName,password);
String sql="select * from user where username=?";
PreparedStatement preparedStatement=connection.prepareStatement(sql);
preparedStatement.setString(1,"张三");
System.out.println(sql);
ResultSet resultSet=preparedStatement.executeQuery();
User user=null;
while(resultSet.next()){
user=new User();
user.setId(resultSet.getInt("id"));
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
userList.add(user);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (!userList.isEmpty()) {
for (User user : userList) {
System.out.println(user.toString());
}
}
}
大家是不是发现了上面有哪些不友好的地方?
我这里总结了以下几点:
- 数据库的连接信息存在硬编码,即是写死在代码中的。
- 每次操作都会建立和释放 connection 连接,操作资源的不必要的浪费。
- sql 和参数存在硬编码。
- 将返回结果集封装成实体类麻烦,要创建不同的实体类,并通过 set 方法一个个的注入。
存在上面的问题,所以 mybatis 就对上述问题进行了改进。 对于硬编码,我们很容易就想到配置文件来解决。mybatis 也是这么解决的。 对于资源浪费,我们想到使用连接池,mybatis 也是这个解决的。 对于封装结果集麻烦,我们想到是用 JDK 的反射机制,好巧,mybatis 也是这么解决的。
设计思路
既然如此,我们就来写一个自定义持久层框架,来解决上述问题,当然是参照 mybatis 的设计思路,这样我们在写完之后,再来看 mybatis 的源码就恍然大悟,这个地方这样配置原来是因为这样啊。
我们分为使用端和框架端两部分。
学海无涯,我们一起勉力前行!
Ps:有需要的小伙伴可以点击直接进入:暗号:csdn,免费获取。
使用端
我们在使用 mybatis 的时候是不是需要使用 SqlMapConfig.xml 配置文件,用来存放数据库的连接信息,以及 mapper.xml 的指向信息。mapper.xml 配置文件用来存放 sql 信息。
所以我们在使用端来创建两个文件 SqlMapConfig.xml 和 mapper.xml。
框架端
框架端要做哪些事情呢?如下:
- 获取配置文件。也就是获取到使用端的 SqlMapConfig.xml 以及 mapper.xml 的文件
- 解析配置文件。对获取到的文件进行解析,获取到连接信息,sql,参数,返回类型等等。这些信息都会保存在 configuration 这个对象中。
- 创建 SqlSessionFactory,目的是创建 SqlSession 的一个实例。
- 创建 SqlSession ,用来完成上面原始 JDBC 的那些操作。
那在 SqlSession 中 进行了哪些操作呢?
- 获取数据库连接
- 获取 sql ,并对 sql 进行解析
- 通过内省,将参数注入到 preparedStatement 中
- 执行 sql
- 通过反射将结果集封装成对象
使用端实现
好了,上面说了一下,大概的设计思路,主要也是仿照 mybatis 主要的类实现的,保证类名一致,方便我们后面阅读源码。我们先来配置好使用端吧,我们创建一个 maven 项目。
在项目中,我们创建一个 User 实体类
public class User {
private Integer id;
private String username;
private String password;
private String birthday;
//getter()和 setter()方法
}
创建 SqlMapConfig.xml 和 Mapper.xml SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false"></property>
<property name="userName" value="root"></property>
<property name="password" value="123456"></property>
<mapper resource="UserMapper.xml">
</mapper>
</configuration>
可以看到我们 xml 中就配置了数据库的连接信息,以及 mapper 一个索引。mybatis 中的 SqlMapConfig.xml 中还包含其他的标签,只是丰富了功能而已,所以我们只用最主要的。
mapper.xml 是每个类的 sql 都会生成一个对应的 mapper.xml 。我们这里就用 User 类来说吧,所以我们就创建一个 UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="cn.quellanan.dao.UserDao">
<select id="selectAll" resultType="cn.quellanan.pojo.User">
select * from user
</select>
<select id="selectByName" resultType="cn.quellanan.pojo.User" paramType="cn.quellanan.pojo.User">
select * from user where username=#{username}
</select>
</mapper>
可以看到有点 mybatis 里面文件的味道,有 namespace 表示命名空间,id 唯一标识,resultType 返回结果集的类型,paramType 参数的类型。 我们使用端先创建到这,主要是两个配置文件。
我们接下来看看框架端是怎么实现的。
框架端实现
架端,我们按照上面的设计思路一步一步来。
获取配置
怎么样获取配置文件呢?我们可以使用 JDK 自带自带的类 Resources 加载器来获取文件。我们创建一个自定义 Resource 类来封装一下:
import java.io.InputStream;
public class Resources {
public static InputStream getResources(String path){
//使用系统自带的类 Resources 加载器来获取文件。
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}
这样通过传入路径,就可以获取到对应的文件流啦。
解析配置文件
上面获取到了 SqlMapConfig.xml 配置文件,我们现在来解析它。 不过在此之前,我们需要做一点准备工作,就是解析的内存放到什么地方?
所以我们来创建两个实体类 Mapper 和 Configuration 。
Mapper Mapper 实体类用来存放使用端写的 mapper.xml 文件的内容,我们前面说了里面有 id、sql、resultType 和 paramType .所以我们创建的 Mapper 实体如下:
public class Mapper {
private String id;
private Class<?> resultType;
private Class<?> parmType;
private String sql;
//getter()和 setter()方法
}
这里我们为什么不添加 namespace 的值呢? 聪明的你肯定发现了,因为 mapper 里面这些属性表明每个 sql 都对应一个 mapper , 而 namespace 是一个命名空间,算是 sql 的上一层,所以在 mapper 中暂时使用不到,就没有添加了。
Configuration Configuration 实体用来保存 SqlMapConfig 中的信息。所以需要保存数据库连接,我们这里直接用 JDK 提供的 DataSource 。还有一个就是 mapper 的信息。每个 mapper 有自己的标识,所以这里采用 hashMap 来存储。如下:
public class