简介
在 Mybatis3 的源码解析系列中,我们对其核心功能有了一定的了解,下面我们尝试简单写一下 Demo,让其有简单的 Mybatis 的一些核心功能,本篇是基础功能的搭建
Dome 编写
完整的工程已放到 GitHub 上:https://github.com/lw1243925457/MybatisDemo/tree/master/
本篇文章的代码对应的 Tag 是: V1
本篇的目标是完成 MapperProxy 和运行不带参数和无自定义返回类型的 SQL
测试代码如下:
public class SelfMybatisTest {
@Test
public void test() {
try(SelfSqlSession session = buildSqlSessionFactory()) {
PersonMapper personMapper = session.getMapper(PersonMapper.class);
personMapper.createTable();
personMapper.save();
List<Object> personList = personMapper.list();
for (Object person: personList) {
System.out.println(person.toString());
}
}
}
public static SelfSqlSession buildSqlSessionFactory() {
String JDBC_DRIVER = "org.h2.Driver";
String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
String USER = "sa";
String PASS = "";
HikariConfig config = new HikariConfig();
config.setJdbcUrl(DB_URL);
config.setUsername(USER);
config.setPassword(PASS);
config.setDriverClassName(JDBC_DRIVER);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
DataSource dataSource = new HikariDataSource(config);
SelfConfiguration configuration = new SelfConfiguration(dataSource);
// configuration.getTypeHandlerRegistry().register(String[].class, JdbcType.VARCHAR, StringArrayTypeHandler.class);
configuration.addMapper(PersonMapper.class);
return new SelfSqlSession(configuration);
}
}
复制代码
如上所示,我们集成其他 DataSource,这里使用 HikariCP,然后初始化 Mapper,生成表、插入、查询
最终的结果如下:
初始化我们的 PersonMapper,最后输出查询的结果
add sql source: mapper.mapper.PersonMapper.list
add sql source: mapper.mapper.PersonMapper.save
add sql source: mapper.mapper.PersonMapper.createTable
executor
executor
executor
[1, 1]
复制代码
基本达到了要求,下面开始讲解核心代码:
Mapper 初始化
我们的思路目前是:
1.SelfConfiguration 作为全局配置类,从中取得 DataSource 和 Mapper 相关的信息(目前是接口方法对应的 SQL 语句)
2.SelfConfiguration 构造函数保存 DataSource
3.SelfConfiguration 添加 Mapper 时,保存其中方式对应的 SQL 语句信息到 Map,方法的路径作为唯一标识
SelfConfiguration 相关的代码
public class SelfConfiguration {
private final DataSource dataSource;
private final Map<String, SqlSource> sqlCache = new HashMap<>();
public SelfConfiguration(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Mapper添加
* 保存接口方法的SQL类型和方法
* 方法路径作为唯一的id
* @param mapperClass mapper
*/
public void addMapper(Class<?> mapperClass) {
final String classPath = mapperClass.getPackageName();
final String className = mapperClass.getName();
for (Method method: mapperClass.getMethods()) {
final String id = StringUtils.joinWith("." ,classPath, className, method.getName());
for (Annotation annotation: method.getAnnotations()) {
if (annotation instanceof Select) {
addSqlSource(id, ((Select) annotation).value(), SqlType.SELECT);
continue;
}
if (annotation instanceof Insert) {
addSqlSource(id, ((Insert) annotation).value(), SqlType.INSERT);
}
}
}
}
private void addSqlSource(final String id, final String sql, final SqlType selectType) {
System.out.println("add sql source: " + id);
final SqlSource sqlSource = SqlSource.builder()
.type(selectType)
.sql(sql)
.build();
sqlCache.put(id, sqlSource);
}
public DataSource getDataSource() {
return dataSource;
}
public SqlSource getSqlSource(final String id) {
if (sqlCache.containsKey(id)) {
return sqlCache.get(id);
}
throw new RuntimeException("don't find mapper match: " + id);
}
}
复制代码
MapperProxy 生成
MapperProxy 从 SelfSqlSession 中进行获取,MapperProxy 中调用 Executor 的方法即可,比较简单
SelfSqlSession 主要是返回 MapperProxy
public class SelfSqlSession implements Closeable {
private final SelfConfiguration config;
public SelfSqlSession(SelfConfiguration configuration) {
this.config = configuration;
}
@Override
public void close() {
}
public <T> T getMapper(Class<?> mapperClass) {
final MapperProxy<T> proxy = new MapperProxy(config);
return (T) Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[] {mapperClass}, proxy);
}
}
复制代码
MapperProxy 简单调用下 Executor
public class MapperProxy<T> implements InvocationHandler {
private final SelfConfiguration config;
public MapperProxy(final SelfConfiguration configuration) {
this.config = configuration;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return Executor.executor(config, proxy, method, args);
}
}
复制代码
SQL 语句执行
主要逻辑如下:
Executor 代码如下,获取数据库连接,StatementHandler 执行,ResultHandler 处理结果返回
public class Executor {
private static final StatementHandler statementHandler = new StatementHandler();
private static final ResultHandler resultHandler = new ResultHandler();
public static Object executor(SelfConfiguration config, Object proxy, Method method, Object[] args) throws SQLException {
System.out.println("executor");
try (Connection conn = config.getDataSource().getConnection()) {
ResultSet resultSet = statementHandler.prepare(conn, proxy, method, args, config);
return resultHandler.parse(resultSet);
}
}
}
复制代码
StetementHandler 主要是从 Config 从获取需要执行的 SQL 信息
目前比较简单,根据 SQL 类型,直接执行即可
public class StatementHandler {
public ResultSet prepare(Connection conn, Object proxy, Method method, Object[] args, SelfConfiguration config) throws SQLException {
final String classPath = method.getDeclaringClass().getPackageName();
final String className = method.getDeclaringClass().getName();
final String methodName = method.getName();
final String id = StringUtils.joinWith(".", classPath, className, methodName);
final SqlSource sqlSource = config.getSqlSource(id);
if (sqlSource.getType().equals(SqlType.SELECT)) {
return select(conn, sqlSource);
}
if (sqlSource.getType().equals(SqlType.INSERT)) {
return insert(conn, sqlSource);
}
throw new RuntimeException("don't support this sql type");
}
private ResultSet insert(Connection conn, SqlSource sqlSource) throws SQLException {
final Statement statement = conn.createStatement();
statement.execute(sqlSource.getSql());
return null;
}
private ResultSet select(Connection conn, SqlSource sqlSource) throws SQLException {
final Statement statement = conn.createStatement();
return statement.executeQuery(sqlSource.getSql());
}
}
复制代码
ResultHandler 目前就是循环读取结果,然后返回
public class ResultHandler {
public List<Object> parse(ResultSet res) throws SQLException {
if (res == null) {
return null;
}
final List<Object> list = new ArrayList<>(res.getFetchSize());
final ResultSetMetaData metaData = res.getMetaData();
while (res.next()) {
final int count =metaData.getColumnCount();
final List<Object> val = new ArrayList<>(count);
for (int i=1; i <= count; i++) {
final String name = metaData.getColumnName(i);
final Object value = res.getObject(name);
val.add(value);
}
list.add(val);
}
return list;
}
}
复制代码
总结
本篇中搭建了 MyBatis Demo 的基础部分,除了结果返回有点不好看,其他有了点日常 MyBatis 的味道了,结果处理后面也会完善上去
评论