iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0
Software Development

一個新鮮人如何完轉Spring boot與DevOps從0到101系列 第 7

第一個 Spring boot 應用程式開發

第一個 Spring boot API 開發範例。使用的元件有以下,開發使用 vscode 建立專案時透過工具進行建立非常的容易。這一個專案使用了 Mysql 進行 DB 連線,使用 JPA 的框架進行簡易的 API 實現。

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
...

主要的資料夾結構

/springboot-crud/src/main/java/com/example/cch/crud$ tree
.
├── CrudApplication.java
├── controller # 靠近 UI 層
│   └── ProductController.java
├── model # 實體層,宣告一個領域
│   └── Product.java
├── repository # 有關 DB 的操作
│   └── ProductRepository.java
└── service # 業務邏輯
    ├── ProductService.java
    └── ProductServiceImp.java

我們在 resource 資料夾下的 application.properties 檔案進行關於 DB 連線的設置

# Mysql
# demo DB name
spring.datasource.url=jdbc:mysql://192.168.134.146:3306/crudb
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=springcrud
spring.datasource.password=123456


# Hibernate
# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MYSQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=TRACE

我們假設設計一個有關於產品的領域的實體,有個簡單的主鍵 ID 和商品名稱以及價格。

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column()
    private String name;
    @Column()
    private float price;
    // ... get、set and constructor
}

建立實體後,需要操作 DB 以獲得 DB 中的資料或是存儲資料,簡單的透過繼層 JpaRepository 即可實現簡易的 CRUD 操作。JpaRepository<Product, Long> 中 Product 是實體類它會對應 DB 中的欄位,Long 則是主鍵。

public interface ProductRepository extends JpaRepository<Product, Long>{
    
}

接這我們撰寫業務邏輯,首先我們定義一個 Interface,分別是以下

public interface ProductService {
    List<Product> getAllProduct(); //獲取所有
    void saveProduct(Product product); // 儲存
    Product getProductById(Long id); // 透過主鍵獲取資料
    void deleteProductById(Long id); // 刪除資料
}

實現介面,也就是實現業務邏輯,簡單來說就是把 DB 的操作邏輯寫在這

@Service
public class ProductServiceImp implements ProductService {
    @Autowired // 將 DB 的操作注入,這可以將其想像成是建構方法
    private ProductRepository productRepository;

    @Override
    public List<Product> getAllProduct() {
        // TODO Auto-generated method stub
        return this.productRepository.findAll(); 
    }

    @Override
    public void saveProduct(Product product) {
        // TODO Auto-generated method stub
        productRepository.save(product);
    }

    @Override
    public Product getProductById(Long id) {
        // TODO Auto-generated method stub
        Optional<Product> optional = productRepository.findById(id);
        Product product = null;
        if (optional.isPresent()){
            product = optional.get();
        }
        return product;
    }
    
    @Override
    public void deleteProductById(Long id) {
        // TODO Auto-generated method stub
        this.productRepository.deleteById(id);
    }
    
}

接著把靠近 UI 層部分就是 controller 進行實現,這邊會使用 HTTP 的 Method 進行 CRUD 實現。

@RestController
public class ProductController {
    @Autowired
    private ProductService productService;

    @GetMapping("/products")
    public List<Product> list() {
        return productService.getAllProduct();
    }

    @GetMapping("/products/{id}")
    public ResponseEntity<Product> get(@PathVariable(value = "id") Long id) {
        // 這邊的防呆其實可以拉到 Service 層進行
        try {
            Product product = productService.getProductById(id);
            if (product == null){
                return new ResponseEntity<Product>(HttpStatus.NOT_FOUND);
            }
            return new ResponseEntity<Product>(product, HttpStatus.OK);
        } catch (Exception e) {
            // TODO: handle exception
            return new ResponseEntity<Product>(HttpStatus.NOT_FOUND);
        }
    }

    @PostMapping("/products")
    public void add(@RequestBody Product product) {
        productService.saveProduct(product);
    }

    @PutMapping("/products/{id}")
    public ResponseEntity<?> update(@RequestBody Product product, @PathVariable(value = "id") Long id){
        try {
            Product existProduct = productService.getProductById(id);
            if (existProduct == null){
                return new ResponseEntity<Product>(HttpStatus.NOT_FOUND);
            }
            productService.saveProduct(product);
            return new ResponseEntity<Product>(HttpStatus.OK);
        } catch (Exception e) {
            // TODO: handle exception
            return new ResponseEntity<Product>(HttpStatus.NOT_FOUND);
        }
        
    }

