• JDBCTemplate与模板设计方法(二)


    前言:上一篇博客介绍了模板方法模式,并且给出了一个小demo,简单对模板方法进行了实现,接下来我们把目光转向spring的源码JDBCTemplate,看一看spring是如何对jdbc进行高度封装的。

    本篇博客的目录:

    一:传统的jdbc存在的缺陷和不足

    二:JDBCTemplate的实现原理

    三:JDBCTemplate的使用方法

    五:总结

    正文:

    一:传统的jdbc存在的缺陷和不足

      1.1首先我们来一下以往我们使用jdbc进行数据库操作的代码是什么样的?

    public class UpdateDateBase {
     
       static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";  
       static final String DB_URL = "jdbc:mysql://localhost/jdbc_db";
       static final String USER = "root";
       static final String PASS = "123456";
    
       public static void main(String[] args) {
       Connection conn = null;
       Statement stmt = null;
       try{
          
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection(DB_URL, USER, PASS);
          stmt = conn.createStatement();
          String sql = "UPDATE student " +"SET age = 22 WHERE id in (100, 101)";
          stmt.executeUpdate(sql);
          sql = "SELECT id, first, last, age FROM student";
          ResultSet rs = stmt.executeQuery(sql);
          while(rs.next()){
             int id  = rs.getInt("id");
             int age = rs.getInt("age");
             String first = rs.getString("first");
             String last = rs.getString("last");
          }
          rs.close();
       }catch(SQLException se){
          se.printStackTrace();
       }catch(Exception e){
          e.printStackTrace();
       }finally{
          try{
             if(stmt!=null)
                conn.close();
          }catch(SQLException se){
          }
          try{
             if(conn!=null)
                conn.close();
          }catch(SQLException se){
             se.printStackTrace();
          }
       }
    }
    }

    这里只是做了一个简单的操作,对数据进行更新,再查询显示出来,可以看到这么一大长串,假如我们再进行一个删除操作,还得把这些步骤再来一遍,天呐,头都要大了,写代码最烦的就是写重复烦躁的代码。这就是传统jdbc的编码,存在以下不足

    1:对外暴露的细节太多,比如把数据库名、密码、用户名、Ip等都暴露出来,万一代码落入到还有不轨企图的人手里,只要看一下你的jdbc,它就可以得到你数据库的所有信息。其实安全这一点对企业来说非常重要,如果没有参加工作的童鞋可能还意识不到这一点

    2:冗余、重复的代码太多,都是做一些重复机械的操作,首先是获取驱动、得到连接、生成Statement,再编译sql,执行,取结果遍历,最后还得再关闭结果集、关闭连接、关闭statement,防止抛异常,还得考虑事务,万一出错还得考虑回滚等等。

    3:代码编写比较繁复,这其中万一有一个步骤出错了,比如忘记关闭数据库连接池了,这样就会发生很多莫名奇妙的问题,比如再次更新数据库发现没有变化,这就是没有关闭连接池导致的。这种细节性操作很容易酿造大的事情,程序员需要关注的细节过多

    4: 向方法中的sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应,这其中万一出错了,就会导致无法操作数据库

    二:JDBCTemplate源码分析

        2.1:关于JDBCTemplate:它是这样一个类,它把我们对数据库的操作jdbc代码高度封装,并且采用了模板方法的设计思想,将其抽象成回调接口和工具类调用的方法,把特定的步骤通过工具类来组装,这样可以实现固定步骤的高度可重用。只需要编写一次,我们就可以进行重复利用起来。

       2.2:JDBCTemplate的继承关系

    上面是它的继承关闭图,它继承自抽象类JdbcAccessor和JdbcOperations接口。其中JdbcAccessor设定了处理数据源DataSoucre和sql的异常检验操作,而JdbcOperations则负责具体的查询query、update操作、批处理batchUpdate等,同时继承自两个父类,让JDBCTemplate具有操作数据库和处理数据源、获取异常的能力。

    2.3:JDBCTemplate的成员变量

    这些成员变量是它本身的,我来翻译一下它的含义:

    private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";//前缀
    private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";//计数前缀
    private NativeJdbcExtractor nativeJdbcExtractor;//本地JDBC执行器
    private boolean ignoreWarnings = true;//是否忽略警告
    private int fetchSize = 0;//查询到的大小
    private int maxRows = 0;//最大行数
    private int queryTimeout = 0;//查询时间
    private boolean skipResultsProcessing = false;//是否跳过结果处理
    private boolean skipUndeclaredResults = false;//是否跳过非公共结果
    private boolean resultsMapCaseInsensitive = false;//Map结果的是否大小写敏感

    但是因为它继承了一个抽象类和接口,它同样享有JdbcAccesor的成员变量,可以看到DataSoucre已经注入了,但它是private的,虽然可以继承,但是不能使用,不过可以引用JdbcAccesor中的setter方法进行设值

    父类中的这些很简单,基本上可以很直接的看出来,它们分别是日志记录器、数据源、sql异常转换器、是否懒加载,这些成员变量是全局的,也就是说它们存在着线程安全的问题,为此Spring还专门采用了单例加锁的设计模式来获取exceptionTranslator对象,以下是源码:

     可以看到它使用了synchronize关键字,这个锁是方法级别上的,获取exceptionTranslator对象,根据dataSoure是否为空来判断实现的不同子类SQLErrorCodeSQLExceptionTranslator(需要DataSource)和SQLStateSQLExceptionTranslator(不需要DataSource);

    2.4:jdbcTemplate的构造方法

    可以看到它有三个构造方法,标记为①的是个空构造方法,这个不用多说了。第二个它的话它引用了JdbcAccesor注入了DataSource,然后调用了父类中的属性设置后的方法this.arterPropertiesSet(),我们来看看它的源码:

    可以看到这是和懒加载有关的一个方法,没有懒加载,就会调用本类中的方法调用2.3中讲的方法获取异常转换器中

    第三个构造方法,它在第二个的基础上增加了设置LazyInit的字段,设置它是否支持懒加载,关于懒加载:它是一种节省资源提高效率的做法,数据只有在真正用到的时候才去加载它。

     2.5:jdbcTemplate执行sql原理

    我们来看一下它执行sql的方法exexute的源码:

    首先它是一个日志记录,级别在debug,然后方法中声明了一个类,这个类继承了StatementCallback接口和sqlProvider接口,这个接口只有一个方法,主要负责的就是用Statement这个对象执行sql。

    最后再把这个接口传入execute重载方法,我们来看一下它对接口作了哪些处理:

    我们来分析一下这段源码,它主要是获取connection对象,再通过对象赋值,通过本地jdbc提取器再次获取,然后再通过该对象创建Statement,再进行设值,再通过对象赋值给另一个对象,至于Spring中为什么经常出现这种代码-指的是对象赋值转接,这里可以理解为对象的一个复制过程,然后再做重新的处理。再调用接口中的doInstatement方法执行sql获取结果,又再次出现把对象转接过去,对结果进行赋值,然后在catch代码块中关闭Statement和释放连接,再抛出异常,在finally块中确保关闭Statement和Connection.

    2.6:jdbcTemplate的update方法源码分析

    我们看到这就是它的update方法,首先是一个断言机制:sql必须不为null,关于断言就是一种假设机制,假设程序运行到这里对参数进行校验,如果不满足的话,将会抛出AssertionError异常。然后是日志记录sql,接着是一个方法内部类,这里又用到实现了回调接口StatementCallback,它的主要方法是doInstatement,调用jdbc中的的executeUpdate方法执行完返回一个int类型的值,最后返回这个值,然后再次调用execute方法,把方法内部的实现类传进去,和刚才的一样,做了必要的关闭Statement和connection操作,还有一些异常的抛出操作和警告等的处理

    2.7:jdbcTemplate的批量更新方法源码

    批量更新batchupdate可以看到它传入的是一个sql的数组,那么我们在调用的时候需要传入肯定是一个sql的数组集合。然后和上面的一样断言、日志开启,再在方法内部新建一个类实现Statement回调接口,再其中先判断是否支持批量查询,再进行循环遍历,通过appendSql方法拼接sql语句,再用statement批量更新,最后返回影响的行数。

    上面的源码是进行一场判断的处理,抛出具体哪个sql异常,else的部分是指不支持批量查询的情况,它就会抛出异常Invaild batch sql Statement+具体的sql语句,最后再返回影响的行数

    三:jdbcTemplate的使用方法

    3.1:首先在application.xml中配置jdbcTemplate Bean:

    这里可以看到配置的过程中引用了DataSource数据源,这里的主要原因是我在2.3中讲到的它有DataSource这个成员变量,然后我们来看一下DataSource配置了哪些东西

    这里的DateSource主要是数据库的配置,数据库也就是我们数据的来源,至于其中的${jdbc.property}指的是读取一个配置文件的值,一个.properties文件结束,关于如何读取文件,我的博客之前也有讲过:点我到达

    不过使用这种方式的话,记得要在Spring的配置文件中写上,location是资源文件的路径:

    3.2:如果其他配置好的话,接下来我们看看如何具体使用它:

    jdbcTemplate肯定是在Dao层了,我们用@AutoWired注入这个对象,然后可以调用它的方法执行sql就行了。其他的比如关闭操作、异常处理、回滚等操作压根不用操心,这个类已经帮我们处理好了一切,怎么样,够简单吧。

     ps:其他的使用方法同理。只需要用这个jdbctemplate调用方法就可以了。

     四:总结

    本篇博客主要讲解了原始jdbc的不足和jdbctemplate采用模板设计模式对其进行了高度封装的过程,并且分析它的源码,和使用过程,源码只要理解了就行, 用的时候可以直接调用,其中它本身大量使用了回调机制,在方法内部声明一个类实现回调接口,调用接口中的方法进行处理。主要的重要部分是源码的剖析,理解它的回调接口,然后理解模板设计方法带给我们的好处,在项目中多多使用它,毕竟封装可是面向对象的三大特性之一。

  • 相关阅读:
    js画矩形
    js加载pdf截屏生成图片调用ocr识别成文字
    C#List或者Set集合相同的key合并Value的值
    Oracle学习笔记读懂执行计划(十八)
    Java 阻塞队列
    SpringMVC(三):参数绑定、输入输出转换
    springMVC(二): @RequestBody @ResponseBody 注解实现分析
    Spring Security 4.2.3 Filters 解析
    MySQL 加锁处理分析
    Innodb semi-consistent 简介
  • 原文地址:https://www.cnblogs.com/wyq178/p/7518339.html
Copyright © 2020-2023  润新知