手写Mybatis
一、MyBatis核心组件
在开始实现我们的mybatis框架之前我觉得有必要先学习一下MyBatis核心组件,如下示意图(出自前文),在前面这个链接中可以了解到更多的细节。这里附上代码的github链接:github源码
二、MyBatis手写实现
1. 从测试用例作为入口
/**
* 测试用例,将整个工程串联起来
*/
public class MybatisTest {
public static void main(String[] args) throws IOException {
//1.读取配置文件,将配置文件转换为输入流
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//返回DefaultSqlSessionFactory对象,利用XMLConfigBuilder类从输入流中读取配置信息。
// 封装成Configuration对象传入DefaultSqlSessionFactory的构造方法
SqlSessionFactory factory = builder.build(in);
//3.使用工厂(DefaultSqlSessionFactory)生产SqlSession对象,
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
IUserMapper mapper = session.getMapper(IUserMapper.class);
//5.使用代理对象执行方法
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
//6.释放资源
session.close();
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们根据这个测试用例将整个mybatis执行sql过程分为几步去实现它:
SqlSessionFactoryBuilder
:从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例,将Configuration对象传入。(配置信息封装成Configuration对象)。SqlSessionFactory
: 根据之前builder传入的封装好的Configuration,可以创建SqlSession对象。Configuration
: 将从XML配置文件中解析到的内容封装起来,其中包括数据源信息(数据库连接配置),还有一个sqlMappers(HashMap对象),其中key是由Mapper接口的全限定类名和方法名组成,value是一个SqlMapper对象。SqlMapper
: 存放的是要执行的SQL语句和要封装的实体类全限定类名。
SqlSession
: 其中有要执行sql语句用的sqlMappers,并且持有数据库的连接。这些属性,都是在SqlSessionFactory创建SqlSession时传入的Configuration中的属性。它可以用于获取Mapper接口的增强代理对象(由jdk动态代理实现,非常关键的一步)。MapperProxy
: 实现了InvocationHandler接口,定义了代理对象的增强行为。他持有sqlMappers的引用,和数据库连接(SqlSession创建代理对象时传入)。然后拦截Mapper接口的所有方法,根据Method对象获取方法名和接口的全限名。然后拼接在一起,就是sqlmappers中需要的key,然后获取到maper对象,在使用数据库连接去数据库查询,并且封装返回数据。我认为上面这段分析远远比具体实现代码更加重要,如果有不懂可以查看代码后,反过来结合这段内容去分析代码,你会发现整个脉络分非常清晰。对动态代理不熟悉的可以查看我的另一篇文章两万字吐血总结,代理模式及手写实现动态代理(aop原理,基于jdk动态代理),动态代理在所有的框架中如spring、mybatis都扮演着十分重要的角色。
2.整个项目结构
3. 实现代码
可以打开右边的目录栏来快速跳转
(1)、Resources
/**
* 使用类加载器读取配置文件的类
*/
public class Resources {
public static InputStream getResourceAsStream(String filePath) {
return Resources.class.getClassLoader().getResourceAsStream(filePath);
}
}
(2)、SqlSessionFactoryBuilder
/**
* 根据输入流构建SqlSessionFactory对象
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
return new DefaultSqlSessionFactory(
XMLConfigBuilder.loadConfiguration(inputStream)
);
}
}
(3)、SqlSessionFactory
/**
* 创建SqlSession对象的工厂类
*/
public interface SqlSessionFactory {
SqlSession openSession();
}
/**
* 根据传入的Configuration创建SqlSession
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration cfg;
@Override
public SqlSession openSession() {
return new DefaultSqlSession(cfg.getMappers(), DataSourceUtil.getConnection(cfg));
}
public DefaultSqlSessionFactory(Configuration cfg) {
this.cfg = cfg;
}
}
(4)、SqlSession
/**
* 自定义Mybatis中和数据库交互的核心类,可以创建dao包中Mapper接口的代理对象
*/
public interface SqlSession {
/**
* 通过mapper字节码获取代理对象
*
* @param mapperClass 被代理对象字节码对象
* @param <T> 被代理类型
* @return 代理对象
*/
<T> T getMapper(Class<T> mapperClass);
/**
* 释放资源
*/
void close();
}
/**
* 获取Mapper代理对象执行查询
*/
public class DefaultSqlSession implements SqlSession {
private Map<String, SqlMapper> sqlMappers;
private Connection connection;
public DefaultSqlSession(Map<String, SqlMapper> sqlMappers, Connection connection) {
this.sqlMappers = sqlMappers;
this.connection = connection;
}
@Override
public <T> T getMapper(Class<T> mapperClass) {
return (T) Proxy.newProxyInstance(mapperClass.getClassLoader(),
new Class[]{
mapperClass},
new MapperProxy(sqlMappers, connection));
}
@Override
public void close() {
if (connection != null) {
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(5)、MapperProxy
/**
* 增强Mapper,生成代理对象
*/
public class MapperProxy implements InvocationHandler {
private Map<S