MyBatis实现原理

85 阅读2分钟

1、项目中MyBatis常见的配置:

<configuration>
 <settings>
   <setting name="cacheEnabled" value="false"/>  // 分布式应用禁用查询缓存
   <setting name="lazyLoadingEnabled" value="false"/>  // 禁用懒加载
   <setting name="defaultStatementTimeout" value="30"/> // 默认执行超时时间
   <setting name="useGenerateKeys" value="true"/>  // 默认开启insert后返回自增的主键
 </settings>
</configuration>

2、常见的问题

1、MyBatis底层源码实现
2、MyBatis参数解析底层原理
3、MyBatis解析SQL底层原理
4、MyBatis执行SQL底层原理
5、MyBatis结果处理底层原理

3、过程解析

mapper是一个接口,实际实现的时候都是使用了代理类,使用代理类执行方法。根据代理类,就可以获取参数、注解等配置信息,解析sql语句

代理类中执行的内容: 1、获取数据库链接
2、获取sql语句
3、构造prepareStatement:替换MyBatis的sql语句
jdbc的statement的setXXX时需要执行参数类型
4、执行prepareStatement:执行sql
5、获取sql执行结果的返回值

具体过程:
1、使用代理 获取method的所有参数,将参数名和参数值组装成一个map:Map<String, Object> paramValueMap
2、将select * from user where name=#{name} and age =#{age} and first_name = #{name} 转换成 select * from user where name=? and age = ? and first_name = ?
3、将#{xxx} 保存到一个List中 [name, age, name]
4、代理类中获取到的Map<argName, value> 根据上面的List 取值并set到 select * from user where name=? and age = ? and first_name = ? 语句中
5、如何获取最终的结果?JDBC返回的是ResultSet
获取到返回值之后怎么对应到mapper的接口的返回值类型
根据代理的method获取method对应的返回值类型,根据返回值类型;
根据返回值类型,生成对应的对象

public class MapperProxyFactory {
    public static <T> T getMapper(Class<T> mapper) {
        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 代理对象执行方法时都会执行该方法
                // MyBatis:解析sql -> 执行sql —> 结果处理
                Map<String, Object> paramValueMap = new HashMap<>();

                // 获取方法上面的注解,取出注解指定的value
                // 实际内容是:select * from user where name=#{name} and age =#{age} and first_name = #{name}
                Select annotation1 = method.getAnnotation(Select.class);
                String sql = annotation1.value();

                Class resultType = null;
                // 根据代理的method获取method对应的返回值类型,根据返回值类型
                Type genericReturnType = method.getGenericReturnType();
                if (genericReturnType instanceof Class) {
                    // 不是泛型
                    resultType = (Class) genericReturnType;
                } else if (genericReturnType instanceof ParameterizedType) {
                    // 是泛型
                    Type[] argList = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                    resultType = (Class) argList[0];
                }


                // 解析代理的方法名和参数列表为Map
                Parameter[] parameters = method.getParameters();
                for (int i = 0 ; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    // 在MyBatis的mapper上针对参数定义的注解,通过注解,拿到参数名
                    Param annotation = parameter.getAnnotation(Param.class);
                    String value = annotation.value();

                    // parameter.getName()拿到的参数名是arg0、arg1....不是我们自定义的参数名,此处需要添加一个Map,用来映射Map<自定义参数名-value,参数实际的值>
                    //String name = parameter.getName();
                    //if (args[i] instanceof Integer) {
                        // integer的处理
                    //}

                    System.out.println(args[i].getClass());
                    paramValueMap.put(value, args[i]);
                }

                // 遍历接口的set方法,生成Map<column, Method[setXXX()]>的map对象
                Map<String, Method> setterMethodMap = new HashMap<>();
                Method[] declaredMethods = resultType.getDeclaredMethods();
                for (Method singleMethod : declaredMethods) {
                    if (singleMethod.getName().startsWith("set")) {
                        // set开头的方法
                        String substring = singleMethod.getName().substring(3);
                        // setId -> 获取这里的id
                        // xml形式下xml文件中的resultMap 主要做的就是实体bean的字段与数据库表字段的映射关系
                        substring = substring.substring(0, 1).toLowerCase(Locale.ROOT) + substring.substring(1);
                        setterMethodMap.put(substring, singleMethod);
                     }
                }

                // 返回值有可能是List<T> ,也有可能是单独的T,根据method的返回值
                Object result;
                List<Object> instanceList = new ArrayList<>();
                // 获取jdbc查询结果的所有字段名
                ResultSet resultSet = null;
                while (resultSet.next()) {
                    // 返回值的每一条数据,有多个实体类的字段
                    ResultSetMetaData metaData = resultSet.getMetaData();
                    // 反射生成方法返回值的对象
                    Object instance = resultType.newInstance();  // 根据返回值类型,生成对应的对象
                    List<String> columnList = new ArrayList<>();
                    // 返回的每条数据包含多个字段的信息
                    for (int i = 0; i < metaData.getColumnCount(); i++) {
                        // 获取字段名
                        String catalogName = metaData.getCatalogName(i + 1);
                        columnList.add(catalogName);

                        // 根据字段获取字段对应的setXXX方法
                        Method execMethod = setterMethodMap.get(catalogName);
                        // 获取方法setXXX的入参类型,因为只有一个入参
                        Class<?> clazz = execMethod.getParameterTypes()[0];

                        // 执行接口的setXXX方法
                        // 需要用到resultSet的getXXX方法:通过Map<类型,TypeHandler>的方式获取Result中字段的value
                        // typeHandler.getResult()  是调用了resultSet的getInt/getString/getBoolean方法的返回值
                        execMethod.invoke(instance, typeHandler.getResult(resultSet, catalogName));  // 根据类型区分getXXX()
                        instanceList.add(instance);
                    }
                }


                // 返回值有可能是List<T> ,也有可能是单独的T,根据method的返回值
                if (method.getReturnType().equals(List.class)) {
                    // list
                    result = instanceList;
                } else {
                    result = instanceList.get(0);
                }

                // 从mapper的select注解的value映射到实际执行mysql的 select * from user where name=? and age = ? and first_name = ?
                return result;
            }
        });
        return (T) proxy;
    }
}