(一)Mybatis总体流程
(1)加载配置并初始化触发条件:加载配置文件
配置来源于两个地方,一个是配置文件(主配置文件conf. xml, mapper文件*.xml),一个是java代码中的注解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement对象,存储在内存之中
(2)接收调用请求
触发条件:调用Mybatis提供的API传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3)处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象处理过程:
- 根据SQL的ID查找对应的MappedStatement对象。
- 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
- 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
- 根据MappedStatement对象中的结果映射配置对得到的执行结果进入转换处理,并得到最终的处理结果。
- 释放连接资源。
(4)返回处理结果
将最终的处理结果返回。
(二)Mybatis核心构建
核心构建 | 描述 |
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合 |
ParameterHandler | 负责对用户传递的参数转换成JDBC Statement所需要的参数 |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合 |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条<select | update | delete | insert>节点的封装 |
sqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
(三)Mybatis层次结构
(四)源码解析
public class Entry {
public static void main(String[] args) {
MtConfiguration configuration = new MtConfiguration("mybatis-config.properties");
MtSqlSessionFactoryBuilder sqlSessionFactoryBuilder = new MtSqlSessionFactoryBuilder(configuration);
MtSqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build();
MtSqlSession sqlSession = sqlSessionFactory.openSession();
PersonDao personDao = sqlSession.getMapper(PersonDao.class);
Person person = personDao.queryPersonById(1l);
System.out.println(person);
}
}
下面是模拟Mybatis框架的代码目录。
从上面的图中可以看到涵盖了executor、plugin、typehandler等基础组件。然后再看下是如何使用的。
解析
怎么才能写出这样的框架呢?其实只需要对Mybatis的工作原理有大致的了解,知道那些模块是做什么的又是怎么实现的,然后画个流程图,然后定义需要哪些类和接口去实现,最后只需要将这些定义好的类和接口填空就可以了。
那么现在我们来看看这个Mybatis是怎么工作的
1.configuration模块
顾名思义,就是框架配置类,用于解析配置文件加载相关环境。这里代码里对配置文件的初始化主要有以下这么一个流程。
public MtConfiguration(String configLocation){
this.configLocation = configLocation;
init();
}
private void init(){
try {
//记载配置文件,这里使用properties代替xml解析
loadConfigProperties();
//初始化数据源信息
initDataSource();
//解析并加载mapper文件
loadMapperRegistory();
//解析加载plugin
initPluginChain();
//解析加载typeHandler
initTypeHandler();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
可以看到实例类的时候调用init方法进行初始化,分别加载配置信息、初始化数据源信息、解析并加载mapper文件、解析加载plugin、解析加载typeHandler这几个步骤。具体如何进行小伙伴们可以看下代码实现,代码写得简单,相信你们一看就懂。
2.session模块
这个模块用于用户与框架交互的入口。包括常用的sqlSession、sqlSessionFactory以及sqlSessionFactoryBuilder。
3.Mapper映射模块
这个模块主要用来注册我们的mapper映射文件的。
这里需要说明下MapperData这个类,这个类主要记录sql语句和sql语句返回类型信息的。然后MapperRegistory用来保存方法与MapperData的映射关系,这样我们执行某个方法是就能找到对应的Sql语句和对应的返回类型了。这个过程可以结合Mybatis解析mapper.xml的过程去理解。
public class MapperData {
private String sql;
private Class type;
public MapperData(String sql, Class type) {
this.sql = sql;
this.type = type;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Class getType() {
return type;
}
public void setType(Class type) {
this.type = type;
}
@Override
public String toString() {
return "MapperData{" +
"sql='" + sql + '\'' +
", resultType=" + type +
'}';
}
4.Executor执行模块
这个模块主要用于执行SQL语句并且结合各种handler处理,然后这里只是写了一简单的执行器类。
5.handler模块
这里模仿Mybatis执行SQL时的过程,stamentHandler用于处理语句集、parameterHandler用于处理参数、resultHandler用于处理结果映射。
6.plugin模块
这里模仿Myabtis插件的工作原理实现的,也就是说这里和原生Mybatis的plugin基本一样
7.typeHandler模块
这里模仿Mybatis类型处理的机制,用于javaType到jdbcType的映射处理以及jdbcType到javaType的映射处理
8.配置模块
这里的对框架的配置使用了简单的properties
(五)手写一个简单的mybatis框架,实现查询功能
1、定义表blog
2、定义实体类、maper
public class Blog implements Serializable{
Integer bid; // 文章ID
String name; // 文章标题
Integer authorId; // 文章作者ID
public Integer getBid() {
return bid;
}
public void setBid(Integer bid) {
this.bid = bid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAuthorId() {
return authorId;
}
public void setAuthorId(Integer authorId) {
this.authorId = authorId;
}
@Override
public String toString() {
return "Blog{" +
"bid=" + bid +
", name='" + name + '\'' +
", authorId='" + authorId + '\'' +
'}';
}
}
public interface BlogMapper {
/**
* 根据主键查询文章
* @param bid
* @return
*/
public Blog selectBlogById(Integer bid);
}
3、定义SqlSession
public class GPSqlSession {
private GPConfiguration configuration;
private GPExecutor executor;
public GPSqlSession(GPConfiguration configuration, GPExecutor executor){
this.configuration = configuration;
this.executor = executor;
}
public <T> T selectOne(String statementId, Object paramater){
// 根据statementId拿到SQL
String sql = GPConfiguration.sqlMappings.getString(statementId);
if(null != sql && !"".equals(sql)){
return executor.query(sql, paramater );
}
return null;
}
public <T> T getMapper(Class clazz){
return configuration.getMapper(clazz, this);
}
}
4、定义Executor
public class GPExecutor {
public <T> T query(String sql, Object paramater) {
Connection conn = null;
Statement stmt = null;
Blog blog = new Blog();
try {
// 注册 JDBC 驱动
Class.forName("com.mysql.jdbc.Driver");
// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8&autoReconnect=true&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true", "root", "1234");
// 执行查询
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(String.format(sql, paramater));
// 获取结果集
while (rs.next()) {
Integer bid = rs.getInt("bid");
String name = rs.getString("name");
Integer authorId = rs.getInt("author_id");
blog.setAuthorId(authorId);
blog.setBid(bid);
blog.setName(name);
}
System.out.println(blog);
rs.close();
stmt.close();
conn.close();
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (stmt != null) stmt.close();
} catch (SQLException se2) {
}
try {
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
return (T)blog;
}
}
5、定义configuration
public class GPConfiguration {
public static final ResourceBundle sqlMappings;
static {
sqlMappings = ResourceBundle.getBundle("v1sql");
}
public <T> T getMapper(Class clazz, GPSqlSession sqlSession) {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{clazz},
new GPMapperProxy(sqlSession));
}
}
6、定义MapperProxy
public class GPMapperProxy implements InvocationHandler{
private GPSqlSession sqlSession;
public GPMapperProxy(GPSqlSession sqlSession){
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String mapperInterface = method.getDeclaringClass().getName();
String methodName = method.getName();
String statementId = mapperInterface + "." + methodName;
return sqlSession.selectOne(statementId, args[0]);
}
}
7、定义springboot启动类
public class MyBatisBoot {
public static void main(String[] args) {
GPSqlSession sqlSession = new GPSqlSession(new GPConfiguration(), new GPExecutor());
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
blogMapper.selectBlogById(1);
}
}