zoukankan      html  css  js  c++  java
  • spring成神之路第四十二篇:玩转 JdbcTemplate

    本来这篇文章要写spring事务的,但是事务中大部分案例会用到JdbcTemplate相关的功能,所以先把JdbcTemplate拿出来说一下。

    什么是JdbcTemplate?

    大家来回顾一下,java中操作db最原始的方式就是纯jdbc了,是不是每次操作db都需要加载数据库驱动、获取连接、获取PreparedStatement、执行sql、关闭PreparedStatement、关闭连接等等,操作还是比较繁琐的,spring中提供了一个模块,对jdbc操作进行了封装,使其更简单,就是本文要讲的JdbcTemplate,JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。

    下面我们来看一下JdbcTemplate到底怎么玩的?

    JdbcTemplate使用步骤

    1. 创建数据源DataSource

    2. 创建JdbcTemplate,new JdbcTemplate(dataSource)

    3. 调用JdbcTemplate的方法操作db,如增删改查

      public class DataSourceUtils {
          public static DataSource getDataSource() {
              org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
              dataSource.setDriverClassName("com.mysql.jdbc.Driver");
              dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
              dataSource.setUsername("root");
              dataSource.setPassword("root123");
              dataSource.setInitialSize(5);
              return dataSource;
          }
      }
      
      @Test
      public void test0() {
          //1.创建数据源DataSource
          DataSource dataSource = DataSourceUtils.getDataSource();
          //2.创建JdbcTemplate,new JdbcTemplate(dataSource)
          JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
          //3.调用JdbcTemplate的方法操作db,如增删改查
          List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from t_user");
          System.out.println(maps);
      }

    输出

    [{id=114, name=路人}, {id=115, name=java高并发}, {id=116, name=spring系列}]

    t_user表数据

    mysql> select id,name from t_user;
    +-----+---------------+
    | id  | name          |
    +-----+---------------+
    | 114 | 路人          |
    | 115 | java高并发    |
    | 116 | spring系列    |
    +-----+---------------+
    3 rows in set (0.00 sec)

    上面查询返回了t_user表所有的记录,返回了一个集合,集合中是一个Map,Map表示一行记录,key为列名,value为列对应的值。

    有没有感觉到特别的方便,只需要jdbcTemplate.queryForList("select * from t_user")这么简单的一行代码,数据就被获取到了。

    下面我们继续探索更强大更好用的功能。

    增加、删除、修改操作

    JdbcTemplate中以update开头的方法,用来执行增、删、改操作,下面来看几个常用的。

    无参情况

    Api

    int update(final String sql)

    案例

    @Test
    public void test1() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        int updateRows = jdbcTemplate.update("INSERT INTO t_user (name) VALUE ('maven系列')");
        System.out.println("影响行数:" + updateRows);
    }

    有参情况1

    Api

    int update(String sql, Object... args)

    案例

    sql中使用?作为占位符。

    @Test
    public void test2() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        int updateRows = jdbcTemplate.update("INSERT INTO t_user (name) VALUE (?)", "mybatis系列");
        System.out.println("影响行数:" + updateRows);
    }

    有参情况2

    Api

    int update(String sql, PreparedStatementSetter pss)

    通过PreparedStatementSetter来设置参数,是个函数式接口,内部有个setValues方法会传递一个PreparedStatement参数,我们可以通这个参数手动的设置参数的值。

    案例

    @Test
    public void test3() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        int updateRows = jdbcTemplate.update("INSERT INTO t_user (name) VALUE (?)", new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {
                ps.setString(1, "mysql系列");
            }
        });
        System.out.println("影响行数:" + updateRows);
    }

    获取自增列的值

    Api

    public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)

    案例

    @Test
    public void test4() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "INSERT INTO t_user (name) VALUE (?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();
        int rowCount = jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                //手动创建PreparedStatement,注意第二个参数:Statement.RETURN_GENERATED_KEYS
                PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                ps.setString(1, "获取自增列的值");
                return ps;
            }
        }, keyHolder);
        System.out.println("新记录id:" + keyHolder.getKey().intValue());
    }

    输出

    新记录id:122
    mysql> select id,name from t_user;
    +-----+-----------------------+
    | id  | name                  |
    +-----+-----------------------+
    | 114 | 路人                  |
    | 115 | java高并发            |
    | 116 | spring系列            |
    | 117 | maven系列             |
    | 118 | mysql系列             |
    | 122 | 获取自增列的值        |
    +-----+-----------------------+
    6 rows in set (0.00 sec)

    批量增删改操作

    Api

    int[] batchUpdate(final String[] sql);
    int[] batchUpdate(String sql, List<Object[]> batchArgs);
    int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes);

    案例

    @Test
    public void test5() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        List<Object[]> list = Arrays.asList(
                new Object[]{"刘德华"}, 
                new Object[]{"郭富城"}, 
                new Object[]{"张学友"}, 
                new Object[]{"黎明"});
        int[] updateRows = jdbcTemplate.batchUpdate("INSERT INTO t_user (name) VALUE (?)", list);
        for (int updateRow : updateRows) {
            System.out.println(updateRow);
        }
    }

    查询操作

    查询一列单行

    Api

    /**
     * sql:执行的sql,如果有参数,参数占位符?
     * requiredType:返回的一列数据对应的java类型,如String
     * args:?占位符对应的参数列表
     **/
    <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args)

    案例

    @Test
    public void test6() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String name = jdbcTemplate.queryForObject("select name from t_user where id = ?", String.class, 114);
        System.out.println(name);
    }

    输出

    路人

    db中对应数据

    mysql> select name from t_user where id = 114;
    +--------+
    | name   |
    +--------+
    | 路人   |
    +--------+
    1 row in set (0.00 sec)

    使用注意

    若queryForObject中sql查询无结果时,会报错

    如id为0的记录不存在

    mysql> select name from t_user where id = 0;
    Empty set (0.00 sec)
    @Test
    public void test7() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String name = jdbcTemplate.queryForObject("select name from t_user where id = ?", String.class, 0);
        System.out.println(name);
    } 

    运行,会弹出一个异常EmptyResultDataAccessException,期望返回一条记录,但实际上却没有找到记录,和期望结果不符,所以报错了

    org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0
    
     at org.springframework.dao.support.DataAccessUtils.nullableSingleResult(DataAccessUtils.java:97)
     at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:784)

    这种如何解决呢,需要用到查询多行的方式来解决了,即下面要说到的queryForList相关的方法,无结果的时候会返回一个空的List,我们可以在这个空的List上面做文章。

    查询一列多行

    Api

    以queryForList开头的方法。

    <T> List<T> queryForList(String sql, Class<T> elementType);
    <T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args);
    <T> List<T> queryForList(String sql, Object[] args, Class<T> elementType);
    <T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType);

    注意:

    上面这个T虽然是泛型,但是只支持Integer.class String.class 这种单数据类型的,自己定义的Bean不支持。(所以用来查询单列数据)

    elementType:查询结果需要转换为哪种类型?如String、Integer、Double。

    案例

    @Test
    public void test8() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        //<T> List<T> queryForList(String sql, Class<T> elementType);
        List<String> list1 = jdbcTemplate.queryForList("select name from t_user where id>131", String.class);
        System.out.println("list1:" + list1);
    
        //<T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object... args);
        List<String> list2 = jdbcTemplate.queryForList("select name from t_user where id>?", String.class, 131);
        System.out.println("list2:" + list2);
    
        //<T> List<T> queryForList(String sql, Object[] args, Class<T> elementType);
        List<String> list3 = jdbcTemplate.queryForList("select name from t_user where id>?", new Object[]{131}, String.class);
        System.out.println("list3:" + list3);
    
        //<T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType);
        List<String> list4 = jdbcTemplate.queryForList("select name from t_user where id>?", new Object[]{131}, new int[]{java.sql.Types.INTEGER}, String.class);
        System.out.println("list4:" + list4);
    }

    输出

    list1:[郭富城, 张学友, 黎明]
    list2:[郭富城, 张学友, 黎明]
    list3:[郭富城, 张学友, 黎明]
    list4:[郭富城, 张学友, 黎明]

    sql结果:

    mysql> select name from t_user where id>131;
    +-----------+
    | name      |
    +-----------+
    | 郭富城    |
    | 张学友    |
    | 黎明      |
    +-----------+
    3 rows in set (0.00 sec)

    查询单行记录,将记录转换成一个对象

    Api

    <T> T queryForObject(String sql, RowMapper<T> rowMapper);
    <T> T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper);
    <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper);
    <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);

    上面这些方法的参数中都有一个rowMapper参数,行映射器,可以将当前行的结果映射为一个自定义的对象。

    @FunctionalInterface
    public interface RowMapper<T> {
    
     /**
      * @param ResultSet 结果集
      * @param 当前结果集的第几行
      * @return 当前行的结果对象,将当前行的结果映射为一个自定义的对象返回
      */
     @Nullable
     T mapRow(ResultSet rs, int rowNum) throws SQLException;
    
    }

    JdbcTemplate内部会遍历ResultSet,然后循环调用RowMapper#mapRow,得到当前行的结果,将其丢到List中返回,如下:

    List<T> results = new ArrayList<>();
    int rowNum = 0;
    while (rs.next()) {
        results.add(this.rowMapper.mapRow(rs, rowNum++));
    }
    return results;

    案例

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class User {
        private Integer id;
        private String name;
    }
    @Test
    public void test9() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "select id,name from t_user where id = ?";
        //查询id为34的用户信息
        User user = jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
            @Nullable
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User();
                user.setId(rs.getInt(1));
                user.setName(rs.getString(1));
                return user;
            }
        }, 134);
        System.out.println(user);
    }

    输出

    User(id=134, name=134)

    使用注意

    当queryForObject中sql查询无结果的时候,会报错,必须要返回一行记录

    查询单行记录,返回指定的javabean

    RowMapper 有个实现了类 BeanPropertyRowMapper,可以将结果映射为javabean。

    @Test
    public void test10() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "select id,name from t_user where id = ?";
        //查询id为34的用户信息
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        User user = jdbcTemplate.queryForObject(sql, rowMapper, 134);
        System.out.println(user);
    }

    查询多列多行,每行结果为一个Map

    Api

    List<Map<String, Object>> queryForList(String sql);
    List<Map<String, Object>> queryForList(String sql, Object... args);

    每行结果为一个Map,key为列名小写,value为列对应的值。

    案例

    @Test
    public void test11() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "select id,name from t_user where id>?";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, 130);
        System.out.println(maps);
    }

    输出

    [{id=131, name=刘德华}, {id=132, name=郭富城}, {id=133, name=张学友}, {id=134, name=黎明}]

    查询多列多行,将结果映射为javabean

    Api

    <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)

    案例

    @Test
    public void test12() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "select id,name from t_user where id>?";
        List<User> maps = jdbcTemplate.query(sql, new RowMapper<User>() {
            @Nullable
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                User user = new User();
                user.setId(rs.getInt(1));
                user.setName(rs.getString(1));
                return user;
            }
        }, 130);
        System.out.println(maps);
    }

    运行输出

    [User(id=131, name=刘德华), User(id=132, name=郭富城), User(id=133, name=张学友), User(id=134, name=黎明)]

    更简单的方式,使用BeanPropertyRowMapper

    @Test
    public void test13() {
        DataSource dataSource = DataSourceUtils.getDataSource();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "select id,name from t_user where id>?";
        List<User> maps = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), 130);
        System.out.println(maps);
    }

    输出

    [User(id=131, name=刘德华), User(id=132, name=郭富城), User(id=133, name=张学友), User(id=134, name=黎明)]

    总结

    1. 使用注意:JdbcTemplate中的getObject开头的方法,要求sql必须返回一条记录,否则会报错
    2. BeanPropertyRowMapper可以将行记录映射为javabean
    3. JdbcTemplate采用模板的方式操作jdbc变的特别的容易,代码特别的简洁,不过其内部没有动态sql的功能,即通过参数,动态生成指定的sql,mybatis在动态sql方面做的比较好,大家用的时候可以根据需求进行选择。

    案例源码

    git地址:
    https://gitee.com/javacode2018/spring-series
    
    本文案例对应源码:
    spring-serieslesson-003-jdbctemplatesrcmainjavacomjavacode2018jdbctemplatedemo1Demo1Test.java

    路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。

    来源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648936449&idx=2&sn=da1e98e5914821f040d5530e8ca9d9bc&scene=21#wechat_redirect

  • 相关阅读:
    HearthBuddy投降插件2019-11-01的使用
    正则表达式在线分析 regex online analyzer
    Tips to write better Conditionals in JavaScript
    The fileSyncDll.ps1 is not digitally signed. You cannot run this script on the current system.
    Cannot capture jmeter traffic in fiddler
    JMETER + POST + anti-forgery token
    input type color
    HearthBuddy修改系统时间
    What are all the possible values for HTTP “Content-Type” header?
    UDK性能优化
  • 原文地址:https://www.cnblogs.com/konglxblog/p/15515827.html
Copyright © 2011-2022 走看看