第8章 離不開的數據庫

目錄

8.1 集成JdbcTemplate

8.2 集成Spring Data JPA

8.4 小結


前面講了這麼多,都沒有涉及到數據的存儲。現在的軟件系統多多少少都會涉及到數據庫的存儲。不管做什麼,App、web、C/S客戶端軟件,都需要將涉及到的數據存儲起來,一般來說,目前最常用的數據存儲方式還是關係型數據庫。

本章我們就一起來看下在以Spring Boot爲基礎的項目中,如何方便地操作數據庫。

Spring Boot應用中訪問數據庫的方式有多種。常用的有下面幾種:

  • JdbcTemplate
  • Spring Data JPA
  • Mybatis集成(本文不涉及,後續再寫)

8.1 集成JdbcTemplate

JdbcTemplate是Spring Boot提供的一個數據操作組件,封裝了基本的數據庫操作,本節主要針對常用數據庫mysql來展示如何使用JdbcTemplate進行數據操作。

首先,在pom文件中添加JdbcTemplate和mysql的支持。

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

接下來,在application.properties中添加數據源配置項。

spring.datasource.url=jdbc:mysql://localhost:3306/m_customer
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.main.allow-bean-definition-overriding=true

新增類UserService,在其中直接注入JdbcTemplate使用。

package cn.com.hanbinit.customer.service;

import cn.com.hanbinit.customer.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Service
public class UserService {

@Autowired
private JdbcTemplate jdbcTemplate;

/**
* 創建用戶
* @param nickname
* @param age
*/
public Boolean create(String nickname, Integer age){
    int number = jdbcTemplate.update("insert into tb_user(nickname, age) values
    (?,?)", nickname, age);
    if(number == 1){
        return true;
    }
    return false;
}

/**
* 根據用戶Id刪除單個用戶
* @param userId
* @return
*/
public Boolean deleteById(Integer userId){
    int number = jdbcTemplate.update("delete from tb_user where id = ?", userId);
    if(number == 1){
        return true;
    }
    return false;
}

/**
* 根據Id獲取單個用戶信息
* @param userId
* @return
*/
public User getUserById(Integer userId){
    return jdbcTemplate.queryForObject("select id, nickname, age from tb_user where
    id = ?", (resultSet, i) -> {
        User user = new User();
        user.setId(resultSet.getInt("id"));
        user.setNickname(resultSet.getString("nickname"));
        user.setAge(resultSet.getInt("age"));
        return user;
    }, userId);
}

/**
* 獲取所有用戶列表
* @return
*/
public List<User> getAllUsers(){

    List<User> userList = jdbcTemplate.query("select *from tb_user", (resultSet,
    i) -> {
        User user = new User();
        user.setId(resultSet.getInt("id"));
        user.setNickname(resultSet.getString("nickname"));
        user.setAge(resultSet.getInt("age"));
        return user;
    });
    return userList;
    }
}

其中使用到的User類,代碼如下:

package cn.com.hanbinit.customer.model;

/**

* 用戶基本信息
  */
  public class User {

    private Integer id;
    private String nickname;
    private Integer age;
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
      this.nickname = nickname;
    }

    public Integer getAge() {
      return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
  }

最後,將UserService注入到UserController中,就可以直接使用了。

@Autowired
private UserService userService;

……

@GetMapping("/users/{userId}")
public User getUserInfoByUserId(@PathVariable Integer userId){
    return userService.getUserById(userId);
}

@PostMapping("/users")
public Boolean saveUser(@RequestBody User user){
    return userService.create(user.getNickname(), user.getAge());
}

@GetMapping("/users")
public List<User> getAllUsers(){
    return userService.getAllUsers();
}

@DeleteMapping("/users/{userId}")
public Boolean deleteUserById(@PathVariable Integer userId){
    return userService.deleteById(userId);
}

UserController省略了部分之前已經存在的代碼。

以上的代碼操作了數據庫中的tb_user表,表結構如下:

CREATE TABLE `tb_user` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `nickname` varchar(50) DEFAULT NULL,
    `age` int(3) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

通過圖8.1-8.3可以看出來,我們通過POST來向數據庫中添加數據。

6e8fd5c9297b1778ad71075e47fe9d5b.png
圖8.1 調用接口,向數據庫中談添加數據

 

7caeaf1114ad46178b412245be6f63e4.png
圖8.2 查詢單個用戶信息

 

a70cc5b234187325d881e3736744f373.png
圖8.3 刪除Id=2的用戶

 

在查詢所有的用戶列表,可以看到,id=2的數據已經被刪除掉,如圖8.4所示。

2c5eee3211ac94112b33ae6919c34f6b.png
圖8.4 用戶列表查詢

 

8.2 集成Spring Data JPA

JPA相傳是爲了整合第三方ORM框架,建立統一持久化標準而來的。在大名鼎鼎的Hibernate中,JPA的使用整合地就相當完美。JPA本身是一系列的接口,Hibernate實現了JPA的接口後,今天我們要介紹的Spring Data JPA,其實也可以簡單理解爲Spring實現了JPA的接口。

實際項目開發中,大部分的數據庫操作都是相對簡單的單表“增刪改差”。正常情況下,開發人員需要寫許多額外的代碼來實現很多的基本數據操作。Spring Data JPA提供了最基本的JpaRepository接口,繼承這個接口就可以實現絕大多數的數據庫操作。

下面我們改造一下order微服務,添加基本訂單表,使用Spring Data JPA實現數據表的簡單操作。

首先在pom.xml中添加Spring Data JPA的引用。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

