手寫簡(jiǎn)易版MyBatis框架

本文主要內(nèi)容摘抄自架構(gòu)師教程之手寫MyBatis框架【完】

雖然感覺他們有點(diǎn)不地道掂墓,程序關(guān)鍵代碼一閃而過(guò)不提供源碼湃累,視頻中間頻繁插入廣告,但也算讓我學(xué)到了點(diǎn)東西永部,在此表示感謝字管。

建議別看這篇文章啰挪,代碼可以看看,文章太亂了嘲叔,別看亡呵,拿著我的代碼去看視頻吧。代碼可以點(diǎn)擊這里下載硫戈。

手寫一個(gè)簡(jiǎn)易版的MyBatis框架:

1.讀取mybatis-config.xml配置文件

2.構(gòu)建SqlSessionFactory

3.打開SqlSession

4.獲取Mapper接口對(duì)象

5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)

準(zhǔn)備

MyBatis

MyBatis 是一款優(yōu)秀的持久層框架锰什,它支持自定義 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射丁逝。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作汁胆。MyBatis可以通過(guò)簡(jiǎn)單的 XML 或注解來(lái)配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects霜幼,普通老式 Java 對(duì)象)為數(shù)據(jù)庫(kù)中的記錄嫩码。

我們的目的是手寫一個(gè)簡(jiǎn)易版MyBatis,我們不需要支持MyBatis的緩存罪既、動(dòng)態(tài)SQL铸题、高級(jí)映射,只要能把Mapper文件與Mapper接口相關(guān)聯(lián)并執(zhí)行Mapper中定義的SQL即可琢感。

項(xiàng)目開始之前我們先抽取下MyBatis執(zhí)行的大致過(guò)程丢间。

MyBatis大致執(zhí)行流程

一個(gè)普通的MyBatis項(xiàng)目大致由Mapper文件、Mapper接口驹针、MyBatis配置文件組成千劈,如下為MyBatis常用形式:

    public  void testMybatis () throws IOException {
        // 獲取配置文件輸入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 通過(guò)SqlSessionFactoryBuilder的build()方法創(chuàng)建SqlSessionFactory實(shí)例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 調(diào)用openSession()方法創(chuàng)建SqlSession實(shí)例
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 獲取UserMapper代理對(duì)象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 執(zhí)行Mapper方法,獲取執(zhí)行結(jié)果
        List<UserEntity> userList = userMapper.listAllUser();
        System.out.println(JSON.toJSONString(userList));
    }

獲取配置文件

這個(gè)沒(méi)啥好說(shuō)的牌捷,讀取配置文件

構(gòu)建SqlSessionFactory

看這個(gè)類的名字就知道這個(gè)接口可以用于構(gòu)建SqlSession墙牌,這是一個(gè)接口,它有一個(gè)默認(rèn)實(shí)現(xiàn)DefaultSqlSessionFactory暗甥,其維護(hù)了Configuration對(duì)象:

  //  將XML配置文件構(gòu)建為Configuration
  private final Configuration configuration;

Configuration類十分重要喜滨,代表著MyBatis的配置項(xiàng):

它的Environment中的DataSource屬性則保存著數(shù)據(jù)源,比如用戶名撤防、密碼虽风、URL等

image-20200921091119526

示例代碼通過(guò)SqlSessionFactoryBuilder的build方法讀取配置文件同時(shí)將Configuration構(gòu)建完成。

不斷debug可以看到寄月,SqlSessionFactoryBudiler的build方法會(huì)執(zhí)行XMLConfigBuilder的parse方法辜膝,這個(gè)方法返回Configuration。

parse方法解析配置文件configuration下的所有信息漾肮。

進(jìn)入parse方法可以后又調(diào)用parseConfiguration方法厂抖,這里有一個(gè)重要的mappersElement方法,mappersElement方法解析配置文件里的mappers信息克懊,通常這包含Mapper映射文件忱辅。

mappers解析完成后通過(guò)Configuration的addMapper方法加入到Configuraiton維護(hù)的MapperRegistry mapperRegistry 。

調(diào)用棧:

image-20200921093216974

獲取SqlSession

SqlSessionFactory構(gòu)建完成后可以構(gòu)建SqlSession了谭溉,SqlSession是MyBatis暴露給外部使用的統(tǒng)一接口層墙懂。它有一個(gè)默認(rèn)實(shí)現(xiàn)DefaultSqlSession:

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
}

