天天看点

自定义持久层框架前言一、JDBC问题分析二、解决策略分析二、框架设计三、实现总结

自定义持久层框架

文章目录

    • 自定义持久层框架
  • 前言
  • 一、JDBC问题分析
  • 二、解决策略分析
  • 二、框架设计
    • 1.使用端
    • 2. 框架端
  • 三、实现
    • 1.使用端
    • 2.框架端
  • 总结

前言

例如:为什么连接数据库不再使用JDBC,而改用诸如Mybatis的持久层框架,此篇通过自定义持久层框架分析对比了二者的区别,及持久层的实现原理

提示:以下是本篇文章正文内容,下面案例可供参考

一、JDBC问题分析

  1. 数据库连接创建、释放频繁,造成系统资源浪费,从而影响系统性能;
  2. sql语句存在硬编码,不易扩展和维护;
  3. 查询结果集的封装存在硬编码,sql变化会导致解析变化,不易维护;

二、解决策略分析

  1. 连接频繁---->使用连接池
  2. sql语句硬编码----->使用配置文件
  3. 封装结果集硬编码 ------>使用反射、内省封装pojo对象

二、框架设计

1.使用端

提供核心配置文件

sqlMapConfig.xml 提供数据库配置信息,引入mapper.xml

mapper.xml 提供sql语句配置信息

2. 框架端

  1. 加载配置文件,以字节流读取到内存中

    创建Resources类,getResourceAsStaream方法,返回InputStream

  2. 创建Bean对象,存放解析出来的内容

    Configuration:核心配置类,存放sqlMapConfig.xml 解析出来的内容;

    MappedStatement:映射配置类,存放mapper.xml解析春来的内容;

    Configuration 中引入MappedStatement,便于传递执行。

  3. 解析配置文件

    创建类SqlSessionFactoryBuilder

    创建方法build

    该方法做了两件事:

    第一,dom4j解析字节输入流,封装成Configuration对象中;

    第二,创建工厂类SqlSessionFactory对象。

  4. 创建SQLSessionFactory接口及实现类DefaultSqlSessionFactory

    方法openSession,该方法获取SqlSession接口的实例类对象

  5. 创建SqlSession接口及实现类DefaultSession

    创建CRUD方法:selectList()、selectOne()、insert()、update()、delete()等,方法中调用Executor接口的实力类对象的方法

    优化:在SqlSession中添加getMapper方法,通过代理实现(代理对象在调用任何方法,都会执行getMapper中的invoke方法)

  6. 创建Executor接口及实现类SimpleExecutor

    该类的crud方法中执行的就是jdbc的代码:注册驱动获连接、获取sql、获取预处理对象、设置参数、执行sql、封装结果集

三、实现

1.使用端

sqlMapConfig.xml 代码如下:

<configuration>
    <!-- 数据库配置信息  -->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/lagou?useUnicode=true&amp;characterEncoding=utf8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </dataSource>
    <!--存放mapper.xml全路径-->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>
           

sqlMapConfig.xml 代码如下:

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

<!--    sql唯一标识:namespace.id组成:statementId-->
    <select id="findAll" resultType="com.lagou.pojo.User">
        select * from user
    </select>

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

    <insert id="addUser" paramterType="com.lagou.pojo.User">
        insert into user values(#{id},#{username})
    </insert>

    <update id="updateUser" paramterType="com.lagou.pojo.User">
        update user set username = #{username} where id = #{id}
    </update>

    <delete id="deleteUser" paramterType="com.lagou.pojo.User">
        delete from user where id = #{id}
    </delete>



</mapper>
           

2.框架端

Resources.java代码如下:

public static InputStream getResourceAsStaream(String path){
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
           

SqlSessionFactoryBuilder.java代码如下:

public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {

        //第一:使用dom4j解析配合文件,将解析出来的配置文件封装到Configuration中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        //第二:创建SqlsessionFactory对象:工厂类:生产sqlSession:会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }
           

XMLConfigBuilder.java代码如下:

/**
     *
     * 该方法就是将配置文件进行解析,封装到Configuration
     */
    public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(in);
        //根标签<configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }
        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);

        //mapper.xml解析:拿到路径--字节输入流---解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStaream = Resources.getResourceAsStaream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStaream);
        }



        return configuration;
    }
           

xmlMapperBuilder.java代码如下:

public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namesapce = rootElement.attributeValue("namespace");
        List<Element> selectlist = rootElement.selectNodes("//select");
        List<Element> insertlist = rootElement.selectNodes("//insert");
        List<Element> updatelist = rootElement.selectNodes("//update");
        List<Element> deletelist = rootElement.selectNodes("//delete");
        //组装方法类型与标签集合的对应关系
        Map map = new HashMap<String,Object>();
        map.put("select",selectlist);
        map.put("insert",insertlist);
        map.put("update",updatelist);
        map.put("delete",deletelist);

        Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
        while(it.hasNext()){
            Map.Entry<String, Object> entry = it.next();
            dealConfiguration(configuration,(List<Element>)entry.getValue(),(String)entry.getKey(),namesapce);
        }

    }


    /**
     把配置文件增删改查类型的标签配置到MappedStatement,从而封装到Configuration
     */
    private void dealConfiguration(Configuration configuration,List<Element> list,String methodType,String namesapce){
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();

            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            mappedStatement.setMethodType(methodType);
            String key = namesapce + "." + id;
            configuration.getMappedStatementMap().put(key,mappedStatement);
        }

    }
           

DefaultSqlSession.java代码如下:

@Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception{
        //完成对SimpleExecutor里query方法的调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }
           

DefaultSqlSession.java代码如下:

@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}
        String sql = mappedStatement.getSql();
        //解析sql语句: select * from user where id=? and username=? 转换过程中,对#{}里面的值进行存储
        BoundSql boundSql = getBoundSql(sql);

        //3. 获取预处理对象:paramterStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        //4. 设置参数
         //获取参数全路径
        String paramterType = mappedStatement.getParamterType();
        Class<?> paramterClass = getClassType(paramterType);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String content = parameterMapping.getContent();
            //反射
            Field declaredField = paramterClass.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.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;
    }
           

总结

以上就是自定义持久层框架的思路及内容,其中对sql语句解析的类时mybatis中自带的类。

继续阅读