1. 程式人生 > >手寫spring+springmvc+mybatis框架篇——Mybatis

手寫spring+springmvc+mybatis框架篇——Mybatis

  整合Mybatis是本專案中的一個難點。

實現功能:

1 動態繫結使用者輸入引數

2 Mybatis的resultType動態繫結返回實體類。

3 在spring中的介面注入

4 xml版本的mapper注入。

 關於Mybatis的優秀文章給大家推薦兩個

1 手寫簡化版mybatis               https://my.oschina.net/liughDevelop/blog/1631006

  http://www.crazyant.net/2022.html

 手寫板大致思路如下,圖片引用自https://my.oschina.net/liughDevelop/blog/1631006


這裡的Myconfiguration

和我的JDBCUtils類似。

實現思路: 首先用XmlBuilderMapper類讀取mapper.xml(我是在initBean中指定要讀取的配置檔案,並沒有寫成在xml中配置動態的讀取xml。)檔案,獲取MapperInfo物件儲存資訊。使用者用MysqlSessiongetMapper方法,返回一個代理物件,用這個代理物件來執行MySqlSession定義的selectOne方法來查詢,Mybaits中的SqlSession中定義了大量的方法,我這裡簡化只有一個selectOne方法。然後這個方法呼叫executor執行器來解析sql,傳入引數,執行資料庫操作。最後返回結果。將返回結果封裝成物件。

動態代理一般有兩種,一種是jdk一種是cglib動態代理,本專案採用是jdk動態代理實現。

XmlBuilderMapper.class

package spring.mybatis;

import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import spring.constants.Constants;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
 * @ClassName XmlBuilder
 * @Description
 * @Data 2018/7/8
 * @Author xiao liang
 */
@Slf4j
public class XmlBuilderMapper {
    public List<MapperInfo> buildMapper(String xmlMapperPath){
        //例項化mapperInfo的連結串列,每一條sql對應一個MapperInfo物件
        List<MapperInfo> mapperInfoList = new ArrayList<>();
        MapperInfo mapperInfo = new MapperInfo();
        // 建立saxReader物件
        SAXReader reader = new SAXReader();
        // 通過read方法讀取一個檔案 轉換成Document物件
        Document document = null;

        String pathName = Constants.PATH + xmlMapperPath;
        try {
            document = reader.read(new File(pathName));
        } catch (DocumentException e) {
            log.error("檔案沒有找到,{}", pathName);
        }
        //獲取根節點元素
        Element node = document.getRootElement();
        mapperInfo.setInterfaceName(node.attributeValue("namespace"));
        //獲取所有的bean
        List<Element> elementsList = node.elements();
        for (Element element :
                elementsList) {
            if ("select".equals(element.getName())){
                mapperInfo.setMethodName(element.attributeValue("id"));
                mapperInfo.setResultClassName(element.attributeValue("resultType"));
                mapperInfo.setSqlContent(element.getText());
            }
            mapperInfoList.add(mapperInfo);
        }
        return mapperInfoList;
    }
}

然後介紹一下MapperInfo物件

package spring.mybatis;

import lombok.Data;
/**
 * @ClassName MapperInfo
 * @Description 用來封裝讀取mapper.xml檔案後的資訊
 * @Data 2018/7/8
 * @Author xiao liang
 */
@Data
public class MapperInfo {
    //namespace名稱空間
    private String interfaceName;
    //sql內容
    private String sqlContent;
    //對應的方法名
    private String methodName;
    //返回值的class名
    private String resultClassName;
}

JDBCUtils工具類我在開篇就介紹了,也沒什麼新內容,不貼在這裡了

其實關鍵點就是動態代理執行器

MySqlSession(動態代理部分

package spring.mybatis;

import java.lang.reflect.Proxy;
/**
 * @ClassName MySqlSession
 * @Description
 * @Data 2018/7/8
 * @Author xiao liang
 */
public class MySqlSession {
    public <T> T selectOne(MapperInfo mapperInfo ,Object[] paremeters){
        MyExecutor myexecutor = new MyExecutor();
        return myexecutor.query(mapperInfo,paremeters);
    }
  
    public <T> T getMapper(Class<?> aClass,String mybatisXmlName){
        return (T) Proxy.newProxyInstance(aClass.getClassLoader(),new Class[]{aClass},new MyMapperProxy(this,mybatisXmlName));
    }
}
MyMapperProxy(jdk動態代理的實現) 代理之後執行的還是sqlSession中的方法
package spring.mybatis;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
/**
 * @ClassName MyMapperProxy
 * @Description
 * @Data 2018/7/8
 * @Author xiao liang
 */
public class MyMapperProxy implements InvocationHandler {
    private MySqlSession mySqlSession;
    private String mybatisXmlName;
   //mybatisXmlName傳入的要讀取的xml檔名
   public MyMapperProxy(MySqlSession mySqlSession , String mybatisXmlName){
       this.mySqlSession = mySqlSession;
       this.mybatisXmlName = mybatisXmlName;
   }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        XmlBuilderMapper xmlBuilderMapper = new XmlBuilderMapper();
        List<MapperInfo> mapperInfoList = xmlBuilderMapper.buildMapper(mybatisXmlName);
        //如果存在sql,開始執行方法
        if (mapperInfoList != null && mapperInfoList.size() != 0){
            for (MapperInfo mapperInfo :
                    mapperInfoList) {
                if (!method.getDeclaringClass().getName().equals(mapperInfo.getInterfaceName())){
                    return null;
                }
                if (method.getName().equals(mapperInfo.getMethodName())){
                    //其實最後執行的mySqlSession中的方法,args是使用者傳遞的引數陣列
                    return mySqlSession.selectOne(mapperInfo,args);
                }
            }
        }
        return null;
    }
}

返回到MySqlSession後,就輪到執行器MyExcutor出場了。

MyExcutor:也是一個難點,先說一下程式的邏輯。讀取到mapperinfo物件,獲得定義的sql,返回類的名稱等資訊。用正則解析sql,根據#{},判斷sql中有幾處?,然後將使用者傳遞的引數按照順序依次寫入到sql的?中。最後讀取結果集,用set方法注入到返回的物件中,這樣就實現了返回時候的實體類綁定了。

package spring.mybatis;

import lombok.extern.slf4j.Slf4j;
import spring.Utils.GetMethodName;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * @ClassName MyExecutor
 * @Description 執行器
 * @Data 2018/7/8
 * @Author xiao liang
 */
@Slf4j
public class MyExecutor {
    public <T> T query(MapperInfo mapperInfo, Object[] paremeters) {
        //獲取mapper.xml檔案中的sql語句
        String preSql = mapperInfo.getSqlContent();
        //正則匹配規則,匹配有幾個#{}
        String rgex = "#\\{.*?}";
        String sql = null;
        String resultClassName = mapperInfo.getResultClassName();
        Class<?> aClass = null;
        Field[] fields = null;
        Method[] methods = null;
        Object object = null;
        Pattern pattern = Pattern.compile(rgex);
        Matcher m = pattern.matcher(preSql);
        Connection connection = null;
        ResultSet rs = null;
        PreparedStatement preparedStatement = null;
        //Preparement注入引數的個數
        int orderPre = 0;
        //每匹配一次,加一
        while (m.find()) {
            orderPre++;
        }
        //匹配完成之後,將#{}用?代替,preparement執行sql
        sql = m.replaceAll("?");
        try {
            aClass = Class.forName(resultClassName);
            fields = aClass.getDeclaredFields();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            JDBCUtils jdbcUtils = new JDBCUtils();
             connection = jdbcUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //將使用者傳遞過來的引數按照順序依次賦值給prepareStatement的sql
            for (int i = 1; i <= orderPre; i++) {
                preparedStatement.setObject(i, paremeters[i - 1]);
            }
            rs = preparedStatement.executeQuery();
            object = aClass.newInstance();
            while (rs.next()) {
                int i = 1;
                for (Field field :
                        fields) {
                    //遍歷每個屬性,然後將結果集中的資料用set方法注入到返回的物件中
                    String setMethodNameByField = GetMethodName.getSetMethodNameByField(field.getName());
                    Method method2 = aClass.getMethod(setMethodNameByField, field.getType());
                    if (field.getType().getSimpleName().equals("String")) {
                        method2.invoke(object, rs.getString(i));
                    } else if (field.getType().getSimpleName().equals("Integer")) {
                        method2.invoke(object, rs.getInt(i));
                    }
                    i++;
                }
            }
            return (T) object;

        } catch (SQLException e) {
            log.error("sql語句異常,請檢查sql{}", sql);
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        finally {
            JDBCUtils.colseResource(connection,preparedStatement,rs);
        }
        return null;
    }
}

我將此專案上傳到了github,需要的童鞋可以自行下載。

https://github.com/836219171/MySSM