手擼Mybatis源碼-基礎版

放假閒來無事,重新看了一遍Mybatis,有感而發,記錄下來。

What is Mybatis?

按照官方文檔的說法:

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

請說人話:Mybatis是一款持久層框架,支持自定義SQL、存儲過程、和高級的映射關係等等……簡單來說,它將我們從原始的jdbc操作底層數據庫的繁瑣操作當中解放出來,使用註解或者xml配置文件的方式形成 實體-表-sql語句的一系列有序對應,通過java運行期間動態的解析sql,操作底層的jdbc工具來實現數據的CRUD。
先看一張原理草圖:
在這裏插入圖片描述

  1. 程序啓動,輸入流讀取配置文件,解析xml節點,使用Confirgation對象存儲數據庫連接信息,並且持有所有方法語句映射集合,類型爲Map對象。Key:ClassName+MethodName. Value:SqlMapper. 每一個SqlMapper代表一個映射方法,保存有動態sql語句。
  2. 創建session工廠,SqlSessionFactory。其默認實現類DefaultSqlSessionFactory,將配置對象存進對象屬性。
  3. 使用工程返回一個會話SqlSession,其默認實現類DefaultSqlSession。SqlSession持有SqlMappers。
  4. 使用jdk動態代理代理我們的持久層接口,SqlSession負責生成代理類,根據反射獲取運行時方法名和類名get最終的sqlMapper,將參數封裝成Map,解析Mapper中的xml的spel語句,替換參數。
  5. 代理類代理接口方法,使用Excuter執行器執行具體的sql,操作jdbc創建連接、執行、控制事務、封裝結果、關閉連接。
  6. 返回我們要的結果。

在初學Mybatis的時候,我記得是從最原始的配置開始操作,以至於到後面的mybatis-spring到現在的mybatis-springboot-starter和mybatis-plus等越來越多的與spring框架集成的優秀mybatis工具,發展不可謂不迅速。不過萬變不離其宗,瞭解了其原理,不僅有利於更快理解新技術,甚至於你自己寫一個出來也不是不可能。So,以下我們通過一個簡單的demo,封裝一個基於原生java的查詢版本的mybatis框架。

衝

搭建簡單maven項目,導入dom4j、mysql驅動等工具,開擼

pom.xml

 <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.8.0-beta2</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.8.0-beta2</version>
        </dependency>
        <!-- 數據庫驅動包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!-- 代碼自動生成-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
        <!--解析xml -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/jaxen/jaxen xpath解析的時候需要引入 jaxen包 否則會報錯 java.lang.NoClassDefFoundError: org/jaxen/JaxenException-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream 支持xml轉bean -->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.11.1</version>
        </dependency>

在此之前,我們需要自己實現一個xml解析的工具類和讀取路徑下的靜態文件的工具類。

package com.xjr.config;

import java.io.InputStream;

/**
 * @program: YourBatis
 * @description: Resource資源類
 * @author: xjr
 * @create: 2020-04-04 00:11
 **/

