1. 程式人生 > >Spring Boot 使用 JDBC 操作資料庫

Spring Boot 使用 JDBC 操作資料庫

1    第3-1課:Spring Boot 使用 JDBC 操作資料庫

《精通 Spring Boot 42 講》共分五大部分,第三部分主要講解 Spring Boot 和資料庫開發,共 8 課。Spring Boot 支援了主流的 ORM 框架:MyBatis、Hibernate 和 Spring JDBC,幾種 ORM 在不同的場景下各有優勢,在 Spring Boot 體系內都有對應的 Starter 包以方便整合。首先將講解 Spring JDBC 的使用,然後介紹 MyBatis 和 JPA 的各種使用場景以及多資料來源的使用,接著演示如何整合流行的資料庫連線池 Druid,最後結合本課程第二部分內容綜合實踐 JPA 和 Thymeleaf 的使用。

JDBC(Java Data Base Connectivity,Java 資料庫連線)是一種用於執行 SQL 語句的 Java API,可以為多種關係資料庫提供統一訪問,它由一組用 Java 語言編寫的類和介面組成。JDBC 提供了一種基準,據此可以構建更高階的工具和介面,使資料庫開發人員能夠編寫資料庫應用程式。

說白了 JDBC 就是一套 Java 訪問資料庫的 API 規範,利用這套規範遮蔽了各種資料庫 API 呼叫的差異性。當 Java 程式需要訪問資料庫時,直接呼叫 JDBC API 相關程式碼進行操作,JDBC 呼叫各類資料庫的驅動包進行互動,最後資料庫驅動包和對應的資料庫通訊,完成 Java 程式操作資料庫。

直接在 Java 程式中使用 JDBC 比較複雜,需要 7 步才能完成資料庫的操作:

  • 載入資料庫驅動
  • 建立資料庫連線
  • 建立資料庫操作物件
  • 定義操作的 SQL 語句
  • 執行資料庫操作
  • 獲取並操作結果集
  • 關閉物件,回收資源

關鍵程式碼如下:

try {
    // 1、載入資料庫驅動
    Class.forName(driver);
    // 2、獲取資料庫連線
    conn = DriverManager.getConnection(url, username, password);
    // 3、獲取資料庫操作物件
    stmt = conn.createStatement();
    // 4、定義操作的 SQL 語句
    String sql = "select * from user where id = 6";
    // 5、執行資料庫操作
    rs = stmt.executeQuery(sql);
    // 6、獲取並操作結果集
    while (rs.next()) {
    // 解析結果集
    }
} catch (Exception e) {
    // 日誌資訊
} finally {
    // 7、關閉資源
}

通過上面的示例可以看出直接使用 JDBC 來操作資料庫比較複雜,因此後期在 JDBC 的基礎上又發展出了很多著名的 ORM 框架,其中最為流行的是 Hibernate、MyBatis 和 Spring JDBC。這三個流行的 ORM 框架在後續的課程中都會講到,這裡主要了解一下 Spring JDBC 在 Spring Boot 中的使用。

Spring Boot 針對 JDBC 的使用提供了對應的 Starter 包:spring-boot-starter-jdbc,它其實就是在 Spring JDBC 上做了進一步的封裝,方便在 Spring Boot 生態中更好的使用 JDBC,下面進行示例演示。

1.1     快速上手

Spring Boot 整合 JDBC 很簡單,需要引入依賴並做基礎配置即可,在開發專案之前需要先建立表,作為專案演示使用。設計一個 User 使用者表,有 id、name、password、age 等欄位,對應的 SQL 指令碼如下:

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `name` varchar(32) DEFAULT NULL COMMENT '使用者名稱',
  `password` varchar(32) DEFAULT NULL COMMENT '密碼',
  `age`  int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

1.1.1   新增配置

新增依賴包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

演示專案中使用 MySQL 作為資料庫,因此專案中需要引入 MySQL 驅動包,同時引入 spring-boot-starter-jdbc。開啟 pom.xml 檔案,按下快捷鍵:Ctrl + Alt + SHIFT + U,或者單擊右鍵,選擇 Diagrams | Show Dependencies 選項,檢視專案依賴類圖。

 

