第一个 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


<<:  Day6 我承认我是视觉动物

>>:  [想试试看JavaScript ] 流程控制 if...else

Day19【Web】网路攻击:网路钓鱼(Phishing)

网路钓鱼常被简称为网钓, 即攻击者透过伪装成正规的法人媒体, 以获得如使用者名称、密码和信用卡明细等...

【Day 26】- 分析卫生福利部疾病管制署(CDC)官网并取得确诊者 API,并用小程序及时取得官方确诊者数量(实战分析网站向外请求 API 加快爬虫节奏)

前情提要 昨天实战了用 Python 向猫咪图片的 API 请求。使用者可以输入一个数字,让程序可以...

Day 0x7 - Laravel 资料库连接设定、资料表规划

0x1 Laravel 资料库连接 请先确认 php.ini 的 pdo_pgsql extensi...

在GCP VM上开发Python

订阅patreon即可看到更多文章 https://www.patreon.com/wade3c ...

再谈中断与异常

想知道我们在使用滑鼠操作电脑时作业系统在背後做了什麽事情吗? 又或者为什麽我们在写 C 语言时,老师...