它持有Configuration和Executor的引用,Executor是真正操作數(shù)據(jù)庫(kù)的接口扮念。

SqlSessionFactory的openSession方法最后返回的正是DefaultSqlSession损搬。

獲取Mapper接口

顯然,Mapper接口不可能一個(gè)個(gè)實(shí)現(xiàn)柜与,所以MyBatis采用JDK自帶的動(dòng)態(tài)代理生成Mapper接口的代理對(duì)象并返回巧勤。

public class MapperProxy<T> implements InvocationHandler, Serializable

MapperProxy里的invoke方法就是具體的攔截邏輯。

MyBatis通過(guò)MappedStatement類描述Mapper的信息旅挤,包括命名空間踢关、ID(每條SQL命令都有唯一ID,在SqlSession中執(zhí)行哪條SQL命令由ID指定)粘茄、屬性類型签舞、返回值類型、SQL語(yǔ)句等柒瓣。

MappedMethod則封裝了Mapper接口的方法儒搭,invoke方法內(nèi)通過(guò)調(diào)用MapperMethod的execute執(zhí)行SQL。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 其中command為MapperMethod構(gòu)造是創(chuàng)建的SqlCommand對(duì)象
    // 獲取SQL語(yǔ)句類型
    switch (command.getType()) {
        case INSERT: {
            // 獲取參數(shù)信息
            Object param = method.convertArgsToSqlCommandParam(args);
            // 調(diào)用SqlSession的insert()方法芙贫,然后調(diào)用rowCountResult()方法統(tǒng)計(jì)行數(shù)
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
            ...
    }

更加詳細(xì)的過(guò)程可以看看這篇文章搂鲫。

項(xiàng)目準(zhǔn)備

以下為我們可能會(huì)用到的類:

  • MyConfiguration 封裝配置文件的配置信息
  • MyEnvironment 封裝數(shù)據(jù)源信息,其實(shí)可以直接將數(shù)據(jù)源信息放MyConfiguration中
  • MyMapperProxy Mapper接口的代理對(duì)象
  • MyXMLParser XML文件解析
  • MySqlSession 封裝與數(shù)據(jù)庫(kù)打交道方法的類
  • MySqlSessionFactory 構(gòu)建MySqlSession
  • MyExecutor 執(zhí)行具體的查詢磺平、處理結(jié)果集

大致步驟如下:

//        1.讀取mybatis-config.xml配置文件
//        2.構(gòu)建SqlSessionFactory
//        3.打開SqlSession
//        4.獲取Mapper接口對(duì)象
//        5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)

下面就正式開始吧魂仍。

新建Maven項(xiàng)目拐辽,導(dǎo)入JDBC依賴:

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
    </dependencies>

數(shù)據(jù)庫(kù)準(zhǔn)備:

create database handwritten_mybatis;
create table handwritten_mybatis.h_user_info
(
    id       int auto_increment
        primary key,
    username varchar(20) null,
    phone    varchar(20) null
);
insert into h_user_info values (default, "Q", "18888888888");

UserInfo類:

public class UserInfo {
    private Integer id;
    private String username;
    private String phone;
    //Getter、Setter
}

UserInfoMapper映射接口:

public interface UserInfoMapper {
    UserInfo selectByPrimaryKey(Integer id);
}

主測(cè)試類:

public class Application {
    public static void main(String[] args) {
//        1.讀取mybatis-config.xml配置文件
//        2.構(gòu)建SqlSessionFactory
//        3.打開SqlSession
//        4.獲取Mapper接口對(duì)象
//        5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
//        業(yè)務(wù)處理
    }
}

MyBatis配置文件mybatis-config.xml擦酌,存放在resource目錄下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="autoCommit" value="true"/>
            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <property name="url" value="jdbc:mysql://localhost:3306/handwritten_mybatis?userUnicode=true&amp;serverTimezone=GMT%2B8"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserInfoMapper.xml" />
    </mappers>
</configuration>

開始

讀取配置文件

InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");

構(gòu)建SqlSessionFactory

先抽取我們的MyConfiguration存儲(chǔ)配置文件配置信息:

public class MyConfiguration {
//    mybatis-config.xml
    private MyEnvironment myEnvironment;
//    xxMapper.xml
    private Map<String ,MyMapperStatement> myMapperStatementMap;
    //Getter俱诸、Setter
}

myMapperStatementMap用來(lái)存儲(chǔ)Mapper文件方法映射,關(guān)于MyMapperStatement:

public class MyMapperStatement {
    private String namespace;
    private String id;

    private String parameterType;
    private String resultType;

    private String sql;
    //Getter赊舶、Setter
}

用于封裝Mapper中的方法睁搭,namespace為類的全限定名,id則是方法名笼平,其他三個(gè)分別為參數(shù)類型园骆、返回值類型以及SQL語(yǔ)句。

MyEnvironment則是用來(lái)存儲(chǔ)數(shù)據(jù)源信息的:

public class MyEnvironment {
    private String driver;
    private String url;
    private String username;
    private String password;
    //Getter寓调、Setter
}

對(duì)XML文件進(jìn)行讀取處理不是我們的目的锌唾,所以我這里直接給出工具類的代碼(我當(dāng)時(shí)弄了蠻久豪治,視頻中代碼一閃而過(guò)而且不全):

public class MyXMLConfigBuilder {

    private MyXPathParser parser;

    private boolean parsed = false;

    public MyXMLConfigBuilder(InputStream in) {
        this.parser = new MyXPathParser(in);
    }

    public MyConfiguration parse() {
        if (parsed) {
            throw new RuntimeException("Each XMLConfigBuilder can only be used once.");
        }

        //是否解析過(guò)xml配置文件的開關(guān)
        parsed = true;

        //parser 是一個(gè) XPath的解析器侍郭,evalNode 評(píng)估、評(píng)價(jià)锌介、計(jì)算 節(jié)點(diǎn)的值秋麸,得到一個(gè)封裝后的XNode對(duì)象
        Node dataSourceNode = parser.evalNode("/configuration/environments/environment/dataSource");

        NodeList childNodes = dataSourceNode.getChildNodes();

        MyConfiguration configuration = new MyConfiguration();
        MyEnvironment environment = new MyEnvironment();
        for (int i = 0; i<childNodes.getLength();i++) {
            Node item = childNodes.item(i);
            if (item.getNodeType() == Node.ELEMENT_NODE){
                String name = item.getAttributes().getNamedItem("name").getNodeValue();
                String value = item.getAttributes().getNamedItem("value").getNodeValue();
                if (name.equals("driver")){
                    environment.setDriver(value);
                } else if (name.equals("url")){
                    environment.setUrl(value);
                } else if (name.equals("username")){
                    environment.setUsername(value);
                } else if (name.equals("password")){
                    environment.setPassword(value);
                }
            }
        }
        configuration.setMyEnvironment(environment);

        //parser 是一個(gè) XPath的解析器渐排,evalNode 評(píng)估、評(píng)價(jià)灸蟆、計(jì)算 節(jié)點(diǎn)的值驯耻,得到一個(gè)封裝后的XNode對(duì)象
        Node MapperNode = parser.evalNode("/configuration/mappers");

        NodeList mapperNodes = MapperNode.getChildNodes();

        Map<String, MyMapperStatement> mapperXmlMap = new HashMap<>();

        for (int i = 0; i<mapperNodes.getLength();i++) {
            Node item = mapperNodes.item(i);
            if (item.getNodeType() == Node.ELEMENT_NODE){
                String resource = item.getAttributes().getNamedItem("resource").getNodeValue();
                InputStream mapperStream = this.getClass().getClassLoader().getResourceAsStream(resource);

                this.parser = new MyXPathParser(mapperStream);

                Node mapperNode = parser.evalNode("/mapper");
                String namespace = mapperNode.getAttributes().getNamedItem("namespace").getNodeValue();

                NodeList mapperChildNodes = mapperNode.getChildNodes();

                for (int j = 0; j<mapperChildNodes.getLength();j++) {
                    Node itemj = mapperChildNodes.item(j);
                    if (itemj.getNodeType() == Node.ELEMENT_NODE){
                        String sqlId = itemj.getAttributes().getNamedItem("id").getNodeValue();
                        String parameterType = itemj.getAttributes().getNamedItem("parameterType").getNodeValue();
                        String resultType = null;
                        if (itemj.getAttributes().getNamedItem("resultType") != null){
                            resultType = itemj.getAttributes().getNamedItem("resultType").getNodeValue();
                        }

                        String sql = itemj.getTextContent();
                        // 操作類型(增刪改查)
//                        String sqlType = itemj.getNodeName();

                        MyMapperStatement mapperXml = new MyMapperStatement();
                        mapperXml.setId(sqlId);
                        mapperXml.setNamespace(namespace);
                        mapperXml.setParameterType(parameterType);
                        mapperXml.setResultType(resultType);
                        mapperXml.setSql(sql);
//                        mapperXml.setSqlType(sqlType);
                        mapperXmlMap.put(namespace + "." + sqlId,mapperXml);
                    }
                }
            }
        }

        configuration.setMyMapperStatementMap(mapperXmlMap);
        //返回解析后的數(shù)據(jù)封裝對(duì)象configuration
        return configuration;
    }
}
public class MyXPathParser {

    private Document document;
    private boolean validation;
    private XPath xpath;


    public MyXPathParser(InputStream in) {

        //初始化當(dāng)前類的四個(gè)成員變量:validation; entityResolver; variables; xpath;
        commonConstructor(true);

        //初始化當(dāng)前類的document對(duì)象,創(chuàng)建Document對(duì)象
        this.document = createDocument(new InputSource(in));
    }

    /**
     * 通用的構(gòu)造器炒考,初始化當(dāng)前類的幾個(gè)成員變量
     *
     * @param validation
     */
    private void commonConstructor(boolean validation) {
        this.validation = validation;
        //XPath方式的xml解析對(duì)象
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
    }

    /**
     * 評(píng)估可缚、評(píng)價(jià)、計(jì)算 節(jié)點(diǎn)的值
     *
     * @param expression
     * @return
     */
    public Node evalNode(String expression) {
        //jdk的xml文件一個(gè)節(jié)點(diǎn)
        Node node = (Node) evaluate(expression, document, XPathConstants.NODE);
        if (node == null) {
            return null;
        }
        //XNode是mybatis封裝的斋枢,代表xml節(jié)點(diǎn)的一個(gè)對(duì)象
        return node;
    }

    /**
     * 在一個(gè)指定的上下文文檔中 評(píng)估帘靡、評(píng)價(jià)、計(jì)算 一個(gè)XPath表達(dá)式的值瓤帚,并返回指定的類型
     *
     * @param expression
     * @param root
     * @param returnType
     * @return
     */
    private Object evaluate(String expression, Object root, QName returnType) {
        try {
            return xpath.evaluate(expression, root, returnType);
        } catch (Exception e) {
            throw new RuntimeException("Error evaluating XPath.  Cause: " + e, e);
        }
    }

    /**
     * 創(chuàng)建Document對(duì)象
     *
     * @param inputSource
     * @return
     */
    private Document createDocument(InputSource inputSource) {
        // important: this must only be called AFTER common constructor
        try {
            //JDK提供的文檔解析工廠對(duì)象
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            //設(shè)置是否驗(yàn)證
            factory.setValidating(validation);
            //設(shè)置是否支持命名空間
            factory.setNamespaceAware(false);
            //設(shè)置是否忽略注釋
            factory.setIgnoringComments(true);
            //設(shè)置是否忽略元素內(nèi)容的空白
            factory.setIgnoringElementContentWhitespace(false);
            //是否將CDATA節(jié)點(diǎn)轉(zhuǎn)換為文本節(jié)點(diǎn)
            factory.setCoalescing(false);
            //設(shè)置是否展開實(shí)體引用節(jié)點(diǎn)描姚,這里是sql片段引用
            factory.setExpandEntityReferences(true);

            //創(chuàng)建一個(gè)DocumentBuilder對(duì)象
            DocumentBuilder builder = factory.newDocumentBuilder();
            //設(shè)置解析mybatis xml文檔節(jié)點(diǎn)的解析器,也就是上面的XMLMapperEntityResolver
//            builder.setEntityResolver(entityResolver);

            //設(shè)置解析文檔錯(cuò)誤的處理
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void warning(SAXParseException exception) throws SAXException {
                }
            });

            //解析輸入源的xml數(shù)據(jù)為一個(gè)Document文件
            return builder.parse(inputSource);

        } catch (Exception e) {
            throw new RuntimeException("Error creating document instance.  Cause: " + e, e);
        }
    }
}

好的,我們需要達(dá)到的目的是獲取MySqlSessionFactory:

//        1.讀取mybatis-config.xml配置文件
        InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
//        2.構(gòu)建SqlSessionFactory
        MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);

很明顯MySqlSessionFacotryBuilder類用于構(gòu)建MySqlSessionFactory戈次,我們可以這樣:

public class MySqlSessionFactoryBuilder {
    public MySqlSessionFactory build(InputStream inputStream) {
//        MyBatis配置信息
        MyConfiguration myConfiguration = new MyXMLConfigBuilder(inputStream).parse();
        return new MySqlSessionFactory(myConfiguration);
    }
}

也就是通過(guò)MyXMLConfigBuilder類解析輸入流并構(gòu)建MyConfiguration轩勘,之后再將MyConfiguration注入MySqlSessionFactory后返回。

構(gòu)建SqlSession

//        1.讀取mybatis-config.xml配置文件
        InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
//        2.構(gòu)建SqlSessionFactory
        MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
//        3.打開SqlSession
        MySqlSession mySqlSession = mySqlSessionFactory.openSession();

關(guān)于上文中的MySqlSessionFacotry:

public class MySqlSessionFactory {

    private MyConfiguration myConfiguration;

    public MySqlSessionFactory(MyConfiguration myConfiguration) {
        this.myConfiguration = myConfiguration;
    }

    public MySqlSession openSession() {
        MyExecutor myExecutor = new MyExecutor(myConfiguration);
        return new MySqlSession(myConfiguration, myExecutor);
    }
}

目的就是構(gòu)建MySqlSession怯邪。

而MySqlSession就是暴露給外部應(yīng)用使用的接口層:

public class MySqlSession {
    private MyConfiguration myConfiguration;
    private MyExecutor myExecutor;

    public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
        this.myConfiguration = myConfiguration;
        this.myExecutor = myExecutor;
    }
}

MyExecutor則是真正與數(shù)據(jù)庫(kù)打交道的類:

public class MyExecutor {

    private MyDataSource dataSource;

    public MyExecutor(MyConfiguration myConfiguration) {
        dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
    }
}

所以它需要有查詢方法:

    public <T> List<T> query(MyMapperStatement mapperStatement, Object param) {
        List<T> resultList = new ArrayList<>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = dataSource.getConnection();
            String sql = mapperStatement.getSql();
            String parse = SQLTokenParser.parse(sql);
            preparedStatement = connection.prepareStatement(parse);
            if (param instanceof Integer) {
                preparedStatement.setInt(1, (Integer) param);
            } else if (param instanceof Long) {
                preparedStatement.setLong(1, (Long) param);
            } else if (param instanceof Double) {
                preparedStatement.setDouble(1, (Double) param);
            } else if (param instanceof String) {
                preparedStatement.setString(1, (String) param);
            }
            resultSet = preparedStatement.executeQuery();
//            處理查詢之后的結(jié)果
            handleResultSet(resultSet, resultList, mapperStatement.getResultType());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }

            if (null != connection) {
                //                    將連接歸還連接池
                dataSource.release(connection);
            }
        }
        return resultList;
    }


    private <T> void handleResultSet(ResultSet resultSet, List<T> resultList, String resultType) {
        try {
            Class<?> aClass = Class.forName(resultType);
            while (resultSet.next()) {
                T result = (T) aClass.getConstructor(null).newInstance();
//                把從數(shù)據(jù)庫(kù)查詢出來(lái)的結(jié)果集字段的數(shù)據(jù)要設(shè)置到result
                ReflectUtil.setProToBeanFromResult(result, resultSet);
                resultList.add(result);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }finally {
            if (null != resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }

關(guān)于ReflectUtil:

public class ReflectUtil {
    public static void setProToBeanFromResult(Object entity, ResultSet resultSet) throws SQLException {
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        int count = resultSetMetaData.getColumnCount();
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (int i = 0; i < count; i++) {
            String columnName = resultSetMetaData.getColumnName(i + 1).replace("_", "").toUpperCase();
            for (int i1 = 0; i1 < declaredFields.length; i1++) {
                String fieldName = declaredFields[i1].getName().toUpperCase();
                if (columnName.equalsIgnoreCase(fieldName)) {
                    if (declaredFields[i1].getType().getSimpleName().equals("Integer")) {
                        setProToBean(entity,declaredFields[i1].getName(),resultSet.getInt(resultSetMetaData.getColumnName(i + 1)));
                    } else if (declaredFields[i1].getType().getSimpleName().equals("Long")) {
                        setProToBean(entity,declaredFields[i1].getName(),resultSet.getLong(resultSetMetaData.getColumnName(i + 1)));
                    }else if (declaredFields[i1].getType().getSimpleName().equals("String")) {
                        setProToBean(entity,declaredFields[i1].getName(),resultSet.getString(resultSetMetaData.getColumnName(i + 1)));
                    }else if (declaredFields[i1].getType().getSimpleName().equals("Date")) {
                        setProToBean(entity,declaredFields[i1].getName(),resultSet.getDate(resultSetMetaData.getColumnName(i + 1)));
                    }else if (declaredFields[i1].getType().getSimpleName().equals("Boolean")) {
                        setProToBean(entity,declaredFields[i1].getName(),resultSet.getBoolean(resultSetMetaData.getColumnName(i + 1)));
                    }else if (declaredFields[i1].getType().getSimpleName().equals("BigDecimal")) {
                        setProToBean(entity,declaredFields[i1].getName(),resultSet.getBigDecimal(resultSetMetaData.getColumnName(i + 1)));
                    }
                    break;
                }
            }
        }
    }

    private static void setProToBean(Object bean, String name, Object value) {
        try {
            Field field = bean.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(bean, value);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

獲取Mapper接口

//        1.讀取mybatis-config.xml配置文件
        InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
//        2.構(gòu)建SqlSessionFactory
        MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
//        3.打開SqlSession
        MySqlSession mySqlSession = mySqlSessionFactory.openSession();
//        4.獲取Mapper接口對(duì)象
        UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);

所以我們還需要為我們的MySqlSession類添加一個(gè)getMapper方法绊寻,以獲取Mapper接口的代理:

    public <T> T getMapper(Class<T> tClass) {
        MyMapperProxy myMapperProxy = new MyMapperProxy(this);
        return (T)Proxy.newProxyInstance(tClass.getClassLoader(),
//                class com.sun.proxy.$Proxy0 cannot be cast to class top.hellooooo.mapper.UserInfoMapper
//                tClass.getInterfaces(),
                new Class[]{tClass},
                myMapperProxy);
    }

MyMapperProxy也就是代理類:

public class MyMapperProxy implements InvocationHandler {

    private MySqlSession mySqlSession;

    public MyMapperProxy(MySqlSession mySqlSession) {
        this.mySqlSession = mySqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        Class<?> returnType = method.getReturnType();
//        如果是集合的子類
        if (Collection.class.isAssignableFrom(returnType)) {
            return mySqlSession.selectList(args);
        } else if (Map.class.isAssignableFrom(returnType)) {
            return mySqlSession.selectMap(args);
        } else {
            String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
//            返回對(duì)象數(shù)據(jù)
            return mySqlSession.selectOne(statementKey, args);
        }
    }
}

invoke方法里調(diào)用了mySqlSession的selectOne方法:

    public <T> T selectOne(String statementKey, Object[] args) {
//        key = namespace . selectId
        MyMapperStatement mapperStatement = myConfiguration.getMyMapperStatementMap().get(statementKey);
        List<T> query = myExecutor.query(mapperStatement, args != null ? args[0] : null);
        if (query != null && query.size() > 1) {
            throw new RuntimeException("Too many result..");
        } else {
            return query.get(0);
        }
    }

可以看到,最后也還是調(diào)用了MyExecutor的query方法,MyExecutor類中有兩個(gè)方法澄步,一個(gè)為query為查詢方法冰蘑,另一個(gè)則是處理返回結(jié)果的handleResultSet:

    public <T> List<T> query(MyMapperStatement mapperStatement, Object param) {
        List<T> resultList = new ArrayList<>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = dataSource.getConnection();
            String sql = mapperStatement.getSql();
            String parse = SQLTokenParser.parse(sql);
            preparedStatement = connection.prepareStatement(parse);
            if (param instanceof Integer) {
                preparedStatement.setInt(1, (Integer) param);
            } else if (param instanceof Long) {
                preparedStatement.setLong(1, (Long) param);
            } else if (param instanceof Double) {
                preparedStatement.setDouble(1, (Double) param);
            } else if (param instanceof String) {
                preparedStatement.setString(1, (String) param);
            }
            resultSet = preparedStatement.executeQuery();
//            處理查詢之后的結(jié)果
            handleResultSet(resultSet, resultList, mapperStatement.getResultType());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }

            if (null != connection) {
                //                    將連接歸還連接池
                dataSource.release(connection);
            }
        }
        return resultList;
    }


    private <T> void handleResultSet(ResultSet resultSet, List<T> resultList, String resultType) {
        try {
            Class<?> aClass = Class.forName(resultType);
            while (resultSet.next()) {
                T result = (T) aClass.getConstructor(null).newInstance();
//                把從數(shù)據(jù)庫(kù)查詢出來(lái)的結(jié)果集字段的數(shù)據(jù)要設(shè)置到result
                ReflectUtil.setProToBeanFromResult(result, resultSet);
                resultList.add(result);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }finally {
            if (null != resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }

調(diào)用接口的方法

//        1.讀取mybatis-config.xml配置文件
        InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
//        2.構(gòu)建SqlSessionFactory
        MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
//        3.打開SqlSession
        MySqlSession mySqlSession = mySqlSessionFactory.openSession();
//        4.獲取Mapper接口對(duì)象
        UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
//        5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
        UserInfo userInfo = userInfoMapper.selectByPrimaryKey(1);

可以看到調(diào)用了selectByPrimaryKey,然而userInfoMapper是一個(gè)接口驮俗,很明顯沒(méi)法用懂缕,所以JVM會(huì)自動(dòng)調(diào)用代理對(duì)象的invoke方法:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        Class<?> returnType = method.getReturnType();
//        如果是集合的子類
        if (Collection.class.isAssignableFrom(returnType)) {
            return mySqlSession.selectList(args);
        } else if (Map.class.isAssignableFrom(returnType)) {
            return mySqlSession.selectMap(args);
        } else {
            String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
//            返回對(duì)象數(shù)據(jù)
            return mySqlSession.selectOne(statementKey, args);
        }
    }

測(cè)試:

    public static void main(String[] args) {
//        1.讀取mybatis-config.xml配置文件
        InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
//        2.構(gòu)建SqlSessionFactory
        MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
//        3.打開SqlSession
        MySqlSession mySqlSession = mySqlSessionFactory.openSession();
//        4.獲取Mapper接口對(duì)象
        UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
//        5.調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù)
        UserInfo userInfo = userInfoMapper.selectByPrimaryKey(1);
//        業(yè)務(wù)處理
        System.out.println(userInfo);
    }

結(jié)果:

UserInfo{id=1, username='Q', phone='18888888888'}

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市王凑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聋丝,老刑警劉巖索烹,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異弱睦,居然都是意外死亡百姓,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門况木,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)垒拢,“玉大人,你說(shuō)我怎么就攤上這事火惊∏罄啵” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵屹耐,是天一觀的道長(zhǎng)尸疆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)惶岭,這世上最難降的妖魔是什么寿弱? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮按灶,結(jié)果婚禮上症革,老公的妹妹穿的比我還像新娘。我一直安慰自己鸯旁,他們只是感情好噪矛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著羡亩,像睡著了一般摩疑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上畏铆,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天雷袋,我揣著相機(jī)與錄音,去河邊找鬼。 笑死楷怒,一個(gè)胖子當(dāng)著我的面吹牛蛋勺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸠删,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抱完,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了刃泡?” 一聲冷哼從身側(cè)響起巧娱,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烘贴,沒(méi)想到半個(gè)月后禁添,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桨踪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年老翘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锻离。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铺峭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汽纠,到底是詐尸還是另有隱情卫键,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布疏虫,位于F島的核電站永罚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏卧秘。R本人自食惡果不足惜呢袱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翅敌。 院中可真熱鬧羞福,春花似錦、人聲如沸蚯涮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)遭顶。三九已至张峰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棒旗,已是汗流浹背喘批。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饶深。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓餐曹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親敌厘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子台猴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355