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”(大小寫都行),理由同上;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM