Spring JdbcTemplate 模板剖析 之 常用 增刪改查

目錄

JdbcTemplate 概述 與 環境準備

JdbcTemplate 常用 CRUD

JdbcTemplate 增刪改、刪表、建表

JdbcTemplate 查詢 與 分頁


JdbcTemplate 概述 與 環境準備

1、Spring 對數據庫的操作在 jdbc 上面做了簡單的封裝(類似 DBUtils),使用 spring 的注入功能,可以把 DataSource 註冊到 JdbcTemplate 之中。

2、org.springframework.jdbc.core.JdbcTemplate 位於 spring-jdbc-xxx.RELEASE.jar 包中,同時還需要依賴事務和異常控制包 spring-tx-xxx.RELEASE.jar。官網 Using JdbcTemplate。

3、JdbcTemplate 主要提供以下五類方法:

1)execute 方法:能執行任何 SQL 語句,一般用於執行 DDL 語句;以及 建表、刪表等等 SQL.
2)update、batchUpdate 方法:update 方法用於執行新增、修改、刪除等語句;batchUpdate 方法用於執行批處理相關語句;
3)queryForObject、queryForList、query 方法:用於執行查詢相關語句;queryForObject查詢的結果只能是1條,多餘或少於都會拋異常;
4)queryForList 與 query 查詢結果爲空時,返回的 list 大小爲0,不會引發空指針異常。
5)call 方法:用於執行存儲過程、函數相關語句。

4、本文是介紹 SpringBoot 使用 JdbcTemplate 操作 Mysql 數據庫,學習 JdbcTemplate 的常用 API。

環境 Mysql 5.6.11 + Java JDK 1.8 + Spring Boot 2.1.5 + mysql 驅動 "mysql-connector-java-8.0.16.jar" + IDEA 2018.

5、新建一個 spring boot 的 web 應用,spring boot 只需要導入了 spring-boot-starter-jdbc 就包含了 JdbcTemplate 以及默認的 HikariDataSource 數據源,然後就可以操作數據庫了,其 pom.xml 文件依賴如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
//...
<dependencies>
    <!--web應用,方便從瀏覽器發起請求進行測試-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 引入Spring封裝的jdbc。包含了 JdbcTemplate 以及默認的數據源 HikariDataSource -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!-- 引入mysql數據庫連接驅動-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
        <version>8.0.16</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <!--爲了操作 json 方便-->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency>
</dependencies>

6、配置文件配置數據源如下:

spring:
  datasource:
    username: root
    password: root
    #使用的 mysql 版本爲:Server version: 5.6.11 MySQL Community Server (GPL)
    #mysql 驅動版本:mysql-connector-java-8.0.16.jar
    #高版本 Mysql 驅動時,配置的 driver-class-name 不再是 com.mysql.jdbc.Driver,url 後面必須設置時區 serverTimezone
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

7、如上面配置文件所示使用的是 mysql 默認的 test 數據庫,爲了保證數據源配置正確,以及能正確獲取連接,可以在測試類中先進行測試:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@RunWith(SpringRunner.class)
@SpringBootTest
public class JdbcTemplateAppApplicationTests {
    /**
     * Spring Boot 默認已經配置好了數據源,程序員可以直接 DI 注入然後使用即可
     */
    @Resource
    DataSource dataSource;
    @Test
    public void contextLoads() throws SQLException {
        Connection connection = dataSource.getConnection();
        System.out.println("數據源>>>>>>" + dataSource.getClass());
        System.out.println("連接>>>>>>>>>" + connection);
        System.out.println("連接地址>>>>>" + connection.getMetaData().getURL());
        connection.close();//關閉連接
    }
}

8、代碼中新建 Person 實體如下:

import java.io.Serializable;
import java.util.Date;
/**
 * @author wangmaoxiong
 */
public class Person implements Serializable {
    private Integer pId;
    private String pName;
    private Float salary;
    private Date birthday;
    private String summary;

   //省略了 getter、setter、toString 方法沒有粘貼
}

JdbcTemplate 常用 CRUD

1、新建一個 PersonController 類,頁面發起請求,直接在此控制層操作 mysql 數據庫,然後將結果返回頁面,省去業務層與 dao 層。

2、Spring Boot 默認已經配置好了 JdbcTemplate 放在了容器中,程序員只需自己注入(@Autowired、@Resource)即可使用。

