第 3-1 課:Spring Boot 使用 JDBC 操作數據庫

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,下面進行示例演示。

快速上手

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;

添加配置

添加依賴包:

<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。

實體類

創建表對應的實體類:

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

封裝 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() 即可自動解析返回數據。

測試

接下里我們對封裝好的 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 中方法正確。

多數據源的使用

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

配置文件

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。

初始化 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。

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());
    }

  //其他方法省略,詳細內容可以查看源碼
}

多數據源測試

測試類中注入了兩個不同數據源的 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 實例即可。

總結

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

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