package com.mybatis.wyh;
import com.mybatis.mybatis_Wu.MapperProxyFactory;
import java.util.List;
/**
* @author wuyuhang
*/
public class MybatisWyhApplication {
public static void main(String[] args) {
//1.通过代理工厂得到UserMapper的代理对象
UserMapper userMapper = MapperProxyFactory.getMapper(UserMapper.class);
//2.执行目标方法,这里就会进行代理中invoke方法
List<TUser> res = userMapper.getUser("admin");
System.out.println(res.get(0));
}
}
package com.mybatis.mybatis_Wu;
/**
* @author wuyuhang 2022/8/30
*/
import com.mybatis.wyh.TUser;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
/**
* mybatis代理工厂
*
* @author 邬雨航
*/
public class MapperProxyFactory {
private static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();
static {
//初始化两个类型转换器
typeHandlerMap.put(String.class, new StringTypeHandler());
typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
try {
//加载我们的数据库驱动
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static <T> T getMapper(Class<T> mapper) {
/**
* 1.jdk动态代理生成代理对象——>本质就是通过类加载加载这个mapper,invoke执行目标方法
*/
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
*2.执行sql的逻辑(解析sql->执行sql->结果处理)
*/
Connection connection = getConnection();
/**
* 3.通过反射得到正在执行方法上的注解——>得到注解select上的value值
*/
Select annotation = method.getAnnotation(Select.class);
String sql = annotation.value();
System.out.println("正在执行的sql语句:" + sql);
/**4.构造map方法参数与value映射 ——>name:admin
* 1.创建一个map映射参数和值,参数为method的参数名称,值为method传入的参数值
* 2.然后根据parameter得到修饰它的注解值@Param——>目的就是为了得到具体参数名称
* 因为mybatis,jdk编译get方法参数名称默认是ages0这些(表示第一个参数)
* 所以我们如果使用了Param注解,一个参数会得到两个参数名称——>一个默认的,一个args0
*/
HashMap<String, Object> paramValueMap = new HashMap<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String name = parameter.getAnnotation(Param.class).value();
paramValueMap.put(name, args[i]);
}
/**
* 5.new一个解析器,利用handler将定义的#{}替换为?——>GenericTokenParser发现是以#{开头}结尾,将内容单独解析出来
* ——>就会调用tokenHandler中的handleToken()将#{}里面的内容传到这个方法里面
* ——>将符号中的内容传到ParameterMapping中,然后添加到集合当中——>并且返回?
* parse():解析sql中占位字段并且将内容解析出来,通过handleToken()方法存入ParameterMapping,并放入集合返回?
*
*/
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
String parseSql = parser.parse(sql);
System.out.println("解析后的sql:" + parseSql);
/**
* 6.获取集合中占位符中的sql参数值
* 为后续作为map的key从而得到注入sql时的value值作准备
*/
List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
System.out.println("存入list集合中#{}中的内容:" + parameterMappings);
/**
* 7.预编译
*/
PreparedStatement statement = connection.prepareStatement(parseSql);
/**8.遍历得到parameterMappings集合中被ParameterMapping封装的字段属性,并且赋值给sql占位符
* 1.property:对应sql中#{}中的参数,也就是map中的key,我们通过这个key得到map中的args
* 2.最后将args注入到sql语句中,代替?——>根据类型,选择一个类型转换器完成sql中的字段注入
* ——>本质也就是PreparedStatement.setXXX(位置,字段值),只是这个类型转换器在此上做了一层类型封装
*/
for (int i = 0; i < parameterMappings.size(); i++) {
//0.通过属性map得到对应的value——>并确定value类型
String property = parameterMappings.get(i).getProperty();
Object value = paramValueMap.get(property);
Class<?> type = value.getClass();
//1.通过map集合通过对应的类型调用对应的tyHandler类型转换器执行setParameter()完成sql属性的注入
typeHandlerMap.get(type).setParameter(statement, i + 1, value);//注意sql中是从1开始
}
/**
* 9.执行preparedStatement(防止sql注入)
*/
statement.execute();
/**
* 10.结果的封装(封装到TUser)——>(得到结果集)
* 1.首先new一个集合,然后通过PreparedStatement得到执行的结果集
* 2.利用while循环遍历结果集,new一个TUser,将遍历到每层结果属性封装到实体类TUser中,然后放入集合
*/
ArrayList<Object> list = new ArrayList<>();
ResultSet resultSet = statement.getResultSet();//得到结果集
//根据当前执行方法返回的类型
Object result = null;
/**
* 10.1封装结果需要考虑是否是泛型 ——>可通过方法返回的类型(TUser)得到里面的方法
* 1.通过反射得到method返回类型——>2.进行判断
*/
Class resultType = null;
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof Class) {//不是泛型
resultType = (Class) genericReturnType;
} else if (genericReturnType instanceof ParameterizedType) {//是泛型
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();//泛型中可能是数
resultType = (Class) actualTypeArguments[0];
}
/**
* 10.2根据结果集resultSet得到表中所有字段
*/
ResultSetMetaData metaData = resultSet.getMetaData();
ArrayList<String> columnList = new ArrayList<>();
for (int i = 0; i < metaData.getColumnCount(); i++) {
columnList.add(metaData.getColumnName(i + 1));//sql里面都是从1开始的
}
/**
* 10.3得到方法类型的对象方法
* 1.因为我们通过method反射得到了方法返回的类型,但是并不知道它的对象方法(setxxx)
* ——>2.利用反射得到所有的对象方法,然后进行截取 ——>3.放到map中
*/
HashMap<String, Method> methodMap = new HashMap<>();
for (Method declaredMethod : resultType.getDeclaredMethods()) {
//对setxxx的方法进行截取,set后面的作为key
if (declaredMethod.getName().startsWith("set")) {
String propertyName = declaredMethod.getName().substring(3);
propertyName = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1);//首字母小写
methodMap.put(propertyName, declaredMethod);
}
}
/**
* 10.4将结果集中查询的字段数据->注入到字段中
* 1、首先遍历字段集合——>2.根据字段得到对应的set方法——>3.数据从结果集中取出需要考虑类型
* ——>4.从set方法的参数得到类型class——>5.得到对应的类型转换器——>6.执行类型转换器中的getResult方法
* ——> 本质就是封装了一层resultSet.getXXX(列名)——>7.最后利用反射执行set方法
*/
while (resultSet.next()) {
//1.需要考虑接口方法查询内容是什么类型——>resultType——>得到对象
Object instance = resultType.newInstance();
//2.遍历字段集合,根据字段作为key得到methodMap中的set方法
for (int i = 0; i < columnList.size(); i++) {
String column = columnList.get(i);
Method setterMethod = methodMap.get(column);
Class<?> clazz = setterMethod.getParameterTypes()[0];//得到set方法第一个参数类型
//3.执行set方法完成数据注入(这里需要根据类型区分 ——>从结果集中获取对应字段数据)
TypeHandler typeHandler = typeHandlerMap.get(clazz);
setterMethod.invoke(instance, typeHandler.getResult(resultSet, column));
}
list.add(instance);
// TUser user = new TUser();
// user.setId(resultSet.getInt("id"));//从resultSet结果集中获取对应字段的数据
// user.setUserName(resultSet.getString("username"));
// user.setPassWord(resultSet.getString("password"));
// user.setEmail(resultSet.getString("email"));
//
// list.add(user);
}
result=list;
/**
* 11.进行方法method返回类型的判断
*/
if(method.getReturnType().equals(List.class)){//method返回类型是list
result=list;
}else{//其余情况,如果也返回了多条记录,需要进行额外处理
result=list.get(0);
}
/**
* 11.关闭数据库连接
*/
connection.close();
return result;
}
});
return (T) proxy;
}
/**
* 数据库连接
*
* @return
* @throws SQLException
*/
private static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/book", "Wuyuhang", "2002514wyh11");
}
}