如何自定義MyBatis框架

第一個 MyBatis 程序(XML配置)

在上一篇中,簡單總結了一下原生 JDBC 的一些侷限性,同時引出了 MyBatis 這個框架,算較爲詳細的整理如何搭建 MyBatis 的工做環境java

這一篇,咱們在開篇,如今搭建好工做環境的基礎上,開始咱們的第一個例程,可是,簡單的讓程序跑起來之後,咱們卻要講解如何自定義 MyBatis 框架,它的意義是什麼呢?mysql

雖然第一個例程雖然比較簡單,可是其中有不少點倒是容易引發疑惑的,例如爲何用工廠模式後還有構建者對象,經過自定義框架,可讓本身對於 MyBatis 的理解更加深入,從而更好的應用這個框架spring

首先,咱們想讓咱們的第一個程序運行起來sql

一、搭建好環境,在主配置文件 (SqlMapConfig.xml) 中指定映射配置文件的位置數據庫

<!-- 指定映射配置文件的位置 -->
<mappers>
    <mapper resource="cn/ideal/mapper/UserMapper.xml"/>
</mappers>

二、在test文件夾下,建立一個如圖結構測試類apache

因爲咱們的 mapper 接口中寫的方法是一個 查詢全部信息的方法,因此咱們直接以下圖所寫就好了,這就是第一個例程,後面咱們會詳細的講到其中的點,先讓本身的程序跑起來看看設計模式

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //讀取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //建立SqlSessionFactory工廠
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = factoryBuilder.build(in);
        //使用工廠生產SqlSession對象
        SqlSession session = factory.openSession();
        //使用SqlSession建立Mapper接口的代理對象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //使用代理對象執行方法
        List<User> users = userMapper.findAllUserInfo();
        for (User user : users) {
            System.out.println(user);
        }
        //釋放資源
        session.close();
        in.close();
    }
}

第一個 MyBatis 程序(註解配置)

註解配置,天然咱們剛纔的 UserMapper.xml 就能夠刪除了掉了,同時咱們須要在mapper接口方法中添加註解如圖所示數組

public interface UserMapper {
    /**
     * 查詢全部用戶信息
     *
     * @return
     */
    @Select("select * from user")
    List<User> findAllUserInfo();
}

而後在主配置文件中,使用class屬性指定被註解的mapper全限定類名微信

<mappers>
    <mapper class="cn.ideal.mapper.UserMapper"/>
</mappers>

兩種方式執行結果都是一致的,以下圖session

自定義 MyBatis 框架 (首先使用XML)

首先咱們建立一個 Maven 工程,修改其 pom.xml 文件 增長一些必要依賴的座標,因爲咱們使用dom4j的方式解析 xml 文件因此,須要引入 dom4j 和 jaxen

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.ideal</groupId>
    <artifactId>code_02_user_defined</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>
    
</project>

因爲今天咱們是要使用自定義 MyBatis 框架 進行一個簡單的查詢,即查詢表中的全部用戶信息,因此咱們須要根據數據庫內容,對應的建立出其 User 類實體

CREATE DATABASE ideal_mybatis; -- 建立數據庫