public class Resources {
    public static InputStream getXmlConfig(String filePath){
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}

package com.xjr.util;
import com.xjr.annotations.Select;
import com.xjr.config.Resources;
import com.xjr.config.SqlMapper;
import com.xjr.config.confiruation;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * @program: YourBatis
 * @description: xml配置文件解析工具
 * @author: xjr
 * @create: 2020-04-04 00:35
 **/
@Slf4j
public final class XmlUtils {
    public static confiruation parseToConfirguration(InputStream inputStream){
        try {
            //定義封裝連接信息的配置對象(mybatis的配置對象)
            confiruation cfg = new confiruation();

            //1.獲取SAXReader對象
            SAXReader reader = new SAXReader();
            //2.根據字節輸入流獲取Document對象
            Document document = reader.read(inputStream);
            //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) {
                    log.info("正在讀取xml語句信息");
                    //表示有resource屬性,用的是XML
                    //取出屬性的值
                    String mapperPath = attribute.getValue();
                    //把映射配置文件的內容獲取出來,封裝成一個map
                    Map<String, SqlMapper> sqlMappers = loadMapperConfiguration(mapperPath);
                    //給configuration中的sqlMappers賦值
                    cfg.getSqlMappers().putAll(sqlMappers);
                } else {
                    log.info("正在讀取註解 sql語句");
                    //表示沒有resource屬性,用的是註解
                    //獲取class屬性的值
                    String maperClassPath = mapperElement.attributeValue("class");
                    //根據daoClassPath獲取封裝的必要信息
                    Map<String, SqlMapper> sqlMappers = loadMapperAnnotation(maperClassPath);
                    //給configuration中的sqlMappers賦值,這裏的set方法是putAll,追加進去,不要覆蓋。
                    cfg.getSqlMappers().putAll(sqlMappers);
                }
            }
            //返回Configuration
            return cfg;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根據傳入的參數,解析XML,並且封裝到Map中
     *
     * @param mapperPath 映射配置文件的位置
     * @return map中包含了獲取的唯一標識(key是由Mapper接口的全限定類名和方法名組成)
     * 以及執行所需的必要信息(value是一個Mapper對象,裏面存放的是執行的SQL語句和要封裝的實體類全限定類名)
     */
    private static Map<String, SqlMapper> loadMapperConfiguration(String mapperPath) throws IOException {
        InputStream in = null;
        try {
            //定義返回值對象
            Map<String, SqlMapper> sqlMappers = new HashMap<>();
            //1.根據路徑獲取字節輸入流
            in = Resources.getXmlConfig(mapperPath);
            //2.根據字節輸入流獲取Document對象
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //3.獲取根節點
            Element root = document.getRootElement();
            //4.獲取根節點的namespace屬性取值
            String namespace = root.attributeValue("namespace");
            //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
                SqlMapper sqlMapper = new SqlMapper();
                sqlMapper.setQueryString(queryString);
                sqlMapper.setResultType(resultType);
                //把key和value存入sqlMappers中
                sqlMappers.put(key, sqlMapper);
            }
            return sqlMappers;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            in.close();
        }
    }

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

根據原理圖,我們先創建一個SqlSessionFactoryBuilder,實現build方法返回一個工廠類。

package com.xjr.config;

import com.xjr.util.XmlUtils;

import java.io.InputStream;

/**
 * @program: YourBatis
 * @description: 讀取輸入流創建SqlSessionFactory
 * @author: xjr
 * @create: 2020-04-04 00:16
 **/

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream stream){
        return new DefaultSqlSessionFactory(XmlUtils.parseToConfirguration(stream));
    }
}

保存xml配置信息的配置類:

package com.xjr.config;

import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @program: YourBatis
 * @description: 保存xml重要配置信息
 * @author: xjr
 * @create: 2020-04-04 00:33
 **/
@Data
@Accessors(chain = true)
public class confiruation {
    //數據庫驅動
    private String driver;

    //連接地址
    private String url;

    //用戶名
    private String username;

    //密碼
    private String password;

    //維護一個Map引用,保存所有sqlMapper的對象(初始化時完成)
    private Map<String,SqlMapper> sqlMappers=new HashMap();

    public confiruation(){

    }

}

SqlSessionFactory

package com.xjr.config;

import com.xjr.execute.SqlSession;

/**
 * @program: YourBatis
 * @description: sqlsession工廠類
 * @author: xjr
 * @create: 2020-04-04 00:17
 **/

public interface SqlSessionFactory {
    //可以看到,我們這邊只提供一個打開方法
    SqlSession openSession();

}

package com.xjr.config;

import com.xjr.execute.DefaultSqlSeession;
import com.xjr.execute.SqlSession;
import com.xjr.util.DataSourceUtil;

/**
 * @program: YourBatis
 * @description: 默認工廠實現類
 * @author: xjr
 * @create: 2020-04-04 00:32
 **/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    //配置類對象引用
    private confiruation confiruation;
    @Override
    public SqlSession openSession() {
        return new DefaultSqlSeession(this.confiruation.getSqlMappers(), DataSourceUtil.getConction(this.confiruation));
    }

    //通過構造重載,注入配置類對象
    public DefaultSqlSessionFactory(confiruation confiruation){
        this.confiruation=confiruation;
    }

}

獲取數據庫連接的工具類:

package com.xjr.util;

import com.xjr.config.confiruation;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * @program: YourBatis
 * @description: 數據源工具類
 * @author: xjr
 * @create: 2020-04-04 00:52
 **/
@Slf4j
public final class DataSourceUtil {

    //使用threadlocal保證併發場景下保證一個線程獲取一個連接
    private static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL=new ThreadLocal<>();



    public static  Connection getConction(confiruation cfg)  {
        if (CONNECTION_THREAD_LOCAL.get()!=null){
            return CONNECTION_THREAD_LOCAL.get();
        }
        try {
            Class.forName("com.mysql.jdbc.Driver");
            CONNECTION_THREAD_LOCAL.set(DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword()));
        } catch (Exception e) {
           log.info("數據庫連接建立失敗,錯誤信息:{}",e.getMessage());
        }
        return CONNECTION_THREAD_LOCAL.get();
    }
}

使用threadLocal的原因是方便在運行當中多個實例共享connection,並且可以避免在高併發場景的讀寫問題。

SqlSession

package com.xjr.execute;

/**
 * @program: YourBatis
 * @description: 數據庫連接會話
 * @author: xjr
 * @create: 2020-04-04 00:30
 **/
public interface SqlSession {
    //獲得代理
    <T> T getMapper(Class<T> clazz);

   //關閉連接
    void close();
}

package com.xjr.execute;

import com.xjr.config.SqlMapper;
import com.xjr.proxy.MapperProxy;

import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: YourBatis
 * @description: sqlsession默認實現類
 * @author: xjr
 * @create: 2020-04-04 00:31
 **/

public class DefaultSqlSeession implements SqlSession {
    //持有保存所有sqlMappers 的map對象引用
    private Map<String, SqlMapper> sqlMappers=new HashMap<>();

    //一個數據庫連接信息
    private Connection connection;

    public DefaultSqlSeession(Map<String, SqlMapper> sqlMappers, Connection connection) {
        this.sqlMappers = sqlMappers;
        this.connection = connection;
    }

    @Override
    public <T> T getMapper(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},new MapperProxy(sqlMappers));
    }

    @Override
    public void close() {
        if (connection != null) {
            try {
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}

執行器:

package com.xjr.execute;

import com.xjr.config.SqlMapper;
import com.xjr.util.DataSourceUtil;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: YourBatis
 * @description: 執行sql並且封裝結果
 * @author: xjr
 * @create: 2020-04-04 01:30
 **/

public class Executor {

    public <E> List<E> selectList(SqlMapper mapper){
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1.取出mapper中的數據
            String queryString = mapper.getQueryString();
            String resultType = mapper.getResultType();
            Class domainClass = Class.forName(resultType);
            //2.獲取PreparedStatement對象
            pstm = DataSourceUtil.getConction(null).prepareStatement(queryString);
            //3.執行SQL語句,獲取結果集
            rs = pstm.executeQuery();
            List<E> list = new ArrayList<E>(rs.getRow());
            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();
            }
        }
    }

}

記得在封裝結果集的時候,arrayList需要指定初始值,避免結果集過大導致arraylist重複擴容,影響效率。

SqlMapper:

package com.xjr.config;

import lombok.Data;

/**
 * @program: YourBatis
 * @description: 接口方法的包裝類
 * @author: xjr
 * @create: 2020-04-04 00:45
 **/
@Data
public class SqlMapper {

    //xml語句中保存的crud 語句
    private String queryString;

    //語句返回類型
    private String resultType;
}

使用jdk動態代理:

package com.xjr.proxy;

import com.xjr.config.SqlMapper;
import com.xjr.execute.Executor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @program: YourBatis
 * @description: dao接口動態代理類
 * @author: xjr
 * @create: 2020-04-04 01:26
 **/
@Slf4j
public class MapperProxy implements InvocationHandler {

    private Map<String, SqlMapper> sqlMappers;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName=method.getName();
        String className=method.getDeclaringClass().getName();
        SqlMapper sqlMapper=sqlMappers.get(className+"."+methodName);
        return new Executor().selectList(sqlMapper);
    }

    public MapperProxy(Map<String,SqlMapper> sqlMappers){
        this.sqlMappers=sqlMappers;
    }
}

基本工具搭建完成,爲了達到跟mybatis接近的效果,再提供一個查詢註解:

package com.xjr.annotations;

import java.lang.annotation.*;

/**
 * @program: YourBatis
 * @description: 查詢註解
 * @author: xjr
 * @create: 2020-04-04 01:13
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Select {
    String value();
}

數據庫創建一張用戶表,表中只有三個字段:userid、name、age。(表中有二十萬條數據,爲了測試性能,也就拼了)

逆向工程得到實體、xml、mapper文件.

package com.xjr.entity;

import lombok.Data;

import java.io.Serializable;

/**
 * @program: YourBatis
 * @description: 用戶表
 * @author: xjr
 * @create: 2020-04-04 01:42
 **/
@Data
public class User implements Serializable {
    private Integer userid;

    private String name;

    private Integer age;
}

package com.xjr.mapper;

import com.xjr.annotations.Select;
import com.xjr.entity.User;

import java.util.List;

/**
 * @program: YourBatis
 * @description: 用戶持久層接口
 * @author: xjr
 * @create: 2020-04-04 01:45
 **/
public interface UserDao {
    @Select("select * from mybatis_user")
    List<User> selectList();
}

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.xjr.mapper.UserDao">
    <select id="selectList" resultType="com.xjr.entity.User">
        select userid from mybatis_user
    </select>
</mapper>

爲了更直觀的體現兩種方式,xml查詢單個字段,註解查詢所有字段。

最後,按照你使用mybatis一樣,寫一個簡單的mybatis-config.xml文件。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/sophi"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <!--註解形式-->
    <mappers>
<!--        <mapper class="com.xjr.mapper.UserDao"/>-->
        <mapper resource="mapping/UserMapper.xml"/>
    </mappers>

</configuration>

讓我們試一下YourBatis是不是可以完成Mybatis的一個查詢功能。

package com.xjr;

import com.xjr.config.Resources;
import com.xjr.config.SqlSessionFactory;
import com.xjr.config.SqlSessionFactoryBuilder;
import com.xjr.entity.User;
import com.xjr.execute.SqlSession;
import com.xjr.mapper.UserDao;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.time.Duration;
import java.time.LocalTime;
import java.util.List;
@Slf4j
public class Main {

    public static void main(String[] args) throws Exception {
        //1.讀取配置文件,將配置文件轉換爲輸入流
         InputStream in=Resources.getXmlConfig("mybatis-config.xml");
        //2.創建SqlSessionFactory工廠
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //返回DefaultSqlSessionFactory對象,利用XMLConfigBuilder類從輸入流中讀取配置信息。
        // 封裝成Configuration對象傳入DefaultSqlSessionFactory的構造方法
        SqlSessionFactory factory = builder.build(in);
        //3.使用工廠(DefaultSqlSessionFactory)生產SqlSession對象,
        @Cleanup SqlSession session = factory.openSession();
        //4.使用SqlSession創建Dao接口的代理對象
        UserDao dao = session.getMapper(UserDao.class);
        LocalTime start=LocalTime.now();
        //5.使用代理對象執行方法
        List<User> users = dao.selectList();
        LocalTime end=LocalTime.now();
        log.info("查詢耗時:{}毫秒", Duration.between(start,end).toMillis());
        log.info("總條數:{}", users.size());
        users.forEach(d->log.info("查詢結果:{}",d));

    }
    }



控制檯輸出:

在這裏插入圖片描述
在這裏插入圖片描述
可以看到返回了二十萬條只有單個字段的數據。

再試一下@select註解是否也可以工作:
在這裏插入圖片描述
在這裏插入圖片描述

it seems that everything goes well.

大功告成。 筆者在下次會帶來手動實現mybatis-spring 與手擼springboot-mybatis-starter的集成教程,覺得可以的話請點個贊哈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章