彈出“類圖”對話方塊後,滾動滑鼠放大檢視,發現 spring-boot-starter-jdbc 直接依賴於 HikariCP 和 spring-jdbc。

 

  • HikariCP 是 Spring Boot 2.0 預設使用的資料庫連線池,也是傳說中最快的資料庫連線池。
  • spring-jdbc 是 Spring 封裝對 JDBC 操作的工具包。

資料來源配置:

spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

值得注意的是,在 Spring Boot 2.1.0 中,com.mysql.jdbc.Driver 已經過期,推薦使用 com.mysql.cj.jdbc.Driver。

1.1.2   實體類

建立表對應的實體類:

public class User  {
    private Long id;
    private String name;
    private String password;
    private int age;
 
    public User(String name, String password, int age) {
        this.name = name;
        this.password = password;
        this.age = age;
    }
    // 省略 getter setter
}

實體類的資料型別要和資料庫欄位一一對應:

  • Long 對應 bigint 
  • String 對應 varchar 
  • int 對應 int 

1.1.3   封裝 Repository

建立 UserRepository 定義我們常用的增刪改查介面:

public interface UserRepository  {
    int save(User user);
    int update(User user);
    int delete(long id);
    List<User> findALL();
    User findById(long id);
}

建立 UserRepositoryImpl 類實現 UserRepository 類介面:

@Repository
public class UserRepositoryImpl implements UserRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

類上使用 @Repository 註解用於標註資料訪問元件,同時在類中注入 JdbcTemplate,其是 Spring 操作 JDBC 提供的工具類。

接下來封裝儲存使用者的方法:

@Override
public int save(User user) {
    return jdbcTemplate.update("INSERT INTO users(name, password, age) values(?, ?, ?)",
          user.getName(), user.getPassword(), user.getAge());
}

通過以上程式碼可以看出,其實就是拼接資料庫插入的 SQL,作為引數傳給 update 方法。

更新使用者資訊和上面類似:

@Override
public int update(User user) {
    return jdbcTemplate.update("UPDATE users SET name = ? , password = ? , age = ? WHERE id=?",
           user.getName(), user.getPassword(), user.getAge(), user.getId());
}

封裝刪除使用者的方法:

@Override
public int delete(long id) {
    return jdbcTemplate.update("DELETE FROM users where id = ? ",id);
}

根據使用者 id 查詢使用者:

@Override
public User findById(long id) {
    return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id=?", new Object[] { id }, new BeanPropertyRowMapper<User>(User.class));
}

這裡使用了 new BeanPropertyRowMapper<User>(User.class) 對返回的資料進行封裝,它可自動將一行資料對映到指定類的例項中,首先將這個類例項化,然後通過名稱匹配的方式,對映到屬性中去。

最後封裝獲取使用者列表:

@Override
public List<User> findALL() {
    return jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());
    // return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper(User.class));
}

findALL() 使用了一個新的方式來封裝結果集的返回,建立一個內部類 UserRowMapper。

class UserRowMapper implements RowMapper<User> {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));
        user.setAge(rs.getInt("age"));
    }
}

UserRowMapper 繼承了 RowMapper,RowMapper 可以將資料中的每一行資料封裝成使用者定義的類,實現 RowMapper 介面覆蓋 mapRow 方法,在 mapRow 方法封裝對資料的返回處理。通過上面程式碼可以看出 UserRowMapper 迴圈遍歷了查詢返回的結果集,遍歷的同時按照屬性進行賦值。這樣在查詢使用時只需要傳入 new UserRowMapper() 即可自動解析返回資料。

1.1.4   測試

接下里我們對封裝好的 UserRepository 進行測試,測試 UserRepository 中的各個方法是否正確。建立 UserRepositoryTests 類,將 userRepository 注入到類中。

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {
    @Autowired
    private UserRepository userRepository;
}

測試插入資料,直接呼叫 userRepository 對應的 save 方法。

@Test
public void testSave() {
    User user =new User("neo","123456",30);
    userRepository.save(user);
}

執行成功後會在資料庫中發現一條 name 為 neo 的值,證明插入成功。

按照同樣的方法測試更新:

@Test
public void testUpdate() {
    User user =new User("neo","123456",18);
    user.setId(1L);
    userRepository.update(user);
}