3、spring boot 對 JdbcTemplate 的自動配置原理在 org.springframework.boot.autoconfigure.jdbc 包下的 org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration 類中。

JdbcTemplate 增刪改、刪表、建表

1、常用方法如下,因爲查詢通常是最複雜的,提供的方法也最多,所以後面再單獨分一節:

1.1、public int update(String sql, @Nullable Object... args):sql 是增刪改語句,args 是 sql 中的佔位符(?)參數值
1.2、public int update(final String sql) :sql 是增刪改語句
1.3、public int[] batchUpdate(final String... sql):批量處理增刪改 sql
1.4、public int[] batchUpdate(String sql, List<Object[]> batchArgs) :批量處理增刪改 sql,注意 sql 是同一條,根據 batchArgs(參數)不同來區分
1.5、public void execute(final String sql):因爲沒有返回值,所以不適宜做查詢,除此之外可用於執行任意 SQL 語句,
如增加、刪除、修改、TRUNCATE、drop 刪除表,create 建表等等。

2、控制層代碼如下,測試時需要先調用 person/createTable 接口進行建表:

import com.wmx.jdbc_template_app.pojo.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.Date;
import java.util.logging.Logger;
/**
 * @author wangmaoxoing
 */
@RestController
public class PersonController {
    Logger logger = Logger.getAnonymousLogger();
    /**
     * JdbcTemplate 用於簡化 JDBC 操作,還能避免一些常見的錯誤,如忘記關閉數據庫連接
     * Spring Boot 默認提供了數據源與 org.springframework.jdbc.core.JdbcTemplate
     * spring boot 默認對 JdbcTemplate 的配置,已經注入了數據源創建好了實例,添加到了容器中,程序員直接獲取使用即可
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * 創建 person 表:http://localhost:8080/person/createTable
     *
     * @return
     */
    @GetMapping("person/createTable")
    public String createTable() {
        String sql = "create table if not EXISTS person(" +
                "pId int primary key auto_increment," +
                "pName varchar(18) not null," +
                "birthday date not null," +
                "salary float(10,2)," +
                "summary varchar(256)" +
                ")";
        //execute 可以執行任意 sql(不宜做查詢)
        jdbcTemplate.execute(sql);
        return sql;
    }
    /**
     * 刪除 person 表:http://localhost:8080/person/dropTable
     *
     * @return
     */
    @GetMapping("person/dropTable")
    public String dropTable() {
        String sql = "DROP TABLE if EXISTS PERSON";
        jdbcTemplate.execute(sql);
        return sql;
    }
    /**
     * 保存用戶
     * 爲了方便使用的是 get 請求:
     * http://localhost:8080/person/save?pName=admin&summary=重要人物&salary=9999.00
     *
     * @param person
     * @return
     */
    @GetMapping("person/save")
    public String savePerson(Person person) {
        String message = "保存用戶:" + person;
        logger.info(message);
        person.setpName(person.getpName() == null ? "scott" : person.getpName());
        person.setSummary(person.getSummary() == null ? "" : person.getSummary().trim());

        String sql = "INSERT INTO PERSON(pName,birthday,salary,summary) VALUES (?,?,?,?)";
        Object[] params = new Object[4];
        params[0] = person.getpName();
        params[1] = new Date();
        params[2] = person.getSalary();
        params[3] = person.getSummary();
        //update 方法用於執行新增、修改、刪除等語句
        jdbcTemplate.update(sql, params);
        return sql;
    }
    /**
     * 修改用戶描述
     * 爲了方便使用的是 get 請求:http://localhost:8080/person/update?summary=大咖&pId=1
     *
     * @param person
     * @return
     */
    @GetMapping("person/update")
    public String updatePerson(Person person) {
        String message = "修改用戶描述:" + person;
        logger.info(message);
        person.setSummary(person.getSummary() == null ? "" : person.getSummary().trim());
        person.setpId(person.getpId() == null ? 0 : person.getpId());
        StringBuffer sqlBuff = new StringBuffer("UPDATE PERSON SET ");
        //sql 中的字符串必須加單引號
        sqlBuff.append(" SUMMARY='" + person.getSummary() + "' ");
        sqlBuff.append(" WHERE pId=" + person.getpId());
        logger.info("SQL 確認:" + sqlBuff.toString());
        //update 方法用於執行新增、修改、刪除等語句
        jdbcTemplate.update(sqlBuff.toString());
        return sqlBuff.toString();
    }
    /**
     * 根據 id 刪除一個或者多條數據,多個 id 時用 "," 隔開
     * http://localhost:8080/person/delete?ids=2,3,4
     *
     * @param ids
     * @return
     */
    @GetMapping("person/delete")
    public String deletePerson(String ids) {
        String message = "刪除用戶:" + ids;
        logger.info(message);
        if (ids == null || "".equals(ids)) {
            return message;
        }
        String[] id_arr = ids.split(",");
        String[] sql_arr = new String[id_arr.length];
        for (int i = 0; i < id_arr.length; i++) {
            sql_arr[i] = "DELETE FROM PERSON WHERE pId = " + id_arr[i];
        }
        logger.info("SQL 確認:" + Arrays.asList(sql_arr));
        //batchUpdate 方法用於執行批處理增加刪除、修改等 sql
        jdbcTemplate.batchUpdate(sql_arr);
        return Arrays.asList(sql_arr).toString();
    }
    /**
     * 根據單個 id 刪除單條數據:http://localhost:8080/person/deleteById?pId=4
     *
     * @param pId
     * @return
     */
    @GetMapping("person/deleteById")
    public String deletePersonById(Integer pId) {
        String message = "根據 pId 刪除:" + pId;
        logger.info(message);
        if (pId == null) {
            return message;
        }
        String sql = "DELETE FROM PERSON WHERE pId = " + pId;
        //execute 同樣可以執行任意 DDL 語句
        jdbcTemplate.execute(sql);
        return sql;
    }
    /**
     * 刪除整表數據:http://localhost:8080/person/deletesAll
     *
     * @return
     */
    @GetMapping("person/deletesAll")
    public String deleteAll() {
        String sql = "TRUNCATE TABLE PERSON";
        jdbcTemplate.execute(sql);
        return sql;
    }
}

