手写源码(四):自己实现Mybatis

栏目: Java · 发布时间: 5年前

内容简介:创建一个接口UserMapper,再创建一个实体类User使用JDK的动态代理,创建一个代理处理器包装上面的代理
  • 如何在没有实例的情况下创建Mapping接口的实现类并且调用接口中的方法
    • 使用字节技术创建子类
    • 使用匿名内部类
    • 使用动态代理创建对象(我们使用这个)

创建一个接口UserMapper,再创建一个实体类User

使用JDK的动态代理,创建一个代理处理器

public class InvocationHandlerMybatis implements InvocationHandler {
    /**
     *
     * @param proxy     代理对象
     * @param method    拦截的方法
     * @param args      方法上的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始代理");
        return 1;
    }
}
复制代码

包装上面的代理

public class SqlSession {
    /**加载Mapper接口*/
    public static <T> T getMapper(Class clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandlerMybatis());
    }
}
复制代码

使用测试类测试一下

public static void main(String[] args) {
        UserMapper userMapper = SqlSession.getMapper(UserMapper.class);
        int i = userMapper.insertUser("", "");
        System.out.println(i);
    }
复制代码

测试结果如下

手写源码(四):自己实现Mybatis

这样就可以实现拿到接口的方法参数并且自行控制对象的返回值

一、 @Insert 的实现步骤

@Insert

具体实现如下

/**
 * @author libi
 * 用于动态代理,获取方法的参数并且给返回值
 */
public class InvocationHandlerMybatis implements InvocationHandler {
    /**
     * @param proxy     代理对象
     * @param method    拦截的方法
     * @param args      方法上的参数
     * @return          方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始代理");
        //判断方法上是否存在Insert注解
        ExtInsert extInsert = method.getDeclaredAnnotation(ExtInsert.class);
        if (extInsert != null) {
            //执行插入的操作,返回影响行数
            return doInsert(method, args, extInsert);
        }
        return null;
    }

    /**
     * 执行插入的操作
     * @param method
     * @param args
     * @param extInsert
     * @return
     */
    private int doInsert(Method method, Object[] args, ExtInsert extInsert) {
        //获取 Sql 语句
        String sql = extInsert.value();
        System.out.println("insert sql:" + sql);
        //获取方法参数和Sql语句进行匹配
        //定义一个Map,Key是参数名,Value是参数值
        ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
        //获取方法上的参数
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //获取参数名称和参数的值
            ExtParam param = parameters[i].getDeclaredAnnotation(ExtParam.class);
            if (param != null) {
                String name = param.value();
                Object value = args[i];
                System.out.println("paramName:"+name+",paramValue:"+value);
                map.put(name, value);
            }
        }
        //怕打乱顺序而把sql语句的参数放在一个有序的数组里
        List<Object> sqlParam = new ArrayList<>();
        String[] sqlInsertParameter = SQLUtils.sqlInsertParameter(sql);
        for (String paramName : sqlInsertParameter) {
            Object paramValue = map.get(paramName);
            sqlParam.add(paramValue);
        }
        System.out.println();
        //把参数替换成?
        sql = SQLUtils.parameQuestion(sql, sqlInsertParameter);
        System.out.println("new sql:"+sql);
        //执行JDBC
        return JDBCUtils.insert(sql, false, sqlParam);
    }
}
复制代码

测试这个方法

我们定义一个Mapper

public interface UserMapper {
    @ExtInsert("insert into user(username,password) values (#{userName},#{password})")
    int insertUser(@ExtParam("userName") String userName, @ExtParam("password") String password);
}
复制代码

然后再主函数里使用代理调用这个方法

public class Cluster {
    public static void main(String[] args) {
        UserMapper userMapper = SqlSession.getMapper(UserMapper.class);
        int i = userMapper.insertUser("name", "123");
        System.out.println(i);
    }
}
复制代码

然后运行结果如下

手写源码(四):自己实现Mybatis