在application.properties中添加如下配置項。

spring.datasource.url=jdbc:mysql://localhost:3306/m_order
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.main.allow-bean-definition-overriding=true
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop

其中,spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置屬性,其主要作用是:自動創建、更新、驗證數據庫表結構。該參數的幾種配置如下:

  • create:每次加載hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因。
  • create-drop:每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。
  • update:最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器後,表結構是不會被馬上建立起來的,是要等應用第一次運行起來後纔會。
  • validate:每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值。

添加model包,新建類Order,如下代碼,指定了接下來需要操作的實體對象。

package cn.com.hanbinit.order.model;

import javax.persistence.*;
import java.util.Date;

// @Entity 指明這個類是一個可以持久化的對象
@Entity
// @Table 指明瞭本類對應的數據庫中表的信息
@Table(name = "tb_order")
public class Order {

    // @Id修飾的變量是主鍵
    @Id
    // @GeneratedValue 是一個簡單的主鍵生成策略,在Mysql中指的是Auto-Increment
    @GeneratedValue
    private Long id;

    private String title;

    // 指明成員變量createDate對應的表字段爲create_date且不能爲空
    @Column(name="create_date", nullable = false)
    private Date createDate;

    // 指明成員變量createBy對應的表字段爲create_by
    @Column(name="create_by", nullable = false)
    private String createBy;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;    
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = new Date();
    }

    public String getCreateBy() {
        return createBy;
    }

    public void setCreateBy(String createBy) {
        this.createBy = createBy;
    }
}

接下來,創建repository包,並新建接口OrderRepository繼承JpaRepository。代碼如下:

package cn.com.hanbinit.order.repository;

import cn.com.hanbinit.order.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {

}

上面代碼中的JpaRepository接口是Jpa中簡單的一個接口類,通過下面的代碼可方便看到其中定義了很多的相對口語化的方法。

@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T,
ID>, QueryByExampleExecutor<T> {

    /*
    * (non-Javadoc)
    * @see org.springframework.data.repository.CrudRepository#findAll()
    */
    List<T> findAll();

    /*
    * (non-Javadoc)
    * @see
      org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
    */
    List<T> findAll(Sort sort);

    /*
    * (non-Javadoc)
    * @see
      org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
    */
    List<T> findAllById(Iterable<ID> ids);

    /*
    * (non-Javadoc)
    * @see
      org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
    */
    <S extends T> List<S> saveAll(Iterable<S> entities);

    /**
    * Flushes all pending changes to the database.
    */
    void flush();

    /**
    * Saves an entity and flushes changes instantly.
    *
    * @param entity
    * @return the saved entity
    */
    <S extends T> S saveAndFlush(S entity);

    /**
    * Deletes the given entities in a batch which means it will create a single
      {@link Query}. Assume that we will clear
    * the {@link javax.persistence.EntityManager} after the call.
    *
    * @param entities
    */
    void deleteInBatch(Iterable<T> entities);

    /**
    * Deletes all entities in a batch call.
    */
    void deleteAllInBatch();

    /**
    * Returns a reference to the entity with the given identifier.
    *
    * @param id must not be {@literal null}.
    * @return a reference to the entity with the given identifier.
    * @see EntityManager#getReference(Class, Object)
    * @throws javax.persistence.EntityNotFoundException if no entity exists for
      given {@code id}.
    */
    T getOne(ID id);

    /*
    * (non-Javadoc)
    * @see
      org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
    */
    @Override
    <S extends T> List<S> findAll(Example<S> example);

    /*
    * (non-Javadoc)
    *@see
    org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example,
    org.springframework.data.domain.Sort)
    */
    @Override
    <S extends T> List<S> findAll(Example<S> example, Sort sort);

}

接口中定義的這些方法都是可以在繼承了這個類的接口中直接使用的。使用代碼:

@Autowired
private OrderRepository orderRepository;

將OrderRepository注入到OrderController中,並在OrderController中添加接口。

/**
* 獲取所有的用戶
* @return
  */
  @GetMapping("/orders")
  public List<Order> queryAllOrders(){
  return orderRepository.findAll();
  }

/**
* 新增用戶
* @param order
* @return
  */
  @PostMapping("/orders")
  public Order createOrder(@RequestBody Order order){
  order.setCreateDate(new Date());
  return orderRepository.save(order);
  }

啓動order微服務,在數據庫中可以看到已經自動創建表tb_order,使用desc
tb_order;查看錶結構,可以看到如圖8.5所示結果。

ab83dbaf34bbf37abda8e86fb02a3376.png
圖8.5 自動創建表tb_order的結構

 

在postman中模擬調用上面新增的保存order信息接口,可以看到圖8.6所示的結果。

8d5ee30b810fb85d097be61bf1d6fea9.png
圖8.6 模擬調用使用Spring Data JPA實現的order保存功能

使用圖8.6中的方式一次向數據庫中插入多條數據,然後將請求的Method改爲GET重新調用。可以看到圖8.7中的結果。

0aaa9e8dc99a096487bb026d125c1e13.png
圖8.7 模擬調用使用Spring Data JPA實現的order列表查詢功能

Spring Data JPA的上手難度比較低,對單表的操作也很方便,但是對於多表關聯操作來說略顯麻煩,接下來筆者會帶大家一起了解下Spring Data JPA是如何處理多表關聯操作情況的。

8.4 小結

本章通過爲之前創建的微服務項目添加JdbcTemplate,Spring Data JPA支持,大家可以掌握到如何爲Spring Boot項目添加關係型數據庫的操作支持。

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