MyBatis源码(1)-手写持久层


一、JDBC

1、介绍

JDBC 是 SUM 公司提供的一套 Java 连接各种数据库的规范(接口),各大数据库产商通过该规范开发自己的数据库驱动。

JDBC中定义了操作数据库的各种接口和类型:

接口 作用
Driver 驱动接口,定义建立链接的方式
DriverManager 工具类,用于管理驱动,可以获取数据库的链接
Connection 表示Java与数据库建立的连接对象(接口)
PreparedStatement 发送SQL语句的工具
ResultSet 结果集,用于获取查询语句的结果

简单地说,JDBC 可做三件事:与数据库建立连接、发送 操作数据库的语句并处理结果。

2、JDBC连接数据库

(0)步骤

1. 加载驱动
2. 获取链接
3. 准备SQL以及发送SQL的工具
4. 执行SQL
5. 处理结果集
6. 释放资源

依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>

完整代码

String url = "jdbc:mysql://127.0.0.1:3306/rewind_ms_dev?characterEncoding=UTF-8";
String user = "root";
String password = "root";

Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;

try{
    // 1、加载数据库驱动
    Class.forName("com.mysql.jdbc.Driver");

    // 2、过驱动管理类获取数据库连接
    connection = DriverManager.getConnection(url, user, password);

    // 3、定义sql语句,?表示占位符
    String sql = "select * from wx_user where nick_name = ?";

    // 4、获取预处理statement
    preparedStatement = connection.prepareStatement(sql);

    // 5、设置参数,第一个参数为sql中参数的序号(从1开始),第二个参数为设置的值
    preparedStatement.setString(1, "Rewind");

    // 6、向数据库发送sql执行查询,查询出结果集
    resultSet = preparedStatement.executeQuery();

    // 7、遍历出结果集
    while(resultSet.next()){
        int id = resultSet.getInt("id");
        String name = resultSet.getString("nick_name");

        // 封装结果集...
    }

}catch (Exception e){
    e.printStackTrace();
}finally {
    try {
        if (preparedStatement != null){
            preparedStatement.close();
        }
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    } finally {
        try{
            if (connection != null){
                connection.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

(1)加载驱动

加载JDBC驱动是通过调用方法 java.lang.Class.forName(),下面列出常用的几种数据库驱动程序加载语句的形式 :

Class.forName(“oracle.JDBC.driver.OracleDriver”)//使用Oracle的JDBC驱动程序
Class.forName(“com.microsoft.JDBC.sqlserver.SQLServerDriver”)//使用SQL Server的JDBC驱动程序
Class.forName(“com.ibm.db2.JDBC.app.DB2Driver”)//使用DB2的JDBC驱动程序
Class.forName(“com.mysql.JDBC.Driver”);//使用MySql的JDBC驱动程序

(2)创建数据库连接

与数据库建立连接的方法是调用DriverManager.getConnection(String url, String user, String password )方法

String url = "jdbc:mysql://114.116.86.101:3306/rewind_ms_dev?characterEncoding=UTF-8";
String user = "root";
String password = "root";

Connection connection = null;
connection = DriverManager.getConnection(url, user, password);

Connection 常用方法:

  • createStatement():创建向数据库发送sql的statement对象。
  • prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
  • prepareCall(sql):创建执行存储过程的callableStatement对象。
  • setAutoCommit(boolean autoCommit):设置事务是否自动提交。
  • commit() :在链接上提交事务。
  • rollback() :在此链接上回滚事务。

(3)创建Statement

Statement 对象用于将 SQL 语句发送到数据库中,或者理解为执行sql语句

有三种 Statement对象:

  • Statement:用于执行不带参数的简单SQL语句;

  • PreparedStatement(从 Statement 继承):用于执行带或不带参数的预编译SQL语句;

  • CallableStatement(从PreparedStatement 继承):用于执行数据库存储过程的调用。

PreparedStatement preparedStatement = null;

// 定义sql语句,?表示占位符
String sql = "select * from wx_user where nick_name = ?";

// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);

// 设置参数,第一个参数为sql中参数的序号(从1开始),第二个参数为设置的值
preparedStatement.setString(1, "Rewind");

(4)执行SQL

preparedStatement 可以通过以下方法执行 SQL

方法 作用
ResultSet executeQuery() 执行查询语句
int executeUpdate() 执行增、删、改
boolean execute() 可执行任何语句,返回值表示是否返回 ResultSet
//向数据库发送sql执行查询,查询出结果集
ResultSet resultSet = preparedStatement.executeQuery();

(5)处理结果集

ResultSet 对象是 executeQuery() 方法的返回值,它被称为结果集,它代表符合SQL语句条件的所有行,并且它通过一套 getXXX 方法(这些get方法可以访问当前行中的不同列)提供了对这些行中数据的访问。

ResultSet 里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用 ResultSetnext() 方法 ,如果我们想要得到 ResultSet 里的所有记录,就应该使用 while 循环。

ResultSet 对象自动维护指向当前数据行的游标。每调用一次 next() 方法,游标向下移动一行。

初始状态下记录指针指向第一条记录的前面,通过 next() 方法指向第一条记录。循环完毕后指向最后一条记录的后面。

// 将游标下移一行
boolean next();

// 将游标上移一行
boolean previous();

// 关闭 ResultSet 对象
void close();

// 以 String 的形式获取指定列号的值
String getString(int columnIndex);

// 以 String 的形式获取指定列名的值
String getString(String columnLabel);

// 还有 getBoolean()、getByte() ...

(6)关闭资源

应在不需要 Statement 对象和 Connection 对象时显式地关闭它们。

用户不必关闭 ResultSet。当它的 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该 ResultSet 将被自动关闭。

注意:要按先 ResultSet 结果集,后 Statement,最后Connection 的顺序关闭资源,因为StatementResultSet 是需要连接是才可以使用的,所以在使用结束之后有可能其他的Statement 还需要连接,所以不能先关闭 Connection

ResultSet → Statement → Connection

try {
    if (preparedStatement != null){
        preparedStatement.close();
    }
} catch (SQLException throwables) {
    throwables.printStackTrace();
} finally {
    try{
        if (connection != null){
            connection.close();
        }
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
}

二、手写持久层(无代理)

1、思路分析

image-20221029143420607

image-20221029143525092

文件结构

2、框架使用端

(0)引入自定义模块

<dependency>
    <groupId>show.rewind</groupId>
    <artifactId>rewind-batis</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

(1)核心配置文件

sqlMapConfig.xml

<configuration>

    <!-- 1、配置数据库信息 -->
    <dataSource>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/rewind_ms_dev"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>

    <!-- 2、引入映射配置文件 -->
    <mappers>
        <mapper resource="mapper/userMapper.xml"></mapper>
    </mappers>

</configuration>

(2)映射配置文件

UserMapper.xml

<mapper namespace="show.rewind.dao.IUserDao">

    <!-- 一条sql语句的唯一标识: namespace.id 称为 statementId -->

    <select id="selectOne" resultType="show.rewind.pojo.User" parameterType="show.rewind.pojo.User">
        select * from user where id = #{id} and name = #{name}
    </select>

    <select id="selectList" resultType="show.rewind.pojo.User">
        select * from user
    </select>

</mapper>

(3)无反射调用代码

/**
     * 传统方式(不使用 mapper 代理)
     * @throws DocumentException
     */
@Test
public void test1() throws Exception {

    // 1、根据配置文件的路径,加载配置文件为字节输入流,但此时配置文件还未解析
    InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");

    // 2、解析配置文件,封装 Configuration 对象 ;并创建 sqlSessionFactory 对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3、生产 sqlSession 对象 和 执行器对象
    SqlSession sqlSession = sqlSessionFactory.openSqlSession();

    // 4、执行sql
    User user = new User();
    user.setId(1L);
    user.setName("rewind");
    User res = sqlSession.selectOne("show.rewind.dao.IUserDao.selectOne", user);
    System.out.println(res);

    // 5、关闭资源
    sqlSession.close();
}

3、依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>
<!-- dom4j -->
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>
<!-- xpath -->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.8</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.15</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

4、加载配置文件

public class Resource {

    /**
     * 根据配置文件的路径,加载配置文件成字节输入流,存到内存中
     * @param path
     * @return
     */
    public static InputStream getResourceAsStream(String path){
        // 获取类加载器
        ClassLoader classLoader = Resource.class.getClassLoader();
        // 返回用于读取指定资源的输入流。
        InputStream resourceAsStream = classLoader.getResourceAsStream(path);
        return resourceAsStream;
    }
}

5、核心配置类

/**
 * 全局配置类:存放核心配置文件解析出来的内容
 */
@Data
public class Configuration {

    /**
     * 数据源对象
     */
    private DataSource dataSource;

    /**
     * key: statementId
     * value: 封装好的MappedStatement对象
     */
    private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}

一个 MappedStatement 就相当于一条 sql 语句

/**
 * 映射配置类:存放 mapper.xml 解析内容
 */
@Data
public class MappedStatement {

    /**
     * 唯一标识: namespace.id
     */
    private String statementId;

    /**
     * 返回值类型
     */
    private String resultType;

    /**
     * 参数值类型
     */
    private String parameterType;

    /**
     * sql语句
     */
    private String sql;


    /**
     * 判断当前是什么操作的一个属性
     */
    private String sqlCommandType;

}

6、解析配置文件

(1)SqlSessionFactoryBuilder

SqlSessionFactoryBuilder通过建造者模式生产 SqlSessionFactory,在生产的时候会解析核心配置文件

public class SqlSessionFactoryBuilder {

    /**
     * 1、解析配置文件,封装容器对象
     * 2、创建 SqlSessionFactory 对象
     * @param inputStream
     * @return
     */
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {

        // 1、解析配置文件,封装容器对象;XMLConfigBuilder:专门解析核心配置文件的解析类
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parse(inputStream);

        // 2、创建 sqlSessionFactory 工厂对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }
}

(2)XMLConfigBuilder

XMLConfigBuilder 用于解析核心配置文件

public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder(){
        this.configuration = new Configuration();
    }


    /**
     * 使用 dom4j + xpath 解析配置文件,封装 Configuration 对象
     * @param inputStream
     * @return
     */
    public Configuration parse(InputStream inputStream) throws DocumentException {

        /* --------- 解析核心配置文件sqlMapConfig.xml --------- */
        // 获取配置文件解析后的 Document 对象
        Document document = new SAXReader().read(inputStream);
        // 获取根节点
        Element rootElement = document.getRootElement();
        // 查找根节点内的 property 标签
        List<Element> propertyList = rootElement.selectNodes("//property");
        // 封装 properties
        Properties properties = new Properties();
        for (Element propertyElment : propertyList) {
            String name = propertyElment.attributeValue("name");
            String value = propertyElment.attributeValue("value");
            properties.setProperty(name, value);
        }

        // 创建数据源对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getProperty("driverClassName"));
        druidDataSource.setUrl(properties.getProperty("url"));
        druidDataSource.setUsername(properties.getProperty("username"));
        druidDataSource.setPassword(properties.getProperty("password"));

        configuration.setDataSource(druidDataSource);

        /* --------- 解析映射配置文件 UserMapper.xml --------- */
        // 获取映射配置文件路径
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element mapper : mapperList) {
            String mapperPath = mapper.attributeValue("resource");
            // XMLMapperBuilder 专门解析映射配置文件的对象
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            // 根据路径解析映射配置文件
            InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
            // 封装到 MappedStatement ->configuration
            xmlMapperBuilder.parse(resourceAsStream);
        }

        return configuration;
    }
}

(3)XMLMapperBuilder

/**
 * 用于解析映射配置文件
 */
public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration){
        this.configuration = configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {
        // 获取配置文件解析后的 Document 对象
        Document document = new SAXReader().read(inputStream);

        // 获取根节点
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");

        // 获取所有 select 标签
        List<Element> selectList = rootElement.selectNodes("//select");

        for (Element element : selectList) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sql = element.getTextTrim();

            // 封装 mappedStatement 对象
            MappedStatement mappedStatement = new MappedStatement();
            String statementId = namespace + "." + id;
            mappedStatement.setStatementId(statementId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sql);
            mappedStatement.setSqlCommandType("select");

            configuration.getMappedStatementMap().put(statementId, mappedStatement);
        }
    }
}

(4)sqlSessionFactory

接口

public interface SqlSessionFactory {

    /**
     * 1、生产sqlSession 对象;2、创建执行器对象
     * @return
     */
    SqlSession openSqlSession();

}

默认实现类

public class DefaultSqlSessionFactory implements SqlSessionFactory {


    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 1、生产sqlSession 对象;2、创建执行器对象
     * @return
     */
    @Override
    public SqlSession openSqlSession() {
        // 1、创建执行器对象
        Executor excutor = new SimpleExecutor();
        // 2、生产 sqlSession 对象
        SqlSession sqlSession = new DefaultSqlSession(configuration, excutor);

        return sqlSession;
    }
}

7、创建 SqlSession

接口

/**
 * 用于执行 sql
 */
public interface SqlSession {


    /**
     * 查询多个结果
     * @param statementId namespace.id
     * @param param 查询条件参数
     */
    <E> List<E> selectList(String statementId, Object param) throws Exception;

    <T> T selectOne(String statementId, Object param) throws Exception;

    /**
     * 清除资源
     */
    void close();
}

默认实现类

public class DefaultSqlSession implements SqlSession{

    private Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor excutor) {
        this.configuration = configuration;
        this.executor = excutor;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object param) throws Exception {
        // 将查询操作委派给底层的执行器, 执行底层的jdbc操作
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<E> list = executor.query(configuration, mappedStatement, param);
        return list;
    }

    @Override
    public <T> T selectOne(String statementId, Object param) throws Exception {
        List<Object> list = this.selectList(statementId, param);
        if (list.size() == 1){
            return (T)list.get(0);
        } else if (list.size() > 1){
            throw new RuntimeException("selectOne 查询出多条结果");
        } else {
            return null;
        }
    }

    @Override
    public void close() {
        executor.close();
    }
}

8、创建Executor

/**
 * 调用底层 JDBC 代码
 */
public interface Executor {
    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) throws Exception;

    void close();

}