CREATE TABLE USER (
  id        INT(11)NOT NULL AUTO_INCREMENT,
  username     VARCHAR(32) NOT NULL COMMENT '用戶名',
  telephone     VARCHAR(11) NOT NULL COMMENT '手機',
  birthday    DATETIME DEFAULT NULL COMMENT '生日',
  gender     CHAR(1) DEFAULT NULL COMMENT '性別',
  address    VARCHAR(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

建立出UserMapper接口,並書寫相關的方法

package cn.ideal.mapper;

public interface UserMapper {
    /**
     * 查詢全部用戶信息
     */
    List<User> findAllUserInfo();
}

設置好其主配置文件(SqlMapConfig.xml)須要說明的是:因爲咱們是要本身模擬設計一個 MyBatis 框架,因此不要習慣性的加上對應的 DTD 規範約束,如下是具體代碼

<?xml version="1.0" encoding="UTF-8"?>
<!-- mybatis 主配置文件 -->
<configuration>
    <!-- 配置環境,和spring整合後 environments配置將會被廢除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC事務管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 數據庫鏈接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ideal_mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root99"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置 -->
    <mappers>
        <mapper resource="cn/ideal/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

修改其SQL映射配置文件

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="cn.ideal.mapper.UserMapper">
    <select id="findAllUserInfo" resultType="cn.ideal.domain.User">
        select * from user
    </select>
</mapper>

測試類,咱們的今天用的測試類,與直接使用MyBatis 框架使用的測試類是一致的,只不過咱們須要本身實現其中的一些類和方法

package cn.ideal.test;

import cn.ideal.domain.User;
import cn.ideal.mapper.UserMapper;
import cn.ideal.mybatis.io.Resources;
import cn.ideal.mybatis.sqlsession.SqlSession;
import cn.ideal.mybatis.sqlsession.SqlSessionFactory;
import cn.ideal.mybatis.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //讀取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //建立SqlSessionFactory工廠
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = factoryBuilder.build(in);
        //使用工廠生產SqlSession對象
        SqlSession session = factory.openSession();
        //使用SqlSession建立Mapper接口的代理對象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //使用代理對象執行方法
        List<User> users = userMapper.findAllUserInfo();
        for (User user : users) {
            System.out.println(user);
        }
        //釋放資源
        session.close();
        in.close();
    }
}

咱們參考一下真正的 MyBatis 先規整一下,咱們須要,Resources 以及 SqlSessionFactoryBuilder 類須要 SqlSessionFactory 、SqlSession 這樣兩個接口,而後咱們還須要建立出其對應的實現類,其他的咱們根據須要再進行建立,而後根據測試類中的須要,爲建立出來的類加上對應須要的方法

(一) Resources類

首先先建立一個 Resources類,而且在中增長一個,getResourceAsStream 方法,參數類型天然是一個字符串類型,根據測試類中的接收類型,判斷出返回值類型爲 InputStream,也所以能夠寫出具體的方法體了,以下圖

package cn.ideal.mybatis.io;

import java.io.InputStream;

