手写Mybatis源码(原来真的很简单!!!)

news2024/11/15 10:20:15

目录

  • 一、JDBC操作数据库_问题分析
  • 二、自定义持久层框架_思路分析
  • 三、自定义框架_编码
    • 1、加载配置文件
    • 2、创建两个配置类对象
    • 3、解析配置文件,填充配置类对象
    • 4、创建SqlSessionFactory工厂接口及DefaultSqlSessionFactory实现类
    • 5、创建SqlSession会话接口及DefaultSqlSession实现类
    • 6、创建Executor执行器接口及SimpleExecutor实现类
    • 7、项目使用端利用自定义框架测试
  • 四、自定义框架_优化
    • 1、优化思路
    • 2、优化代码
    • 3、优化后测试
  • 五、Spring整合优化
  • 六、gitee代码


一、JDBC操作数据库_问题分析

JDBC使用流程

  1. 加载数据库驱动
  2. 创建数据库连接
  3. 创建编译对象
  4. 设置入参执行SQL
  5. 返回结果集

代码示例

public class JDBCTest {
    public static void main(String[] args) throws Exception {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 通过驱动管理类获取数据库链接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis",
            "root","123456789");
            // 定义sql语句?表示占位符
            String sql = "select * from user where name = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "zhangsan");
            // 向数据库发出sql执行查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("name");
                // 封装User
                User user = new User();
                user.setId(id);
                user.setName(username);
                System.out.println(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            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();
                }
            }
        }
    }
}

问题分析

存在问题解决思路
数据库配置信息存在硬编码问题使用配置文件
频繁创建、释放数据库连接问题使用数据库连接池
SQL语句使用配置文件
数据库配置信息存在硬编码问题使用配置文件

二、自定义持久层框架_思路分析

主要分两部分,项目使用端:平常写代码所说的后台服务;持久层框架:即项目使用端引入的jar包

在这里插入图片描述

核心接口/类重点说明:

类名定义角色定位分工协作
Resources资源辅助类负责读取配置文件转化为输入流
Configuration数据库资源类负责存储数据库连接信息
MappedStatementSQL与结果集资源类负责存储SQL映射定义、存储结果集映射定义
SqlSessionFactoryBuilder会话工厂构建者负责解析配置文件,创建会话工厂SqlSessionFactory
SqlSessionFactory会话工厂负责创建会话SqlSession
SqlSession会话指派执行器Executor
Executor执行器负责执行SQL (配合指定资源Mapped Statement)

项目使用端:

  1. 引入自定义持久层框架的jar包
  2. sqlMapConfig.xml:数据库配置信息,以及mapper.xml的全路径
  3. mapper.xml:SQL配置信息,存放SQL语句、参数类型、返回值类型相关信息

注意: sqlMapConfig.xml中引入mapper.xml是为了只读取一次配置文件,否则每个实体类会有一个mapper.xml,则需要读取很多次

自定义框架本身:

  1. 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中

在这里插入图片描述

  1. 创建两个javaBean(容器对象):存放配置文件解析出来的内容

在这里插入图片描述

  1. 解析配置文件(使用dom4j),并创建SqlSession会话对象

在这里插入图片描述

  1. 创建SqlSessionFactory接口以及实现类DefaultSqlSessionFactory

在这里插入图片描述

  1. 创建SqlSession接口以及实现类DefaultSqlSession

在这里插入图片描述

SqlSession接口定义以上方法,DefaultSqlSession来决定什么操作调用对应的sql执行器

  1. 创建Executor执行器接口以及实现类SimpleExecutor简单执行器

在这里插入图片描述

三、自定义框架_编码

项目使用端

创建sqlMapConfig核心配置文件:

<configuration>

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

    <!--引入映射配置文件,为了只加载一次xml-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>

</configuration>

创建映射配置文件:

获取某个sql语句的唯一标示statementId:namespace.id 如:user.selectList

<mapper namespace="user">

    <!--查询所有-->
    <select id="selectList" resultType="com.xc.pojo.User" >
        select * from user
    </select>

    <!--按条件查询-->
    <select id="selectOne" resultType="com.xc.pojo.User" parameterType="com.xc.pojo.User" >
        select * from user where id = #{id} and name = #{name}
    </select>

</mapper>

User实体:

@Data
public class User {
    private Integer id;
    private String name;
}

pom.xml引入自定义框架

 <dependency>
     <groupId>com.xc</groupId>
     <artifactId>own-mybatis</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>

自定义框架本身

1、加载配置文件

  • 根据配置文件的路径,加载配置文件成字节输入流,存到内存中
public class Resources {
    public static InputStream getResourceAsStream(String path){
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}

2、创建两个配置类对象

  • 映射配置类:mapper.xml解析出来内容
  • 每个pojo实体都会对应一个mapper.xml文件即一个MapperStatement对象
  • sqlCommandType:第四节 自定义框架_优化才会用到
@Data
public class MapperStatement {

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

    //返回值类型
    private String resultType;

    //参数类型
    private String parameterType;

    //sql语句
    private String sql;
    
    // 判断当前是什么操作的一个属性-增删改查
    private String sqlCommandType;
}
  • 核心配置类:数据库配置信息以及映射配置类的map集合
  • 将多个MapperStatement对象存入Map集合,statementId(namespace.id)作为key
  • 将所有的配置文件都聚合到Configuration中,方便一次读取以及统一管理
@Data
public class Configuration {

    //数据源对象
    private DataSource dataSource;

    //map.xml对象集合 key:statementId
    private Map<String,MapperStatement> mapperStatementMap = new HashMap<>();
}

3、解析配置文件,填充配置类对象

  • XMLConfigBuilder类的parse方法:解析核心配置类,返回Configuration对象
  • 创建SqlSession工厂对象,以便之后创建SqlSession会话
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) throws Exception {
        //1.解析配置文件,封装容器对象:专门解析核心配置文件的解析类
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parse(inputStream);
        //2.创建SqlSessionFactory工厂对象
        return new DefaultSqlSessionFactory(configuration);
    }

}
  • XMLConfigBuilder核心配置解析类里面嵌套着XMLMapperBuilder映射配置文件解析类
  • 输入流转化为Document对象,一是根据property标签获取数据库配置信息并创建数据源添加到configuration
  • 二是根据mapper标签通过XMLMapperBuilder解析类遍历解析配置文件同样添加到configuration的map集合类
public class XMLConfigBuilder {

    private Configuration configuration;

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

	// 使用dom4j+xpath解析
    public Configuration parse(InputStream inputStream) throws Exception {
        //将xml转化为Document对象
        Document document = new SAXReader().read(inputStream);
        //获取跟节点,对于sqlMapConfig.xml来说就是<Configuration>标签
        Element rootElement = document.getRootElement();

        // -------------解析数据库配置文件----------------

        // "//"表示从匹配选择的当前节点,而不考虑它们的位置
        // 即这里获取数据源url用户密码信息
        // 例:<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        List<Element> propertyList = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : propertyList) {
            // 获取<property>标签中,name和value属性的值
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
        // 创建数据源对象
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(properties.getProperty("driverClassName"));
        dataSource.setUrl(properties.getProperty("url"));
        dataSource.setUsername(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        // 将创建好的数据源添加到Configuration对象中
        configuration.setDataSource(dataSource);

        // -------------解析映射配置文件----------------

        /*
        1.获取映射配置文件路径
        2.根据路径进行映射文件的加载解析
        3.封装到MapperStatement,存入configuration的map集合中
        */
        // 例:<mapper resource="mapper/UserMapper.xml"></mapper>
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String resource = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(resource);
            // XMLMapperBuilder 专门解析映射配置文件的对象-最后会存入configuration的map集合对象中
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }
}
  • 与XMLConfigBuilder解析类原理一样
  • 传入configuration,并将解析好的MapperStatement对象添加到mapperStatementMap
public class XMLMapperBuilder {

    private Configuration configuration;

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

    public void parse(InputStream resourceAsStream) throws Exception {
        // 将输入流转化为Document对象,并获取跟节点<mapper>
        Document document = new SAXReader().read(resourceAsStream);
        Element rootElement = document.getRootElement();

        // 例:<mapper namespace="user">
        String namespace = rootElement.attributeValue("namespace");
        /* 例:
            <select id="selectOne" resultType="com.xc.pojo.User" parameterType="com.xc.pojo.User" >
                select * from user where id = #{id} and name = #{name}
            </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();

            // 封装MapperStatement对象
            MapperStatement mapperStatement = new MapperStatement();
            String statementId = namespace + "." + id;
            mapperStatement.setStatementId(statementId);
            mapperStatement.setParameterType(parameterType);
            mapperStatement.setResultType(resultType);
            mapperStatement.setSql(sql);
            //第四节 自定义框架_优化才会用到
            mapperStatement.setSqlCommandType("select");

            // 添加到configurations的map集合中
            configuration.getMapperStatementMap().put(statementId,mapperStatement);
        }
    }
}

4、创建SqlSessionFactory工厂接口及DefaultSqlSessionFactory实现类

  • 为了创建SqlSession会话,调用增删改查方法
public interface SqlSessionFactory {

    // 创建SqlSession对象
    SqlSession openSession();

}
  • 创建简单执行器,与核心配置类共同创建SqlSession会话实现类
  • configuration提供数据配置和sql以及参数和结果集封装
  • simpleExecutor提供JDBC执行sql底层原理
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

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

    @Override
    public SqlSession openSession() {
        // 1.创建执行器对象-具体的包装jdbc的sql操作,关闭连接等
        Executor simpleExecutor = new SimpleExecutor();

        // 2.创建sqlSession对象-判断执行增删改查哪些操作等
        return new DefaultSqlSession(configuration,simpleExecutor);
    }
}

5、创建SqlSession会话接口及DefaultSqlSession实现类

  • statementId(“namespace.id”):定位具体Mapper.xml的sql语句以及入参和返回
  • param:替换sql语句中的占位符?,可能字符串、对象、Map、集合
public interface SqlSession {

    // 查询多个结果
    <E> List<E> selectList(String statementId, Object param) throws Exception;

    // 查询单个结果
    <T> T selectOne(String statementId, Object param) throws Exception;

    // 清理资源
    void close();
    
}
  • 利用聚合进来的configuration对象获取MapperStatement映射配置对象向下传给执行器
  • 另外一个聚合进来的executor简单执行器来执行底层JDBC操作
  • DefaultSqlSession的作用则是聚合配置类分发到不同执行器的不同方法
  • 执行器种类:简单执行器、可重用执行器、批量执行器(这里只模拟第一种)
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    private Executor executor;

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

    @Override
    public <E> List<E> selectList(String statementId, Object param) throws Exception {
        // 根据StatementId获取映射配置对象MapperStatement
        MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
        // 然后将具体的查询操作委派给SimpleExecutor执行器
        // 执行底层jdbc需要:1.数据库配置,2.sql配置信息
        return executor.query(configuration,mapperStatement,param);
    }

    @Override
    public <T> T selectOne(String statementId, Object param) throws Exception {
        // 调用selectList()
        List<Object> selectList = selectList(statementId, param);
        if (selectList.size() == 1){
            return (T) selectList.get(0);
        }else if (selectList.size() > 1){
            throw new Exception("返回数据不止一条!!!");
        }else {
            return null;
        }
    }

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

}

6、创建Executor执行器接口及SimpleExecutor实现类

  • 执行器接口定义增删改查方法,具体的JDBC底层操作由它的实现类来完成
public interface Executor { 

    <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object param) 
    	throws Exception;

    void close();
}
  • 执行器实现类整体流程就是JDBC那一套,从加载驱动到处理结果集
  • getBoundSql方法功能:
    • 一是将 <select>标签的sql语句“#{字段名}”替换成?赋值给finalSql。(这里使用的mybatis代码,不用深究就是根据正则表达式替换,最后有gitee代码链接)
    • 二是将替换?时候的字段名取出来放到ParameterMapping对象中,有多个,根据?次序,放入parameterMappingList集合
  • 入参根据<select>标签的parameterType属性获取全限定类名,反射获取Class对象
    1. 遍历parameterMappingList,获取字段名,加上Class对象获取Field属性类
    2. query查询方法有个param参数,即入参对象(有可能字符串,集合这里只考虑对象),通过Field属性和param对象通过反射获取属性值
  • 结果集根据<select>标签的resultType属性获取全限定类名,反射获取实例对象
    1. 通过结果集元数据获取字段名columnName,再resultSet.getObject获取数据库中对应字段值
    2. 通过java实体字段名和Class对象获取对应字段的Get方法的Method
    3. 这里说下1和2步骤中两个字段不是一回事,如果数据库和实体中不一样,这里需要转化成一致
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, MapperStatement mapperStatement, Object param) 
    		throws Exception {
        // 1.加载驱动,获取数据库连接
        connection = configuration.getDataSource().getConnection();

        // 2.获取preparedStatement预编译对象
        // 从mapperStatement中获取sql语句
        // 例:select * from user where id = #{id} and username = #{username}
        String sql = mapperStatement.getSql();

        // 1)需要转化为:select * from user where id = ? and username = ?
        // 2)需要将替换的值保存下来,后续?赋值需要用到
        BoundSql boundSql = getBoundSql(sql);
        String finalSql = boundSql.getFinalSql();
        preparedStatement = connection.prepareStatement(finalSql);

        // 3.根据入参赋值?
        // 获取入参的全限定类名 com.xc.pojo.User
        String parameterType = mapperStatement.getParameterType();
        // 没有入参类型,表示sql没有参数,这里不需要?赋值
        if (parameterType != null){
            // 入参对象Class类
            Class<?> parameterTypeClass = Class.forName(parameterType);
            // 获取#{}参数字段list
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                // 获取字段名称
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String paramName = parameterMapping.getContent();
                // 通过反射获取入参对象的Field属性
                Field field = parameterTypeClass.getDeclaredField(paramName);
                // 禁用安全检查,不用排除private,效率提升好多倍
                field.setAccessible(true);
                // param为User对象,通过属性反射获取到user对象id和name的属性值
                Object value = field.get(param);
                // 赋值占位符,占位符?数字从1开始
                preparedStatement.setObject(i + 1, value);
            }
        }

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

        // 5.处理返回结果集
        List<E> list = new ArrayList<>();
        while (resultSet.next()){
            // 通过<select>标签的返回值类型,创建返回对象
            String resultType = mapperStatement.getResultType();
            Class<?> resultTypeClass = Class.forName(resultType);
            Object obj = resultTypeClass.newInstance();

            // 结果集的元数据信息:字段名,字段值等
            // resultSet: 一条结果集对应一张表的所有字段
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //数据库字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);
                // columnName:数据库字段,而下方需要实体中的字段,如果两边不一样,则这需要有一个转化
                // 获取读写方法即get、set方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                // 获取读方法-字段的set方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 反射为返回对象赋值
                // 参数1:实例对象 参数2:要设置的值
                writeMethod.invoke(obj,value);
            }
            list.add((E) obj);
        }

        return list;
    }

    /**
     * @Description 获取?占位符的sql,以及保存#{}中的字段名称
     *
     * 1、将<select>标签中的sql的 "#{字段名}" 整个部分替换成?,赋值 finalSql
     * 2、截取#{}中的字段名称,添加到ParameterMapping对象的content属性,多个,赋值List<ParameterMapping>
     *
     * @Author xuchang
     * @Date 2022/10/19 16:44:51
     */
    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();
        return new BoundSql(finalSql, parameterMappings);
    }