默认实现类

public class SimpleExecutor implements Executor{

    private  Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) throws Exception {

        // 1、加载驱动,获取数据库连接
        connection = configuration.getDataSource().getConnection();

        // 2、获取 prepareStatement 预编译对象
        // 获取要执行的 sql
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        String finalSql = boundSql.getFinalSql();
        preparedStatement = connection.prepareStatement(finalSql);

        // 3.设置参数
        String parameterType = mappedStatement.getParameterType();

        if(parameterType !=null ){
            Class<?> parameterTypeClass = Class.forName(parameterType);

            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {

                ParameterMapping parameterMapping = parameterMappingList.get(i);
                // id || username
                String paramName = parameterMapping.getContent();
                // 反射
                Field declaredField = parameterTypeClass.getDeclaredField(paramName);
                // 暴力访问
                declaredField.setAccessible(true);

                Object value = declaredField.get(param);
                // 赋值占位符
                preparedStatement.setObject(i+1,value);
            }

        }

        // 4.执行sql,发起查询
        resultSet = preparedStatement.executeQuery();

        // 5.处理返回结果集
        ArrayList<E> list = new ArrayList<>();
        while (resultSet.next()){
            // 元数据信息 包含了 字段名  字段的值
            ResultSetMetaData metaData = resultSet.getMetaData();

            // com.itheima.pojo.User
            String resultType = mappedStatement.getResultType();
            Class<?> resultTypeClass = Class.forName(resultType);
            Object o = resultTypeClass.newInstance();

            for (int i = 1; i <= metaData.getColumnCount() ; i++) {

                // 字段名 id  username
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);

                // 问题:现在要封装到哪一个实体中
                // 封装
                // 属性描述器:通过API方法获取某个属性的读写方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 参数1:实例对象 参数2:要设置的值
                writeMethod.invoke(o,value);
            }
            list.add((E) o);

        }

        return list;
    }

    @Override
    public void close() {
        // 释放资源
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将#{} 占位符替换为 ?,并将#{}中的值保存下来
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql){
        // 1.创建标记处理器:配合标记解析器完成标记的处理解析工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        // 2.创建标记解析器
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);

        // #{}占位符替换成? 2.解析替换的过程中 将#{}里面的值保存下来 ParameterMapping
        String finalSql = genericTokenParser.parse(sql);

        // #{}里面的值的一个集合 id username
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(finalSql, parameterMappings);

        return boundSql;
    }
}

