学习视频资料来源:https://www.bilibili.com/video/BV1R14y1W7yS

1. Ipersistent 持久层框架端核心源码

核心思路:

  1. 使用SqlSessionFactory,创建SqlSession对象。
  2. SqlSession对象提供select方法,用于通过jdbc与数据库交互,执行sql。但是SqlSession不直接执行使用jdbc执行sql,而是委托给Executor执行。
  3. 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正确执行~

更多推荐