    @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();
            }
        }
    }
}
@Data
@AllArgsConstructor
public class BoundSql {

    // 用?做占位符的sql语句
    private String finalSql;

    // 字段名称的集合
    private List<ParameterMapping> parameterMappingList;
}
@Data
@AllArgsConstructor
public class ParameterMapping {
    // 保存#{}中对于的字段名称
    private String content;
}

7、项目使用端利用自定义框架测试

  • 优点:完成一张表的增删改查只需要创建一个实体类。(其他类接口都是必须要有的)
  • 缺点1:sqlSession会话对象创建频繁,每次需要User的CRUD,就需要创建
  • 缺点2:调用方法需要手动添加参数statementId:namespace.id
@Test
public void test1() throws Exception {
    // 1.根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析
    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2.解析配置文件,封装到Configuration对象,创建sqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    // 3.生产sqlSession 创建执行器对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.调用sqlSession方法
    User user = new User();
    user.setId(100);
    user.setName("zhangsan");
    User userOne = sqlSession.selectOne("user.selectOne", user);
    System.out.println("查询单个用户:"+userOne);

    List<User> userList = sqlSession.selectList("user.selectList",null);
    System.out.println("查询所有用户:"+userList.toString());

    // 5.释放资源
    sqlSession.close();
}

输出结果:

