前面已經介紹過各類資料操作框架的特色之後,這邊來介紹其中我比較熟悉也蠻多人使用的 Spring Data Jpa,雖然比較不用寫 SQL 所以可能會對於 SQL 掌握比較少,但我覺得透過物件的方式來操作資料也是本身 Java 的特色之一,搭配 Spring Boot 能夠整合在框架中使用是很方便的
使用前概念 根據常用開發架構的 MVC 架構,資料庫的部分主要是由 Model 層部分進行處理,這邊對應在 Spring Boot 的開發上會放在 Dao 和 Service,Dao 主要是定義我們與資料庫的溝通連結,在 Spring Data Jpa 主要就是繼承 JpaRepository 去處理資料的存取,最後將拿到得資料返回給 Service 進行業務邏輯的操作或是資料的組裝,最後回給 Controller。
引入套件 pom.xml 加入下面套件,spring-boot-starter-data-jpa 就是我們要使用的 JPA 套件,另外一個則是 mysql 連線用的套件,可以根據使用不同資料庫做更換
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <scope > runtime</scope > </dependency >
配置 application properties 這部分根據你使用的資料庫不同會有一些差異,以下拿 Mysql 為例,主要需要配置概念都差不多,就是配置你的資料庫 Driver, 資料來源, 使用者名稱, 密碼等等
1 2 3 4 5 6 7 8 9 10 11 12 13 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/{databaseName} spring.datasource.username={username} spring.datasource.password={userPassword} spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.show_sql=true spring.jpa.properties.hibernate.format_sql=true
假設我們想要設計產品 Product
的資料操作,會定義他的實體類別 (Entity),來對應到資料庫的 products
表,當我們進行資料都是用 Product 這個物件來操作資料的存取,查詢就將資料轉成我們定義好的物件回給我們,儲存、修改、刪除等等的操作也是由此物件去進行。
資料庫建立及資料表建立 資料庫建立部分大家可以自行找自己所使用的用法,這邊就不說明,提供一個簡單通用的 SQL 例子方便沒使用過的可以有環境進行操作
1 2 3 4 5 6 7 8 CREATE TABLE products ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255 ) NOT NULL, price DECIMAL (10 , 2 ) NOT NULL, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
Entity 新增一個 class,定義我們要作為資料庫操作物件,會對應到資料表的個別欄位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import jakarta.persistence.Entity;import jakarta.persistence.GeneratedValue;import jakarta.persistence.GenerationType;import jakarta.persistence.Id;import jakarta.persistence.Table;@Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Double price; private String description; }
@Entity 告訴 Jpa 它是要儲存到資料庫的實體類別
@Table 則設定它在資料庫中,要對應到叫做「products」的 table 。
@Id 表示 table 中的主鍵(Primary Key,PK)
@GernatedValue() 定義了主鍵值的產生方式。設定的方式 strategy = GenerationType.IDENTITY 就是表示主鍵會自動增長。
後面章節會講到一些 Lombok 註解的應用,可以讓 Spring Data Jpa 使用上更加簡潔方便,但希望各位使用時還是要清楚知道每個註解的由來跟原始作法
Dao 層繼承 JpaRepository 新增 Repository 來做為資料庫溝通介面
1 2 3 4 5 6 7 8 import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repository public interface ProductRepository extends JpaRepository <Product, Long> { Product findByName (String name) ; }
Respository 就是屬於 Dao 的部分,這邊會新增一個介面( interface) 來繼承 JpaRepository,就可以將 JPA 對於物件實體的映射包含資料庫的連線等等實現,後面<> 內需要放入你要操作的物件實體及他內部主鍵型別 (id 的型別) 最後會是像這樣 JpaRepository<Entity, 主鍵型別>,下面可以加入任何你想要使用的方法來操作資料庫,通常預設已經有基本可以針對 id 搜尋單筆,或是直接查詢整筆,就不用宣告出來,透過繼承就可以後續直接再 Service 層中使用。
Service 層中用慣例優於配置來進行 CRUD 操作 新增 Service 層應用 JPA Repository 定義好的或是預設的方法操作資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;import java.util.Optional;@Service public class ProductService { @Autowired private ProductRepository productRepository; public Product createProduct (Product product) { return productRepository.save(product); } public Optional<Product> getProductById (Long id) { return productRepository.findById(id); } public Product getProductByName (String name) { return productRepository.findByName(name); } public List<Product> getAllProducts () { return productRepository.findAll(); } public Product updateProduct (Long id, Product productDetails) { Product product = productRepository.findById(id) .orElseThrow(() -> new RuntimeException ("Product not found with id: " + id)); product.setName(productDetails.getName()); product.setPrice(productDetails.getPrice()); product.setDescription(productDetails.getDescription()); return productRepository.save(product); } public void deleteProduct (Long id) { Product product = productRepository.findById(id) .orElseThrow(() -> new RuntimeException ("Product not found with id: " + id)); productRepository.delete(product); } }
這邊就可以看到我們使用 JPA 所帶來的好處,已經直接提供給我們很多 ORM 包裝好的方法,像是 findById(), findAll(), save(), delete(),這些本該是自己要撰寫 Sql 才能和資料庫溝通的,但我們在 Repository 沒有定義任何方法就可以使用,除了一個 findByName 這個是我們自己定義的,但你也會發現這方法也沒進行任何處理,只是一個簡單明瞭的名稱就可以做到查詢,其實都是 JPA 幫我們處理好,他會自行匹配你定義好在 Entity 的個別欄位,所以當你寫 findBy{欄位名} 他就會自行匹配對應的 sql ( SELECT * FROM prodcuts WHERE {欄位名} = ‘{值}’
; ),確實都不用寫到任何 SQL 就可以完成基本的操作。
這些慣例優於配置的用法其實還有很多,是你想要進行複雜查詢要自己寫的話要怎麼使用,後面會有進一步介紹。
Controller 建立接口接收請求及回傳結果 最後就是開端口接收請求及回傳 Service 的結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import java.util.List;import java.util.Optional;@RestController @RequestMapping("/api/products") public class ProductController { @Autowired private ProductService productService; @PostMapping public Product createProduct (@RequestBody Product product) { return productService.createProduct(product); } @GetMapping("/{id}") public ResponseEntity<Product> getProductById (@PathVariable Long id) { Optional<Product> product = productService.getProductById(id); return product.map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } @GetMapping public List<Product> getAllProducts () { return productService.getAllProducts(); } @PutMapping("/{id}") public ResponseEntity<Product> updateProduct (@PathVariable Long id, @RequestBody Product productDetails) { try { Product updatedProduct = productService.updateProduct(id, productDetails); return ResponseEntity.ok(updatedProduct); } catch (RuntimeException e) { return ResponseEntity.notFound().build(); } } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteProduct (@PathVariable Long id) { try { productService.deleteProduct(id); return ResponseEntity.noContent().build(); } catch (RuntimeException e) { return ResponseEntity.notFound().build(); } } }
看完你會對於 Spring Data JPA 有進一步的了解了,也大概知道其中的特色在哪,有太多太多細節再透過後面的章節慢慢介紹。
參考資料 :