zoukankan      html  css  js  c++  java
  • JdbcTemplate完全学习

    概述

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

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

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

    Spring除了提供JdbcTemplate核心类,还提供了基于JdbcTemplate实现的NamedParameterJdbcTemplate类用于支持命名参数绑定、 SimpleJdbcTemplate类用于支持Java5+的可变参数及自动装箱拆箱等特性。

    JdbcTemplate类支持的回调类:

    • 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;

    PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;

    CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;

    • 预编译语句设值回调:用于给预编译语句相应参数设值;

             PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;

             BatchPreparedStatementSetter:;类似于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)来处理结果集,用户必须处理整个结果集;

    下面详细讲解jdbcTmeplate的CRUD操作:

    (一)增加、删除、修改操作:

    1)增加、更新、删除(一条sql语句)(sql固定,不需要参数):

        (a) int update(final String sql)

          其中sql参数为需要传入的插入sql语句。

        (b)int update(PreparedStatementCreator psc)

     
        public void test() {
            jdbcTemplate.update(new PreparedStatementCreator() {
                
                @Override
                public PreparedStatement createPreparedStatement(Connection conn)
                        throws SQLException {
                    return conn.prepareStatement("insert into test(name) values('name1')");   
                }
            });
        }
     

         (c)如果需要返回新插入数据的主键,采用如下方法(使用KeyHolder keyholder=new GeneratedKeyHolder();获得主键,jdbcTemplate和NamedParameterJdbcTemplate都可以通过此方法获得主键):

           int update(PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)

     
    public void test() {
            KeyHolder keyHolder = new GeneratedKeyHolder(); 
            jdbcTemplate.update(new PreparedStatementCreator() {
                
                @Override
                public PreparedStatement createPreparedStatement(Connection conn)
                        throws SQLException {
                    
                    return conn.prepareStatement("insert into test(name) values('name1')");  
                     
                }
            },keyHolder);
            int i = keyHolder.getKey().intValue();//这就是刚插入的数据的主键
        }
     

    2)增加、更新、删除(一条sql语句)(sql需要注入参数填充‘?’):

      (a)int update(String sql, PreparedStatementSetter pss)

     
    public void test() {
            String sql = "insert into test(name) values (?)";
            //返回的是更新的行数
            int count = jdbcTemplate.update(sql, new PreparedStatementSetter(){
    
                @Override
                public void setValues(PreparedStatement pstmt)
                        throws SQLException {
                    pstmt.setObject(1, "name4"); 
                }
            });
        }
     

          (b)int update(String sql, Object[] args, int[] argTypes)

    其中参数含义:   sql:预处理sql语句; args:sql需要注入的参数; argTypes:需要注入的sql参数的JDBC类型(java.sql.Types中来获取类型的常量);

     
        public void test() {
            String sql = "insert into test(name,age,create_date) values (?,?,?)";
            Date now = new Date(System.currentTimeMillis());
            //返回的是更新的行数
            int count = jdbcTemplate.update(sql, new Object[]{"小明",14,now}, new int[]{Types.VARCHAR,Types.INTEGER,Types.DATE});
        }
     

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

    其实内部还是调用方法a实现的,JdbcTemplate提供这种更简单的方式“update(String sql, Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter(上面方法a)。

     
    public void test() {
            String sql = "insert into test(name,age,create_date) values (?,?,?)";
            Date now = new Date(System.currentTimeMillis());
            //返回的是更新的行数
            int count = jdbcTemplate.update(sql, "小明", 14, now);
        }
     
     
    public void test() {
            String sql = "insert into test(name,age,create_date) values (?,?,?)";
            Date now = new Date(System.currentTimeMillis());
            //返回的是更新的行数
            int count = jdbcTemplate.update(sql, new Object[]{"小明",14,now});
        }
     

    这两种实际上调用的都是该方法,由此可见Object...args实际上就是可变的数组,而数组长度是固定的,必须先定义一个数组,而Object...args在传递时参数可以任意,所以也可以传递一个固定的Object数组。

        (d)int update(PreparedStatementCreator psc)

    使用该方法可以自己使用原始jdbc方式给预编译sql注入参数,来进行增加、删除、更新操作:

     
    public void test(final Customer customer) {//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用)
            //方法局部必须是final的,匿名内部类中才能引用
            final String sql = "insert into test(name,age,create_date) values (?,?,?)";
            Date now = new Date(System.currentTimeMillis());
            //返回的是更新的行数
            int count = jdbcTemplate.update(new PreparedStatementCreator() {
                
                @Override
                public PreparedStatement createPreparedStatement(Connection conn)
                        throws SQLException {
                    PreparedStatement ps = conn.prepareStatement(sql);
                    ps.setString(1, customer.getName());
                    ps.setInt(2, customer.getAge());
                    ps.setDate(3, customer.getCreateDate());
                    
                    return ps;
                }
            });
            
        }
     

    如果需要返回插入的主键,只能用此方法,增加KeyHolder参数:

     
    public void test(final Customer customer) {//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用)
            KeyHolder keyHolder = new GeneratedKeyHolder(); 
            //方法局部必须是final的,匿名内部类中才能引用
            final String sql = "insert into test(name,age,create_date) values (?,?,?)";
            Date now = new Date(System.currentTimeMillis());
            //返回的是更新的行数
            int count = jdbcTemplate.update(new PreparedStatementCreator() {
                
                @Override
                public PreparedStatement createPreparedStatement(Connection conn)
                        throws SQLException {
                    PreparedStatement ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS); //有的数据库版本不一样,需要添加第二个参数,不然会报错;
              ps.setString(1, customer.getName());
              ps.setInt(2, customer.getAge());
              ps.setDate(3, customer.getCreateDate());
              return ps;
            }
        },keyHolder);
        
        int i = keyHolder.getKey().intValue();//这就是刚插入的数据的主键 }
     

    3)批量增加、删除、更新数据(多条sql语句)

      (a)批量执行多条sql(固定的sql,不需要注入参数,但是sql格式不固定)

          int[] batchUpdate(final String[] sql)

    参数是一个String数组,存放多条sql语句;返回值是int数组,即每条sql更新影响的行数。

      (b)批量执行多条sql(预处理sql,需要注入参数)

        int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss)

    参数sql:一条预处理sql(如果是批量处理预处理sql,那么sql的格式就是固定的,只填充参数而已);第二个参数就是回调类,前面有统一介绍回调类。

    举两个例子,一个更新,一个插入:

     
    批量插入:
    public void test(final List<Customer> customer) {//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用)
            
            String sql = "insert into test(name,age,create_date) values (?,?,?)";
            //返回的是更新的行数
            int[] count = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                
                @Override
                public void setValues(PreparedStatement ps, int i)
                        throws SQLException {
                    //注入参数值
                    ps.setString(1, customer.get(i).getName());
                    ps.setInt(2, customer.get(i).getAge());
                    ps.setDate(3, customer.get(i).getCreateDate());
                }
                
                @Override
                public int getBatchSize() {
                    //批量执行的数量
                    return customer.size();
                }
            });
            
        }
     
     
    批量更新:
    public void test(final List<Customer> customer) {//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用)
            String sql = "update test set name = ?,age = ? where id = ?";
            //返回的是更新的行数
            int[] count = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                
                @Override
                public void setValues(PreparedStatement ps, int i)
                        throws SQLException {
                    //注入参数值
                    ps.setString(1, customer.get(i).getName());
                    ps.setInt(2, customer.get(i).getAge());
                    ps.setInt(3, customer.get(i).getId());
                }
                
                @Override
                public int getBatchSize() {
                    //批量执行的数量
                    return customer.size();
                }
            });
            
        }
     

      (c)批量处理多条预处理sql语句还有下面几种简单方法(参数和前面类似,这里就不详解):

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

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

    前面讲了增加、删除、更新操作,这节讲一下查询。

    查询操作:

    (一)查询一个值(不需要注入参数)

    queryForObject(String sql, Class<T> requiredType);

    注意:参数requiredType只能是String,Integer这种类型,不能是自定义的实体类型,只能返回一个值,不能映射对象(映射对象下面会说);

      sql:预处理sql;requiredType:查询单列结果的类型;

    public void test() {
            String sql = "select count(*) from test";
            int count = jdbcTemplate.queryForObject(sql, Integer.class);
            
        }

    (二)查询一个值(使用预处理sql,需要注入参数)

    queryForObject(String sql, Object[] args, Class<T> requiredType);

    public void test(Integer id) {
            String sql = "select name from test where id = ?";
            String name = jdbcTemplate.queryForObject(sql, new Object[]{id}, String.class);
        }

    还有如下方式:queryForObject(String sql, Object[] args, int[] argTypes, Class<T> requiredType);

    (三)查询单行记录,转换成一个对象(固定sql,不需要参数)

    <T> T queryForObject(String sql, RowMapper<T> rowMapper)

    public void test() {
            String sql = "select name,age from test where id = 10";
            Customer customer = jdbcTemplate.queryForObject(sql, new RowMapper<Customer>() {
    
                @Override
                public Customer mapRow(ResultSet rs, int i)
                        throws SQLException {
                    Customer c = new Customer();
                    c.setName(rs.getString("name"));
                    c.setAge(rs.getInt("age"));
                    return c;
                }
                
            });
        }

    (四)查询单行记录,转换成一个对象(预处理sql,需要注入参数)

    <T> T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper)

    public void test(Integer id) {//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用)
            String sql = "select name,age from test where id = ?";
            Customer customer = jdbcTemplate.queryForObject(sql, new Object[]{id}, new RowMapper<Customer>() {
    
                @Override
                public Customer mapRow(ResultSet rs, int paramInt)
                        throws SQLException {
                    Customer c = new Customer();
                    c.setName(rs.getString("name"));
                    c.setAge(rs.getInt("age"));
                    return c;
                }
                
            });
        }

    也可以使用如下方式:(1)<T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper);

              (2)<T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);

    (五)查询数据库中一列多行数据,即查询数据库中单列数据存入一个list中,方式如下:(固定sql,没参数)

      (a)List<Map<String, Object>> queryForList(String sql)

               这个方法封装成map放入list中,key:列名(Oracle数据库sql执行结果列名默认为大写,需要小写用as取别名,别名用双引号)  value:列的值

    public void test() {//如果Oracle用这个sql查询,返回的列名就是NAME(大写的),对应Map里面的key就是NAME
            String sql = "select name from test where id > 0";
            //如果用这个sql查询,返回的列名就是name(小写的),对应Map里面的key就是name
            String sql2 = "select name as \"name\" from test where id > 0";
            
            List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);//这里用的是第一个sql
        }

      (b)<T> List<T> queryForList(String sql, Class<T> elementType)

               这个方法就是直接将单类型数据存入List中。

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

    public void test() {
            
            String sql = "select name from test where id > 0";
            List<String> list = jdbcTemplate.queryForList(sql, String.class);
        }

    (六)查询数据库中一列多行数据,即查询数据库中单列数据存入一个list中,方式如下:(预处理sql,需要注入参数)

      (a)<T> List<T> queryForList(String sql, Object[] args, Class<T> elementType)

        注意:这个T虽然是泛型,但是只支持Integer.class String.class 这种单数据类型的,自己定义的Bean不支持。(所以用来查询单列数据,同前面一个意思,后面不再解释)

    public void test(Integer id) {
            
            String sql = "select name from test where id > ?";
            List<String> list = jdbcTemplate.queryForList(sql, new Object[]{id}, String.class);
        }

      还有如下方式实现:(1)<T> List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elementType);

                  (2)<T> List<T> queryForList(String sql, Class<T> elementType, Object... args);

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

      封装成map存入List,和之前一样,要注意Oracle数据库返回的列名默认是大写的,如果需要,用别名变小写。

    public void test(Integer id) {
            
            String sql = "select name from test where id > ?";
            List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, new Object[]{id});
        }

      还有一种方式实现:List<Map<String, Object>> queryForList(String sql, Object[] args, int[] argTypes);

    (七)查询多条数据(固定sql,没有参数)

      (a)<T> List<T> query(String sql, RowMapper<T> rowMapper) 

        每条数据映射为java对象,放入List中。

    public void test() {
            String sql = "select name,age from test where id > 10";
            List<Customer> list = jdbcTemplate.query(sql, new RowMapper<Customer>() {
    
                @Override
                public Customer mapRow(ResultSet rs, int rowNum)
                        throws SQLException {
                    //这里必须new对象,不能在方法外new,然后用同一个,因为是一个List,查询出来多个对象映射,
                    //必须保证每一次调用都使用一个新的。
                    //如果不new,而是使用同一个对象,会导致存入到List中的都是一样的对象(都是最后一个对象)。
                    Customer customer = new Customer();
                    customer.setName(rs.getString("name"));
                    customer.setAge(rs.getInt("age"));
                    return customer;
                }
            });
        }

      该方法也可以把每一行数据转换为自定义key-value的map对象放入list中,如下:(所以说使用回调类比简单方法更强大,里面逻辑自己按需求写)

    public void test() {
            String sql = "select name,age from test where id > 10";
            List<Map<Integer,Object>> list = jdbcTemplate.query(sql, new RowMapper<Map<Integer,Object>>() {
    
                @Override
                public Map<Integer,Object> mapRow(ResultSet rs, int rowNum)
                        throws SQLException {
                    Map<Integer, Object> map = new HashMap<Integer, Object>();
                    map.put(rowNum, rs.getString("name"));
                    map.put(rowNum, rs.getInt("age"));
                    return map;
                }
            });
        }

    map中的key,value类型根据需要自定义,非常方便。像这种实现RowMapper<T>接口的匿名类,T可以为Map,也可以为自定义的对象类型,如上两种,根据需要选择。

      (b) void query(String sql, RowCallbackHandler rch)

       注意:如果使用RowCallbackHandler 回调类,这个方法是没有返回值的,而是在回调类中将结果放入预先定义的List中,用法如下:

    public void test() {
            String sql = "select name,age from test where id > 10";
            
            //局部变量,必须用final修饰,内部类中才能访问(全局变量不用)
            final List<Customer> list = new ArrayList<Customer>();
            jdbcTemplate.query(sql, new RowCallbackHandler() {
                
                @Override
                public void processRow(ResultSet rs) throws SQLException {
                    Customer customer = new Customer();
                    customer.setName(rs.getString("name"));
                    customer.setAge(rs.getInt("age"));
                    list.add(customer);
                }
            });
        }

    当然,这种方式也可以转换为map,存入list中,和上面a方式一样,这里就不详解了。

      (c)<T> T query(final String sql, final ResultSetExtractor<T> rse)

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

    public void test() {
            String sql = "select name,age from test where id > 10";
            
            
            List<Customer> list = jdbcTemplate.query(sql, new ResultSetExtractor<List<Customer>>() {
    
                @Override
                public List<Customer> extractData(ResultSet rs)
                        throws SQLException, DataAccessException {
                    List<Customer> result = new ArrayList<Customer>();
                    while(rs.next()) {
                        Customer customer = new Customer();
                        customer.setName(rs.getString("name"));
                        customer.setAge(rs.getInt("age"));
                        result.add(customer);
                    }
                    return result;
                }
            });
        }

    同样也可以转换为map对象放入list中,如下:

    public void test() {
            String sql = "select name,age from test where id > 10";
    
            List<Map<String, Integer>> list = jdbcTemplate.query(sql, new ResultSetExtractor<List<Map<String, Integer>>>() {
    
                @Override
                public List<Map<String, Integer>> extractData(ResultSet rs)
                        throws SQLException, DataAccessException {
                    List<Map<String, Integer>> result = new ArrayList<Map<String, Integer>>();
                    while(rs.next()) {
                        Map<String, Integer> map = new HashMap<String, Integer>();
                        map.put(rs.getString("name"), rs.getInt("age"));
                        result.add(map);
                    }
                    return result;
                }
            });
        }

       (d)<T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper)

    public void test() {//局部变量,必须用final修饰,内部类中才能访问(全局变量不用)
            final String sql = "select name,age from test where id > 10";
            
            List<Customer> list = jdbcTemplate.query(new PreparedStatementCreator() {
                
                @Override
                public PreparedStatement createPreparedStatement(Connection conn)
                        throws SQLException {
                    PreparedStatement ps = conn.prepareStatement(sql);
             //如果sql是预处理的,需要传入参数,可以在这里写jdbc代码传入,后面就不列举这种方法了 return ps; } }, new RowMapper<Customer>() { @Override public Customer mapRow(ResultSet rs, int rowNum) throws SQLException { Customer customer = new Customer(); customer.setAge(rs.getInt("age")); customer.setName(rs.getString("name")); return customer; } }); }

    可以将RowMapper换成ResultSetExtractor或者RowCallbackHandler回调类,和前面一样,因此还有下面两种方法:

      (1)void query(PreparedStatementCreator psc, RowCallbackHandler rch);

      (2)<T> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse);

    (八)查询多条数据(预处理sql,需要传入参数)

      (a)<T> List<T> query(String sql, PreparedStatementSetter pss, RowMapper<T> rowMapper)

    public void test(final Integer id) {//参数也是局部变量,也必须用final修饰,内部类中才能访问(全局变量不用)
            String sql = "select name,age from test where id > ?";
            
            List<Customer> list = jdbcTemplate.query(sql, new PreparedStatementSetter() {
                
                @Override
                public void setValues(PreparedStatement ps)
                        throws SQLException {
                    ps.setInt(1, id);
                }
            }, new RowMapper<Customer>() {
    
                @Override
                public Customer mapRow(ResultSet rs, int rowNum)
                        throws SQLException {
                    Customer customer = new Customer();
                    customer.setName(rs.getString("name"));
                    customer.setAge(rs.getInt("age"));
                    
                    return customer;
                }
            });
        }

    用RowMapper回调类还有三种方法:

        (1)<T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper);

        (2)<T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper);

        (3)<T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args);

    而如果把回调类换成ResultSetExtractor或者RowCallbackHandler回调类,又有八种方法(像上面一样各有四种),这里就不举出来了,和前面用法一致。

    使用Spring的JdbcTemplate或者NamedParameterJdbcTemplate查询数据库,获取结果,数据库表字段和实体类自动对应,可以使用BeanPropertyRowMapper

    注意:

      自动绑定,需要列名称和Java实体类名字一致,如:属性名 “userName” 可以匹配数据库中的列字段(这里说的列字段是sql执行结果的列名,也就是如果有别名就用别名,(Oracle默认列名大写)) "USERNAME" 或 “user_name”。这样,我们就不需要一个个手动绑定了,大大提高了开发效率。

    下面讲解BeanPropertyRowMapper用法:

    BeanPropertyRowMapper<T>     implements RowMapper<T>这个类是实现了RowMapper接口的,所以之前讲的查询中,能用到RowMapper回调类的都可以用BeanPropertyRowMapper来将结果直接映射为实体类。

    public List<UserEntity> findUser(UserEntity user) {  
            List<UserEntity> userList = jdbcTemplate.query(SEL_BY_USERNAME_PWD,  
                    new Object[] { user.getUserName(), user.getPwd() },  
                    new BeanPropertyRowMapper<UserEntity>(UserEntity.class));  
            return userList;  
        }  

    正如上面,直接利用BeanPropertyRowMapper的构造方法传递一个需要映射的类的class对象进去即可实现,当然必须满足之前说的要求:

    1.属性名“userName”要按如下规则匹配sql结果列:结果列要是"user_name"(大小写都行),因为BeanPropertyRowMapper的中会将结果列都转为小写去和对象中set属性对应;

    2.属性名“user”这对应结果列为“USER”(大小写都行),理由同上;

  • 相关阅读:
    Java如何编写自动售票机程序
    install windows service
    redis SERVER INSTALL WINDOWS SERVICE
    上传文件
    This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed.
    解决Uploadify上传控件加载导致的GET 404 Not Found问题
    OracleServiceORCL服务不见了怎么办
    Access to the temp directory is denied. Identity 'NT AUTHORITYNETWORK SERVICE' under which XmlSerializer is running does not have sufficient permiss
    MSSQL Server 2008 数据库安装失败
    数据库数据导出成XML文件
  • 原文地址:https://www.cnblogs.com/wangyujun/p/10687780.html
Copyright © 2011-2022 走看看