0%

后端练习生(二)

后端基础学习(二)

  SpringBoot的学习主要是设计模式的练习和参数注入的熟练度提升,本身没有多大的难度,主要是比较杂,需要持续投入时间。
  相比于看教程,最高效的方法是快速搭建一个项目,用到啥学啥,在此记录一下基本的框架。

SpringBoot 工程结构

我采用的是如下的工程结构,和其它的一些结构可能不尽相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
java
+-com
+- nowcoder
+- community
+- Application.java
|
+- config
| +- AlphaConfig.java
+- controller
| +- AlphaController.java
| +- HomeController.java
+- dao
| +- AlphaDao.java
| +- UserMapper.java
| +- AlphaDaoMyBatisImpl.java
+- entity
| +- DiscussPost.java
| +- Page.java
| +- User.java
+- service
| +- AlphaService.java
| +- DiscusspPostService.java
| +- UserService.java
resource
| +- Application properties
| +- mapper
| +- discusspost-mapper.xml
| +- user-mapper.xml
| +- templates
| +- demo
| +- mail
| +- site
| +- index.html
| +- static
| +- html
| +- css
| +- img
| +- js

最关键的是controller, servicedao

  • Controller:业务控制层;
      就是控制业务层Service的,它的作用主要是架起了外界与业务层沟通的桥梁,移动端,前端在调用接口访问相关业务时,都会通过Controller,由Controller去调相关的业务层代码并把数据返回给移动端和前端;
  • Service:业务层/服务层;
      业务层,所有的内部的业务逻辑都会放在这里处理,比如用户的增删改查,或者发送个验证码或邮件,或者做一个抽奖活动等等等等,都会在Service中进行,当然,业务离肯定是离不开数据的支持,因此Dao层是必不可少的;
  • Dao:数据库持久化层;
      数据持久化层,就是和数据库打交道的,而实现持久化层的框架又有很多,而常用的有两种:JPA和MyBatis,JPA是SpringBoot官方的,前身就是著名的三大框架之一的Hibernate,好处是不用手写SQL(当然它也支持手写,如果必要的话),国外用的比较多,而MyBatis则在国内比较流行,原因是它的灵活性非常高,但是需要手写SQL语句。
    • 项目复杂程度一般,追求稳定,迭代速率低的可以用JPA;
    • 项目较复杂,需求变更频繁,迭代速度快的可以用MyBatis;
  • Entity:实体层—>数据库在项目中的类
    主要用于定义与数据库对象应的属性,提供get/set方法,tostring方法,有参无参构造函数。

为什么必须要写Service层,我直接用Controller层操作Dao层,省去Service层,岂不是更简单?
  Controller的作用,只是一个桥梁,中间者。是不允许直接操作数据库的!另外,Service对以后的分布式部署有极大的作用,它就像一个服务员,哪桌客人需要点菜了,就喊一声服务员!对应的,外界需要完成什么样的业务,就通过Controller去调用不同的Service,需要记住的是,Controller只是一个中间者或者转发者,不应该在Controller里暴露Service的业务逻辑,而应该直接转发Service的业务处理结果!
  一般的,一个Controller对应一个Service,一个Service对应一个Dao,一个Dao对应一个数据库表,当然根据项目或业务复杂程度,一个Controller可以调用多个Service,而一个Service也可以调用多个Dao,但是Controller层不允许互调,Service层也不允许互调,意思就是A Controller不能直接调用B Controller,A Service也不能直接去调用B Service,遵循高内聚低耦合原则。

Controller 业务控制层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Controller//这里要写上controller注解
@RequestMapping("/alpha")//这里表示访问的路径
public class AlphaController {

@Autowired
private AlphaService alphaService;

@RequestMapping("/data")//这里代表的是自路径,/alpha/data
@ResponseBody//表示返回的是经过渲染的主题,如果没有@ResponseBody注解,则返回的是html
public String getData() {
return alphaService.find();//这里就是`controller`控制`service`,再由`service`传给
}

// /student/123 GET请求
@RequestMapping(path = "/student/{id}", method = RequestMethod.GET)
@ResponseBody
public String getStudent(@PathVariable("id") int id) {
System.out.println(id);
return "a student";
}

// 响应HTML数据
@RequestMapping(path = "/teacher", method = RequestMethod.GET)
public ModelAndView getTeacher() {
ModelAndView mav = new ModelAndView();
mav.addObject("name", "姚澜");
mav.addObject("age", 23);
mav.setViewName("/demo/view");
return mav;
}

@RequestMapping(path = "/school", method = RequestMethod.GET)
public String getSchool(Model model) {
model.addAttribute("name", "哈尔滨工业大学");
model.addAttribute("age", 100);
return "/demo/view";
}

// 响应JSON数据(异步请求)
// Java对象 -> JSON字符串 -> JS对象

@RequestMapping(path = "/emps", method = RequestMethod.GET)
@ResponseBody
public List<Map<String, Object>> getEmps() {
List<Map<String, Object>> list = new ArrayList<>();

Map<String, Object> emp = new HashMap<>();
emp.put("name", "泉宇");
emp.put("age", 23);
emp.put("salary", 35000.00);
list.add(emp);

emp = new HashMap<>();
emp.put("name", "姚澜");
emp.put("age", 23);
emp.put("salary", 40000.00);
list.add(emp);
return list;
}

}

Service 业务层/服务层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service//Service层要添加Service注解
public class DiscussPostService {
@Autowired
private DiscussPostMapper discussPostMapper;//这里连接的是dao层的Mapper

public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
//返回调用dao层的方法
return discussPostMapper.selectDiscussPosts(userId, offset, limit);
}

public int findDiscussPostRows(int userId) {
return discussPostMapper.selectDiscussPostRows(userId);
}
}

dao 数据库持久化层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Mapper
//mapper需要添加@mapper注解,或者也可以添加@Repository注解
public interface UserMapper {
//下面的方法为对数据库的操作,但是不需要定义方法逻辑
//剩下的部分连接Resource中的mapper,进行数据库的操作
User selectById(int id);

User selectByName(String username);

User selectByEmail(String email);

int insertUser(User user);

int updateStatus(int id, int status);

int updateHeader(int id, String headerUrl);

int updatePassword(int id, String password);
}

entity 实体层

实体层比较简单,就是定义变量,并设置Get, Set, ToString方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public class Page {

// 当前页码
private int current = 1;
// 显示上限
private int limit = 10;
// 数据总数(用于计算总页数)
private int rows;
// 查询路径(用于复用分页链接)
private String path;

public int getCurrent() {
return current;
}

public void setCurrent(int current) {
if (current >= 1) {
this.current = current;
}
}

public int getLimit() {
return limit;
}

public void setLimit(int limit) {
if (limit >= 1 && limit <= 100) {
this.limit = limit;
}
}

public int getRows() {
return rows;
}

public void setRows(int rows) {
if (rows >= 0) {
this.rows = rows;
}
}

public String getPath() {
return path;
}

public void setPath(String path) {
this.path = path;
}

/**
* 获取当前页的起始行
*
* @return
*/
public int getOffset() {
// current * limit - limit
return (current - 1) * limit;
}

/**
* 获取总页数
*
* @return
*/
public int getTotal() {
// rows / limit [+1]
if (rows % limit == 0) {
return rows / limit;
} else {
return rows / limit + 1;
}
}

/**
* 获取起始页码
*
* @return
*/
public int getFrom() {
int from = current - 2;
return from < 1 ? 1 : from;
}

/**
* 获取结束页码
*
* @return
*/
public int getTo() {
int to = current + 2;
int total = getTotal();
return to > total ? total : to;
}

}

mapper dao层对数据库的映射

mapper文件主要是添加对数据库的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?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.nowcoder.community.dao.UserMapper">
<!--前面是对mybatis的文件说明,namespace应该写对应dao层的全界名-->
<!--一个mapper文件对应一个dao层文件-->

<sql id="insertFields"> <!--定义系列的变量域,便于日后维护-->
username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>

<sql id="selectFields">
id, username, password, salt, email, type, status, activation_code, header_url, create_time
</sql>

<select id="selectById" resultType="User">
select <include refid="selectFields"></include>
from user
where id = #{id}
</select>

<select id="selectByName" resultType="User">
select <include refid="selectFields"></include>
from user
where username = #{username}
</select>

<select id="selectByEmail" resultType="User">
select <include refid="selectFields"></include>
from user
where email = #{email}
</select>

<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user (<include refid="insertFields"></include>)
values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
</insert>

<update id="updateStatus">
update user set status = #{status} where id = #{id}
</update>

<update id="updateHeader">
update user set header_url = #{headerUrl} where id = #{id}
</update>

<update id="updatePassword">
update user set password = #{password} where id = #{id}
</update>

</mapper>

Thymeleaf

Thymeleaf是一个动态的html模版, 它能够通过实体类的值实时改变html的响应。上文的controller层的最终返回是index.html,意思就是返回了这个动态页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"><!--这里要声明是thymeleaf模版-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<title>首页</title>
</head>
<body>
<!-- 分页 -->
<nav class="mt-5" th:if="${page.rows>0}">
<ul class="pagination justify-content-center">
<li class="page-item"><!--这里是动态的变量,用th来声明-->
<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
</li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a></li>
<li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
<a class="page-link" href="#" th:text="${i}">1</a>
</li>
<li th:class="|page-item ${page.current==page.total?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
</li>
</ul>
</nav>
</body>
</html>

Application properties 配置

用来配置服务器容器的一些属性,如

  • server.port=8080 配置端口号
  • server.servlet.context-path=/community 配置上下文环境
  • spring.thymeleaf.cache=false 配置thymeleaf模版缓存

更多的配置信息可以直接访问Spring官网的Reference