美文网首页
自定义持久层框架 -- Mybatis阉割版

自定义持久层框架 -- Mybatis阉割版

作者: Travis_Wu | 来源:发表于2021-01-19 23:01 被阅读0次

一、分析JDBC操作问题

public class Test {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 通过驱动管理类获取数据库链接
            connection =
                    DriverManager.getConnection(
                            "jdbc:mysql://localhost:3306/mybatis? characterEncoding = utf - 8", " root", " root");
            // 定义sql语句?表示占位符
            String sql = "select * from user where username = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
            preparedStatement.setString(1, "tom");
            // 向数据库发出sql执⾏查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            // 遍历查询结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                // 封装User
                user.setId(id);
                user.setUsername(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语句,设置参数,获取结果集参数 均存在硬编码问题
    解决方案:利用配置文件,但是由于这几项信息是频繁改动的内容,所以不应该和数据库配置信息放在同一份配置文件中,应该分开存放
  • 手动封装返回结果集,较为繁琐
    解决方案:反射、内省

二、自定义框架设计思路分析

  • 使用端(项目)
    1. 引入自定义持久层框架的jar包
    2. 提供两部分配置信息
      • sqlMapConfig.xml : 存放数据源信息
      • Mapper.xml : sql 语句的配置文件信息
  • 自定义持久层框架本身(工程)
    1. 加载配置文件
      根据配置文件的路劲,加载配置文件成字节输入流,存入内存中
      创建 Resources 类,方法:InputSteam getResourceAsSteam(String path)
    2. 创建两个javaBean(容器对象)
      存放对配置文件解析出来的内容
      Configuration(核心配置类) : 存放 sqlMapConfig.xml 解析出来的内容
      MappedStatement(映射配置类):存放 Mapper.xml 解析出来的内容
    3. 使用dom4j解析配置文件
      创建 sqlSessionFactoryBuilder 类,方法:build(InputSteam in)
      创建 SqlSessionFactory 对象,生产 SqlSession
    4. 创建 SqlSessionFactory 接口及实现类 DefaultSqlSessionFactory
      方法:openSession生产SqlSession
    5. 创建SqlSession接口及实现类DefaultSession
      定义对数据库的CRUD操作
    6. 创建Executor接口及实现类SimpleExecutor
      方法:query(Configuration c, MappedStatement m, Object...params)执行JDBC代码

三、使用端代码编写

sqlMapConfig.xml 定义数据源信息

<configuration>

    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///wujun_test"></property>
        <property name="username" value="wujun"></property>
        <property name="password" value="wujun@2020"></property>
    </dataSource>

    <!--存放mapper.xml的全路径-->
    <mapper resource="UserMapper.xml"></mapper>

</configuration>

UserMapper.xml 定义 sql 信息
SQL唯一标识(StatementId)由 namespace.id 来组成
例如:com.wujun.dao.IUserDao.findAll

<mapper namespace="com.wujun.dao.IUserDao">

    <select id="findAll" resultType="com.wujun.pojo.User">
        select * from user
    </select>

    <select id="findByCondition" resultType="com.wujun.pojo.User" paramterType="com.wujun.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</mapper>

四、自定义持久层框架代码编写

  1. 创建 Resources 类,加载配置文件
package com.wujun.io;

import java.io.InputStream;

public class Resources {

    // 借助类加载器,将指定路劲的配置文件加载成字节输入流,存储在内存中
    public static InputStream getResourceAsSteam(String path) {
       return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}
  1. 创建两个 javaBean(容器对象)用来存放配置文件解析出来的信息
/**
 * 存放解析出来的数据源对象 以及 封装 MappedStatement
 * 以便于后续作为参数层层传递,最后进行JDBC操作
 */
@Data
public class Configuration {

    private DataSource dataSource;

    /**
     * key:StatementId(由 namespace.id 组成)
     */
    private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}

/**
 * 针对SQL语句配置文件解析之后,用来存放的对象
 * 通俗的来说,有几个select标签,就会生成几个MappedStatement对象
 */
@Data
public class MappedStatement {

    /**
     * id标识
     */
    private String id;

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

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

    /**
     * SQL语句
     */
    private String sql;
}
  1. 使用 dom4j 解析配置文件
/**
 * XML解析
 */
public class XMLConfigBuilder {

    private Configuration configuration;

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

    /**
     * 使用dom4j对配置文件进行解析,封装成 Configuration 对象
     */
    public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(in);

        // 拿到配置文件的根对象 <configuration>
        Element rootElement = document.getRootElement();
        // 查找所有的 property 标签
        List<Element> elementList = rootElement.selectNodes("//property");
        // 遍历 elementList
        Properties properties = new Properties();
        for (Element element : elementList) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        }
        // 使用c3p0数据库连接池存放数据源信息
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(comboPooledDataSource);

        // 解析sql配置文件信息,存入 configuration 中
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream mapperResource = Resources.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(mapperResource);
        }

        return configuration;
    }
}
/**
 * Mapper XML解析
 */
public class XMLMapperBuilder {

    private Configuration configuration;

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

    /**
     * 使用dom4j对 mapper.xml 配置文件进行解析,放入 Configuration 对象
     */
    public void parse(InputStream in) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(in);

        // 拿到配置文件的根对象 <mapper>
        Element rootElement = document.getRootElement();
        // 获取 namespace
        String namespace = rootElement.attributeValue("namespace");
        // 查找所有的 select 标签
        List<Element> elementList = rootElement.selectNodes("//select");
        // 遍历 elementList
        for (Element element : elementList) {
            String selectId = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sql = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(selectId);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            configuration.getMappedStatementMap().put(namespace + "." + selectId, mappedStatement);
        }
    }
}
  1. 创建SqlSession
public class SqlSessionFactoryBuild {

    /**
     * 解析配置文件,创建 SqlSession
     */
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        // 1、使用dom4j解析配置文件,将解析出来的内容封装到 Configuration 中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        // 2、创建SqlFactory对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;

    }
}
  1. 定义对数据库的CRUD操作
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

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

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor(configuration);
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> list = selectList(statementId, params);
        if (list != null && list.size() == 1) {
            return (T) list.get(0);
        } else {
            throw new RuntimeException("查询得到的结果为空或者查询得到多个结果");
        }
    }
}
  1. 在SimpleExecutor 中编写JDBC代码
public class SimpleExecutor implements Executor {

    private Configuration configuration;

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

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

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

        /*
         * 2、获取sql语句 : select * from user where id = #{id} and username = #{username}
         * 转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
         */
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3、获取预处理对象 preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());

        // 4、设置参数
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i + 1, o);
        }

        // 5. 执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 6. 封装返回结果集
        while (resultSet.next()) {
            Object o = resultTypeClass.getDeclaredConstructor().newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                String columnName = metaData.getColumnName(i);
                Object value = resultSet.getObject(columnName);
                //使用内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }

    /**
     * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
     * 具体工具类都是从 Mybatis 中直接拿来使用的
     */
    private BoundSql getBoundSql(String sql) {
        // 标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        // #{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }

    private Class<?> getClassType(String type) throws ClassNotFoundException {
        if (type != null) {
            Class<?> aClass = Class.forName(type);
            return aClass;
        }
        return null;
    }
}
  1. 使用端编写测试类
package com.wujun.test;

import com.alibaba.fastjson.JSON;
import com.wujun.io.Resources;
import com.wujun.pojo.User;
import com.wujun.sqlSession.SqlSession;
import com.wujun.sqlSession.SqlSessionFactory;
import com.wujun.sqlSession.SqlSessionFactoryBuild;
import org.junit.Test;

import java.io.InputStream;

public class IPersistenceTest {

    @Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuild().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = new User();
        user.setId(1L);
        user.setUsername("吴俊1");
        User sqlResult = sqlSession.selectOne("com.wujun.dao.IUserDao.findByCondition", user);
        System.out.println(JSON.toJSONString(sqlResult));
    }


}
  1. 测试返回结果
Connected to the target VM, address: '127.0.0.1:60755', transport: 'socket'
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.dom4j.io.SAXContentHandler (file:/D:/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar) to method com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser$LocatorProxy.getEncoding()
WARNING: Please consider reporting this to the maintainers of org.dom4j.io.SAXContentHandler
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
log4j:WARN No appenders could be found for logger (com.mchange.v2.log.MLog).
log4j:WARN Please initialize the log4j system properly.
{"id":1,"username":"吴俊1"}
Disconnected from the target VM, address: '127.0.0.1:60755', transport: 'socket'

Process finished with exit code 0
  1. 数据库截图


相关文章

  • 自定义持久层框架 -- Mybatis阉割版

    一、分析JDBC操作问题 数据库配置信息存在硬编码问题解决方案:利用配置文件 频繁创建释放数据库链接解决方案:连接...

  • Mybatis(一)

    Mybatis简介 MyBatis 是一个可以自定义SQL、存储过程和高级映射的持久层框架。MyBatis 摒除了...

  • mybatis入门

    MyBatis框架-倪升武 标签: MyBatis 持久层 mybatis的执行流程 mybatis框架是一个持久...

  • 精心整理了15道面试官喜欢问的MyBatis面试题

    1、什么是 MyBatis? 答:MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。 2、...

  • SSM框架之Mybatis初始(一)

    一、简介 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除...

  • 36道MyBatis面试专题,offer在向你招手

    1、什么是 MyBatis? 答:MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。 2、...

  • Mybatis

    1、简介 1.1、什么是Mybatis MyBatis 是一款优秀的持久层框架 它支持自定义 SQL、存储过程以及...

  • 2020-03-28

    mybatis 什么是mybaits MyBatis是一个可以自定义SQL、存储过程和高级映射的持久层框架。 My...

  • mybatis使用及SQL语句优化小结

    【摘要】 MyBatis 作为一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。 一、mybatis...

  • springboot常用starter⑥-mybatis

    mybatis 引入 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBa...

网友评论

      本文标题:自定义持久层框架 -- Mybatis阉割版

      本文链接:https://www.haomeiwen.com/subject/fuvlzktx.html