測試刪除:

@Test
public void testDetele() {
    userRepository.delete(1L);
}

測試查詢:

@Test
public void testQueryOne()  {
    User user=userRepository.findById(1L);
    System.out.println("user == "+user.toString());
}

測試查詢使用者列表:

@Test
public void testQueryAll()  {
    List<User> users=userRepository.findALL();
    for (User user:users){
        System.out.println("user == "+user.toString());
    }
}

測試執行正常,則表明 userRepository 中方法正確。

1.2     多資料來源的使用

在專案中使用多個數據源是很常見的情況,Spring Boot 中多資料來源的使用需要自行封裝。我們在上面示例專案的基礎上進行改造。

1.2.1   配置檔案

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
 
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

添加了兩個資料來源,一個是 test1 庫,一個是 test2 庫。

注意,這裡使用的是 spring.datasource.*.jdbc-url,因為預設連線池 HikariCP 讀取的是 jdbc-url。

1.2.2   初始化 JDBC

在專案啟動的時候讀取配置檔案中的資訊,並對 JDBC 初始化。

@Configuration
public class DataSourceConfig {
    @Primary
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.primary")
    public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();      
    }
 
    @Bean(name="primaryJdbcTemplate")
    public JdbcTemplate primaryJdbcTemplate (
        @Qualifier("primaryDataSource")  DataSource dataSource ) {
        return new JdbcTemplate(dataSource);
    }
 
    @Bean(name="secondaryJdbcTemplate")
    public JdbcTemplate  secondaryJdbcTemplate(
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

這段程式碼表示在啟動的時候根據特定的字首載入不同的資料來源,根據構建好的資料來源再建立不同的 JDBC。

1.2.3   UserRepository 改造

我們對 UserRepository 中的所有方法進行改造,增加一個引數為 JdbcTemplate,如果方法中傳輸了 JdbcTemplate,方法內就會使用傳遞的 JdbcTemplate 進行操作,如果傳遞的 JdbcTemplate 為空,使用預設的 JdbcTemplate 連線操作。

@Repository
public class UserRepositoryImpl implements UserRepository {
    @Autowired
    private JdbcTemplate primaryJdbcTemplate;
 
    @Override
    public int save(User user,JdbcTemplate jdbcTemplate) {
        if(jdbcTemplate == null){
            jdbcTemplate= primaryJdbcTemplate;
        }
        return jdbcTemplate.update("INSERT INTO users(name, password, age) values(?, ?, ?)",
              user.getName(), user.getPassword(), user.getAge());
    }
 
  //其他方法省略,詳細內容可以檢視原始碼
}

1.2.4   多資料來源測試

測試類中注入了兩個不同資料來源的 JdbcTemplate,同時注入 UserRepository。測試使用不同的 JdbcTemplate 插入兩條資料,檢視兩個資料庫中是否都儲存成功。

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private JdbcTemplate primaryJdbcTemplate;
    @Autowired
    private JdbcTemplate secondaryJdbcTemplate;
 
    @Test
    public void testSave() {
        User user =new User("smile","123456",30);
        userRepository.save(user,primaryJdbcTemplate);
        userRepository.save(user,secondaryJdbcTemplate);
    }
}

測試前請先建立 test1 和 test2 資料庫,以及兩個資料庫中的使用者表。

執行 testSave() 成功後,登入 test1 和 test 2 資料庫檢視 user 表,都存在一條 name 為 smile 的使用者資訊,說明多資料來源插入資料成功,其他方法的測試大體相同。這樣在專案中,我們想使用哪個資料來源操作資料庫時,只需要傳入資料來源對應的 JdbcTemplate 例項即可。

1.3     總結

通過本節課程的學習,瞭解到使用原生的 JDBC 操作資料庫非常繁瑣,需要開發者自行封裝資料庫連線,執行完成後手動關閉對應的資源,這樣不利於統一規範,也容易出現問題。後期 Spring 針對 JDBC 的使用推出了 Spring JDBC,Spring Boot 在此基礎上又進行了一步封裝,如今在 Spring Boot 專案中 JDBC 操作資料庫非常簡單。

點選這裡下載原始碼