Live-Server-5-SpringBoot中一個請求的流程
Live專案:
1. Idea IDE搭建SpringBoot
2. Mybatis-generator逆向生成Pojo、Mapper介面和XML等
3. WebMvcConfigurerAdapter資源攔截
4. Shiro的簡單使用
在上述文章中,已經完成了Springboot環境的搭建、資料庫的配置、Pojo、MyBatis對映檔案、Dao層、MVC的配置、資源的過濾與攔截、使用者的認證與許可權等。Spring一般搭配SpringMvc,也就是MVC模式一起使用,那麼在MVC中,我們已經完成了model的編寫,接下來要寫Controller層、Service層如何響應請求、如何請求資料。
Spring註解
在SpringBoot中,有如下常用的註解:
- @Controller:用於定義控制器類,在Spring羨慕中由控制器負責將使用者發來的URL請求轉發到對應的服務介面(Service層)
- @RequestMapping:提供路由資訊,複雜URL到Controller中具體函式的對映。通常包含請求的相對地址、GET\Post等請求方式
- @ResponseBody:該註解表示該方法返回的結果直接寫入HTTP Response Body中,,如果使用該註解,返回值就是Json資料,不使用該註解函式的返回值就解析為跳轉路徑。通常的用法就是通過Map<String,Object>來設定Json資料的鍵值對。
- @RestController:使用者表明控制層元件,是@ResponseBody和@Controller的集合。該註解定義的類中所有的類方法返回值型別都是Json資料。
- @GetMapping@PostMapping:是一個組合註解,是@RequestMapping(method = RequestMethod.GET\POST)的縮寫
- @SpringBootApplication:讓SpringBoot自動給程式進行配置的註解。
- @Configuration:相當於傳統的Xml配置檔案,只不過該註解作用與類中,通過Java程式碼來設定配置。
- @Import:用來匯入其他配置類
-
@ImportResource
- @Autowired:自動匯入依賴的bean,自動注入,直接使用配置好的bean,常常用於對類成員變數、方法以及構造方法進行標註,完成自動裝配工作。
- @Service:用於修飾Service層的元件
Spring層的概念
也許,看到這裡,你會有些奇怪,什麼是Dao層,什麼是Service層,Controller層又是什麼呢?為什麼要用Controller來處理響應呢?@Controller註解與Controller又有什麼關係呢?這裡就要提到SpringMVC層的概念:
- Controller層:負責具體業務模組流程的控制,呼叫Service層的介面來控制業務流程,並實現url對映 。在Spring中,Controller層通過@Controller、@RestController來標註
- Service層:建立在Dao層之上,Controller之下,負責業務模組的邏輯設計,先設計介面,再完成其實現類。Service層通過@Service來標註
- Dao層:負責資料的持久化,與資料庫打交道的都封裝在其中,Dao的資料來源在Spring配置檔案中進行配置。主要還是實現資料庫CRUD方法並支援Service的拓展。Dao層類通過@Dao來標註。
- Pojo:資料實體類
層的引用、請求的流程
MyBatis-generator工具已經幫我們實現了Dao層的大部分工作,但是我們還需要在Spring配置檔案中配置資料來源。
Dao層概覽如下:
首先是Dao層的對映介面:
- updateByExample:在更新時,需要將實體物件中所有的屬性都提供,不提供的屬性將會賦值為NULL
- updateByExampleSelective:這個介面就明智多了,只對提供的屬性進行修改,其他熟悉不變。
public interface UserMapper {
int countByExample(UserExample example);
int deleteByExample(UserExample example);
int deleteByPrimaryKey(Integer id);
int insert(User record);
int insertSelective(User record);
List<User> selectByExample(UserExample example);
User selectByPrimaryKey(Integer id);
int updateByExampleSelective(@Param("record") User record,@Param("example") UserExample example);
int updateByExample(@Param("record") User record,@Param("example") UserExample example);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
}
複製程式碼
Dao層具體的對映檔案如下:
<?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.ljh.dao.UserMapper" >
<resultMap id="BaseResultMap" type="com.ljh.po.User" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="account" property="account" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="role_id" property="roleId" jdbcType="INTEGER" />
</resultMap>
<sql id="Example_Where_Clause" >
<where >
<foreach collection="oredCriteria" item="criteria" separator="or" >
<if test="criteria.valid" >
<trim prefix="(" suffix=")" prefixOverrides="and" >
<foreach collection="criteria.criteria" item="criterion" >
<choose >
<when test="criterion.noValue" >
and ${criterion.condition}
</when>
<when test="criterion.singleValue" >
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue" >
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue" >
and ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause" >
<where >
<foreach collection="example.oredCriteria" item="criteria" separator="or" >
<if test="criteria.valid" >
<trim prefix="(" suffix=")" prefixOverrides="and" >
<foreach collection="criteria.criteria" item="criterion" >
<choose >
<when test="criterion.noValue" >
and ${criterion.condition}
</when>
<when test="criterion.singleValue" >
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue" >
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue" >
and ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List" >
id,name,account,password,role_id
</sql>
<select id="selectByExample" resultMap="BaseResultMap" parameterType="com.ljh.po.UserExample" >
select
<if test="distinct" >
distinct
</if>
<include refid="Base_Column_List" />
from user
<if test="_parameter != null" >
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null" >
order by ${orderByClause}
</if>
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.ljh.po.User" >
insert into user (id,role_id)
values (#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{account,#{password,#{roleId,jdbcType=INTEGER})
</insert>
<update id="updateByExample" parameterType="map" >
update user
set id = #{record.id,name = #{record.name,account = #{record.account,password = #{record.password,role_id = #{record.roleId,jdbcType=INTEGER}
<if test="_parameter != null" >
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
</mapper>
複製程式碼
程式碼有所刪減,在該對映檔案中首先定義了xml檔案的型別,隨後定義了User這個類及類屬性在資料庫中的定義,再定義example,最後就是各個介面檔案對於資料庫的具體實現。
上述的類、對映檔案都是通過資料庫逆向生成的,那麼如果我需要在SpringBoot中查詢某一資料,我該到哪裡去查呢?這時候就要定義資料來源。在專案的application.properties檔案中新增如下配置:
#資料來源配置,預設使用tomcate-jdbc連線池
spring.datasource.url=jdbc:mysql://資料庫地址:3306/資料庫名字?useUnicode=true&characterEncoding=UTF8
spring.datasource.username=使用者名稱
spring.datasource.password=使用者密碼
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
複製程式碼
Service層
既然已經打通了Dao層與資料庫之間的障礙,那麼我們來看一下Service層是如何實現他的價值。我覺得寫程式碼時,只要敢想,就沒有做不到的事情,一個介面可以完成多種應用場景。靈活,就是我對Service介面的評價。
public interface UserService {
...
/**
* 註冊成功則無異常丟擲
* 使用者名稱/手機被使用,則丟擲異常
* @param user
* @throws ServiceException
*/
void register(User user) throws ServiceException;
/**
* 查詢相似的user
* @param likeUser
* @return
*/
List<User> getUsers(User likeUser);
}
複製程式碼
就這麼一個通過傳入的user物件來獲取類似User的介面,實現方式和應用的場景卻是變化萬千。 先來看看簡單的方式:
@Service
public class UserServiceImpl implements UserService {
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private UserMapper userMapper; //Dao層介面
@Override
public List<User> getUsers(User likeUser) {
//構造example
UserExample example = new UserExample();
UserExample.Criteria criteria = example.createCriteria();
if (null != likeUser) {
if (null != likeUser.getId()) {
criteria.andIdEqualTo(likeUser.getId());
}
if (null != likeUser.getName()) {
criteria.andNameLike("%" + likeUser.getName() + "%");
}
if (null != likeUser.getAccount()) {
criteria.andNameLike("%" + likeUser.getAccount() + "%");
}
}
return userMapper.selectByExample(example);
}
}
複製程式碼
在上面的這個實現方式中,通過對傳入的user物件屬性進行判斷,如果存在,則將該條件新增到example中,隨後通過Dao層獲取到類似的User資料。這個介面可以用於模糊搜尋使用者、檢視某使用者資訊、如果使用者的屬性再複雜一些,就可以實現使用者排序等功能。
Controller層
先來看下Controller層處理Url請求的方式
- 通過@RequestMapping(path = {"xxx"},method = {RequestMethod.GET/POST}) 註解,標註目前方法對應的url相對請求是xxx,並可以定義為GET、POST、DELETE等方式。
- 處理方法引數: ①@PathVariable: 獲取路徑引數,即url/{id}的形式 ②@RequestParam:獲取查詢引數,即url?name=的形式 ③@RequestBody:如果傳入的是一個Body引數(通常是Json資料),那麼就需要該註解對該物件進行標註,然後會對該物件進行解析與賦值。 ④Form表單的請求:如果方法引數沒有標註別名,那麼form表單中的名字就必須與方法引數的名字相同,否則獲取不到對應的數值。
看看程式碼例項:
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(path = {"register"},method = {RequestMethod.POST})
@ResponseBody
public Map<String,Object> register(String account,String password,String name,String code) {
Map<String,Object> map = new HashMap<>();
//判斷傳入的資料是否為空
...
//判斷傳入的資料格式是否正確
...
//會話狀態的驗證
Session session = SecurityUtils.getSubject().getSession();
try {
User user = new User();
user.setAccount(account);
if (name == null) {
user.setName(user.getAccount());
}
user.setName(name);
user.setPassword(password);
user.setRoleId(2);//設定使用者角色
//呼叫UserService對使用者進行註冊操作
userService.register(user);
map.put("msg","註冊成功");
return map;
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
複製程式碼
上述的方法是一個Post的form表單請求,看起來引數很多,有點複雜,那麼直接傳入一個物件要怎麼處理呢?
@RequestMapping(path = {"register"},Object> register(@RequestBody User user) {
Map<String,Object> map = new HashMap<>();
//判斷傳入的資料是否為空
...
//判斷傳入的資料格式是否正確
...
//會話狀態的驗證
...
}
複製程式碼
使用註解@ResponseBody返回Json格式的資料,如果不使用該註解,則跳轉到對應的頁面。
整個請求的時序圖如下: