Spring—JdbcTemplate模板类的使用(补充)

Spring JDBC抽象框架core包提供了JDBC模板类,其中JdbcTemplate是core包的核心类,所以其他模板类都是基于它封装完成的,JDBC模板类是第一种工作模式。 

JdbcTemplate类通过模板设计模式帮助我们消除了冗长的代码,只做需要做的事情(即可变部分),并且帮我们做哪些固定部分,如连接的创建及关闭。

JdbcTemplate类对可变部分采用回调接口方式实现,如ConnectionCallback通过回调接口返回给用户一个连接,从而可以使用该连接做任何事情StatementCallback通过回调接口返回给用户一个Statement,从而可以使用该Statement做任何事情等等,还有其他一些回调接口

在此之前要先了解基础:

JdbcTemplate类支持的回调类:

预编译语句及存储过程创建回调

  • 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;
    1. PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement
    2. CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement
@Test    
public void testPpreparedStatement1() {    
  int count = jdbcTemplate.execute(new PreparedStatementCreator() {    
     @Override    
     public PreparedStatement createPreparedStatement(Connection conn)    
         throws SQLException {    
         return conn.prepareStatement("select count(*) from test");    
     }}, new PreparedStatementCallback<Integer>() {    
     @Override    
     public Integer doInPreparedStatement(PreparedStatement pstmt)    
         throws SQLException, DataAccessException {    
         pstmt.execute();    
         ResultSet rs = pstmt.getResultSet();    
         rs.next();    
         return rs.getInt(1);    
      }});        
   Assert.assertEquals(0, count);    
}   

首先使用PreparedStatementCreator创建一个预编译语句,其次由JdbcTemplate通过PreparedStatementCallback回调传回,由用户决定如何执行该PreparedStatement。此处我们使用的是execute方法。

预编译语句设值回调

  • 预编译语句设值回调:用于给预编译语句相应参数设值;
    • PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;
    • BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;
@Test    
public void testPreparedStatement2() {    
  String insertSql = "insert into test(name) values (?)";    
  int count = jdbcTemplate.update(insertSql, new PreparedStatementSetter() {    
      @Override    
      public void setValues(PreparedStatement pstmt) throws SQLException {    
          pstmt.setObject(1, "name4");    
  }});    
  Assert.assertEquals(1, count);        
  String deleteSql = "delete from test where name=?";    
  count = jdbcTemplate.update(deleteSql, new Object[] {"name4"});    
  Assert.assertEquals(1, count);    
}  

通过JdbcTemplate的int update(String sql, PreparedStatementSetter pss)执行预编译sql,其中sql参数为“insert into test(name) values (?) ”,该sql有一个占位符需要在执行前设值,PreparedStatementSetter实现就是为了设值,使用setValues(PreparedStatement pstmt)回调方法设值相应的占位符位置的值。

JdbcTemplate也提供一种更简单的方式“update(String sql, Object… args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter。

自定义功能回调

  • 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作;
    • ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;
    • StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;
    • PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;
    • CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;

结果集处理回调

  • 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式;
    • RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。
    • RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。
    • ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;
@Test    
public void testResultSet1() {    
  jdbcTemplate.update("insert into test(name) values('name5')");    
  String listSql = "select * from test";    
  List result = jdbcTemplate.query(listSql, new RowMapper<Map>() {    
      @Override    
      public Map mapRow(ResultSet rs, int rowNum) throws SQLException {    
          Map row = new HashMap();    
          row.put(rs.getInt("id"), rs.getString("name"));    
          return row;    
  }});    
  Assert.assertEquals(1, result.size());    
  jdbcTemplate.update("delete from test where name='name5'");         
}   

RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法将结果集的每一行转换为一个Map,当然可以转换为其他类,如表的对象画形式。

@Test    
public void testResultSet2() {    
  jdbcTemplate.update("insert into test(name) values('name5')");    
  String listSql = "select * from test";    
  final List result = new ArrayList();    
  jdbcTemplate.query(listSql, new RowCallbackHandler() {    
      @Override    
      public void processRow(ResultSet rs) throws SQLException {    
          Map row = new HashMap();    
          row.put(rs.getInt("id"), rs.getString("name"));    
          result.add(row);    
  }});    
  Assert.assertEquals(1, result.size());    
  jdbcTemplate.update("delete from test where name='name5'");    
}  

RowCallbackHandler接口也提供方法processRow(ResultSet rs),能将结果集的行转换为需要的形式。

@Test    
public void testResultSet3() {    
  jdbcTemplate.update("insert into test(name) values('name5')");    
  String listSql = "select * from test";    
  List result = jdbcTemplate.query(listSql, new ResultSetExtractor<List>() {    
      @Override    
      public List extractData(ResultSet rs)    
     throws SQLException, DataAccessException {    
          List result = new ArrayList();    
          while(rs.next()) {    
              Map row = new HashMap();    
              row.put(rs.getInt("id"), rs.getString("name"));    
              result.add(row);    
           }    
           return result;    
  }});    
  Assert.assertEquals(0, result.size());    
  jdbcTemplate.update("delete from test where name='name5'");    
}  

ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户决定如何处理该结果集

当然JdbcTemplate提供更简单的queryForXXX方法,来简化开发:

//1.查询一行数据并返回int型结果    
jdbcTemplate.queryForInt("select count(*) from test");    
//2. 查询一行数据并将该行数据转换为Map返回    
jdbcTemplate.queryForMap("select * from test where name='name5'");    
//3.查询一行任何类型的数据,最后一个参数指定返回结果类型    
jdbcTemplate.queryForObject("select count(*) from test", Integer.class);    
//4.查询一批数据,默认将每行数据转换为Map         
jdbcTemplate.queryForList("select * from test");    
//5.只查询一列数据列表,列类型是String类型,列名字是name    
jdbcTemplate.queryForList("    
select name from test where name=?", new Object[]{"name5"}, String.class);    
//6.查询一批数据,返回为SqlRowSet,类似于ResultSet,但不再绑定到连接上    
SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");   

常用:

queryForObject()      # 必须且只能返回一条记录,且只能查询一个字段

  • <T>  queryForObject(String sql, T.class)    //不需向sql语句传递参数
  • <T>  queryForObject(String sql, Object[]  args, T.class)    //args是sql语句中?对应的值
  • <T>  queryForObject(String sql, T.class, Object… args)
     String sql="select id from student_tb where name = ? and gender = ?"
     Object[] args=new Object[]{"张三",1};
     int id=jdbcTemplate.queryForObject(sql,args,int.class);

ForObject,顾名思义,必须且只能返回一条记录,如果返回多条记录或没有记录匹配,都会报错;且只能查询一个字段。 

queryForList()      #可以返回0条或多条记录,普通类型的List只能查询一个字段,Map类型的List可以查询多个字段

  • List<T>  queryForList(String sql, T.class)
  • List<T>  queryForList(String sql, Object[]  args, T.class)    //args是sql语句中?的对应值(实参)
  • List<T>  queryForList(String sql, T.class, Object…args)
 String sql="select name from student_tb";
        List<String> list=jdbcTemplate.queryForList(sql,String.class);
        for (String name:list){
            System.out.println(name);
        } 

基本数据类型的List,只能选中数据表的一列。

  • List<Map<String,Object>>   queryForList(String sql)
  • List<Map<String,Object>>   queryForList(String sql, Object…args)
 String sql="select * from student_tb";
        List<Map<String,Object>> list=jdbcTemplate.queryForList(sql);
        //list装的是结果集中所有的记录,一个map装一条记录
        for (Map<String,Object> map:list){
            //map的key是字段名,value是该字段的值。get()的返回值是Object。
            Object id = map.get("id");
            Object name = map.get("name");
            Object age = map.get("age");
            System.out.println("id:"+id+"\tname:"+name+"\tage:"+age);
        }

Map类型的List,可以选择多列。字段名都是String,值可能是各种类型,所以使用Map<String, Obejct>。

queryForMap()     #可以查询多个字段,但只能装一条记录

  • Map<String, Obejct>   queryForMap(String sql)
  • Map<String, Object>   queryForMap(String sql, Object…args)
     String sql="select * from student_tb where id = 1";
        Map<String,Object> map=jdbcTemplate.queryForMap(sql);
        Object name = map.get("name");
        Object age = map.get("age");
        System.out.println("name:"+name+"\tage:"+age);

因为是Map,可以装多个字段,但只能装一条记录。

如果返回多条记录,则只取第一条来装;如果没有匹配的记录,会报错。

queryForRowSet()     #返回结果集

  • SqlRowSet  queryForRowSet(String sql)     
  • SqlRowSet  queryForRowSet(String sql, Object…args)    //args是sql语句中的?对应的值。
     String sql="select * from student_tb";
        SqlRowSet rowSet=jdbcTemplate.queryForRowSet(sql);
        //遍历结果集
        while (rowSet.next()){
            // 参数指定列名,可以用String类型的字段名,也可以用int型的值(该字段在结果集中的第几列,从1开始)
            // int id = rowSet.getInt(1);
            int id = rowSet.getInt("id");
            String name = rowSet.getString("name");
            //......
        }

query()      #将结果集的记录映射为bean类型的List

  • List<T>   query(String sql, RowMapper<T>  rowMapper)
  • List<T>   query(String sql, Object[]  args, RowMapper<T>  rowMapper)
  • List<T>   query(String sql, RowMapper<T>  rowMapper, Object…args)
public class Student {
    private int id;
    private String name;
    private int age;
    ………………………………………………
}
 String sql="select * from student_tb";
        RowMapper<Student> rowMapper=new BeanPropertyRowMapper<>(Student.class);
        List<Student> list=jdbcTemplate.query(sql,rowMapper);
        System.out.println(list);

RowMapper是接口,BeanPropertyRowMapper是spring提供的唯一的实现类。

query()只能将结果集的记录映射为Bean类型的List,不能映射为基本类型的List。

映射时会自动将结果集中的字段赋给同名的成员变量,所以要求Bean的成员变量名要与结果集的字段名相同,很死板。

如果结果集中没有同名的字段,该成员变量就不会被映射(值是JVM赋的初始值)。 映射时是调用setter方法给Bean的成员变量赋值,所以Bean要提供setter方法。

在Spring中尽量不要使用new来创建Bean的实例,上面的RowMapper实例可以这样创建:

<bean name="beanPropertyRowMapper" class="org.springframework.jdbc.core.BeanPropertyRowMapper">
      <!-- value指定目标类型 -->
        <property name="mappedClass" value="com.chy.model.Student" />
</bean>
RowMapper<Student> rowMapper = applicationContext.getBean("beanPropertyRowMapper",BeanPropertyRowMapper.class);