三、优化-代理生成mapper

通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连 接,硬编码,手动封装返回结果集等问题,但依然存在以下问题

  • dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方 法,关闭 sqlsession) – 交由Spring解决

  • dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码

解决:使用代理模式来创建接口的代理对象

1、生成代理类

SqlSession 新增 getMapper 方法,用于生成代理对象

/**
 * 用于执行 sql
 */
public interface SqlSession {

    <E> List<E> selectList(String statementId, Object param) throws Exception;

    <T> T selectOne(String statementId, Object param) throws Exception;

    void close();

    /**
     * 生成代理对象
     */
    <T> T getMapper(Class<?> mapperClass);
}

getMapper 具体实现

public class DefaultSqlSession implements SqlSession{

    private Configuration configuration;

    private Executor executor;

    ...

    /**
     * 通过 jdk 动态代理生成基于接口的代理对象
     */
    @Override
    public <T> T getMapper(Class<?> mapperClass) {


        Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 具体逻辑,执行 JDBC
                // 通过 sqlSession 的方法完成方法的调用,需要参数 statementId、param
                // 接口方法调用的参数即为 sqlSession 方法的param参数
                // statementId 无法获取,所以必须规范为 接口全限定名.方法名
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;

                // 判断是 增删改查 中的哪个方法
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                String sqlCommandType = mappedStatement.getSqlCommandType();
                switch (sqlCommandType){
                    case "select":
                        // 该调用 selectList 还是 selectOne
                        Type genericReturnType = method.getGenericReturnType();
                        Object param = null;
                        if (args != null && args.length > 0){
                            param = args[0];
                        }
                        // 判断返回值类型是否实现 泛型类型参数化 即是否带有泛型
                        if (genericReturnType instanceof ParameterizedType){
                            return selectList(statementId, param);
                        }
                        return selectOne(statementId, param);
                    case "update":
                        break;
                    case "delete":
                        break;
                    case "insert":
                        break;
                    default:
                        break;
                }

                return null;
            }
        });

        return (T) proxy;
    }
}

2、调用

dao 接口

public interface IUserDao {

    /**
     * 查询所有
     */
    List<User> selectList() throws Exception;

    /**
     * 根据多条件查询
     */
    User selectOne(User user) throws Exception;

}

测试方法

@Test
public void test2() throws Exception {
    // 1、根据配置文件的路径,加载配置文件为字节输入流,但此时配置文件还未解析
    InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");

    // 2、解析配置文件,封装 Configuration 对象 ;并创建 sqlSessionFactory 对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3、生产 sqlSession 对象 和 执行器对象
    SqlSession sqlSession = sqlSessionFactory.openSqlSession();

    // 获取 dao 代理对象
    IUserDao iUserDao = sqlSession.getMapper(IUserDao.class);

    // 4、执行sql
    List<User> users = iUserDao.selectList();
    System.out.println(users);

    User user = new User();
    user.setId(1L);
    user.setName("rewind");
    User user1 = iUserDao.selectOne(user);
    System.out.println(user1);

    // 5、关闭资源
    sqlSession.close();
}

  目录