初识——搭建简易mybatis

前言

在国内,mybatis 目前的流行程度不用多说,绝大多数公司的技术栈都有它。mybatis 作为一个轻量级的持久层框架,比起老牌 orm 框架 Hibernate 和 spring 家族的 spring jpa,mybatis 并不透明化 dao 层,而是关注于 java 方法与 sql 的映射,大大简化了用 java 原生 api 进行数据库操作的繁琐。比起掌握 orm 框架所需的额外学习成本,mybaits 的学习成本很低:最终掌控 sql 的还是程序员。

搭建一个 mybatis 框架,需要有这几个部分:

  • 配置
  • sql
  • 对 sql 对应的接口方法

所以 mybatis 的执行流程就呼之欲出了:读取配置,将接口中的方法与 sql 关联。调用方法,执行 sql,完成查询。看起来简单,那么,这次我们就尝试搭建一个简单的 mybaits,来理解其工作的大体流程。
本文代码

导航

流程概述

为了方(偷)便(懒)起见,本文这次我们使用 java api 进行 mybatis 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) throws IOException {
SqlSessionFactory factory=build();
// 从SqlSessionFactory获取SqlSession
SqlSession session = factory.openSession();
try {
// 获取mapper(代理)
UserMapper mapper = session.getMapper(UserMapper.class);
// ...
} finally {
session.close();
}
}

static SqlSessionFactory build() {
DataSource dataSource = new PooledDataSource(driver, url, user, pwd);
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);

// 获取Configuration
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
// 获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
return sqlSessionFactory;
}

我们可以看到,去除类工厂,mybatis 有这些关键的对象:

  • Configuration:包含了数据源、事务管理、mappers 等信息
  • SqlSession:获取 mapper 的代理对象
  • mapper 代理对象:进行数据库操作

那么,我们极简的 mybaits 大体框架也可以出来了:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
// 配置config
DataSource dataSource = new PooledDataSource(driver, url, user, pwd);
Configuration configuration = new Configuration(dataSource);
configuration.addMapper(UserMapper.class);

// 获取session
SqlSession session = new SqlSession(configuration);
// 获取mapper代理
UserMapper mapper = session.getMapper(UserMapper.class);
// ...
}

涉及的类与方法:

  • Configuration
    • Configuration(DataSource dataSource)
    • configuration.addMapper(Class mapper)
  • SqlSession
    • SqlSession(Configuration configuration)
    • getMapper(Class type)
  • MapperProxy:mapper 代理

获取 mapper

Configuration 中用 map 来保存 mapper 及其代理,而 SqlSession 通过调用 Configuration 中的方法,返回代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Configuration {
private Map<Class<?>, MapperProxy<?>> mappers = new HashMap<>();

public <T> void addMapper(Class<T> mapper) {
mappers.put(mapper, new MapperProxy<>());
}

public <T> T getMapper(Class<T> mapper, SqlSession sqlSession) {
MapperProxy<T> mapperProxy = (MapperProxy<T>) mappers.get(mapper);
mapperProxy.setMapper(mapper);
mapperProxy.setSqlSession(sqlSession);
return (T) Proxy.newProxyInstance(mapper.getClassLoader(), new Class[]{mapper}, mapperProxy);
}
}

public class SqlSession {
private Configuration configuration;
private Executor executor;

public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
}

调用 mapper 方法

MapperProxy 采用了 java 反射,通过注解来获得 sql 及其参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MapperProxy<T> implements InvocationHandler {
private SqlSession sqlSession;
private Class<T> mapper;

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = new Object();
// 根据Select注解获取sql
Select annotation = method.getAnnotation(Select.class);
if (annotation != null) {
// 执行查询
result = sqlSession.select(annotation.value(), method, args);
}
return result;
}
}

public class SqlSession {
private Configuration configuration;
private Executor executor;

public Object select(String sql, Method method, Object[] args) throws Exception {
return executor.query(configuration, sql, args, method.getReturnType());
}
}

执行 sql 语句

注意到 SqlSession 的 select 实际上调用了 executor 的 query 方法。也就是说,真正执行 sql 语句的,其实是 executor:

1
2
3
4
5
6
7
8
9
10
11
public class Executor {
public <T> T query(Configuration configuration, String sql, Object[] args, Class<T> resultType) throws Exception {
T result = null;
try (Connection connection = configuration.getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
// 构建PreparedStatement...
ResultSet resultSet = statement.executeQuery();
// 构建返回对象...
return result;
}
}

至此,一个极简 mybaits 的流程就大体介绍完了。而真正的 mybatis,包含了许多模块:

mybatis结构