自定义持久层框架

目录

使用JDBC的问题总结:

解决思路

自定义框架设计

自定义框架的具体实现

一、完成Resource类的定义

二、创建两个容器,存放文件解析后的内容

三、解析配置文件sqlMapConfigxml

四、创建sqlSessionFactory对象,生产sqlSession

五、创建SqlSession接口及实现类DefaultSession

六、总结遇到的问题:

完善自定义持久层框架

1. 首先编写UserMapper.xml

2. 编写IUserDao接口

3.完善XMLMapperBuilder类

4.完善sqlSeesion和DefaultsqlSession类

5.完成初始化参数和结果集的封装


使用JDBC的问题总结:

原始jdbc开发存在的问题如下:

1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。

2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。

3、 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能 少,修改sql还要修改代码,系统不易维护。

4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成 pojo对象解析⽐较⽅便

解决思路

①使⽤数据库连接池初始化连接资源

②将sql语句抽取到xml配置⽂件中

③使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射

自定义框架设计

使⽤端:

提供核⼼配置⽂件:

sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml

Mapper.xml : sql语句的配置⽂件信息

框架端:

1.读取配置⽂件,读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建 javaBean来存储

(1)Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + id

(2)MappedStatement:sql语句、statement类型、输⼊参数java类型、输出参数java类型

2.解析配置⽂件

创建sqlSessionFactoryBuilder类:

⽅法:sqlSessionFactory build():

第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中

第⼆:创建SqlSessionFactory的实现类DefaultSqlSession

3.创建SqlSessionFactory:

⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象

4.创建sqlSession接⼝及实现类:主要封装crud⽅法

⽅法:selectList(String statementId,Object param):查询所有 selectOne(String statementId,Object param):查询单个 具体实现:封装JDBC完成对数据库表的查询操作

涉及到的设计模式: Builder构建者设计模式、⼯⼚模式、代理模式

自定义框架的具体实现

使用端:

1.创建Persistence_test使用端的类

2.在resource文件夹下创建sqlMapConfig.xml

内容:

  • 里面写着数据源的配置信息

  • 还有就是UserMapper.xml的全路径

3.在resource文件夹下创建UserMapper.xml

内容:

  • 里面包含我们的sql语句,不要忘记了初始化参数的类型和返回值结果的类型

  • 我们的statementId由我们的namespace+id组成

自定义持久层框架本身(工程):本质就是对JDBC代码进行了封装

首先再创建一个类Persistence作为我们的持久层框架

总览文件结构:

一、完成Resource类的定义

1.1该类的作用:就是根据xml文件的路径,将配置文件加载成字节输入流,存储在内存中

1.2代码编写:

1.3使用端创建测试类,并引入框架的依赖

选中框架类,点击Maven中的Lifecycle中的install将其打包

然后在使用类Persistence中引入依赖

Maven中显示了,表明引入成功

编写使用端的代码:

注意:xml文件的名字一定要写正确,否则字节输入流找不到文件

二、创建两个容器,存放文件解析后的内容

MappedStatement类:存放UserMapper的解析内容

2.1在框架类中创建MappedStatement类,并完成代码编写

注意:该类包含四个私有属性:id,resultType,paramterType,sql,并且生成属性的get和set方法

Configuration类:存放数据源解析后的内容以及sql的配置内容

这里我们就会思考一个问题,为什么这个类还要存放sql的配置信息呢?因为我们调用JDBC完成CRUD的时候需要传入数据源信息,sql语句信息,还有就是可变参数(用户输入的参数),而我们在Configuration类中将数据源信息和sql语句信息封装在一个Map集合中,我们就可以在形式上少传一个参数。

2.2代码的编写:

注意:要生成数据源和Map集合的get和set方法

三、解析配置文件sqlMapConfigxml

3.1创建SqlSessionFactoryBuilder类,该类用来创建工厂对象

3.2创建XMLConfigBuilder类,该类用dom4j来解析Configuration

过程:

  • 首先获得文本对象

  • 文本对象拿到根标签<configuration>

  • 选择根标签中的属性<property>生成链表list,为了获取里面的值

  • 创建Properties对象,

  • 遍历list,将name和value存储在Peoperties中

  • 最后创建连接池对象,设置数据源

3.3创建XMLMapperBuilder类,该类用dom4j解析sqlMapConfig.xml

过程:

  • 首先获得Configuration,因为等下要将解析出来的sql属性封装到MappedStatement中,然后在封装到Configuration中。

  • 解析sqlMapConfig.xml文件,还是一样获得文本对象

  • 获得文本中的根对象<mapper>

  • 根对象获得namesapce属性值

  • 根对象获得结点<select>链表list(因为有多个select)

  • 遍历list获得属性:id,resultType,paramterType,sqlText

  • 将这些属性封装成MappedStatement对象

  • 设置key值为:namespace.id

  • 再将MappedStatement对象封装成configuration对象

