1. 前言
在這里的就不多啰嗦什么了,先來大概介紹一下項(xiàng)目的體系結(jié)構(gòu)等。還是上張圖
項(xiàng)目結(jié)構(gòu).png
整體架構(gòu)是ssm的前面已經(jīng)說過了,下面來說一下代碼的架構(gòu)。因?yàn)槟壳绊?xiàng)目的規(guī)劃還比較小,所以在com.bgy.ssm包下面就直接建了controller、service等。如果項(xiàng)目情況較復(fù)雜的話,在此包應(yīng)該根據(jù)功能模塊來進(jìn)行分包,然后再建立相應(yīng)的三層,這樣的話結(jié)構(gòu)更清晰。
在這里有個(gè)爭(zhēng)論:關(guān)于到底要不要repo層的問題
- 有人贊成的是直接controller中接收用戶請(qǐng)求,調(diào)用service處理業(yè)務(wù)邏輯,然后所有的組裝數(shù)據(jù)也是在service中完成,最后service直接調(diào)用dao接口。
- 另外有人贊成的是service中只處理業(yè)務(wù)邏輯;對(duì)于數(shù)據(jù)的組裝,如操作多表時(shí)要調(diào)用dao層的多個(gè)接口時(shí),應(yīng)該在repo層中完成,這樣不會(huì)使service層顯得太過臃腫,并且能提高可復(fù)用性和符合設(shè)計(jì)原則中 “針對(duì)接口編程,不針對(duì)實(shí)現(xiàn)編程”的理念。
- 在這里我個(gè)人的話更傾向于后者,service層更注重的應(yīng)該是處理業(yè)務(wù)邏輯,不能因?yàn)閷?shí)現(xiàn)某個(gè)功能而在service中一個(gè)方法寫一堆組裝數(shù)據(jù)的代碼。當(dāng)然這是我個(gè)人的愚見,若有高見,歡迎指正。
在這里這個(gè)項(xiàng)目較小,也是從新手角度出發(fā),所以我違心的使用了controller、service、dao的三層架構(gòu)。這樣思路較簡(jiǎn)單和清晰。
另外說明一點(diǎn),下面的代碼中都有相關(guān)引用,使用時(shí)自行引入相關(guān)引用
2. 數(shù)據(jù)庫(kù)
這里為了演示功能和架構(gòu),我就只使用一張表來實(shí)現(xiàn)操作。在這里,我建議在項(xiàng)目下建立sql文件夾,里面放置建表的語(yǔ)句,方便以后查閱和修改。
sql.png
上面是建表語(yǔ)句文件、下面是有些表需要進(jìn)行初始化的初始化語(yǔ)句文件。
db_ddl.sql
DROP TABLE IF EXISTS `tUser`;
CREATE TABLE `tUser` (
`ID` VARCHAR (45) NOT NULL ,
`UserName` VARCHAR (100) ,
`NickName` VARCHAR (100),
`PassWord` CHAR (32),
`Email` VARCHAR (50),
`Phone` VARCHAR (50),
`Sex` ENUM('S_MALE','S_FEMALE','S_BM'),
`Status` ENUM('S_OFF','S_NORMAL'),
`Avatar` VARCHAR (100),
`Remarks` VARCHAR (200),
`AddAt` BIGINT,
PRIMARY KEY (`ID`)
) DEFAULT CHARSET=utf8;
這里沒有寫備注和null這些的規(guī)范,需要的可自行添加。
(后續(xù)添加:在這里的表名和字段名在下一篇文章中進(jìn)行了修改,原因可翻看下一篇文章)
3. 實(shí)體類
在這里為了簡(jiǎn)便、我就建表完成后直接建立實(shí)體類User。
User.java
public class User {
private String id;
private String userName;
private String nickName;
private String password;
private String email;
private String phone;
private String sex;
private String status;
private String avatar;
private String remarks;
private Long addAt;
public User(){
}
public User genarateID() {
if (this.id == null) {
this.id = UUID.randomUUID().toString();
}
return this;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
public Long getAddAt() {
return addAt;
}
public void setAddAt(Long addAt) {
this.addAt = addAt;
}
}
這個(gè)地方?jīng)]什么特別的,對(duì)于id是直接用genarateID()方法通過uuid生成的,若需要自己設(shè)計(jì)主鍵生成策略的可忽略這個(gè)。
4. Controller
先上代碼
@Controller
@RequestMapping("/user")
public class UserController {
private JSONObject json = new JSONObject();
@Autowired
private UserService userService;
@RequestMapping(value = "",method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
@ResponseBody
public String addUser(@RequestBody String userJson) throws Exception{
String resultInfo = "";
try{
resultInfo = userService.addUser(userJson);
return resultInfo;
}catch (Exception e){
throw new GcsjException(e);
}
}
@RequestMapping(value = "{userName}",method = RequestMethod.GET,produces = "text/html;charset=UTF-8")
@ResponseBody
public String getUserByName(@PathVariable String userName) throws Exception{
String resultInfo = "";
try {
resultInfo = userService.getUserByName(userName);
return resultInfo;
}catch (Exception e){
throw new GcsjException(e);
}
}
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
@ResponseBody
public String login(@RequestBody String userJson) throws Exception{
String resultInfo = "";
try{
resultInfo = userService.login(userJson);
return resultInfo;
}catch (Exception e){
throw new GcsjException(e);
}
}
}
controller這里首先用@Controller標(biāo)記這個(gè)類成為一個(gè)SpringMVC Controller對(duì)象。
因?yàn)槭鞘褂玫氖莚estful風(fēng)格,所以在類前面就通過@RequestMapping("/user")注解指定此類的所有請(qǐng)求方法的父路徑為/user,類中第一個(gè)方法是添加用戶,所以路徑為空,由restful風(fēng)格指定的HTTP請(qǐng)求方式選擇進(jìn)入不同方法;然后直接以json字符串來交互。另外一個(gè)通過用戶名獲取用戶的方法,把用戶名帶在參數(shù)上,所以value中寫了value = "{userName}",然后用@PathVariable String userName接收參數(shù)。這個(gè)只是一個(gè)測(cè)試方法,暫時(shí)無用。第三個(gè)方法是登錄,也是以json字符串來交互。
另外就是使用@Autowired把service注入進(jìn)來了
在這里所有的方法統(tǒng)一用字符串返回。
(另外,這里前后端以JSON字符串交互的方式有待商榷,以前認(rèn)知是用JSON字符串便于統(tǒng)一風(fēng)格;不過最近一年以來的學(xué)習(xí)和工作,現(xiàn)在會(huì)直接使用實(shí)體類進(jìn)行接收對(duì)象,免去JSON轉(zhuǎn)對(duì)象的步驟。)
5. Service
service層主要處理業(yè)務(wù)邏輯,首先使用@Service標(biāo)注這個(gè)類是業(yè)務(wù)層組件,然后也是用@Autowired注解注入Dao
@Service
public class UserService {
@Autowired
private UserDao userDao;
private JSONObject json = new JSONObject();
@Transactional
public String addUser(String userJson) throws Exception {
ResponseResult result = new ResponseResult();
User user = json.parseObject(userJson, User.class);
if (userDao.countCommon(user) > 0) {
result.setCode("400");
result.setMsg("用戶已存在");
return json.toJSONString(result);
}
user.genarateID();//設(shè)置UUID
int num = userDao.addUser(user);
int numScore = userDao.addDefaultIntegral(user.getId());//注冊(cè)用戶送默認(rèn)積分
if (num > 0 && numScore >0) {
result.setCode("200");
result.setMsg("添加成功");
result.setData(user);
} else {
result.setCode("400");
result.setMsg("添加失敗");
}
return json.toJSONString(result);
}
public String getUserByName(String userName) throws Exception {
ResponseResult result = new ResponseResult();
List<User> list = userDao.getUserList(userName);
result.setCode("200");
result.setMsg("Ok");
result.setData(list);
return json.toJSONString(result);
}
public String login(String userJson) throws Exception {
ResponseResult result = new ResponseResult();
User user = json.parseObject(userJson, User.class);
if ("".equals(user.getUserName()) || "".equals(user.getPassword())) {
result.setCode("400");
result.setMsg("用戶名或密碼不能為空");
return json.toJSONString(result);
}
List<User> list = userDao.getUserList(user.getUserName());
if (list.size() == 0) {
result.setCode("400");
result.setMsg("用戶不存在");
} else {
if (!user.getPassword().equals(list.get(0).getPassword())) {
result.setCode("400");
result.setMsg("密碼錯(cuò)誤");
}else {
result.setCode("200");
result.setMsg("登錄成功");
}
}
return json.toJSONString(result);
}
}
在這里自己定義了一個(gè)ResponseResult類來作為所有的返回請(qǐng)求的格式。
public class ResponseResult implements Serializable {
private static final long serialVersionUID = 4832771715671880043L;
private String code;
private String msg;
private Object data;
public ResponseResult(){
this.code = "200";
this.msg = "SUCCESS";
this.data = null;
}
public ResponseResult(String msg) {
this.code = "400";
this.msg = msg;
this.data = null;
}
public ResponseResult(String code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return this.msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return this.data;
}
public void setData(Object data) {
this.data = data;
}
}
這樣以統(tǒng)一的code,msg和data作為返回格式,前端也方便處理。根據(jù)邏輯設(shè)定好這個(gè)類后直接轉(zhuǎn)成json字符串返回即可。
6. Dao
dao這里實(shí)現(xiàn)非常簡(jiǎn)單,就是一個(gè)接口,沒有另外去寫接口的實(shí)現(xiàn),很多系統(tǒng)是另外寫了實(shí)現(xiàn)類的,可以對(duì)數(shù)據(jù)庫(kù)查詢的數(shù)據(jù)再次進(jìn)行處理后返回。
這里我是通過自定義的MybatisSqlMapping注解,讓有這個(gè)注解的interface被Mybatis掃描并生成Mapper對(duì)象,然后與sql-mapping文件夾下面的*.xml對(duì)應(yīng)。
@Repository
@MybatisSqlMapping
public interface UserDao {
public int countCommon(User user);
public int addUser(User user);
public int addDefaultIntegral(@Param("userId") String userId);
public List<User> getUserList(@Param("userName") String userName);
}
首先還是使用@Repository標(biāo)注這個(gè)類為數(shù)據(jù)訪問組件。
然后添加了自定義的@MybatisSqlMapping注解,使這個(gè)類能被掃描。
/**
* 定義一個(gè)空注解,用于MapperScannerConfigurer過濾接口
* 有此注解的interface才會(huì)被Mybatis掃描并生成Mapper對(duì)象
*
*/
public @interface MybatisSqlMapping {
}
7. *.xml
user.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.btt.gcsj.dao.UserDao">
<resultMap id="userMap" type="com.btt.gcsj.model.User">
<id column="ID" property="id"/>
<result column="UserName" property="userName"/>
<result column="NickName" property="nickName"/>
<result column="PassWord" property="password"/>
<result column="Email" property="email"/>
<result column="Phone" property="phone"/>
<result column="Sex" property="sex"/>
<result column="Status" property="status"/>
<result column="Avatar" property="avatar"/>
<result column="Remarks" property="remarks"/>
<result column="AddAt" property="addAt"/>
</resultMap>
<select id="countCommon" parameterType="com.btt.gcsj.model.User" resultType="int">
SELECT COUNT(1) from tUser WHERE UserName = #{userName}
</select>
<insert id="addUser" parameterType="com.btt.gcsj.model.User">
INSERT INTO tUser (ID,UserName,NickName,PassWord,Email,Phone,Sex,Status,Avatar,Remarks,AddAt)
VALUES (#{id},#{userName},#{nickName},#{password},#{email},#{phone},#{sex},#{status},#{avatar},#{remarks},unix_timestamp(now()))
</insert>
<insert id="addDefaultIntegral" parameterType="String">
INSERT INTO tIntegral (ID,UserID,ResidualIntegral)
VALUES (uuid(),#{userId},10)
</insert>
<select id="getUserList" resultMap="userMap">
SELECT ID,UserName,NickName,PassWord,Email,Phone,Sex,Status,Avatar,Remarks,AddAt FROM tUser WHERE UserName = #{userName}
</select>
</mapper>
這是mybatis中sql的映射文件。命名空間namespace即為UserDao接口。
然后下面sql的id即為UserDao接口中的方法名,這樣即可實(shí)現(xiàn)mybatis的sql映射。
8. 上面提到的MybatisSqlMapping注解
/**
* 定義一個(gè)空注解,用于MapperScannerConfigurer過濾接口
* 有此注解的interface才會(huì)被Mybatis掃描并生成Mapper對(duì)象
*
*/
public @interface MybatisSqlMapping {
}
9. 上面提到的ControllerAspect類的切面,用于實(shí)現(xiàn)捕獲異常等。
這里面用到了@Aspect注解,@Around環(huán)繞通知,proceed()執(zhí)行方法等,有興趣的可自行去了解AOP的相關(guān)知識(shí)。
@Aspect
public class ControllerAspect {
private static final Log log = LogFactory.getLog(ControllerAspect.class);
private ObjectMapper mapper = new ObjectMapper();
@Around("execution(public * com.btt.gcsj..controller.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
ResponseResult responseResult = new ResponseResult();
String methodName = joinPoint.getSignature().getName();
log.debug(String.format("%s start",methodName));
String result;
try{
result = joinPoint.proceed().toString();
}catch (Throwable e) {
String errorMessage = String.format("%s error. %s", methodName, e.getMessage());
log.error(errorMessage, e);
responseResult.setCode("400");
responseResult.setMsg(e.getMessage());
result = JSON.toJSONString(responseResult);
}
log.debug(format("result: %s", result));
log.debug(String.format("%s finish", methodName));
return result;
}
}
整個(gè)框架的基礎(chǔ)代碼已經(jīng)寫完,并且實(shí)現(xiàn)了添加用戶,根據(jù)用戶名獲取用戶、和用戶登錄的三個(gè)簡(jiǎn)單功能的接口。當(dāng)然里面的很多邏輯和細(xì)節(jié)沒有處理,這里只是作為一個(gè)演示。
跑一下看看。
添加用戶
addUser.png
db1.png
根據(jù)用戶名獲取用戶
getUser.png
登錄
login1.png
login2.png
到此,整個(gè)框架的搭建和一些基礎(chǔ)代碼的實(shí)現(xiàn)和測(cè)試都已完成,當(dāng)然其中還有很多很多細(xì)節(jié)問題,也沒有去處理這些細(xì)節(jié)問題。不過作為一個(gè)框架的搭建學(xué)習(xí)和對(duì)SSM的初步認(rèn)識(shí)還是足夠了,畢竟是從新手的角度出發(fā)。
文章大概講述了idea的配置,idea中項(xiàng)目的建立,框架的搭建,和功能代碼的編寫。各個(gè)步驟都還是比較詳細(xì),認(rèn)真一邊閱讀和一邊寫代碼的同學(xué)應(yīng)該是可以運(yùn)行的。
編者水平有限,若有錯(cuò)誤或者更優(yōu)的建議歡迎指出。
目前全部文章列表:
idea整合restful風(fēng)格的ssm框架(一)
idea整合restful風(fēng)格的ssm框架(二)
idea整合spring boot+spring mvc+mybatis框架
idea整合springboot+redis
JVM學(xué)習(xí)之—Java內(nèi)存區(qū)域
JVM學(xué)習(xí)之—垃圾回收與內(nèi)存分配策略
專題整理之—不可變對(duì)象與String的不可變
專題整理之—String的字符串常量池