查询单个用户:User(id=100, name=zhangsan)
查询所有用户:[User(id=100, name=zhangsan), User(id=120, name=Lisi)]

四、自定义框架_优化

1、优化思路

  • 去掉statementId硬编码:创建一个UserMapper或IUserDao接口,每个方法名、入参、返回值和mapper.xml的<select>标签的id、parameterType、resultType一一对应
  • sqlSession创建频繁:通过动态代理创建IUserDao的实现类,内容则是sqlSession.selectOne和sqlSession.selectList;这样sqlSession只需要创建一次,以后每次需要User的CRUD,则调用代理对象对应方法即可

2、优化代码

在sqlSession中添加方法

public interface SqlSession {

	...

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

}

对应实现类方法

  • 动态代理对象:不需要编译,运行期间生成字节码,通过传入的类加载器加载,只存在于内存中
  • mapperClass:UserMapper或IUserDao接口的Class对象
  • 代理类调用接口中的方法,则会被拦截进入invoke方法,因为没有目标类,则具体的实现都在invoke里面了
  • 通过mapperClass和方法名获取到statementId
  • statementId和参数有了,但是DefaultSqlSession要有CRUD查询和更新操作,所以sqlCommandType来区分
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    private Executor executor;

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

    @Override
    public <E> List<E> selectList(String statementId, Object param) throws Exception {
        // 根据StatementId获取映射配置对象MapperStatement
        MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
        // 然后将具体的查询操作委派给SimpleExecutor执行器
        // 执行底层jdbc需要:1.数据库配置,2.sql配置信息
        return executor.query(configuration,mapperStatement,param);
    }

    @Override
    public <T> T selectOne(String statementId, Object param) throws Exception {
        // 调用selectList()
        List<Object> selectList = selectList(statementId, param);
        if (selectList.size() == 1){
            return (T) selectList.get(0);
        }else if (selectList.size() > 1){
            throw new Exception("返回数据不止一条!!!");
        }else {
            return null;
        }
    }

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


    @Override
    public <T> T getMapper(Class<?> mapperClass) {

        // 使用JDK动态代理生成基于接口的对象
        // 1、创建一个类(代理类),实现目标接口,实现所有的方法实现
        // 2、动态代理类:代码运行期间生成的,而非编译期
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), 
        	new Class[]{mapperClass}, new InvocationHandler() {
            // proxy:生成的代理对象本身,很少用
            // method:调用接口中哪个方法,则执行对应代理里的对应方法
            // args:调用方法的参数
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 执行底层JDBC
                // 1.获取statementId
                // ps:约定接口中的方法名要与<select>标签的id属性名一致,
                // 这样就可以通过接口获取statementId = namespace.id
                String methodName = method.getName();// 方法名 - findAll
                String className = mapperClass.getName();//接口类名 - com.xc.do.IUserDao
                String statementId = className + "." + methodName;

                // 2.判断调用sqlSession中CRUD的什么方法
                // MapperStatement类添加属性sqlCommandType-sql增删改查类型
                MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
                // select  update delete insert
                String sqlCommandType = mapperStatement.getSqlCommandType();
                switch (sqlCommandType){
                    case "select" :
                        // 3.判断调用selectOne还是selectList
                        // 获取返回值类型,
                        Type genericReturnType = method.getGenericReturnType();
                        // ParameterizedType是Type的子接口,表示一个有参数的类型,例如Collection<T>,Map<K,V>等
                        if(genericReturnType instanceof ParameterizedType){
                            if(args != null) {
                                return selectList(statementId, args[0]);
                            }
                            return  selectList(statementId, null);
                        }
                        return selectOne(statementId,args[0]);
                    case "update":
                        // 执行更新方法调用
                        break;
                    case "delete":
                        // 执行delete方法调用
                        break;
                    case "insert":
                        // 执行insert方法调用
                        break;
                }
                return null;
            }
        });

        return (T) proxyInstance;
    }
}

3、优化后测试

  • 缺点:相较于优化前,需要新添加一个接口
  • 优点:不需要手动添加statementId,SqlSession不用频繁创建
@Test
public void test2() throws Exception {
    // 1.根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析
    InputStream resourceAsSteam = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2.解析了配置文件,封装了Configuration对象  2.创建sqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);

    // 3.生产sqlSession 创建了执行器对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.调用sqlSession方法
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);

    List<User> all = userDao.findAll();
    for (User user : all) {
        System.out.println(user);
    }

    // 5.释放资源
    sqlSession.close();
}