3.4进一步完成XMlConfigBuilder类,完成对UserMapper的解析

过程:

  • 首先获得sqlMpaconfig中的mapper属性,因为mapper属性中有UserMapper的全路径

  • 获取resource中UserMapper的路径值

  • 将全路径转换成字节输入流

  • 调用XMLMapperBuilder类,用dom4j解析UserMapper文件

  • 最后返回一个configuration对象

四、创建sqlSessionFactory对象,生产sqlSession

4.1创建SqlsessionFactory接口

接口调用Sqlsession中的openSession方法

4.2创建DefaultSqlsessionFactory类来实现SqlsessionFactory接口

这个类中要有一个有参构造,因为要传入configuration对象,进行底层的JDBC代码的实现

4.3创建SqlSessionFactory接口

五、创建SqlSession接口及实现类DefaultSession

该方法要传statementId,statementId由namespace+id组成,还要有可变参

5.1在Persistence_test创建User实体

5.2编写Executor接口,添加query方法

5.3编写simpExecutor类实现Executor接口,重写query方法

其中要编写getBoundSql方法:

方法的作用就是:1.将#{}用?代替 2.解析出#{}中值得存储

编写BoundSql类:

5.4编写DefaultSqlsession类实现SqlSession

过程:

  • 获得驱动连接

  • 获得转换后的sql语句

获取到全路径的实体类的对象

编写getClassType方法:

 

完成DefaultSqlsession类的编写

六、测试

运行结果:

六、总结遇到的问题:

  • 自己编写的类中要明白形参是什么,以及返回值是什么

  •  根据列数循环的时候,少了一个=,于是封装结果集的时候只封装了id的属性值,而没有封装username的属性值,所以以后写代码要会自己调试找到问题所在,还有就是要细心!

完善自定义持久层框架

添加增加删除更新方法

1. 首先编写UserMapper.xml

2. 编写IUserDao接口

3.完善XMLMapperBuilder类

为什么要完善XMLMapperBuilder类?

因为我们之前编写的这个类只是抽取了select标签,现在我们要实现增删改还要抽取insert等标签

4.完善sqlSeesion和DefaultsqlSession类

 

 

5.完成初始化参数和结果集的封装

初始化参数:

public class ParamterHander {
    private Configuration configuration;
    private MappedStatement mappedStatement;
    private Object[] params;

    public ParamterHander(Configuration configuration, MappedStatement mappedStatement, Object[] params) {
        this.configuration = configuration;
        this.mappedStatement = mappedStatement;
        this.params = params;

    }

    //该方法为了得到参数已经转换好的sql预处理对象
    public PreparedStatement getPreparedStatement() throws Exception {
        //1.注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        //2.获取sql语句:select * from user where id = #{id} and username = #{username}
        //其中要对#{}中的参数进行解析
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

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

        //4.设置参数
        //获取到参数的全路径
        String paramterType = mappedStatement.getParamterType();
        Class<?> paramterClass = getClassType(paramterType);//获取传入参数类型

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();//获取到#{}中的值

            //反射
            Field declaredField = paramterClass.getDeclaredField(content);//根据class对象中的属性名获取到具体属性
            declaredField.setAccessible(true);//防止该类的属性私有的,可以暴力访问
            Object o = declaredField.get(params[0]);//这里拿到的就是具体属性的值
            preparedStatement.setObject(i + 1, o);
        }

        return preparedStatement;
    }



    /*
     * 完成对#{}的解析工作:1、将#{}用?代替  2.解析出#{}中值得存储
     *
     * */
    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;
    }


}

结果集封装:

public class resultHander {
    private MappedStatement mappedStatement;
    private ResultSet resultSet;

    public resultHander(MappedStatement mappedStatement, ResultSet resultSet) {
        this.mappedStatement = mappedStatement;
        this.resultSet = resultSet;
    }

    public <E> List<E> getresultHander() throws Exception {
        //拿到返回结果的全路径,并获得对应的class对象
        String resultType = mappedStatement.getResultType();//拿到返回结果集的全路径

        Class<?> resultTypeClass = getClassType(resultType);//转换为class对象

        ArrayList<Object> objects = new ArrayList<>();
        //6.封装返回结果集
        while(resultSet.next()){
            Object o = resultTypeClass.newInstance();//获得class对象的具体实现
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <=metaData.getColumnCount() ; i++) {//根据列数进行循环,例如id,name就是两列
                //字段名称
                String columnName = metaData.getColumnName(i);
                //根据字段名称获得值
                Object value = resultSet.getObject(columnName);
                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);//class对象对columnName生成读写方法
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);//将具体的value值封装在了o对象中
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }
}

码云地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值