Spring Boot 2.0 读书笔记_07:Spring JDBC Template

版权声明:未经博主本人同意,请勿私自转发分享。 https://blog.csdn.net/Nerver_77/article/details/84942903

5. Spring JDBC Template

写在开头,JDBC Template 是 Spring 框架在JDBC基础上做了一定的封装。相比当下的DAO层框架,封装度相对较低,很早之前用过几次,由于SQL注入的Web攻击场景,JDBC Template具有很好的防范。

关于SQL注入:JDBC Template中对参数化的SQL查询有着良好的验证机制,因此建议使用参数化SQL的方式,切勿采用SQL拼装的方式。

JDBC Template 模板测试Demo

  1. 配置类:DataSourceConfig.java

     @Configuration
     public class DataSourceConfig {
    
       @Bean(name = "dataSource")
       public DataSource datasource(Environment env) {
         HikariDataSource ds = new HikariDataSource();
         ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
         ds.setUsername(env.getProperty("spring.datasource.username"));
         ds.setPassword(env.getProperty("spring.datasource.password"));
         ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
         return ds;
       }
     }
    

    Environment 类在 Spring Boot 中代表了环境上下文,包含了 application.properties 配置属性、JVM系统属性和操作系统环境变量。

    这里的数据库连接池采用了:HikariCP

  2. JDBC模板注入(Dao层)

     @Repository
     public class UserDao (
    
       @Autowired
       JdbcTemplate jdbcTempalte;
     )
    
  3. 基础操作

    • 查询

      关于查询的返回结果,均采用包装类型。比如,查询count、sum等返回的数据,此外还可以将返回结果包装为POJO、List等。

      例如:
      返回 department_id 下 user 数目总和的查询

      扫描二维码关注公众号,回复: 4679458 查看本文章
      String sql = "select count(*) from user where department_id = ?";
      Integer res = jdbcTeplate.queryForObject(sql, Integer.class, 1);
      

      上述例子中含有参数绑定:department_id --> 1

      返回POJO实例,JDBC Template需要一个RowMapper,将结果集ResultSet映射成POJO对象。
      RowMapper 从字面意思上讲 [行映射] ,可以针对业务层次去实现该接口,进行结果集元组向对象的映射配置。

        @FunctionalInterface
        public interface RowMapper<T> {
        	@Nullable
        	T mapRow(ResultSet rs, int rowNum) throws SQLException;
        }
      

      在案例ch5中采用了内部静态类的方式创建了 UserRowMapper:

        static class UserRowMapper implements RowMapper<User> {
        	public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        		User user = new User();
        		user.setId(rs.getInt("id"));
        		user.setName(rs.getString("name"));
        		user.setDepartmentId(rs.getInt("department_id"));
        		return user;
        	}
        }
      

      可以看出,在UserRowMapper中,创建了User对象,并根据ResultSet结果集进行数据的获取,通过User对象的setter方法进行对象属性的填充。

      结合开头提到的SQL注入问题,做一个小测试:

      final String sql_1 = "select * from user where id = ?" –> User getUserById
      final String sql_2 = "select * from user where department_id = ?" –> List<User> getUserList

      1. Controller层中采用GET方式进行数据请求:

        @GetMapping("/sql/test")
        @ResponseBody
        public Object getUserById(@RequestParam(value = "id") String id) {
        	return userDao.getUserById(id);
        }
      
        @GetMapping("/sql/test1")
        @ResponseBody
        public List<User> getUserList(@RequestParam(value = "id") String d_id) {
        	return userDao.getUserList(d_id);
        }
      

      2. Dao层中注入JDBC Template对象,并分别采用query和queryForObject方法进行操作,由于操作的数据集合为POJO(集合),所以这里采用了上述的 UserRowMapper 对返回的Result结果进行封装。

        public User getUserById(String id) {
        	String sql = "select * from user where id = ?";
        	return jdbcTempalte.queryForObject(sql, new UserRowMapper(), id);
        }
      
        public List<User> getUserList(String d_id) {
        	String sql = "select * from user where department_id = ?";
        	return jdbcTempalte.query(sql, new UserRowMapper(), d_id);
        }
      

      提到的参数化的SQL,就是将SQL的可变参数部分和SQL语句主干区分开,通过方法的方式进行参数的配置。
      在JDBC中并不提倡拼写SQL的做法,相比之下更推荐PreparedStatement:Statement的子接口,可以传入带占位符的SQL语句,提供了补充占位符变量的方法,同时也可以对SQL注入语句进行规避处理,是不是和JDBC Template的方法参数签名很像呢?

      3. 启动项目,使用Postman进行分别测试,这里将请求的参数进行处理,模拟SQL注入情景:id = value or 1=1

      1=1为永真,逻辑表达式中or的成立规则为:一真则真、都假才假。上述情况在id不存在或错误时仍可以实现where条件的正确性。类比登录等场景,username、password在做验证的时候被SQL注入后,也会出现上述类似场景,从而实现越过登录验证,进入操作页面或系统内部。

      数据现状:
      在这里插入图片描述
      先进行queryForObject单个对象的查询小测试:

      • 查询数据库中存在的数据
        在这里插入图片描述
      • 查询数据库中不存在的数据
        在这里插入图片描述
        控制台输出的错误为:org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0
        在这里插入图片描述
        起初并没有对这个异常很感兴趣,后来进行query方法测试的时候,若返回数据为空,则返回一个空数组[ ],于是对这个queryForObject产生了兴趣。该异常的大体意思是空的结果集,后面还追加说明了结果集的大小,期望返回size=1,实际却为0。此外,在浏览器端的500错误也是客户端用户不想看到的,于是带着问题Debug了一下,顺便对返回结果进行异常捕获,返回null。
        在这里插入图片描述

      query方法的测试,返回List<User>
      在这里插入图片描述

      补充:queryForMap方法可以返回map,使用Map作为查询结果有非常多的弊端,最严重的的弊端是Map本身不适合程序阅读。通过Map了解ResultSet结果集难度较大,以及针对多种数据库,数据库字段类型,Map的弊端越显严重。

    • 修改

      JDBC Template 提供 update 方法来实现SQL的修改语句,包括新增、修改、删除、执行存储过程等。

        public void updateInfo(User user) {
          String sql = "update user set name=? and departmet_id=? where id = ?";
          jdbcTempalte.update(sql, user.getName(), user.getDepartmentId(), user.getId());
        }
      

      数据库记录插入操作语句同上,对于MySQL、SqlServer等数据库,含有自增的主键序列,此时需要提供一个KeyHolder来放置返回序列。
      在这里插入图片描述

      update方法签名中需要传入两个参数:PreparedStatement对象,keyHolder对象。其中,keyHolder中包含了自增长字段的结果。但是此处的结果序列无法确定序列类型,需要根据具体业务转换其序列类型。
      在这里插入图片描述

    • NamedParameterJdbcTemplate (提供命名参数绑定的功能)
      相比传统的JDBCTemplate,用户只能通过?占位符声明参数,并使用索引绑定参数,必须确保方法参数中的索引?占位符的位置匹配正确,才可以使得方法执行无误。

      NamedParameterJdbcTemplate模板了支持命名参数变量的SQL,同理在Dao层自动注入NamedParameterJdbcTemplate即可。

      上述:department_id 下 user 数目总和的查询 可以变更为:

        public Integer totalUserInDepartment2(Long departmentId) {
        	String sql = "select count(1) from user where department_id = :deptId";
        	// SQL参数映射map:k-v形式存储参数与值
        	MapSqlParameterSource namedParameters = new MapSqlParameterSource();
        	// key:SQL参数;value:方法接受参数 
        	namedParameters.addValue("deptId", departmentId);
        	// 执行方法,将上述参数映射map对象传入即可
        	Integer count = namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
        	return count;
        }
      

      总结上述代码:

      1. 在SQL语句中,使用:paramName 形式替代 ? 占位符

      2. MapSqlParameterSource,SQL参数映射map对象,以 k-v 形式存储参数与值

      3. Spring 提供 SQLParameterSource 类来封装任意的 JavaBean,为 NamedParameterJdbcTemplate 提供参数。

         public void updateInfoByNamedJdbc(User user) {
           String sql = "update user set name = :name and departmet_id = :departmentId 
         	where id = :id";
           SqlParameterSource source = new BeanPropertySqlParameterSource(user);
           namedParameterJdbcTemplate.update(sql, source);
         }
        

        这里需要注意:sql语句中的参数:name、departmentId、id 需要与 user对象的成员属性对应。

猜你喜欢

转载自blog.csdn.net/Nerver_77/article/details/84942903
今日推荐