Mybatis伪代码getMapper手写

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fairy要carry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值