MyBatis --自定义实现MyBatis 查询过程 (简略化处理)

说明:

  • 项目地址: MyBatis-source-learn

  • sql 地址:test.sql

  • 项目说明: 一个简单的解析注解的select查询过程示例.

  • PS: 若文章字体偏大或者偏小,建议通过 ctrl键+鼠标滑轮 进行修改,以提升阅读效果.(带来不便,请谅解!)

MyBatis 实现查询单条SQL语句的整体流程:

  1. 建立连接, 获取Connection
  2. 解析xml文件 | 解析注解的方式 获得 SQL语句 ,方法的参数列表和返回值类型
  3. 执行SQL命令,返回结果
    1. 查询缓存, 有的话直接返回,没有继续执行
    2. JDBC 执行查询命令
    3. 处理结果集与方法返回值的映射.

简易查询思路:

  1. 建立与数据库的连接, 获取Connection
  2. 采用代理对象(JDK动态代理的方式)执行查询操作
    1. xml方式有点繁琐, out . 通过解析方法注解Annotation的方式获取SQL语句,方法参数等,
    2. 执行方法
      1. 与数据库交互(JDBC),获取结果集
      2. 结果集与方法返回类型绑定(未实现)

具体实现:

UserMapper.java

interface UserMapper {
    @Select("select * from user where id =#{id}")
//    List<User> selectUser(int id);
    User selectUserById(int id);
}

生成代理对象:

    /**
     * 手动实现MyBatis select 查询方法 注解方式
     * 1. 通过反射获取 usermapper
     * 1. 获取SQL 命令, param 参数列表, 结果集类型
         * 1-1. 解析注解
         * 1-2 . 处理方法请求参数
         * 1-3. 封装SQL语句
         * 1-4. 获取方法的返回值, 并且处理 (未做)
     * 2. 与数据库进行交互, 获取结果集
     * 3. 处理结果集, 与返回值绑定, ()未做
     * 4. 返回给用户
     * @return
     */
    UserMapper getUsermapper() {
        UserMapper mapper = (UserMapper) Proxy.newProxyInstance(MyMyBatis.class.getClassLoader(),
                new Class<?>[]{UserMapper.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Select annotation = method.getAnnotation(Select.class);
                        if (annotation != null) {
                            String[] value = annotation.value();
                            String sql = value[0];
                            // 1, 处理方法参数
//                            method.invoke()
                            Map map = parseMethodArgs(args, method);
                            //2. 处理SQL语句 ,将参数 与SQL 语句绑定 即通过这个方法获取到一条完整的sql命令
                            sql = parseSQL(sql, map);
                            System.out.println(sql);
                            // 3. 获取方法的返回类型
                            Class<?> returnType = method.getReturnType();
                            //4 执行SQL方法, 处理结果集
                            return  runSQL(sql, returnType);
                        }
                        return null;
                    }
                });
        return mapper;
    }
处理方法参数:

/**
 *  解析方法参数, 将方法参数通过map的形式返回
 * @param args 方法 参数
 * @param method 具体方法
 * @return map
 */
private Map<String, Object> parseMethodArgs(Object[] args, Method method) {
    Map<String, Object> map = new HashMap<String, Object>();
    Parameter[] parameters = method.getParameters();
    int[] cnt = {0};
    Arrays.asList(parameters).forEach(parameter -> {
        String name = parameter.getName();
        map.put(name, args[cnt[0]++]);
    });
    return map;
}
生成SQL语句:
/**
 * 解析SQL语句
 * // TODO: 2021/3/15 存在问题, 应该把 #{}  更改为 ? 并且保存参数名列表,
 *
 * @param sql 初始sql语句  带#{}
 * @param map 参数map
 * @return sql
 */
private String parseSQL(String sql, Map map) {
    StringBuilder sqlB = new StringBuilder();
    char[] chars = sql.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        char c = chars[i];
        if (c == '#') {
            StringBuilder partB = new StringBuilder();
            i = parsePartSql(sql, partB, i);
            Object o = map.get(partB.toString());
            sqlB.append(o.toString());
        } else {
            sqlB.append(c);
        }

    }
    return sqlB.toString();
}

private int parsePartSql(String sql, StringBuilder partB, Integer i) {
    i++;
    for (; i < sql.length(); i++) {
        if (sql.charAt(i) != '{') {
            throw new RuntimeException("sql error without #{}in");
        } else {

            while (true) {
                i++;
                if (i == sql.length() || sql.charAt(i) == '}') {

                    return ++i;

                } else {
                    partB.append(sql.charAt(i));
                }
            }
        }


    }
    return ++i;
}
执行SQL命令:
    /**
     * 执行SQL 命令返回结果集
     * // TODO: 2021/3/15 未处理 方法的返回值
     *
     * @param sql        sql 命令
     * @param returnType 结果的返回类型
     * @return 结果集
     */
    private Object runSQL(String sql, Class<?> returnType) {
        DataSource dataSource = getDataSource();
        Object result = null;

        try (Connection connection = dataSource.getConnection()) {
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
//            preparedStatement.set
            ResultSet resultSet = preparedStatement.executeQuery();
            // 处理结果集, 对结果集 与xml方法 返回值做映射,
            result = resolveResultSet(resultSet, returnType);
//            preparedStatement
        } catch (Exception e) {
            e.printStackTrace();

        }
        return result;
    }
获取数据库连接:
private static DataSource getDataSource() {
     DruidDataSource dataSource = new DruidDataSource();
     dataSource.setUsername("root");
     dataSource.setPassword("aa12321.");

     dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
     return dataSource;
 }
处理结果集ResultSet:
/**
 * // TODO: 2021/3/15  暂时不知道如何处理结果集 , 先通过打印的方式进行处理
 * 处理结果集, 并将结果集与 method 返回值绑定
 * @param resultSet
 * @param returnType
 * @return
 */
private Object resolveResultSet(ResultSet resultSet, Class<?> returnType) throws SQLException, IllegalAccessException, InstantiationException {
    // returnType.getMethods();
    // 1. 获取 方法的返回值 对象实例化, // TODO: 2021/3/15  暂不处理
    User user = new User();
    //2. 获取结果集列表,并将参数绑定
    while (resultSet.next()) {
        int id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        String username = resultSet.getString("username");
        String password = resultSet.getString("password");
        System.out.println(id + "," + name);
        user.setId(id);
        user.setName(name);
        user.setUsername(username);
        user.setPassowrd(password);
    }// 3. 关闭结果集
    if (!resultSet.isClosed()) {
        resultSet.close();
    }
    return user;
}

测试方法:

public static void main(String[] args) {
    MyMyBatis sol = new MyMyBatis();
    UserMapper usermapper = sol.getUsermapper();
    User user = usermapper.selectUserById(1);
    System.out.println(user);
}

执行结果:

image-20210315165840963

总结:

本测试程序只是简单的模拟了一下 MyBatis 查询过程, 没有根据method.getReturnType() 来映射ResultSet.


TODO:

  • 自己需要去了解下java.lang.reflect 这个包的内容 ,这样自己就会更好的实现instance