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;
}
}