• SpringjdbcTempalate研究


       很多时候,需要使用jdbcTemplate,既有出于性能考虑的因素,也有出于个人偏好。

       关于jdbcTemplate的几个关键性的问题:

    一、简介

        JdbcTemplate位于org.springframework包,组件标识为spring-jdbc。

        处于spring家族的核心区域。spring专注于应用开发,应用开发据大部分和数据库有关,数据库的操作主要由jdbc负责。

        用spring.io自己的话说,spring-jdbc就是默默地干了大家不愿意干,但又不得不干的事情。

        具体哪些是我们不愿意干的,看spring自己提供的图:

        x表示需要做的。

       本文不讨论jdbcTemplate是如何做了大家不想做的事情,而是讨论能用jdbcTemplate做什么。

       要研究透JdbcTemplate,其实光JdbcTemplate自身是不够,还需要了解jdbc的其它一些内容,如果要彻底研究,请阅读spring.io有关的内容。

       限于篇幅,本文只讨论jdbcTempalte等几个template。

       关键字列表:

    • DataSource
    • DataSourceUtils
    • Connection
    • RowMapper
    • SqlParameterSource
    • ListMap
    • InitializingBean

    二、传递SQL参数

       从jdbc底层来说,只有一种传递参数的方式,下面来看参考代码:Lesson: JDBC Basics (The Java™ Tutorials > JDBC Database Access) (oracle.com)

    Processing SQL Statements with JDBC (The Java™ Tutorials > JDBC Database Access > JDBC Basics) (oracle.com)
     public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException {
        String updateString =
          "update COFFEES set SALES = ? where COF_NAME = ?";
        String updateStatement =
          "update COFFEES set TOTAL = TOTAL + ? where COF_NAME = ?";
    
        try (PreparedStatement updateSales = con.prepareStatement(updateString);
             PreparedStatement updateTotal = con.prepareStatement(updateStatement))
        
        {
          con.setAutoCommit(false);
          for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
            updateSales.setInt(1, e.getValue().intValue());
            updateSales.setString(2, e.getKey());
            updateSales.executeUpdate();
    
            updateTotal.setInt(1, e.getValue().intValue());
            updateTotal.setString(2, e.getKey());
            updateTotal.executeUpdate();
            con.commit();
          }
        } catch (SQLException e) {
          JDBCTutorialUtilities.printSQLException(e);
          if (con != null) {
            try {
              System.err.print("Transaction is being rolled back");
              con.rollback();
            } catch (SQLException excep) {
              JDBCTutorialUtilities.printSQLException(excep);
            }
          }
        }
      }

        在原生jdbc中,使用?表示一个参数,?起到占位的作用。

        Spring jdbcTemplate为了传递参数方便,支持多种表示参数和设置参数的方式。

        表示参数的方式:

        a.占位,使用?表示

        b.命名,使用":参数名“表示

       

        传递参数的几种方式:

        a.不定大小的数组,集合。通常对应占位传参

        b.Map,Bean。通常对应命名参数

        来看看Spring JdbcTemplate的一些源码:

    JdbcTemplate
    org.springframework.jdbc.core.JdbcTemplate.batchUpdate(String, Collection<T>, int, ParameterizedPreparedStatementSetter<T>)
    org.springframework.jdbc.core.JdbcTemplate.batchUpdate(String, List<Object[]>)
    org.springframework.jdbc.core.JdbcTemplate.query(String, Object[], int[], ResultSetExtractor<T>)
    org.springframework.jdbc.core.JdbcTemplate.update(String, Object...)
    
    NamedParamterJdbcTempalte
    org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(String, Map<String, ?>)
    org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.queryForObject(String, SqlParameterSource, Class<T>)

        大部分传参都容易理解。命名参数传递总体比较优雅,比较好维护,除了写sql的时候会有那么一点点麻烦。

        但我们感兴趣的是SqlParameterSource

        我们来看下SqlParameterSource

     * @author Thomas Risberg
     * @author Juergen Hoeller
     * @since 2.0
     * @see NamedParameterJdbcOperations
     * @see NamedParameterJdbcTemplate
     * @see MapSqlParameterSource
     * @see BeanPropertySqlParameterSource
     */
    public interface SqlParameterSource 
    
    
    SqlParameterSource 接口有三个真正的实现:
    org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource
    org.springframework.jdbc.core.namedparam.MapSqlParameterSource
    org.springframework.jdbc.core.namedparam.EmptySqlParameterSource
    
    其中BeanPropertySqlParameterSource特别受一些人喜欢(有些人喜欢把任何东西包装成bean)
    
    BeanPropertySqlParameterSource的源码注释:
    SqlParameterSource implementation that obtains parameter valuesfrom bean properties of a given JavaBean object. The names of the beanproperties have to match the parameter names. 
    
    Uses a Spring BeanWrapper for bean property access underneath.

        下面来看看一个例子:

    @Override
        @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,rollbackFor=Exception.class)
        public int addFamilyWithNJT2(String name) {
            String sql="insert into family(name) values(:name)";
            //使用bean/pojo传递参数
            Family family=new Family(name);
            KeyHolder keyHolder=new GeneratedKeyHolder();        
            SqlParameterSource paramSource=new BeanPropertySqlParameterSource(family);
            int qty=njdbcTp.update(sql, paramSource, keyHolder);
            JSONObject.toJSONString(paramSource, true);
            return keyHolder.getKey().intValue();
        }

    三、批处理执行

        批量执行,多用于数据导入,采集的业务场景。

        当然,如果是对付高速大量的数据导入,不建议使用目前这种方式,建议直接使用原生的jdbc或者是数据库产生的api来操作。

        只不过,只要咱的数据量不是太大,一般也够用。

       下面来个例子:

       

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
        @Override
        public String batchExecute() {
            /**
             * 几种基本的batch操作
             */
            String sql = "insert into family(name,batch_no) values(?,?)";
            //1.0 ParameterizedPreparedStatementSetter
            List<Object[]> argList = new ArrayList<>();
            String batchNo = UUID.randomUUID().toString();
            for (int i = 0; i < 2; i++) {
                Object[] a = new Object[2];
                a[0] = UUID.randomUUID().toString();
                a[1] = batchNo;
                argList.add(a);
            }
            jdbcTp.batchUpdate(sql, argList, 4, (PreparedStatement ps, Object[] argument) -> {
                ps.setObject(1, argument[0]);
                ps.setObject(2, argument[1]);
            });
    
            //2.0 BatchPreparedStatementSetter
            BatchPreparedStatementSetter btss = new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    ps.setObject(1, argList.get(i)[0]);
                    ps.setObject(2, argList.get(i)[1]);
                }
    
                @Override
                public int getBatchSize() {
                    //这个大小不能超过参数集合大小,否则会报错。
                    return argList.size();
                }
            };
            int[] qtys = jdbcTp.batchUpdate(sql, btss);
            int ttlQty = 0;
            for (int i = 0, len = qtys.length; i < len; i++) {
                ttlQty += qtys[i];
            }
            System.out.println(ttlQty);
            return batchNo;
        }

    四、插入并返回自增主键值

     @Override
        public int addFamilyWithJT(String name) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcTp.update((Connection con) -> {
                String sql = "insert into family(name) values(?)";
                PreparedStatement ps =con.prepareStatement(sql, new String[]{"custom_id"});
                ps.setInt(1, Integer.valueOf(name));
                return ps;
            }, keyHolder);
            return keyHolder.getKey().intValue();
        }

    五、查询并返回bean/pojo

    @Override
        public HcsThirdsrv getByName(String serviceName) {
            String sql="SELECT\r\n"
                    + "  service_name,\r\n"
                    + "  service_name_cn,\r\n"
                    + "  instance_service_name,\r\n"
                    + "  service_desc,\r\n"
                    + "  status_flag,\r\n"
                    + "  add_time,\r\n"
                    + "  last_optime\r\n"
                    + "FROM\r\n"
                    + "  hcs_thirdsrv \n"
                    + "where  service_name=? or service_name_cn=? \n"
                    + "limit 1";
            RowMapper<HcsThirdsrv> rowMapper =new BeanPropertyRowMapper<HcsThirdsrv>(HcsThirdsrv.class);
            HcsThirdsrv srv=this.jdbcTp.queryForObject(sql, rowMapper,serviceName);
            return srv;
        }

        spring对于返回bean的支持并不友好,希望以后的版本,能够直接出一个不用rowMapper的(现在我们自己都是对jdbcTemplate再封装一遍)。

    六、性能

    1.比较-mybatis、原生jdbc

       主要是和mybatis比较,网上有专门的测试,例如:

       https://blog.csdn.net/liulk20170518/article/details/119358143

       但不是很多,也比较老旧。此外考虑到mybatis的不断进化。

       但毫无疑问,mybatis总会比jdbcTemplate慢一些,因为它花了额外的一些时间做七七八八的处理。

       执行速度上,原生jdbc>jdbcTemplate>mybatis,这是没有异议的。

       我们很多项目还大量使用mybatis,主要是出于工程考虑:用cpu的速度来弥补工程师的思维能力欠缺和手动速度,以提高工程效率。

       灵活优雅,有时候就是慢的代名词。

       简单粗暴,有时候能够更快解决问题。

    2.如何优化

       java的主要性能消耗在于数据转换、反射和解析,后者是先天不可调整,所以只能尽量减少反射操作和数据转换。

       所以,如果可能的话,执行sql的时候,尽量使用ListMap或者Map来返回结果。如果您看不习惯没有关系,只要快就可以了。

    七、异常

       spring提供了几个常见的异常:

    • BadSqlGrammarException --语法错误
    • CannotGetJdbcConnectionException -- 获取连接异常
    • IncorrectResultSetColumnCountException -- 错误结果集合列数异常,例如本来只要一列的,现在有2列
    • InvalidResultSetAccessException  --  不可用的结果集合读取异常,通常发生在列位置或者名称设置错误的情况
    • JdbcUpdateAffectedIncorrectNumberOfRowsException -- 实际影响行数超出预计的异常。例如本来应该只影响1行,但现在2行
    • LobRetrievalFailureException -- 读取大字段数据失败
    • SQLWarningException  -- sql警告异常。没有特别说明
    • UncategorizedSQLException -- 无分类sql异常

        实际执行的时候,更可能抛出的是org.springframework.dao下异常,这个包路径属于spring事务模块。

        在这个包里面,有更多更在明确的异常说明,例如下图:

    八、和spring其它组件关系

        影响比较多,其中主要是事务。其余略。

        如何保证事务?

        从原生jdbc可以看出,要完成一个事务,它的代码大概是这样的:

    https://www.cnblogs.com/azhqiang/p/4044127.html
    ------------------------------------------------------------
    private Connection conn = null;  
    private PreparedStatement ps = null;  
    try {  
        conn.setAutoCommit(false);  //将自动提交设置为false  
        ps.executeUpdate("修改SQL"); //执行修改操作  
        ps.executeQuery("查询SQL");  //执行查询操作                 
        conn.commit();      //当两个操作成功后手动提交  
    } catch (Exception e) {  
        conn.rollback();    //一旦其中一个操作出错都将回滚,使两个操作都不成功  
        e.printStackTrace();  
    } 

        保持事务的关键在于:使用同一个连接。

        以oracle为例子,不同的连接就是不同的会话,它们之间的事务是无关的。这个原则在绝大部分rdbms上是一样的成立的,这也即使rdbms存在的主要理由之一。

        如果spirng要完成事务的关键就是保证在事务传递的情况下,能够使用同样的一个连接(大部分情况下)。

        这里就涉及到spring的事务组件spring-tx和spring-jdbc中的DataSourceUtils。

        spring-tx如何如何保证连接的一致性,是一个有点小小复杂的事情,本文略,总之道路就是这个道理。

       

    九、适用场景

    1. 对性能要求较高的。
    2. 个性化要求高的。有些sql在mapper中写有点麻烦,尤其一些复杂的sql。
    3. 不想多一个依赖的,例如一些核心的东西。能够少依赖就尽量少依赖。这种情况下,可能直接上原生jdbc了,连jdbcTemplate都不用了

        因为这个缘故,所以spring提供了spring-jdbc组件。

        例如公用组件,核心工具,应该尽量少依赖外部的框架。当然实际做的时候,更可能取决于公司的规模和实例,项目和产品的性质。

        如果我们想的再长远一些,那么java是否真有存在的必要性?除了强大的生态,java并没有什么傲人的优势,而计算机最大优势就是人对于速度的最求。所以java要存活下去,则必须

    修改jvm和编译器,虽然现在已经不断在优化,但是还不够。最好的办法是提供一个编译器直接在运行主机上进行编译打包,不要耗费时间每次去询问。

         一处编译处处执行,并没有那么大的必要性,尤其是做项目。

       

  • 相关阅读:
    网页鼠标点击特效
    ElementUI 删除 el-table 当前选中行(不是selection列)
    Vue阻止冒泡
    Vue实现选项卡切换
    Vue的条件渲染
    Vue实现勾选后向数组都添加
    Vue实现商城里面多个商品计算,全选,删除
    VUE实现请求数据
    JS实现数组每次只显示5条数据
    移动端分享到微信和QQ
  • 原文地址:https://www.cnblogs.com/lzfhope/p/16071399.html
Copyright © 2020-2023  润新知