我这个需求的底层逻辑是什么?顶层设计在哪?最终交付价值是什么?过程的抓手在哪里?如何保证结果的闭环?我比别人的亮点在哪里?优势在哪里?我的思考和沉淀在哪里?我有形成自己的方法论么?我这个需求换别人来写是否会一样?我是如何做到对齐拉通的?我是如何赋能的?
一,需求分析
1)它需要实现对连接资源的自动管理,也就是把创建 Connection、Statement、关闭 Connection、Statement、ResultSet 这些操作封装到底层的对象中,不需要在应用层手动调用。
2)它需要把 SQL 语句抽离出来实现集中管理,开发人员不用在业务代码里面写 SQL语句。
3)它需要实现对结果集的转换,也就是我们指定了映射规则之后,这个框架会自动帮我们把 ResultSet 映射成实体类对象。
4)需要提供一个 API 来给我们操作数据库,这里面封装了对数据库的操作的常用的方法。
二,概要设计
核心对象
1、存放参数和结果映射关系、存放 SQL 语句,我们需要定义一个配置类;
2、执行对数据库的操作,处理参数和结果集的映射,创建和释放资源,我们需要定义一个执行器;
3、有了这个执行器以后,我们不能直接调用它,而是定义一个给应用层使用的 API,它可以根据 SQL 的 id 找到 SQL 语句,交给执行器执行;
4、直接使用 id 查找 SQL 语句太麻烦了,我们干脆把存放 SQL 的命名空间定义成一个接口,把 SQL 的 id 定义成方法,这样只要调用接口方法就可以找到要执行的 SQL。这个时候我们需要引入一个代理类。
流程分析
1、定义接口 Mapper 和方法,用来调用数据库操作。Mapper 接口操作数据库需要通过代理类。
2、定义配置类对象 Configuration。
3、定义应用层的 API SqlSession。它有一个 getMapper()方法,我们会从配置类Configuration 里面使用 Proxy.newProxyInatance()拿到一个代理对象 MapperProxy。
4、有了代理对象 MapperProxy 之后,我们调用接口的任意方法,就是调用代理对象的 invoke()方法。
5、代理对象 MapperProxy 的 invoke()方法调用了 SqlSession 的 selectOne()。
6、SqlSession 只是一个 API,还不是真正的 SQL 执行者,所以接下来会调用执行器 Executor 的 query()方法。
7、执行器 Executor 的 query()方法里面就是对 JDBC 底层的 Statement 的封装,最终实现对数据库的操作,和结果的返回。
三,代码
/**
* @author yhd
* @createtime 2021/2/14 16:08
*/
public class SqlSession {
private Configuration configuration;
private Executor executor;
public SqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
public <T> T selectOne(String statementId, Object... parameters)throws Exception {
String sql = Configuration.sqlMappings.getString(statementId);
if (null != sql && !"".equals(sql)) {
return executor.query(sql, parameters);
}
return null;
}
public <T> T getMapper(Class clazz) {
return (T) configuration.getMapper(clazz, this);
}
}
/**
* @author yhd
* @createtime 2021/2/14 16:09
*/
public class Configuration {
public static final ResourceBundle sqlMappings;
static {
sqlMappings = ResourceBundle.getBundle("application");
}
public <T> T getMapper(Class clazz, SqlSession sqlSession) {
return (T) Proxy.newProxyInstance(
this.getClass().getClassLoader()
, new Class[]{clazz}
, new MapperProxy(sqlSession));
}
}
/**
* @author yhd
* @createtime 2021/2/14 16:15
*/
public class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
public MapperProxy(SqlSession 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);
}
}
/**
* @author yhd
* @createtime 2021/2/14 16:10
*/
public class Executor {
public <T> T query(String sql, Object[] parameters)throws Exception {
Connection conn= JDBCUtils.getConnection();
Statement statement = conn.createStatement();
ResultSet result =statement.executeQuery(String.format(sql,parameters));
// JDBC 的业务逻辑....
return (T) result;
}
}
com.yhd.mebatis.mapper.BlogMapper.selectBlogById=select * from blog where id = %d
四,结果评估
1、在 Executor 中,对参数、语句和结果集的处理是耦合的,没有实现职责分离;
2、参数:没有实现对语句的预编译,只有简单的格式化(format),效率不高,还存在 SQL 注入的风险;
3、语句执行:数据库连接硬编码;
4、结果集:还只能处理 Blog 类型,没有实现根据实体类自动映射。确实有点搓,拿不出手。
五,功能迭代
支持参数预编译;
支持结果集的自动处理(通过反射);
对 Executor 的职责进行细化。
在方法上使用注解配置 SQL;
查询带缓存功能;
支持自定义插件。