Spring Boot demo系列(二):簡單三層架構Web應用
1 概述
這是Spring Boot
的第二個Demo
,一個只有三層架構的極簡Web
應用,持久層使用的是MyBatis
。
2 架構
一個最簡單的Spring Boot Web
應用分為三層:
Controller
層:負責具體業務流程的控制,呼叫Service
層來控制業務邏輯Service
層:業務邏輯層,真正執行業務的操作,比如獲取使用者資訊等Dao
層:負責資料持久化,在這一層中與各種資料庫,比如MySQL
、Oracle
等打交道
先來簡單說一下三層所使用到的註解。
2.1 Controller
層
Controller
層也是入口層,一般涉及如下註解:
@Controller
:@Controller
Controller
層註解,@Controller
標識的類代表該類是控制器類@RequestMapping
:使用@RequestMapping
可以對請求進行對映,可以註解在類上或者方法上,在類上的話表示該類所有的方法都是以該地址作為父地址,在方法上就表示可以對映對應的請求到該方法上@GetMapping/@PostMapping
:這兩者實際上是@RequestMapping
對應不同方法的簡化版,因為@RequestMapping
有一個method
屬性,如果該method
指定為GET
那麼就相當於@GetMapping
,如果指定為POST
就相當於@PostMapping
@ResponseBody
JSON
或XML
等等,預設的情況下比如單純字串就直接返回),比如返回語句為return "success";
,如果加上了@ResponseBody
就直接返回success
,如果不加上就會跳轉到success.jsp
頁面@RequestParm
:處理Contrent-Type
為application/x-www-form-urlencoded
的內容,可以接受簡單屬性型別或者物件,支援GET
+POST
@RequestBody
:處理Content-Type
不為application/x-www-form-urlencoded
Content-Type
),不支援GET
,只支援POST
@PathVariable
:可以將佔位符的引數傳入方法引數,比如/path/1
,可以將1
傳入方法引數中@PathParm
:與@RequestParm
一樣,一般使用@RequestParm
@RestController
:相當於@Controller
+@ResponseBody
2.2 Service
層
Service
層用於執行主要的業務邏輯,主要就是下面這個註解:
@Serice
:是一個增強型的@Component
,@Component
表示一個最普通的元件,可以被注入到Spring
容器進行管理,而@Service
是專門用於處理業務邏輯的註解,@Controller
類似,也是一個增強型的@Component
,專門用於Controller
層的處理
2.3 Dao
層
Dao
是資料持久層,這裡進行資料持久化的操作,一般加上@Repository
即可:
@Repository
:也是一個增強型的@Component
,註解在持久層中,具有將具體資料庫丟擲的異常轉為Spring
持久層異常的功能
講完註解了下面就開始實踐一下。
3 實踐
3.1 新建專案
選擇如下依賴:
Lombok
能簡化程式碼,推薦使用,並且需要IDEA
安裝外掛。ORM
框架這裡選擇MyBatis
。
3.2 新建包
新建如下四個包:
controller
dao
entity
service
config
3.3 Controller
層
3.3.1 簡單Controller
在controller
包下新建Controller.java
:
@RestController
@RequestMapping("/")
public class Controller {
@GetMapping("test")
public String testMethod()
{
return "test controller";
}
}
執行之後,如果出現如下錯誤:
這是因為沒有配置資料來源,可以先把MySQL
和MyBatis
的依賴刪去:
執行之後在瀏覽器輸入localhost:8080/test
會返回test controller
:
這樣一個最簡單的Controller
就完成了。
3.3.2 @RequestParm
然後下一步是新增引數,可以考慮使用@RequestParm
新增:
@GetMapping("withParm")
public String withParm(@RequestParam String id)
{
return "id:"+id;
}
這樣直接訪問localhost:8080/withParm
是不行的,因為沒有攜帶id
引數:
加入引數即可,也就是localhost:8080/withParm?id=1
:
3.3.3 @PathVariable
另一種新增引數的方式是使用@PathVariable
:
@GetMapping("path1/{id}")
public String path1(@PathVariable("id") String id)
{
return "id:"+id;
}
這樣不是加入?id=xx
,而是直接加入佔位符,比如localhost:8080/path1/1
:
3.3.4 完整CURD
這裡是一個完整的CRUD
示例:
@RestController
@RequestMapping("/")
@CrossOrigin("http://localhost:3000")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CRUDController {
private final Service service;
@PostMapping("save")
public boolean save(@RequestBody User user)
{
return service.save(user);
}
@GetMapping("delete")
public boolean delete(@RequestParam String id)
{
return service.delete(id);
}
@GetMapping("select")
public User select(@RequestParam String id)
{
return service.select(id);
}
@GetMapping("selectAll")
public List<User> selectAll()
{
return service.selectAll();
}
}
註解基本上都在上面說過了,除了下面兩個:
@RequiredArgsConstrutcor
:這個是Lombok
的註解,用來消除直接使用@Autowired
出現的警告@CrossOrgin
:跨域註解,由於筆者使用Postwoman
測試,預設執行埠為3000
,因此需要加上該註解,使用Postman
測試則不需要
3.4 Service
層
@org.springframework.stereotype.Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Service {
private final UserMapper mapper;
public boolean save(User user)
{
String id = user.getId();
User currentUser = select(id);
if(currentUser != null)
return mapper.update(user) == 1;
return mapper.insert(user) == 1;
}
public boolean delete(String id)
{
return mapper.deleteById(id) == 1;
}
public User select(String id)
{
return mapper.selectById(id);
}
public List<User> selectAll()
{
return mapper.selectAll();
}
}
簡單的CRUD
,呼叫持久層的方法。
3.5 Dao
層
由於使用MyBatis
,這裡的Dao
層只有一個Mapper
:
@Mapper
@Component
public interface UserMapper{
@Select("select * from user where id=#{id}")
User selectById(@Param("id") String id);
@Select("select * from user")
List<User> selectAll();
int insert(@Param("user") User user);
int deleteById(@Param("id") String id);
int update(@Param("user") User user);
}
select
的sql
直接寫在了上面,剩下的sql
語句寫在了xml
配置檔案,另外@Mapper
註解表示在編譯後生成對應的介面實現類。
3.6 實體類
@Data
@AllArgsConstructor
public class User {
private String id;
private String username;
private String password;
@Override
public String toString()
{
return "id:"+id+"\n"+"username"+username+"\npassword"+password+"\n";
}
}
3.7 配置類
@Configuration
@MapperScan("com.example.demo.dao")
public class MyBatisConfig {
}
@Configuration
:定義為配置類@MapperScan
:@Mapper
的掃描路徑
3.8 配置檔案
配置檔案常用的有properties
以及yaml
,yaml
格式更加簡單,這裡使用yaml
格式:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test
username: test
password: test
mybatis:
configuration:
map-underscore-to-camel-case: true
mapper-locations: classpath:mappers/*.xml
分別指定資料庫連結,資料庫使用者名稱以及密碼,還有下劃線轉駝峰命名以及mapper
檔案的位置。
另外還需要建立UserMapper.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.dao.UserMapper">
<insert id="insert">
INSERT INTO `user` (`id`,`username`,`password`)
VALUES (#{user.id},#{user.username},#{user.password})
</insert>
<update id="update">
UPDATE `user` set `username`=#{user.username} , `password`=#{user.password} where id=#{user.id}
</update>
<delete id="deleteById">
DELETE FROM `user` WHERE `id` = #{id}
</delete>
</mapper>
就單純的sql
語句。
另外需要準備建表以及建使用者的sql
:
CREATE DATABASE IF NOT EXISTS test;
CREATE USER IF NOT EXISTS 'test'@'localhost' IDENTIFIED BY 'test';
GRANT ALL ON test.* to 'test'@'localhost';
USE test;
CREATE TABLE user
(
id char(10) primary key ,
username varchar (30) not null,
password varchar (30) not null
);
測試資料:
USE test;
INSERT INTO user(id,username,password) values ('1','username1','password1'),('2','username2','password2');
最終配置檔案如下:
4 其他準備
4.1 建庫建表建使用者
直接執行上面的指令碼即可。
4.2 開啟服務
使用相應命令開啟資料庫服務。
5 測試
5.1 單元測試
修改一下自帶的測試類即可:
@SpringBootTest
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class DemoApplicationTests {
private final Service service;
@Test
void contextLoads() {
}
@Test
void select()
{
System.out.println(service.select("1"));
}
@Test
void selectAll()
{
service.selectAll().forEach(System.out::println);
}
// @Test
// void delete()
// {
// service.delete("3");
// }
@Test
void save()
{
service.save(new User("3","username3","password3"));
}
}
直接點選左邊的按鈕即可執行,測試通過圖如下:
5.2 瀏覽器測試
由於沒有做前端,這裡就使用Postwoman
模擬前端測試:
6 原始碼
Java
版:
Kotlin
版: