09-14手写持久层框架——session创建、sql解析、参数设置、结果集封装
学习视频资料来源:https://www.bilibili.com/video/BV1R14y1W7yS
文章目录
1. Ipersistent 持久层框架端核心源码
核心思路:
- 使用SqlSessionFactory,创建SqlSession对象。
- SqlSession对象提供select方法,用于通过jdbc与数据库交互,执行sql。但是SqlSession不直接执行使用jdbc执行sql,而是委托给Executor执行。
- Executor 提供query方法,用提供的statementId、sql参数、以及透传下来的configuration。通过jdbc与数据库交互,执行sql,封装结果并返回。
1.1 SqlSessionFactory
- 在SqlSessionFactory接口中定义SqlSession openSession(),并创建DefaultSqlSessionFactory实现该方法,用于创建并返回SqlSession对象。
- 在创建SqlSession的同时,创建SimpleExecutor对象。与jdbc交互的逻辑,不是在SqlSession中执行的,而是委托给SimpleExecutor对象去执行。
public interface SqlSessionFactory {
SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
Executor executor = new SimpleExecutor();
return new DefaultSqlSession(configuration, executor);
}
}
1.2 SqlSession
session接口定义方法:
| 方法名 | 作用 |
|---|---|
| List selectList(String statementId, Object param) throws Exception | 调用executor执行sql |
| T selectOne(String statementId, Object param) | 也是调用selectList方法,当返回集合数量大于1返回异常 |
| close | 释放数据库连接,结果集等相关资源 |
public interface SqlSession {
<E> List<E> selectList(String statementId, Object param) throws Exception;
<T> T selectOne(String statementId, Object param) throws Exception;
void close();
}
默认实现类
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <E> List<E> selectList(String statementId, Object param) throws Exception {
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
List<E> list = executor.query(configuration, mappedStatement, param);
return list;
}
@Override
public <T> T selectOne(String statementId, Object param) throws Exception {
List<Object> objects = selectList(statementId, param);
if (objects.size() == 1) {
return (T) objects.get(0);
}
if (objects.size() > 1) {
throw new RuntimeException("查询结果不唯一");
}
return null;
}
@Override
public void close() {
executor.close();
}
}
1.3 Executor
通过JDBC执行SQL,封装并返回结果集。
| 方法名 | 作用 |
|---|---|
| List query(Configuration configuration, MappedStatement mappedStatement, Object param) | 调用executor执行sql |
| close | 释放jdbc相关资源 |
1.3.1 主要步骤
执行sql有5步: 1.获取数据库连接、2.sql解析、3.预编译、4.设置参数、5.封装结果集。
主要步骤:2,4,5。
1. 获取数据库连接
这一步比较简单,因为我们在上一章解析xml配置文件的时候,已经创建了数据源,并封装金configuration中,这里从透传下来的configuration中直接取即可。
connection = configuration.getDataSource().getConnection();
2. sql解析
问题
mappedStatement 中sql,是不能直接拿来预编译的。因为这时的sql是#{}形式,如:select * from user where id = #{id} and username = #{username},而预编译需要的是:select * from user where id = ? and username = ?。
所以预编译前需要将sql中的#{id}等替换成?,顺便将{}内的值解析出来,存到一个List parameterMappings中,用于后续设置参数。把替换成?的sql和parameterMappings封装进BoundSql中,一并返回。
private BoundSql getBoundSql(String sql) {
// 这里的sql是包含自定义占位符#{}的,要替换成?同时把#{}里边的值保存下来
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
String finalSql = genericTokenParser.parse(sql);
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(finalSql, parameterMappings);
}
解析工具类
getBoundSql方法解析sql借用了3个工具类ParameterMappingTokenHandler、GenericTokenParser、TokenHandler,用于将#{} 替换成?,并把{}内值解析出来。把解析出来的值又用ParameterMapping包装一下。这几个类不用自己写,不是我们关注的重点,直接从mybats源码拷贝。把不关心的代码删掉,改改就ok了。附上我的代码:
@Data
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
}
public interface TokenHandler {
String handleToken(String content);
}
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
return new ParameterMapping(content);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
do {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
3. 预编译
和传统使用JDBC方式一样
preparedStatement = connection.prepareStatement(boundSql.getFinalSql());
4. 设置参数
从parameterMappings 获取到参数名,如:“id”,“username”,从mappedStatement获取到sql的请求参数类型,通过反射从param对象中读取具体的参数值,set到preparedStatement。
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
String paramName = parameterMapping.getContent();
// 通过反射找到对应参数的值
String parameterType = mappedStatement.getParameterType();
if (parameterType != null) {
Class<?> parameterTypeClass = Class.forName(parameterType);
Field declaredField = parameterTypeClass.getDeclaredField(paramName);
//暴力访问
declaredField.setAccessible(true);
// param 为传入的参数值
Object value = declaredField.get(param);
preparedStatement.setObject(i + 1, value);
}
}

