第8章 離不開的資料庫
目錄
前面講了這麼多,都沒有涉及到資料的儲存。現在的軟體系統多多少少都會涉及到資料庫的儲存。不管做什麼,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來向資料庫中新增資料。
在查詢所有的使用者列表,可以看到,id=2的資料已經被刪除掉,如圖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所示結果。
在postman中模擬呼叫上面新增的儲存order資訊介面,可以看到圖8.6所示的結果。
使用圖8.6中的方式一次向資料庫中插入多條資料,然後將請求的Method改為GET重新呼叫。可以看到圖8.7中的結果。
Spring Data JPA的上手難度比較低,對單表的操作也很方便,但是對於多表關聯操作來說略顯麻煩,接下來筆者會帶大家一起了解下Spring Data JPA是如何處理多表關聯操作情況的。
8.4 小結
本章通過為之前建立的微服務專案新增JdbcTemplate,Spring Data JPA支援,大家可以掌握到如何為Spring Boot專案新增關係型資料庫的操作支援。