JdbcTemplate 查詢 與 分頁

1、常用的查詢方法如下:

//queryForObject 查詢結果爲單個對象。注意結果只能是一條/個,結果條數 ==0 或者 >1 都會拋異常,只能 ==1 條。requiredType 表示返回的結果類型
//常用於獲取某個單個結果記錄,如 count、avg、sum 等函數返回唯一值
public <T> T queryForObject(String sql, Class<T> requiredType):
public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args):在上面的基礎上爲 sql 加了佔位符,可以通過args設置參數值
public <T> T queryForObject(String sql, Object[] args, Class<T> requiredType):和上面一樣,重載的方法而已。

//如下幾個重載方法中的 RowMapper 是行映射接口,可以將結果直接通過反射轉爲 Java Bean 對象。
//RowMapper接口常用實現類有 BeanPropertyRowMapper、ColumnMapRowMapper、SingleColumnRowMapper
//BeanPropertyRowMapper 要求 sql 查詢出來的列和實體屬性一一對應,否則應該在 sql 語句中用 as 設置別名
public <T> T queryForObject(String sql, RowMapper<T> rowMapper)
public <T> T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper)
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)

1.  當 queryForObject 結果爲空時,拋出 EmptyResultDataAccessException 異常
2.  當 queryForObject 結果 size 大於 1 時,拋 IncorrectResultSizeDataAccessException 異常
3.  Spring 這樣做的目的是爲了防止程序員不對空值進行判斷,保證了程序的健壯性。解決辦法是自己捕獲異常處理,或者使用下面的 queryForList、query


//queryForList 查詢結果爲單條或者多條,有個好處是如果沒有數據,則list大小爲0,不會出現空指針異常
public List<Map<String, Object>> queryForList(String sql)//每一個 map 表示表中的一行記錄,map 的key爲字段名,map 的value 爲字段值
public List<Map<String, Object>> queryForList(String sql, @Nullable Object... args):可以通過 sql 佔位符設置參數值

//query 是查詢的底層方法,上面的 queryForObject、queryForList 底層都是調用 query 方法。查詢結果爲 0條、1條、或者 多條
//沒有數據時,list 大小爲 0,不會出現控制層異常,rowMapper 行映射,直接將結果反射爲 Java Bean 對象。
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
public <T> List<T> query(String sql, @Nullable Object[] args, RowMapper<T> rowMapper)

2、控制層代碼如下:

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.wmx.jdbc_template_app.pojo.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
 * @author wangmaoxiong
 */