public class Resources {
    /**
     * 根據傳入的參數,獲取一個字節輸入流
     * @param filePath
     * @return
     */
    public static InputStream getResourceAsStream(String filePath){
        // 獲取當前類的字節碼,獲取字節碼的類加載器,根據類加載器讀取配置
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}

獲取到主配置文件的字節輸入流後,咱們就須要將其中的 XML 解析出來,這裏就用到了 Dom4j 的XML解析方式,在Pom.xml中咱們已經引入了它的座標,咱們須要建立其工具類,固然咱們能夠找一份現成的工具類,也能夠本身手動的去寫一份,咱們的重心仍是在 MyBatis 的執行流程上

(二) XMLConfigBuilder(解析XML工具類,理解便可)

因爲咱們首先使用的 XML 配置的方式,因此咱們暫時先無論 XML 中的註解部分,因此註釋掉的部分,暫時先不用理會

package cn.ideal.mybatis.utils;

import cn.ideal.mybatis.cfg.Configuration;
import cn.ideal.mybatis.cfg.Mapper;
import cn.ideal.mybatis.io.Resources;
//import com.itheima.mybatis.annotations.Select;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  用於解析配置文件
 */
public class XMLConfigBuilder {
    /**
     * 解析主配置文件,把裏面的內容填充到DefaultSqlSession所須要的地方
     * 使用的技術:
     *      dom4j+xpath,因此須要導入dom4j和jaxen的座標
     */
    public static Configuration loadConfiguration(InputStream config){
        try{
            //定義封裝鏈接信息的配置對象(mybatis的配置對象)
            Configuration cfg = new Configuration();

            //1.獲取SAXReader對象
            SAXReader reader = new SAXReader();
            //2.根據字節輸入流獲取Document對象
            Document document = reader.read(config);
            //3.獲取根節點
            Element root = document.getRootElement();
            //4.使用xpath中選擇指定節點的方式,獲取全部property節點
            List<Element> propertyElements = root.selectNodes("//property");
            //5.遍歷節點
            for(Element propertyElement : propertyElements){
                //判斷節點是鏈接數據庫的哪部分信息
                //取出name屬性的值
                String name = propertyElement.attributeValue("name");
                if("driver".equals(name)){
                    //表示驅動
                    //獲取property標籤value屬性的值
                    String driver = propertyElement.attributeValue("value");
                    cfg.setDriver(driver);
                }
                if("url".equals(name)){
                    //表示鏈接字符串
                    //獲取property標籤value屬性的值
                    String url = propertyElement.attributeValue("value");
                    cfg.setUrl(url);
                }
                if("username".equals(name)){
                    //表示用戶名
                    //獲取property標籤value屬性的值
                    String username = propertyElement.attributeValue("value");
                    cfg.setUsername(username);
                }
                if("password".equals(name)){
                    //表示密碼
                    //獲取property標籤value屬性的值
                    String password = propertyElement.attributeValue("value");
                    cfg.setPassword(password);
                }
            }
            //取出mappers中的全部mapper標籤,判斷他們使用了resource仍是class屬性
            List<Element> mapperElements = root.selectNodes("//mappers/mapper");
            //遍歷集合
            for(Element mapperElement : mapperElements){
                //判斷mapperElement使用的是哪一個屬性
                Attribute attribute = mapperElement.attribute("resource");
                if(attribute != null){
                    System.out.println("XML方式");
                    //表示有resource屬性,用的是XML
                    //取出屬性的值
                    String mapperPath = attribute.getValue();//獲取屬性的值"com/itheima/dao/IUserDao.xml"
                    //把映射配置文件的內容獲取出來,封裝成一個map
                    Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
                    //給configuration中的mappers賦值
                    cfg.setMappers(mappers);
                }else{
//                    System.out.println("註解方式");
//                    //表示沒有resource屬性,用的是註解
//                    //獲取class屬性的值
//                    String daoClassPath = mapperElement.attributeValue("class");
//                    //根據daoClassPath獲取封裝的必要信息
//                    Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
//                    //給configuration中的mappers賦值
//                    cfg.setMappers(mappers);
                }
            }
            //返回Configuration
            return cfg;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            try {
                config.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

    }

    /**
     * 根據傳入的參數,解析XML,而且封裝到Map中
     * @param mapperPath    映射配置文件的位置
     * @return  map中包含了獲取的惟一標識(key是由dao的全限定類名和方法名組成)
     *          以及執行所需的必要信息(value是一個Mapper對象,裏面存放的是執行的SQL語句和要封裝的實體類全限定類名)
     */
    private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {
        InputStream in = null;
        try{
            //定義返回值對象
            Map<String,Mapper> mappers = new HashMap<String,Mapper>();
            //1.根據路徑獲取字節輸入流
            in = Resources.getResourceAsStream(mapperPath);
            //2.根據字節輸入流獲取Document對象
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //3.獲取根節點
            Element root = document.getRootElement();
            //4.獲取根節點的namespace屬性取值
            String namespace = root.attributeValue("namespace");//是組成map中key的部分
            //5.獲取全部的select節點
            List<Element> selectElements = root.selectNodes("//select");
            //6.遍歷select節點集合
            for(Element selectElement : selectElements){
                //取出id屬性的值      組成map中key的部分
                String id = selectElement.attributeValue("id");
                //取出resultType屬性的值  組成map中value的部分
                String resultType = selectElement.attributeValue("resultType");
                //取出文本內容            組成map中value的部分
                String queryString = selectElement.getText();
                //建立Key
                String key = namespace+"."+id;
                //建立Value
                Mapper mapper = new Mapper();
                mapper.setQueryString(queryString);
                mapper.setResultType(resultType);
                //把key和value存入mappers中
                mappers.put(key,mapper);
            }
            return mappers;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            in.close();
        }
    }

    /**
     * 根據傳入的參數,獲得dao中全部被select註解標註的方法。
     * 根據方法名稱和類名,以及方法上註解value屬性的值,組成Mapper的必要信息
     * @param daoClassPath
     * @return
     */
//    private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
//        //定義返回值對象
//        Map<String,Mapper> mappers = new HashMap<String, Mapper>();
//
//        //1.獲得dao接口的字節碼對象
//        Class daoClass = Class.forName(daoClassPath);
//        //2.獲得dao接口中的方法數組
//        Method[] methods = daoClass.getMethods();
//        //3.遍歷Method數組
//        for(Method method : methods){
//            //取出每個方法,判斷是否有select註解
//            boolean isAnnotated = method.isAnnotationPresent(Select.class);
//            if(isAnnotated){
//                //建立Mapper對象
//                Mapper mapper = new Mapper();
//                //取出註解的value屬性值
//                Select selectAnno = method.getAnnotation(Select.class);
//                String queryString = selectAnno.value();
//                mapper.setQueryString(queryString);
//                //獲取當前方法的返回值,還要求必須帶有泛型信息
//                Type type = method.getGenericReturnType();//List<User>
//                //判斷type是否是參數化的類型
//                if(type instanceof ParameterizedType){
//                    //強轉
//                    ParameterizedType ptype = (ParameterizedType)type;
//                    //獲得參數化類型中的實際類型參數
//                    Type[] types = ptype.getActualTypeArguments();
//                    //取出第一個
//                    Class domainClass = (Class)types[0];
//                    //獲取domainClass的類名
//                    String resultType = domainClass.getName();
//                    //給Mapper賦值
//                    mapper.setResultType(resultType);
//                }
//                //組裝key的信息
//                //獲取方法的名稱
//                String methodName = method.getName();
//                String className = method.getDeclaringClass().getName();
//                String key = className+"."+methodName;
//                //給map賦值
//                mappers.put(key,mapper);
//            }
//        }
//        return mappers;
//    }
}

咱們能夠大體看一下代碼,首先獲取全部property節點,將驅動,用戶名,密碼等的值獲取出來,而後

取出mappers中的全部mapper標籤,判斷他們使用了resource仍是class屬性,這就判斷出使用了XML仍是註解的方式,例如本例中的XML方式,就獲取到了主配置文件中 <mapper resource="cn/ideal/mapper/UserMapper.xml"/> 這一句中cn/ideal/mapper/UserMapper.xml 而後對這個 SQL映射的配置文件進行解析,一樣將其中一些必要的信息提取出來

可是咱們若是想要使用這個工具類,能夠看到仍是有一些報錯的地方,這就是由於咱們缺乏一些必要的類,咱們須要本身,補充一下

首先咱們須要建立一個 Configuration 實體類,用來傳遞咱們獲取到的 一些連接信息

public class Configuration {
    
    private String driver;
    private String url;
    private String username;
    private String password;
    private Map<String, Mapper> mappers = new HashMap<String, Mapper>();
    
     補充其對應get set方法   
    //特別說明:setMappers 須要使用putALL的追加寫入方式,不能直接賦值,否則舊的就會被新的覆蓋掉
    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers.putAll(mappers); // 追加的方式
    }
}

其次咱們能夠看到,對 SQL 映射文件進行解析的時候,咱們將其封裝成一個 Map 集合,其中key是由 mapper 的全限定類名和方法名組成的, 即經過分別獲取二者的值,而後進行字符串拼接成key,value值是一個 Mapper對象,它的key爲 id ,而value值爲 SQL 語句 以及 resultType

public class Mapper {

    private String queryString; //SQL
    private String resultType; //實體類的全限定類名
    
    補充其對應get set方法 
}

(三) SqlSessionFactoryBuilder 類

咱們繼續回到測試類中,咱們須要建立一個 SqlSessionFactoryBuilder 類 ,根據測試類看到,咱們將 Recourse 類中獲取到的流文件,做爲參數傳遞到了本類中的 build 方法中,這也就是說,咱們在build方法中,須要對 XML 進行解析,而後使用剛纔建立的 Configuration 類進行接收,可是根據測試類中,咱們能夠知道,測試中使用的是一個 SqlSessionFactory 類型來接收build的返回,而經過 真正MyBatis可知,SqlSessionFactory是一個接口,這裏使用了建造者設計模式,因此咱們真正要返回的就是其對應的實現類,而且將 Configuration 的對象傳進去,代碼以下所示,

public class SqlSessionFactoryBuilder {
    public  SqlSessionFactory build(InputStream config){
        Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
        return new DefaultSqlSessionFactory(cfg);
    }
}

(四) SqlSessionFactory接口

public interface SqlSessionFactory {
    /**
     * 用來打開一個新的SqlSession對象
     * @return
     */
    SqlSession openSession();
}

(五) DefaultSqlSessionFactor實現類

因爲咱們將配置傳入了方法中,因此必要的,咱們須要先建立一個 Configuration 成員,而後建立一個帶參構造,一樣根據測試類建立其 openSession 方法,用於建立操做數據的對象,這裏是一樣的思路,SqlSession是一個接口,因此咱們真正須要返回的仍是其接口

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration cfg;
    public DefaultSqlSessionFactory(Configuration cfg) {
        this.cfg = cfg;
    }