二、 @Select 的实现思路

  • 找到方法里带有 @Select 注解的方法,拿到Spl语句
  • 获取方法上的参数,绑定,然后把参数替换成?
  • 调用JDBC调用底层
  • 使用反射机制实例化实体类对象(获取方法返回的类型,使用反射实例化对象)

核心代码如下

和上面不同的是,我重构了InvocationHandleMybatis类的代码,复用了一些代码

/**
     * 执行查询的操作
     * @param method
     * @param args
     * @param extSelect
     * @return 查询结果,可能是实体类对象,List或者基础类型
     */
    private Object doSelect(Method method, Object[] args, ExtSelect extSelect) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        //获取Sql语句
        String sql = extSelect.value();
        System.out.println("select sql:" + sql);
        //获取方法参数和Sql语句进行匹配
        ConcurrentHashMap<String, Object> paramMap = getParamMap(method, args);

        //怕Sql参数顺序和@Param参数顺序不一致而把sql语句的参数放在一个有序的数组里
        List<Object> sqlParamValue = new ArrayList<>();
        List<String> sqlSelectParameter = SQLUtils.sqlSelectParameter(sql);
        for (String paramName : sqlSelectParameter) {
            Object paramValue = paramMap.get(paramName);
            sqlParamValue.add(paramValue);
        }
        //把参数替换成?
        sql = SQLUtils.parameQuestion(sql, sqlSelectParameter);
        System.out.println("new sql:"+sql);
        //执行JDBC
        ResultSet resultSet = JDBCUtils.query(sql, sqlParamValue);
        //判断是否有结果集
        if (!resultSet.next()) {
            return null;
        }
        resultSet.previous();
        //使用反射获取方法类型
        Class<?> returnType = method.getReturnType();
        //使用反射机制实例化对象
        Object result = returnType.newInstance();
        //遍历这个结果集
        while (resultSet.next()) {
            for (String paramName : sqlSelectParameter) {
                //获取参数的值
                Object resultValue = resultSet.getObject(paramName);
                //使用反射机制赋值
                Field field = returnType.getDeclaredField(paramName);
                field.setAccessible(true);
                field.set(result, resultValue);
            }
        }
        return result;
    }
复制代码

在使用上面的代码时,我会检测方法上是否有 @Select 注解,有的话说明这个方法是用于查询语句的,我们就把这个注解传进来

我们改写UserMapper类,增加Select方法,如下

public interface UserMapper {
    @ExtInsert("insert into user(username,password) values (#{userName},#{password})")
    int insertUser(@ExtParam("userName") String userName, @ExtParam("password") String password);

    @ExtSelect("select * from user where username=#{userName} and password=#{password}")
    User selectUser(@ExtParam("userName") String userName, @ExtParam("password") String password);
}
复制代码

改写测试用的代码,如下

public class Cluster {
    public static void main(String[] args) {
        UserMapper userMapper = SqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUser("name", "123");
        System.out.println(user.getUserName());
    }
}
复制代码

执行后我的运行结果如下

手写源码(四):自己实现Mybatis

附:整个核心的代理类如下

/**
 * @author libi
 * 用于动态代理,获取方法的参数并且给返回值
 */
public class InvocationHandlerMybatis implements InvocationHandler {
    /**
     * @param proxy     代理对象
     * @param method    拦截的方法
     * @param args      方法上的参数
     * @return          方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始代理");
        //判断方法上是否存在Insert注解
        ExtInsert extInsert = method.getDeclaredAnnotation(ExtInsert.class);
        if (extInsert != null) {
            //执行插入的操作,返回影响行数
            return doInsert(method, args, extInsert);
        }
        //判断方法上是否有Select注解
        ExtSelect extSelect = method.getDeclaredAnnotation(ExtSelect.class);
        if (extSelect != null) {
            //执行查询的操作,返回实际实体类或者List
            return doSelect(method, args, extSelect);
        }
        return null;
    }

    /**
     * 执行插入的操作
     * @param method
     * @param args
     * @param extInsert
     * @return 影响行数
     */
    private int doInsert(Method method, Object[] args, ExtInsert extInsert) {
        //获取Sql语句
        String sql = extInsert.value();
        System.out.println("insert sql:" + sql);
        //获取方法参数和Sql语句进行匹配
        ConcurrentHashMap<String, Object> paramMap = getParamMap(method, args);

        //怕Sql参数顺序和@Param参数顺序不一致而把sql语句的参数放在一个有序的数组里
        List<Object> sqlParamValue = new ArrayList<>();
        String[] sqlInsertParameter = SQLUtils.sqlInsertParameter(sql);
        for (String paramName : sqlInsertParameter) {
            Object paramValue = paramMap.get(paramName);
            sqlParamValue.add(paramValue);
        }
        //把参数替换成?
        sql = SQLUtils.parameQuestion(sql, sqlInsertParameter);
        System.out.println("new sql:"+sql);
        //执行JDBC
        return JDBCUtils.insert(sql, false, sqlParamValue);
    }

