手寫源碼(四):本身實現Mybatis

手寫MyBatis

如題,此次我又來做死試試編寫相似Mybatis的持久層框架了java

MyBatis的難點

  • 如何在沒有實例的狀況下建立Mapping接口的實現類而且調用接口中的方法
    • 使用字節技術建立子類
    • 使用匿名內部類
    • 使用動態代理建立對象(咱們使用這個)

建立一個接口UserMapper,再建立一個實體類User
使用JDK的動態代理,建立一個代理處理器sql

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());
    }
}
複製代碼

使用測試類測試一下bash

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

測試結果以下 app

動態代理的測試結果
這樣就能夠實現拿到接口的方法參數而且自行控制對象的返回值

1、@Insert的實現步驟

  • 判斷方法上是否存在@Insert註解,存在的話,獲取上面的SQL語句
  • 獲取方法的參數,和SQL參數經行匹配,而且替換SQL的參數(變成問號)
  • 調用JDBC代碼執行語句並獲取返回值

具體實現以下

/** * @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);
}
複製代碼

而後再主函數裏使用代理調用這個方法ide

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);
    }
}
複製代碼

而後運行結果以下 函數

插入的執行結果

2、@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方法,以下ui

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());
    }
}
複製代碼

執行後個人運行結果以下

執行查詢的結果

附:整個核心的代理類以下

/** * @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;
    }
}
複製代碼
相關文章
相關標籤/搜索