    /**
     * 用於建立一個新的操做數據庫對象
     * @return
     */
    public SqlSession openSession() {
        return new DefaultSqlSession(cfg);
    }
}

(六) SqlSession 接口

在這裏咱們須要使用SqlSession建立Mapper接口的代理對象

public interface SqlSession {
    /**
     * 根據參數建立一個代理對象
     * @param mapperInterfaceClass mapper的接口字節碼
     * @param <T>
     * @return
     */
    <T> T getMapper(Class<T> mapperInterfaceClass);

    /**
     * 釋放資源
     */
    void close();
}

(七) DefaultSqlSession實現類

首先依舊是建立成員以及帶參構造方法,咱們須要建立一個工具類,用來建立數據源,起名爲DataSourceUtil,同時這個類中比較重要的是建立咱們的 getMapper方法,咱們使用Proxy.newProxyInstance 這個方法進行,其中的參數,

  • 第一個參數即咱們須要代理的類加載器,也就是代理誰,就用誰的類加載器
  • 第二個參數就是動態代理類須要實現的接口
  • 第三個參數 動態代理方法在執行時,會調用裏面的方法去執行 (自創)

    • 其中的參數,就是將咱們配置中的信息傳入
public class DefaultSqlSession implements SqlSession {

    private Configuration cfg;
    private Connection connection;

    public DefaultSqlSession(Configuration cfg) {
        this.cfg = cfg;

        connection = DataSourceUtil.getConnection(cfg);
    }