    /**
     * 执行查询的操作
     * @param method
     * @param args
     * @param extSelect
     * @return 查询结果,可能是实体类对象,List或者基础类型
     */
    private Object doSelect(Method method, Object[] args, ExtSelect extSelect) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        //获取Sql语句
        String sql = extSelect.value();
        System.out.println("select sql:" + sql);
        //获取方法参数和Sql语句进行匹配
        ConcurrentHashMap<String, Object> paramMap = getParamMap(method, args);

        //怕Sql参数顺序和@Param参数顺序不一致而把sql语句的参数放在一个有序的数组里
        List<Object> sqlParamValue = new ArrayList<>();
        List<String> sqlSelectParameter = SQLUtils.sqlSelectParameter(sql);
        for (String paramName : sqlSelectParameter) {
            Object paramValue = paramMap.get(paramName);
            sqlParamValue.add(paramValue);
        }
        //把参数替换成?
        sql = SQLUtils.parameQuestion(sql, sqlSelectParameter);
        System.out.println("new sql:"+sql);
        //执行JDBC
        ResultSet resultSet = JDBCUtils.query(sql, sqlParamValue);
        //判断是否有结果集
        if (!resultSet.next()) {
            return null;
        }
        resultSet.previous();
        //使用反射获取方法类型
        Class<?> returnType = method.getReturnType();
        //使用反射机制实例化对象
        Object result = returnType.newInstance();
        //遍历这个结果集
        while (resultSet.next()) {
            for (String paramName : sqlSelectParameter) {
                //获取参数的值
                Object resultValue = resultSet.getObject(paramName);
                //使用反射机制赋值
                Field field = returnType.getDeclaredField(paramName);
                field.setAccessible(true);
                field.set(result, resultValue);
            }
        }
        return result;
    }

    /**
     * 建立方法上的参数和值@Param参数名的映射
     * @param method
     * @param args
     * @return
     */
    private ConcurrentHashMap<String, Object> getParamMap(Method method, Object[] args) {
        //定义一个Map,Key是参数名,Value是参数值
        ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
        //获取方法上的参数
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //获取参数名称和参数的值
            ExtParam param = parameters[i].getDeclaredAnnotation(ExtParam.class);
            if (param != null) {
                String name = param.value();
                Object value = args[i];
                System.out.println("paramName:"+name+",paramValue:"+value);
                map.put(name, value);
            }
        }
        return map;
    }
}
复制代码

以上所述就是小编给大家介绍的《手写源码(四):自己实现Mybatis》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

流量池

流量池

杨飞 / 中信出版集团 / 2018-4 / 68.00

移动互联网时代,信息日益冗余,新闻速朽; 整体流量增长速度放缓,而竞争者数量高速增加; 流量呈现变少、变贵、欺诈频繁的现状; 品效合一的营销策略成为共识,而实现路径成为痛点; 多次开创各营销渠道效果之最的营销人、各种刷屏级营销事件操盘手、神州专车CMO杨飞,这一次倾囊相授,诚恳讲述如何实现流量获取、营销转化以及流量的运营和再挖掘。一起来看看 《流量池》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具