五、Spring整合优化

  • 与Spring整合之后,InputStream、SqlSessionFactory、SqlSession、IUserDao代理对象统统不需要自己创建
  • 全都交给了spring容器管理,我们要做的就是@Autowired IUserDao userDao
  • 然后就可以用代理对象调用增删改查方法了

六、gitee代码

  • 手写源码框架项目: https://gitee.com/xuchang614/own-mybatis

  • 测试框架项目: https://gitee.com/xuchang614/own-mybatis-test

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

R语言和医学统计学(10):正态性和方差齐性检验

本文首发于公众号&#xff1a;医学和生信笔记&#xff0c;完美观看体验请至公众号查看本文。 医学和生信笔记&#xff0c;专注R语言在临床医学中的使用&#xff0c;R语言数据分析和可视化。 文章目录前言正态性检验shapiro wilk检验kolmogorov smimov检验方差齐性检验两样本方差…

SSH框架重构需求,起码读懂代码和前端的原生JS

文章目录SSH(Spring Struts Hibernate)商城老项目JSPHtmlJsJquery 老项目部署Tomcat上面进行部署可以一次性部署多个项目1.Spring2.Struts3.Hibernate(持久层一个重量级框架)4.里面的请求路径解析SSH(Spring Struts Hibernate)商城老项目JSPHtmlJsJquery 老项目部署 Tomca…

【JavaSE之JDK8新特性】三万字详文带你了解JDK8新特性

JDK8新特性一、Lambda1.1需求分析2.Lambda表达式的初级体验3.Lambda表达式的语法规则3.1.Lambda练习13.2.Lambda表达式练习24.FunctionalInterfa注解说明5.Lambda表达式的原理6.Lambda表达式的省略写法7.lambda表达式的使用前提8.lambda和匿名内部类的对比二、接口中新增的方法…

#define宏的妙用!实现你以为的函数offsetof等

目录 一.#define 1.1#define的使用 1.2在define定义标识符的时候&#xff0c;要不要在最后加上 ;? 二.#define定义宏 2.1好代码的写法: 2.2#define 替换规则 2.3#和## 三.带有副作用的宏参数 四.宏和函数的对比 五.实现offetof 思路解析过程&#xff1a; 代码实现&am…

破解系统密码

一、利用5次shift漏洞破解win7密码 1.1 漏洞 1. 在未登录时&#xff0c;连续按5次shift键&#xff0c;弹出程序C:\Windows\System32\sethc.exe 2. 部分win7及win10系统在未进入系统时&#xff0c;可以通过系统修复漏洞篡改系统文件名&#xff01; 注意&#xff1a;如win7或win…

SpringBoot/Spring AOP默认动态代理方式

Spring 5.x中AOP默认依旧使用JDK动态代理SpringBoot 2.x开始&#xff0c;AOP为了解决使用JDK动态代理可能导致的类型转换异常&#xff0c;而使用CGLIB。在SpringBoot 2.x中&#xff0c;AOP如果需要替换使用JDK动态代理可以通过配置项spring.aop.proxy-target-classfalse来进行修…

【Linux】7.0 信号

文章目录信号的基本概念kill -l 查看信号列表信号的处理方式signal( ) 自定义处理信号信号的产生方式键盘产生进程异常&#xff08;core dump&#xff09;系统调用软件条件信号的发送&#xff08;OS&#xff09;信号常见相关名词解释进程接收处理信号原理信号集函数的使用打印p…

【Redis】3.详解分布式锁

文章目录1. 什么是分布式锁2. 分布式锁的特点3. 常见的分布式锁4. 实现分布式锁5.解决分布式锁中的原子性问题5.1 Lua脚本5.2 使用Java代码调用Lua脚本实现原子性1. 什么是分布式锁 分布式锁是指分布式系统或者不同系统之间共同访问共享资源的一种锁实现&#xff0c;其是互斥的…

【Django框架】——20 Django视图 02 路由命名和反向解析

文章目录一、 路由命名二、reverse反向解析三、通过URL模板页面进行传参四、namespace1.reverse反向解析2.url模板标签在 Django 项⽬中&#xff0c;⼀个常⻅需求是获取最终形式的 URL&#xff0c;⽐如⽤于嵌⼊⽣成的内容中&#xff08;视图和资源⽹址&#xff0c;给⽤户展示⽹…

《网络安全笔记》第七章:注册表基础

一、注册表基础 1、概述 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”&#xff0c;也可以说是一个非常巨大的树桩分层结构的数据库系统注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息&#xff0c;它包括了计算…

【UDS】ISO14229之0x2F服务

文章目录前言一、理论描述二、使用步骤1.请求2.响应总结->返回总目录<- 前言 简称&#xff1a; “InputOutputControlByIdentifier”&#xff0c;根据标识符控制输入输出 功能&#xff1a; 根据标识符控制输入输出服务用于替换输入信号的值、电控单元内部参数或控制电子…

Telnet连接

❤️人生没有白走的路&#xff0c;每一步都算数❤️ 你是否安装Telnet没毛病&#xff0c;但登录总报错&#xff1f; 巧了&#xff0c; 我也遇到了。 于是我打开浏览器尝试搜索&#xff0c;有许多说的并不详细。 所以呢就有了这篇文章&#xff01; 首先我们准备实验环境&#xf…

oracle中替换字符串的不同写法

replace函数 replace(原字段&#xff0c;“原字段旧内容“,“原字段新内容“) 例如将DEPTNO字段值中的0替换为1&#xff1a; TRANSLATE TRANSLATE(expr, from_string, to_string) from_string 与 to_string 以字符为单位&#xff0c;对应字符一一替换。 用法示例&#xf…

详解数据结构——二叉排序树

目录 二叉排序树 二叉排序树的查找 二叉排序树的插入 二叉排序树的删除 查找时间效率分析 二叉排序树 二叉排序树&#xff0c;又称二叉查找树&#xff08;BST&#xff0c;Binary Search Tree)一棵二叉树或者是空二叉树&#xff0c;或者是具有如下性质的二叉树: 左子树上所有结…

SpringBoot - SpringBoot整合i18n实现消息国际化

文章目录1. MessageSource源码2. 项目环境搭建1. 创建项目服务auth2. 工具类 I18nUtils3. 自定义异常 CommonException4. 统一异常处理 GlobalExceptionHandler3. 业务实现1. 实体类 UserEntity2. 请求实体 UserQo3. 控制层 UserController4. 业务逻辑层 UserService5. 将异常信…

144. 授人以渔 - 如何查找 SAP UI5 官网上没有提到的控件属性的使用明细

本教程第 113 步骤, SAP UI5 应用开发教程之一百一十三 - 授人以渔 - 如何自行查询任意 SAP UI5 控件属性的文档和技术实现细节我用一整篇文章的篇幅,解答了一位学习者这个疑问: 想请教一下 sap.m.Input 控件中,value里设置的内容,比如path,type,constraints,在哪里可以查…

C++ 多态

目录 一、多态的定义和实现 1.1 多态的构成条件&#xff1a; 1.2 虚函数的重写&#xff08;覆盖&#xff09;&#xff1a; 1.3 多态的两个特殊点&#xff1a; 1.4 析构函数的重写&#xff1a; 1.5 override和final 1.6 重载&#xff0c;重定义&#xff08;隐藏&#xff…

Linux【进程地址空间】

进程地址空间&#x1f4d6;1. 地址空间概念&#x1f4d6;2. 写时拷贝&#x1f4d6;3. 虚拟地址空间的优点&#x1f4d6;1. 地址空间概念 在学习C/C内存管理时&#xff0c;我们可能见过这样一幅图&#xff1a; 但是我们可能不是很理解它&#xff0c;首先有一个问题&#xff1a;…

OpenTCS客户端开发之Web客户端(一)

越来越多人私信我关于OpenTCS的问题。可以感觉到很多人对OpenTCS的研究的人多了很多&#xff0c;很好。这些问题很多是关于算法方面的&#xff0c;也有一部分是关于UI方面的&#xff0c;毕竟OpenTCS本质上是一个算法项目&#xff0c;但是如果希望把它进行商业化&#xff0c;那免…

【微服务】服务拆分和远程调用

2.1 服务拆分原则 这里总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口&#xff0c;供其它微服务调用 2.2 服务拆分示例 以微服务cloud-demo为…