    @DeleteMapping("/products/{id}")
    public void delete(@PathVariable(value = "id") Long id) {
        productService.deleteProductById(id);
    }
}

DB 的設計和 CRUD 成果

建立資料庫和使用者

mysql> create user 'springcrud'@'%' identified by '123456';
Query OK, 0 rows affected (0.02 sec)

mysql> create database crudb;
Query OK, 1 row affected (0.00 sec)

mysql> grant all on crudb.* to 'springcrud'@'%';
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE product ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(45) NOT NULL, price FLOAT NOT NULL );
Query OK, 0 rows affected (0.04 sec)
mysql> show tables;
+-----------------+
| Tables_in_crudb |
+-----------------+
| product         |
+-----------------+
1 row in set (0.01 sec)
mysql> SHOW COLUMNS FROM product;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int         | NO   | PRI | NULL    | auto_increment |
| name  | varchar(45) | NO   |     | NULL    |                |
| price | float       | NO   |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

CRUD

Add

對映 controller 中 add 方法

$ curl -X POST -H "Content-Type: application/json" -d '{"name": "apple", "price": 189.8}' http://localhost:8080/products
mysql> use crudb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SELECT * FROM product;
+----+-------+-------+
| id | name  | price |
+----+-------+-------+
|  1 | apple | 189.8 |
+----+-------+-------+
1 row in set (0.00 sec)

對映 controller 中 list 方法

$ curl http://localhost:8080/products
[{"id":1,"name":"apple","price":189.8}]
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "sony", "price": 120.86}' http://localhost:8080/products
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "samsung", "price": 170.7}' http://localhost:8080/products
$ curl http://localhost:8080/products
[{"id":1,"name":"apple","price":189.8},{"id":2,"name":"sony","price":120.86},{"id":3,"name":"samsung","price":170.7}]

Update

對映 controller 中 update 方法

$ curl -X PUT -H "Content-Type: application/json" -d '{"id": 1, "name": "iphone 12", "price": 999.7}' http://localhost:8080/products/1 -v
*   Trying 127.0.0.1...
* TCP_NODELAY set      
* Connected to localhost (127.0.0.1) port 8080 (#0)
> PUT /products/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 46
>
* upload completely sent off: 46 out of 46 bytes   
< HTTP/1.1 200
< Content-Length: 0
< Date: Sun, 06 Dec 2020 13:28:07 GMT        
<
* Connection #0 to host localhost left intact
$ curl http://localhost:8080/products
[{"id":1,"name":"iphone 12","price":999.7},{"id":2,"name":"sony","price":120.86},{"id":3,"name":"samsung","price":170.7}]
mysql> SELECT * FROM product;
+----+-----------+--------+
| id | name      | price  |
+----+-----------+--------+
|  1 | iphone 12 |  999.7 |
|  2 | sony      | 120.86 |
|  3 | samsung   |  170.7 |
+----+-----------+--------+
3 rows in set (0.01 sec)

當 id 不正確時回應 404

$ curl -X PUT -H "Content-Type: application/json" -d '{"id": 10, "name": "iphone 12", "price": 999.7}' http://localhost:8080/products/10 -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> PUT /products/10 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 47
>
* upload completely sent off: 47 out of 47 bytes
< HTTP/1.1 404
< Content-Length: 0
< Date: Sun, 06 Dec 2020 13:58:54 GMT
<
* Connection #0 to host localhost left intact

Delete

對映 controller 中 deleteProductById 方法

$ curl -X DELETE http://localhost:8080/products/1

mysql> SELECT * FROM product;
+----+---------+--------+
| id | name    | price  |
+----+---------+--------+
|  2 | sony    | 120.86 |
|  3 | samsung |  170.7 |
+----+---------+--------+
2 rows in set (0.00 sec)
$ curl http://localhost:8080/products/
[{"id":2,"name":"sony","price":120.86},{"id":3,"name":"samsung","price":170.7}]

真的有點忙,文章內容都不是很完整,在麻煩見諒~~

這範例都在我github


上一篇
管理 Spring boot 或其他應用程式容器 - Portainer
下一篇
JAVA 8 的使用
系列文
一個新鮮人如何完轉Spring boot與DevOps從0到10130

尚未有邦友留言

立即登入留言