    /**
     * 用於建立代理對象
     *
     * @param mapperInterfaceClass mapper的接口字節碼
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> mapperInterfaceClass) {
        return (T) Proxy.newProxyInstance(mapperInterfaceClass.getClassLoader(),
                new Class[]{mapperInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
    }

    /**
     * 用於釋放資源
     */
    public void close() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

(八) DataSourceUtil 工具類

public class DataSourceUtil {
    public static Connection getConnection(Configuration cfg){
        try {
            Class.forName(cfg.getDriver());
            return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(九) MapperProxy

咱們開始實現剛纔自定義的一個 MapperProxy 類,常規的建立成員以及構造函數,其中傳入Connection 的緣由是,爲了最後執行 Executor() 的須要, 這個類還有一個重要的就是使用 invoke 對方法進行加強,將其獲取並組合,

public class MapperProxy implements InvocationHandler {

    //map的key是全限定類名 + 方法名
    private Map<String, Mapper> mappers;
    private Connection connection;

    public MapperProxy(Map<String, Mapper> mappers,Connection connection) {
        this.mappers = mappers;
        this.connection = connection;
    }

    /**
     * 用於對方法進行加強的,這裏的加強就是調用 selectList方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //獲取方法名
        String methodName = method.getName();
        //獲取方法所在類的名稱
        String className = method.getDeclaringClass().getName();
        //組合key
        String key = className + "." + methodName;
        //獲取mappers中的Mapper對象
        Mapper mapper = mappers.get(key);
        //判斷是否有mapper
        if (mapper == null){
            throw  new IllegalArgumentException("傳入的參數有誤");
        }
        //調用工具類執行查詢全部1
        return new Executor().selectList(mapper,connection);
    }
}

(十) Executor 工具類

接着就是使用一個現成的工具類進行執行咱們的 SQL,由於 SQL語句,以及resultType已經被封裝在了mapper中被傳遞了進來

/**
 * 負責執行SQL語句,而且封裝結果集
 */
public class Executor {

    public <E> List<E> selectList(Mapper mapper, Connection conn) {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1.取出mapper中的數據
            String queryString = mapper.getQueryString();//select * from user
            String resultType = mapper.getResultType();//com.itheima.domain.User
            Class domainClass = Class.forName(resultType);
            //2.獲取PreparedStatement對象
            pstm = conn.prepareStatement(queryString);
            //3.執行SQL語句,獲取結果集
            rs = pstm.executeQuery();
            //4.封裝結果集
            List<E> list = new ArrayList<E>();//定義返回值
            while(rs.next()) {
                //實例化要封裝的實體類對象
                E obj = (E)domainClass.newInstance();

                //取出結果集的元信息:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //取出總列數
                int columnCount = rsmd.getColumnCount();
                //遍歷總列數
                for (int i = 1; i <= columnCount; i++) {
                    //獲取每列的名稱,列名的序號是從1開始的
                    String columnName = rsmd.getColumnName(i);
                    //根據獲得列名,獲取每列的值
                    Object columnValue = rs.getObject(columnName);
                    //給obj賦值:使用Java內省機制(藉助PropertyDescriptor實現屬性的封裝)
                    PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:實體類的屬性和數據庫表的列名保持一種
                    //獲取它的寫入方法
                    Method writeMethod = pd.getWriteMethod();
                    //把獲取的列的值,給對象賦值
                    writeMethod.invoke(obj,columnValue);
                }
                //把賦好值的對象加入到集合中
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm,rs);
        }
    }


    private void release(PreparedStatement pstm,ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

        if(pstm != null){
            try {
                pstm.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

到這裏咱們就能夠來測試一下了,結果是沒有什麼問題的

如何使用註解方式 自定MyBatis

首先咱們須要在主配置文件中修改成註解的形式,即

<mappers>
    <mapper class="cn.ideal.mapper.UserMapper"/>
</mappers>

而後建立一個自定義的Select註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    /**
     * 配置SQL語句的
     * @return
     */
    String value();
}

而後到咱們的 UserMapper 接口方法上添加註解,將SQL語句寫在其中

public interface UserMapper {
    /**
     * 查詢全部用戶信息
     *
     * @return
     */
    @Select("select * from user")
    List<User> findAllUserInfo();
}

最後咱們將 XMLConfigBuilder 工具類中 註釋掉註解的部分取消註釋就能夠了

運行結果以下:

最後,給你們程序結構圖,方便你們建立包,結構爲 cn.ideal.xxxx

總結

我從新捋一下,整個流程

  • MyBatis 相關的配置文件,例中的 SqlMapConfig.xml 被 Rosource 類中的方法讀取,獲得一個流文件
  • 在 SqlSessionFactoryBuilder 中經過 XMLConfigBuilder 對配置文件進行解析,同時使用 Configuration 類,存取提取出來具體的一些配置信息
  • 經過 SqlSessionFactory 建立新的操做數據庫的對象
  • 獲取到 SqlSession
  • 使用代理模式 MapperProxy 執行 SQL,本質是調用 Executor執行器
  • 測試運行

自制一個簡單的流程圖

說明:此文章代碼來自某馬,此文章是我根據其思路,從新整理,總結了一下,固然不是照搬,我也切實的敲了一次,簡單的作了說明與分析,天然沒什麼高深技術可言,或許能對一些朋友有一些幫助,不管如何,仍是感謝支持,若是真有須要原代碼的朋友,我後期更個連接,方便你們參考

結尾

若是文章中有什麼不足,或者錯誤的地方,歡迎你們留言分享想法,感謝朋友們的支持!

若是能幫到你的話,那就來關注我吧!若是您更喜歡微信文章的閱讀方式,能夠關注個人公衆號

在這裏的咱們素不相識,卻都在爲了本身的夢而努力 ❤

一個堅持推送原創開發技術文章的公衆號:理想二旬不止

相關文章
相關標籤/搜索