??? ? 🧸安清h:個人主頁
? ?🎥個人專欄:【計算機網絡】【Mybatis篇】
🚦作者簡介:一個有趣愛睡覺的intp,期待和更多人分享自己所學知識的真誠大學生。
目錄
🎯項目基本介紹
🚦項目功能
🚦開發順序
🚦模塊的開發
🎯項目的開發環境
?🎯搭建項目
?🎯用戶注冊
🚦1.選中數據表
🚦2.創建t_user表
🚦3.創建用戶實體類
??創建BaseEntity
??創建實體類User
🚦4.注冊-持久層?
?規劃需要執行的sql語句
?設計接口和抽象方法
?編寫映射
🚦5.注冊-業務層
?5.1規劃異常
🎃5.1.1RuntimeException異常
🎃5.1.2用戶名被占用異常
🎃5.1.3數據插入異常
? 5.2設計接口和抽象方法?
🚦6.注冊-控制層
?6.1創建響應
?6.2設計請求
?6.3處理請求
?6.4控制層優化設計
🚦7.注冊-前端頁面
?🎯項目基本介紹
🚦項目功能
本項目主要實現功能為:登錄,注冊,熱銷商品,用戶管理(密碼,個人信息,頭像,收貨地址),購物車(展示,增加,刪除),訂單模塊。
🚦開發順序
注冊,登錄,用戶管理,購物車,商品,訂單模塊。
🚦模塊的開發
- 持久層開發:依據前端頁面的設置規劃相關的SQL語句,以及進行配置。
- 業務層開發:核心功能的控制,業務的操作以及異常的處理。
- 控制層開發:接受請求,處理響應。
- 前端開發:JS,QUERY,AJAX等技術連接后臺
🎯項目的開發環境
1.JDK:1.8及以上版本
2.maven:配置到idea,3.6.1版本
3.數據庫:Mysql,要求5.1及以上版本
4.開發的平臺:idea開發
?🎯搭建項目
1.項目的名稱:store,表示商城
2.結構:com.cy.store
3.資源文件:resources文件夾下(static、templates)
4.單元測試:test.com.cy.store
5.
6.點擊next后,先選擇以下三個jar包。
(1)WEB:Spring Web(用于前后端連接)
(2)SQL:Mybatis Framework
(3)SQL:MySQL Driver(用于mysql數據庫驅動)
7.在properties文件中配置數據庫的連接源信息
spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
8.創建一個store數據庫
create database store character set utf8;
9.測試連接:
- 啟動SpringBoot主類,是否有對應的Spring圖形輸出
在src->main->java->com.cy.store->StoreApplication,啟動main方法,得到如下圖形即為啟動成功:
- 在單元測試類中測試數據庫的連接是否可以正常加載
如果出現了以下圖片中的Hikari,則代表成功。Hikari是用來管理數據庫的連接對象。
10.訪問項目的靜態資源是否可以正常加載。所有的靜態資源復制static目錄下。
注意:idea對于JS代碼的兼容性較差,編寫了js代碼但是有時候不能夠正常去加載。
- idea緩存清理
- clear-install,雙擊clean后再次install
- rebuild重新構建
- 重啟idea和操作系統
?🎯用戶注冊
🚦1.選中數據表
use store
🚦2.創建t_user表
CREATE TABLE t_user (uid INT AUTO_INCREMENT COMMENT '用戶id',username VARCHAR(20) NOT NULL UNIQUE COMMENT '用戶名',password CHAR(32) NOT NULL COMMENT '密碼',salt CHAR(36) COMMENT '鹽值',phone VARCHAR(20) COMMENT '電話號碼',email VARCHAR(30) COMMENT '電子郵箱',gender INT COMMENT '性別:0-女,1-男',avatar VARCHAR(50) COMMENT '頭像',is_delete INT COMMENT '是否刪除:0-未刪除,1-已刪除',created_user VARCHAR(20) COMMENT '日志-創建人',created_time DATETIME COMMENT '日志-創建時間',modified_user VARCHAR(20) COMMENT '日志-最后修改執行人',modified_time DATETIME COMMENT '日志-最后修改時間',PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- salt鹽值:在用戶注冊時,對其密碼進行加密操作,重要
- 性別:用阿拉伯數字0和1來表示性別,原因在于前端不太好用文字來展示一個人的性別,性別是單選框,一半給一個整數好去操作和判斷。
- is_delete:大部分廠商都沒有實現真正的刪除,只是在登錄的時候做一個校驗,來檢測是否為0,如果不為0,就表示已經刪除了。
- 四個基礎字段,任何一張表都有的,將其作為整個實體類。,
created_user VARCHAR(20) COMMENT ‘創建人’,
created_time DATETIME COMMENT ‘創建時間’,
modified_user VARCHAR(20) COMMENT ‘修改人’,
modified_time DATETIME COMMENT ‘修改時間’,
🚦3.創建用戶實體類
??創建BaseEntity
實體基類,里面存放的為所有表的公共字段。
public class BaseEntity implements Serializable {private String createdUser;private Date createdTime;private String modifiedUser;private Date modifiedTime;
}//get和set方法、equals和hashCode()方法、toString方法
??創建實體類User
public class User extends BaseEntity implements Serializable {private Integer uid;private String username;private String password;private String salt;private String phone;private String email;private Integer gender;private String avatar;private Integer isDelete;//get和set方法、equals和hashCode()方法、toString方法
}
🚦4.注冊-持久層?
通過Mybatis來操作數據庫。在做mybatis開發的流程。
?規劃需要執行的sql語句
1.用戶的注冊功能相當于在做數據的插入操作。
insert into t_user(username,password) values(值列表)
2.在用戶注冊時首先要去查詢當前的用戶名是否存在,如果存在則不能進行注冊。相當于是一條查詢語句。
select * from t_user where username=?
?設計接口和抽象方法
1.定義Mapper接口。在項目的目錄結構下首先創建一個mapper包,在這個包下再根據不同的功能模塊來創建mapper接口。創建一個UserMapper的接口。要在接口中定義這兩個SQL語句的抽象方法。
public interface UserMapper {
// 插入用戶的數據
// @param user 用戶的數據
// @return 受影響的行數(增,刪,改,都受影響的行數作為返回值,可以根據返回值來判斷是否執行成功)Integer insert(User user);// 根據用戶名來查詢用戶的數據
// @param username 用戶名
// @return 如果找到對應的用戶則返回這個用戶的數據,如果沒有找到則返回null值User findByUsername(String username);
}
2.在啟動類配置mapper接口文件的位置
//MapperScan注解指定當前項目中的Mapper接口路徑的位置,在項目啟動時會自動加載所有的接口
@MapperScan("com.cy.store.mapper")
?編寫映射
1.定義xml映射文件,與對應的接口進行關聯。所有的映射文件需要放置在resources目錄下,在這個目錄下創建一個mapper文件夾,然后在這個文件夾存放Mapper的映射文件。
2.創建接口對應的映射文件,遵循和接口的名稱保持一致即可。創建一個UserMapper.xml的文件。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace屬性:用于指定當前的映射文件和哪個接口進行映射,需要指定接口的文件路徑,需要標注包的完整路徑接口-->
<mapper namespace="com.cy.store.mapper.UserMapper"></mapper>
?3.配置接口中的方法對應上SQL語句。需要借助標簽來完成,insert/update/delete/select,對應的是SQL語句的增刪改查操作。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace屬性:用于指定當前的映射文件和哪個接口進行映射,需要指定接口的文件路徑,需要標注包的完整路徑接口-->
<mapper namespace="com.cy.store.mapper.UserMapper">
<!-- resultMap標簽來完成映射規則的定義--><resultMap id="UserEntityMap" type="com.cy.store.entity.User">
<!-- 配合完成名稱不一致的映射:column:表中的字段名稱property:類中的屬性名稱
-->
<!-- 在定義映射規則時,主鍵是不可以省略的--><id column="uid" property="uid"></id><result column="is_delete" property="isDelete"></result><result column="created_user" property="createdUser"></result><result column="created_time" property="createdTime"></result><result column="modified_user" property="modifiedUser"></result><result column="modified_time" property="modifiedTime"></result></resultMap><!-- id屬性:表示映射的接口中方法的名稱,直接在標簽的內部來編寫SQL語句-->
<!-- useGeneratedKeys:開啟某個字段的值遞增(主鍵設為遞增)-->
<!-- keyProperty:將表中那個字段作為主鍵進行遞增--><insert id="insert" useGeneratedKeys="true" keyProperty="uid">insert into t_user(username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time)values (#{username},#{password},#{salt},#{phone},#{email},#{gender},#{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime})</insert><select id="findByUsername" resultMap="UserEntityMap">select * from t_user where username=#{username}</select>
</mapper>
4.將mapper文件的位置注冊到properties對應的配置文件中,在application.properties文件中添加如下代碼:
mybatis.mapper-locations=classpath:mapper/*.xml
5.單元測試:每個獨立的層編寫完畢后需要編寫單元測試方法,來測試當前功能。在test包接口下創建一個mapper包,在這個包下創建一個持久層的測試。?
//@SpringBootTest標注當前的類是一個測試類,不會隨同項目一塊打包
@SpringBootTest
public class UserMapperTests {@Autowiredprivate UserMapper userMapper;
// 單元測試方法:可以單獨獨立運行,不用啟動整個項目,提高了代碼運行效率
// 1.必須被@Test注解修飾
// 2.返回值類型必須是void
// 3.方法的參數類型不指定任何類型
// 4.方法的訪問修飾符必須是public@Testpublic void insert(){User user=new User();user.setUsername("tim");user.setPassword("123");Integer rows=userMapper.insert(user);System.out.println(rows);}@Testpublic void findByUsername(){User user=userMapper.findByUsername("tim");System.out.println(user);}
}
🚦5.注冊-業務層
?5.1規劃異常
🎃5.1.1RuntimeException異常
作為這異常的子類,然后再去定義具體的異常類型來繼承這個異常。業務層異常的基類,ServiceException異常。這個異常繼承RuntimeException異常。異常機制的建立。
//業務層異常的基類
public class ServiceException extends RuntimeException{public ServiceException() {super();}public ServiceException(String message) {super(message);}public ServiceException(String message, Throwable cause) {super(message, cause);}public ServiceException(Throwable cause) {super(cause);}protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
根據業務層不同的功能來詳細定義具體的異常的類型,統一的去繼承ServiceException異常類。?
🎃5.1.2用戶名被占用異常
用戶在進行注冊時可能會產生用戶名被占用的錯誤,拋出一個異常:UsernameDuplicatedException異常。
//用戶名被占用的異常
public class UsernameDuplicatedException extends ServiceException{public UsernameDuplicatedException() {super();}public UsernameDuplicatedException(String message) {super(message);}public UsernameDuplicatedException(String message, Throwable cause) {super(message, cause);}public UsernameDuplicatedException(Throwable cause) {super(cause);}protected UsernameDuplicatedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
alt+insert打開快捷方式,選中前五個如圖:
🎃5.1.3數據插入異常
正在執行數據插入操作的時候,服務器,數據庫宕機。處于正在執行插入的過程中所產生的異常:InsertException異常。
//數據在插入過程中產生的異常
public class InsertException extends ServiceException{public InsertException() {super();}public InsertException(String message) {super(message);}public InsertException(String message, Throwable cause) {super(message, cause);}public InsertException(Throwable cause) {super(cause);}protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
? 5.2設計接口和抽象方法?
1.在service包下創建一個IUserService接口。
//用戶模塊業務層接口
public interface IUserService {void reg(User user);
}
2.創建一個實現類UserServiceImpl類,需要實現這個接口,并且實現抽象的方法。
//用戶模塊業務層的實現類
@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic void reg(User user) {
// 通過user參數來獲取傳遞過來的usernameString username= user.getUsername();//調用findByUsername(username)判斷用戶是否被注冊過User result = userMapper.findByUsername(username);
// 判斷結果集是否不為null,則拋出用戶名被占用的異常if(result !=null){throw new UsernameDuplicatedException("用戶名被占用");}
// 密碼加密處理的實現:md5算法的形式
// 串+password+串------md5算法進行加密,連續加載三次
// 鹽值+password+鹽值------鹽值就是一個隨機的字符串String oldPassword = user.getPassword();
// 獲取鹽值(隨機生成一個鹽值)String salt = UUID.randomUUID().toString().toUpperCase();
// 補全數據:鹽值的記錄user.setSalt(salt);
// 將密碼和鹽值作為一個整體進行加密處理,忽略原有密碼的強度,提升了數據的安全性String md5Password = getMD5Password(oldPassword,salt);
// 將加密之后的密碼重新補全設置到user對象中user.setPassword(md5Password);// 補全數據:is_delete設置成0user.setIsDelete(0);
// 補全數據:四個日志字段信息user.setCreatedUser(user.getUsername());user.setModifiedUser(user.getUsername());Date date=new Date();user.setCreatedTime(date);user.setModifiedTime(date);// 執行注冊業務功能的實現(rows=1)Integer rows=userMapper.insert(user);if(rows!=1){throw new InsertException("在用戶注冊過程中產生了未知的異常");}}
// 定義一個md5算法的加密處理private String getMD5Password(String password,String salt){
// md5加密算法方法的調用(進行三次加密)for(int i=0;i<3;i++){password=DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();}
// 返回加密之后的密碼return password;}
}
3.在單元測試包下創建一個UserServiceTests類,在這個類中添加單元測試的功能。
//@SpringBootTest標注當前的類是一個測試類,不會隨同項目一塊打包
@SpringBootTest
public class UserServiceTests {@Autowiredprivate IUserService userService;
// 單元測試方法:可以單獨獨立運行,不用啟動整個項目,提高了代碼運行效率
// 1.必須被@Test注解修飾
// 2.返回值類型必須是void
// 3.方法的參數類型不指定任何類型
// 4.方法的訪問修飾符必須是public@Testpublic void reg(){try {User user=new User();user.setUsername("yuanxin01");user.setPassword("123");userService.reg(user);System.out.println("OK");} catch (ServiceException e) {
// 獲取類的對象,再獲取類的名稱System.out.println(e.getClass().getSimpleName());
// 獲取異常的具體描述信息System.out.println(e.getMessage());}}}
🚦6.注冊-控制層
?6.1創建響應
狀態碼、狀態描述信息、數據。這部分功能封裝一個類中,將這類作為方法返回值,返回給前端瀏覽器。
//Json格式的數據進行響應
public class JsonResult<E> implements Serializable {
// 狀態碼private Integer state;
// 描述信息private String message;
// 數據private E data;public JsonResult(Integer state) {this.state = state;}public JsonResult(Throwable e) {this.message = e.getMessage();}public JsonResult(Integer state, E data) {this.state = state;this.data = data;}public Integer getState() {return state;}public void setState(Integer state) {this.state = state;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public E getData() {return data;}public void setData(E data) {this.data = data;}
}
?6.2設計請求
?依據當前的業務功能模塊進行請求設計。
請求路徑:/users/reg
請求參數:user user
請求類型:POST
響應結果:JsonResult<void>
?6.3處理請求
1.創建一個控制層對應的類UserController類。依賴于業務層的接口。
@RestController //@Controller+@RequestBody
@RequestMapping("users")
public class UserController {@Autowiredprivate IUserService userService;@RequestMapping("reg")// @RequestBody //表示此方法的響應結果以json格式進行數據的響應給到前端public JsonResult<Void> reg(User user){JsonResult<Void> result=new JsonResult<>();try {userService.reg(user);result.setState(200);result.setMessage("用戶注冊成功");} catch (UsernameDuplicatedException e) {result.setState(4000);result.setMessage("用戶名被占用");}catch (InsertException e) {result.setState(5000);result.setMessage("注冊時產生未知的異常");}return result;}
}
?6.4控制層優化設計
在控制層抽離一個父類,在這個父類中統一的去處理關于異常的操作。編寫一個BaseController類,統一處理異常。
//控制層類的基類
public class BaseController{
// 操作成功的狀態碼public static final int OK=200;
// 充當請求處理方法,這個方法的返回值就是需要傳遞給前端的數據
// 自動將異常對象傳遞給此方法的參數列表上
// 當項目中產生了異常,會被統一的攔截到此方法中,這個方法此時就充當請求處理方法,方法的返回值直接給到前端@ExceptionHandler(ServiceException.class) //用于統一處理拋出的異常public JsonResult<Void> handleException(Throwable e){JsonResult<Void> result=new JsonResult<>(e);if(e instanceof UsernameDuplicatedException){result.setState(4000);result.setMessage("用戶名已經被占用");}else if(e instanceof InsertException){result.setState(5000);result.setMessage("注冊時產生未知的異常");}return result;}
}
?重新構建了reg()方法。
@RestController //@Controller+@RequestBody
@RequestMapping("users")
public class UserController extends BaseController{@Autowiredprivate IUserService userService;@RequestMapping("reg")public JsonResult<Void> reg(User user){userService.reg(user);return new JsonResult<>(OK);}
}
🚦7.注冊-前端頁面
1.在register頁面中編寫發送請求的方法,點擊事件來完成。選中對應的按鈕($("選擇器")),再去添加點擊的事件,$.ajax()函數發送異步請求。
2.JQuery封裝了一個函數,稱之為$.ajax()函數,通過對象調用ajax,可以異步加載相關的請求。ajax可以把某一部分的局部看做成一個獨立的整體。依靠的是javascript提供的一個對象XHR(XmlHttpResponse),封裝了這個對象。
3..ajax()使用方式。需要傳遞一個方法作為方法的參數使用,一對大括號稱之為方法體。ajax接收多個參數,參數與參數之間要使用","分割,每一組參數之間使用“:”進行分割,參數的組成部分一個是參數的名稱(不能隨意定義),是參數的值,參數的值要求用字符串來表示。參數的聲明順序沒有要求。語法結構:
$.ajax({url:"",type:"",data:"",dataType:"",success:function(){},error:function(){}
});
4.ajax()函數參數的含義:
參數 | 功能描述 |
---|---|
url | 標識請求的地址(url地址),不能包含參數列表部分的內容。例如:url:"localhost:8080/users/reg" |
type | 請求類型(GET和POST請求的類型)。例如:type:"POST" |
data | 向指定的請求url地址提交的數據。例如:data:"username=tom&pwd=123" |
dataType | 提交的數據類型。數據的類型一般指定為json類型。dataType:"json" |
success | 當服務器正常響應客戶端時,會自動調用success參數方法,并且將服務器返回的數據以參數的形式傳遞給這個方法的參數上 |
error | 當服務器未正常響應客戶端時,會自動調用error參數的方法,并且將服務器返回的數據以參數的形式傳遞給這個方法的參數上 |
5.js代碼可以獨立聲明在一個js文件里或者聲明在一個script標簽中。
<script>//1.監聽注冊按鈕是否被點擊,如果被點擊可以執行一個方法$("#btn-reg").click(function () {// 2.發送ajax()的異步請求來完成用戶的注冊功能$.ajax({url: "/users/reg",type: "POST",data: $("#form-reg").serialize(),dataType: "JSON",success: function (json) { if (json.state == 200) {alert("注冊成功")} else {alert("注冊失敗")}},error: function (xhr) { alert("注冊時產生未知的錯誤!"+xhr.status);}});});</script>
6.js代碼無法正常被服務器解析執行,體現在點擊頁面中的按鈕沒有任何響應。解決方案:
- 在項目的maven下clear清理項目-install重新部署
- 在項目的file選項下-cash清理緩存
- 重新的去構建項目:build選項下-rebuild選項
- 重啟idea
- 重啟電腦