idea整合restful风格的ssm框架(二)

1. 前言

在这里的就不多啰嗦什么了,先来大概介绍一下项目的体系结构等。还是上张图

项目结构.png

整体架构是ssm的前面已经说过了,下面来说一下代码的架构。因为目前项目的规划还比较小,所以在com.bgy.ssm包下面就直接建了controller、service等。如果项目情况较复杂的话,在此包应该根据功能模块来进行分包,然后再建立相应的三层,这样的话结构更清晰。

在这里有个争论:关于到底要不要repo层的问题

  • 有人赞成的是直接controller中接收用户请求,调用service处理业务逻辑,然后所有的组装数据也是在service中完成,最后service直接调用dao接口。
  • 另外有人赞成的是service中只处理业务逻辑;对于数据的组装,如操作多表时要调用dao层的多个接口时,应该在repo层中完成,这样不会使service层显得太过臃肿,并且能提高可复用性和符合设计原则中 “针对接口编程,不针对实现编程”的理念。
  • 在这里我个人的话更倾向于后者,service层更注重的应该是处理业务逻辑,不能因为实现某个功能而在service中一个方法写一堆组装数据的代码。当然这是我个人的愚见,若有高见,欢迎指正。

在这里这个项目较小,也是从新手角度出发,所以我违心的使用了controller、service、dao的三层架构。这样思路较简单和清晰。

另外说明一点,下面的代码中都有相关引用,使用时自行引入相关引用

2. 数据库

这里为了演示功能和架构,我就只使用一张表来实现操作。在这里,我建议在项目下建立sql文件夹,里面放置建表的语句,方便以后查阅和修改。

sql.png

上面是建表语句文件、下面是有些表需要进行初始化的初始化语句文件。

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这些的规范,需要的可自行添加。
(后续添加:在这里的表名和字段名在下一篇文章中进行了修改,原因可翻看下一篇文章)

3. 实体类

在这里为了简便、我就建表完成后直接建立实体类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;
    }
}

这个地方没什么特别的,对于id是直接用genarateID()方法通过uuid生成的,若需要自己设计主键生成策略的可忽略这个。

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标记这个类成为一个SpringMVC Controller对象。

因为是使用的是restful风格,所以在类前面就通过@RequestMapping("/user")注解指定此类的所有请求方法的父路径为/user,类中第一个方法是添加用户,所以路径为空,由restful风格指定的HTTP请求方式选择进入不同方法;然后直接以json字符串来交互。另外一个通过用户名获取用户的方法,把用户名带在参数上,所以value中写了value = "{userName}",然后用@PathVariable String userName接收参数。这个只是一个测试方法,暂时无用。第三个方法是登录,也是以json字符串来交互。

另外就是使用@Autowired把service注入进来了
在这里所有的方法统一用字符串返回。

(另外,这里前后端以JSON字符串交互的方式有待商榷,以前认知是用JSON字符串便于统一风格;不过最近一年以来的学习和工作,现在会直接使用实体类进行接收对象,免去JSON转对象的步骤。)

5. Service

service层主要处理业务逻辑,首先使用@Service标注这个类是业务层组件,然后也是用@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();//设置UUID
        int num = userDao.addUser(user);
        int numScore = userDao.addDefaultIntegral(user.getId());//注册用户送默认积分
        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("密码错误");
            }else {
                result.setCode("200");
                result.setMsg("登录成功");
            }
        }
        return json.toJSONString(result);
    }
}

在这里自己定义了一个ResponseResult类来作为所有的返回请求的格式。

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;
    }
}

这样以统一的code,msg和data作为返回格式,前端也方便处理。根据逻辑设定好这个类后直接转成json字符串返回即可。

6. Dao

dao这里实现非常简单,就是一个接口,没有另外去写接口的实现,很多系统是另外写了实现类的,可以对数据库查询的数据再次进行处理后返回。
这里我是通过自定义的MybatisSqlMapping注解,让有这个注解的interface被Mybatis扫描并生成Mapper对象,然后与sql-mapping文件夹下面的*.xml对应。

@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标注这个类为数据访问组件。
然后添加了自定义的@MybatisSqlMapping注解,使这个类能被扫描。

/**
 * 定义一个空注解,用于MapperScannerConfigurer过滤接口
 * 有此注解的interface才会被Mybatis扫描并生成Mapper对象
 *
 */
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接口中的方法名,这样即可实现mybatis的sql映射。

8. 上面提到的MybatisSqlMapping注解

/**
 * 定义一个空注解,用于MapperScannerConfigurer过滤接口
 * 有此注解的interface才会被Mybatis扫描并生成Mapper对象
 *
 */
public @interface MybatisSqlMapping {
}

9. 上面提到的ControllerAspect类的切面,用于实现捕获异常等。

这里面用到了@Aspect注解,@Around环绕通知,proceed()执行方法等,有兴趣的可自行去了解AOP的相关知识。

@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;
    }
}

整个框架的基础代码已经写完,并且实现了添加用户,根据用户名获取用户、和用户登录的三个简单功能的接口。当然里面的很多逻辑和细节没有处理,这里只是作为一个演示。

跑一下看看。

添加用户

addUser.png
db1.png

根据用户名获取用户

getUser.png

登录

login1.png
login2.png

到此,整个框架的搭建和一些基础代码的实现和测试都已完成,当然其中还有很多很多细节问题,也没有去处理这些细节问题。不过作为一个框架的搭建学习和对SSM的初步认识还是足够了,毕竟是从新手的角度出发。

文章大概讲述了idea的配置,idea中项目的建立,框架的搭建,和功能代码的编写。各个步骤都还是比较详细,认真一边阅读和一边写代码的同学应该是可以运行的。

编者水平有限,若有错误或者更优的建议欢迎指出。

目前全部文章列表:
idea整合restful风格的ssm框架(一)
idea整合restful风格的ssm框架(二)
idea整合spring boot+spring mvc+mybatis框架
idea整合springboot+redis
JVM学习之—Java内存区域
JVM学习之—垃圾回收与内存分配策略
专题整理之—不可变对象与String的不可变
专题整理之—String的字符串常量池

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容