5. 封装结果集
从parameterMappings 获取到返回类型类全路径resultType ,通过反射生成实例对象o。在通过metaData获取列名,通过内省技术,将字段值value 写入实例对象中。
ResultSet resultSet = preparedStatement.executeQuery();
List<E> list = new ArrayList<>();
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
String resultType = mappedStatement.getResultType();
Class<?> aClass = Class.forName(resultType);
Object o = aClass.newInstance();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = resultSet.getObject(columnName);
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, aClass);
propertyDescriptor.getWriteMethod().invoke(o, value);
}
list.add((E) o);
}
1.3.2 SimpleExecutor 完整代码
上述5个步骤合起来的完整代码如下:
public class SimpleExecutor implements Executor {
private Connection connection;
private PreparedStatement preparedStatement;
private ResultSet resultSet;
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) throws Exception {
connection = configuration.getDataSource().getConnection();
// 这里的sql是包含自定义占位符#{}的,要替换成?同时把#{}里边的值保存下来
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
preparedStatement = connection.prepareStatement(boundSql.getFinalSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
String paramName = parameterMapping.getContent();
// 通过反射找到对应参数的值
String parameterType = mappedStatement.getParameterType();
if (parameterType != null) {
Class<?> parameterTypeClass = Class.forName(parameterType);
Field declaredField = parameterTypeClass.getDeclaredField(paramName);
//暴力访问
declaredField.setAccessible(true);
Object value = declaredField.get(param);
preparedStatement.setObject(i + 1, value);
}
}
resultSet = preparedStatement.executeQuery();
List<E> list = new ArrayList<>();
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
String resultType = mappedStatement.getResultType();
Class<?> aClass = Class.forName(resultType);
Object o = aClass.newInstance();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
Object value = resultSet.getObject(columnName);
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, aClass);
propertyDescriptor.getWriteMethod().invoke(o, value);
}
list.add((E) o);
}
return list;
}
private BoundSql getBoundSql(String sql) {
// 这里的sql是包含自定义占位符#{}的,要替换成?同时把#{}里边的值保存下来
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
String finalSql = genericTokenParser.parse(sql);
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(finalSql, parameterMappings);
}
@Override
public void close() {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2. Ipersistent-test 持久层框架端使用端
2.1 框架使用端测试类
public class IpersistentTest {
@Test
public void test1() throws Exception {
InputStream resource = Resources.getResource("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
// 创建SqlSession,过程中创建了executor,executor用于执行sql语句
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(1);
user.setUsername("xiaoming");
Object o = sqlSession.selectOne("user.selectOne", user);
System.out.println("selectOne返回结果:" + o.toString());
List<Object> users = sqlSession.selectList("user.selectList", null);
System.out.println("selectLit返回结果:" + users.toString());
sqlSession.close();// 释放资源
}
}
2.2 测试结果

可以看到,sql正确执行~
更多推荐



所有评论(0)