@RestController
public class PersonController2 {
    Logger logger = Logger.getAnonymousLogger();
    /**
     * JdbcTemplate 用於簡化 JDBC 操作,還能避免一些常見的錯誤,如忘記關閉數據庫連接
     * Spring Boot 默認提供了數據源與 org.springframework.jdbc.core.JdbcTemplate
     * spring boot 默認對 JdbcTemplate 的配置,已經注入了數據源創建好了實例,程序員直接獲取使用即可
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * 查詢表中總記錄數:http://localhost:8080/person/findCount
     *
     * @return
     */
    @GetMapping("person/findCount")
    public String findCount() {
        String sql = "SELECT COUNT(1) FROM PERSON";
        //注意結果只能是一條/個,結果條數 ==0 或者 >1 都會拋異常,只能 ==1
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        return sql + " ===> " + count;
    }
    /**
     * 根據 pId 查詢姓名:http://localhost:8080/person/findNameById?pId=1
     *
     * @param pId
     * @return
     */
    @GetMapping("person/findNameById")
    public String findNameById(Integer pId) {
        pId = pId == null ? 1 : pId;
        String sql = "SELECT pName FROM PERSON WHERE pId = ?";
        Object[] param = new Object[]{pId};
        //一定要注意 queryForObject 結果必須是 1 條,多餘 1 條或者沒有1條,都會報錯
        String name = null;
        try {
            name = jdbcTemplate.queryForObject(sql, param, String.class);
        } catch (Exception e) {
            logger.warning("查詢的 pId 不存在:" + pId);
        }
        return sql + " ===> " + name;
    }
    /**
     * 根據 id 查詢實體對象:http://localhost:8080/person/findById?pId=1
     *
     * @param pId
     * @return
     */
    @GetMapping("person/findById")
    public String findByPid(Integer pId) {
        String sql = "SELECT * FROM PERSON WHERE pId = ?";
        Object[] params = new Object[]{pId};

        Person person = new Person();
        try {
            //queryForObject 結果只能是 1 條,小於或者大於1條都會報錯
            person = jdbcTemplate.queryForObject(sql, params, new BeanPropertyRowMapper<>(Person.class));
        } catch (Exception e) {
            logger.info("pId " + pId + " 不存在.");
        }
        JsonObject jsonObject = new JsonObject();
        JsonParser jsonParser = new JsonParser();
        jsonObject.addProperty("sql", sql);
        jsonObject.add("person", jsonParser.parse(new Gson().toJson(person)));
        return jsonObject.toString();
    }
    /**
     * 查詢所有:http://localhost:8080/person/findAll
     *
     * @return
     */
    @GetMapping("person/findAll")
    public String findAll() {
        String sql = "SELECT * FROM PERSON";
        //如果沒有數據,則 list 大小爲 0,不會爲 null 出現空指針異常
        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(sql);
        String message = new Gson().toJson(mapList);
        return message;
    }
    /**
     * 模糊查詢:http://localhost:8080/person/vagueFind?vagueValue=管理員
     *
     * @param vagueValue
     * @return
     */
    @GetMapping("person/vagueFind")
    public String vagueFind(String vagueValue) {
        String sql = "SELECT * FROM PERSON ";
        if (vagueValue != null && !"".equals(vagueValue)) {
            sql += " WHERE pName LIKE '%" + vagueValue + "%' ";
            sql += " OR summary LIKE '%" + vagueValue + "%' ";
        }
        //BeanPropertyRowMapper 要求 sql 查詢出來的列和實體屬性一一對應,否則應該在 sql 語句中用 as 設置別名
        List<Person> personList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Person.class));
        return personList.toString();
    }
    /**
     * 分頁查詢:
     * http://localhost:8080/person/pagingFind
     * http://localhost:8080/person/pagingFind?pageNo=1&rows=3
     *
     * @param pageNo :當前查詢的頁碼,從1開始
     * @param rows   :每頁顯示的記錄條數
     * @return
     */
    @GetMapping("person/pagingFind")
    public String pagingFind(Integer pageNo, Integer rows) {
        //mysql 的 limit 分頁,第一個參數爲起始索引,從0開始,第二個參數爲查詢的條數
        String sql = "SELECT * FROM PERSON LIMIT ?,?";
        pageNo = pageNo == null ? 1 : pageNo;
        rows = rows == null ? 2 : rows;
        Integer startIndex = (pageNo - 1) * rows;

        List<Person> personList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Person.class), startIndex, rows);
        return new Gson().toJson(personList);
    }
}

項目源碼:https://github.com/wangmaoxiong/jdbc_template_app

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章