手写Mybatis简单版本
本人已参与「新人创作礼」活动,一起开启掘金创作之路。
本篇文章是通过看视频学习总结的内容, 如有错误的地方请谅解,并联系博主及时修改,谢谢您的阅读.
前言: 根据之前八篇博客的讲述,现在搞明白了 MyBatis 一系列的工作原理,那么就可以参照 MyBatis 自己写一个 半 ORM 框架,接下来先做分析,看看需要哪些对象、配置文件等等。
源码地址:github 简单版本源码
开始前的准备
- 项目依赖
- statementId 和 sql 的映射关系(mybatis 中是 mapper.xml),这里使用properties代替
- 实体类
- 全局配置对象 Configuration
- 执行器 Executor
- sql 会话 SqlSession 对象
- mapper接口的代理对象 MapperProxy
begin
- 1 项目依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
- 2
sql.properties
# statementId = sql
com.example.ibatis.mapper.BlogMapper.selectById=select * from blog where bid = %d
- 3
Blog.java实体类
@Setter
@Getter
@ToString
public class Blog implements Serializable{
/** 文章ID **/
private Integer id;
/** 文章标题 **/
private String name;
/** 文章作者ID **/
private Integer authorId;
}
- 4
Configuration.java
package com.example.ibatis.v1;
import java.lang.reflect.Proxy;
import java.util.ResourceBundle;
/**
* <p>
* 全局配置文件,目前主要是通过 properties 文件加载sql语句的
* </p>
*
* @author zyred
* @createTime 2020/9/16 16:13
**/
public class Configuration {
public static final ResourceBundle sqlMapping;
static {
// 读取sql.properties文件,类被初始化的时候,会读取文件中的内容
sqlMapping = ResourceBundle.getBundle("sql");
}
/**
* 通过class动态代理mapper接口
*
* @param clazz mapper接口
* @param sqlSession sqlSession
* @param <T> 被代理的对象
* @return
*/
public <T> T getMapper(Class<?> clazz, SqlSession sqlSession) {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{clazz},
new MapperProxy(sqlSession));
}
}
- 5
Executor执行器
package com.example.ibatis.v1;
import com.example.ibatis.doman.Blog;
import java.sql.*;
/**
* <p>
* sql执行器
* </p>
*
* @author zyred
* @createTime 2020/9/16 16:18
**/
public class Executor {
public <T> T query(String sql, Object paramater) {
Connection conn = null;
Statement stmt = null;
Blog blog = new Blog();
try {
// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8", "root", "root");
// 执行查询
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(String.format(sql, paramater));
// 获取结果集
while (rs.next()) {
Integer bid = rs.getInt("id");
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;
}
}
- 6
SqlSession
package com.example.ibatis.v1;
/**
* <p>
* sql会话
* </p>
*
* @author zyred
* @createTime 2020/9/16 16:17
**/
public class SqlSession {
Configuration configuration;
Executor executor;
public SqlSession(Configuration configuration, Executor executor){
this.configuration = configuration;
this.executor = executor;
}
/**
* 单条查询
* @param statementId
* @param param
* @param <T>
* @return
*/
public <T> T selectOne (String statementId, Object param){
// 拿到sql语句
String sql = Configuration.sqlMapping.getString(statementId);
return this.executor.query(sql, param);
}
/**
* 通过动态代理去执行方法,在invoke方法中执行查询
* @param clazz mapper接口
* @param <T> 动态代理的类
* @return
*/
public <T> T getMapper(Class<?> clazz){
return this.configuration.getMapper(clazz, this);
}
}
- 7
MapperProxy
/**
* <p>
* 代理mapper接口的对象
* </p>
*
* @author zyred
* @createTime 2020/9/16 16:25
**/
public class MapperProxy implements InvocationHandler {
private final static String DOT = ".";
SqlSession sqlSession;
public MapperProxy(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String clazzName = method.getDeclaringClass().getName();
String methodName = method.getName();
return this.sqlSession.selectOne(clazzName.concat(DOT).concat(methodName), args[0]);
}
}
- 8
main方法测试
public class MyBatisTest {
public static void main(String[] args) {
SqlSession sqlSession = new SqlSession(new Configuration(), new Executor());
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
// Blog(id=1, name=mybatis深入理解, authorId=1)
mapper.selectById(1);
}
}
流程分析
- 从
main方法入手
SqlSession sqlSession = new SqlSession(new Configuration(), new Executor());这句代码:先new出了Configuration对象,该对象初始化的时候,完成了从sql.properties加载,并且放入到ResourceBundle内部,这个容器模拟了mybatis中MappedStatement对象,然后创建Executor对象(Executor是跟随SqlSession的,所以在这里需要这个对象去执行查询)
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);这里不就是通过JDK动态代理完成接口的实现类代理吗?
mapper.selectById(1);这句代码其实就是执行了动态代理后的类方法,然后调用MapperProxy类中invocke(),而invoke ()调用了session#selectOne(),session这里又调用了执行器的query(),最后执行executor#query()查询数据库
难点分析
- JDK 动态代理必须要实现
InvocationHandler接口,生成出来的代理类的方法一旦被调用,那么InvocationHandler接口的invoke()就会被执行,所以后续的逻辑可以放入到invoke()来处理