JavaWeb
三层解耦
这里会用到面向对象七大原则中的单一职责原则,即每个程序有自己的任务,而不是有很多任务导致单一程序复杂,耦合度高,复用性差
我们可以将后端划分为三个部分,MVC框架是Controller,View,Model
而springboot可以划分为Controller,Service,Dao三层,分别为监听层,逻辑处理层,数据管理层,原来复杂的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
 | @RestController
public class EmpController {
        String file= this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> EmpList= XmlParserUtils.parse(file, Emp.class);
        empList.stream().forEach(emp -> {
        String gender = emp.getGender();
        String job = emp.getJob();
        if(gender.equals("1")){
            emp.setGender("男");
        }else if(gender.equals("2")){
            emp.setGender("女");
        }
        if(job.equals("1")){
            emp.setJob("讲师");
        }else if(job.equals("2")){
            emp.setJob("班主任");
        } else if (job.equals("3")) {
            emp.setJob("就业指导");
        }
        }););
        List<Emp> empList=empServiceA.list();
        System.out.println(empList);
        return Result.success(empList);
    }
}
 | 
 
现在可以使用三层架构来分别放置 Controller,Service,Dao三层
Dao层的接口及实现如下
| 1
2
3
 | public interface EmpDao {
    public List<Emp> listEmp();
}
 | 
 
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 | public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //加载并解析XML文件
        String file= this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> EmpList= XmlParserUtils.parse(file, Emp.class);
        return EmpList;
    }
}
 | 
 
Service层的接口及实现如下
| 1
2
3
 | public interface EmpService {
    public List<Emp> list();
}
 | 
 
|  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
 | import java.util.List;
public class EmpServiceA implements EmpService {
    private EmpDao empDao=new EmpDaoA();
    @Override
    public List<Emp> list() {
    //对数据进行转化处理
    List<Emp> empList=empDao.listEmp();
        empList.stream().forEach(emp -> {
        String gender = emp.getGender();
        String job = emp.getJob();
        if(gender.equals("1")){
            emp.setGender("男");
        }else if(gender.equals("2")){
            emp.setGender("女");
        }
        if(job.equals("1")){
            emp.setJob("讲师");
        }else if(job.equals("2")){
            emp.setJob("班主任");
        } else if (job.equals("3")) {
            emp.setJob("就业指导");
        }
    });
        return empList;
    }
}
 | 
 
这里通过创建Dao层对象然后调用其方法来获取数据,但这会让Dao层和Service层紧耦合
Controller代码如下
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | @RestController
public class EmpController {
    private EmpServiceA empServiceA =new EmpServiceA();
    @RequestMapping("/listEmp")
    public Result list() {
        List<Emp> empList=empServiceA.list();
        System.out.println(empList);
        return Result.success(empList);
    }
}
 | 
 
同样的,这里的创建对象也会让Service层和Controller层紧耦合
我们可以考虑使用设计模式中的工厂模式来解决这个紧耦合办法,但是springboot已经想好了解决对策,那就是
控制反转与依赖注入
控制反转:Inversion Of control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。
Bean对象:IOC容器中创建、管理的对象,称之为Bean
解耦之后的代码演示:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 | //Dao层
@Component
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //加载并解析XML文件
        String file= this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> EmpList= XmlParserUtils.parse(file, Emp.class);
        return EmpList;
    }
}
 | 
 
|  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
 | //Service层
@Component
public class EmpServiceA implements EmpService {
    @Autowired
    private EmpDao empDao;
    @Override
    public List<Emp> list() {
    //对数据进行转化处理
    List<Emp> empList=empDao.listEmp();
        empList.stream().forEach(emp -> {
        String gender = emp.getGender();
        String job = emp.getJob();
        if(gender.equals("1")){
            emp.setGender("男");
        }else if(gender.equals("2")){
            emp.setGender("女");
        }
        if(job.equals("1")){
            emp.setJob("讲师");
        }else if(job.equals("2")){
            emp.setJob("班主任");
        } else if (job.equals("3")) {
            emp.setJob("就业指导");
        }
    });
        return empList;
    }
}
 | 
 
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 | //Controller层
@RestController
public class EmpController {
    @Autowired
    private EmpService empService;
    @RequestMapping("/listEmp")
    public Result list() {
        List<Emp> empList=empService.list();
        System.out.println(empList);
        return Result.success(empList);
    }
}
 | 
 
如果我此时要加入EmpDaoB(通过MySQL等数据库传送数据),那就吧EmpDaoA的@Component注释了
springboot给三层架构分别出了三个衍生注解@Repository,@Service,@Controller
后续基本上都用数据库传输,并且springboot继承了Mybatis,Mybatis可以使用注解@Mapper来替代@Repository,而Controller层自带@RestController注解,因此可以不用@Controller
@Component注解可以在不属于这三层,但是很有用的工具类上加这个注解
Pojo文件
pojo文件中存放各种JavaBean
Springboot特有的JavaBean写法,使用前要引入Lombok依赖
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 | package com.test.springboottest03_crud.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Short gender;
    private String image;
    private Short job;
    private LocalDate entryDate;
    private Integer deptId;
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//修改时间
}
 | 
 
这里用到了三个注解
- 
@Data 同时包含了toString方法,HashCode,所有get,set方法 
- 
@NoArgsConstructor 是无参构造 
- 
@AllArgsConstructor 是全参构造 
Mybatis的增删改查(注解写法)
在文件中创建mapper文件夹,创建对应的Mapper接口
使用注解@Mapper
| 1
2
3
4
5
6
7
8
9
 | @Mapper//程序开始时会自动创建代理对象
public interface EmpMapper {
    @Delete("delete from emp where id=#{id}")
    public int delete(Integer id);
    @Options(useGeneratedKeys = true,keyProperty = "id")
    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
            " values (#{username},#{name},#{gender},#{image},#{job},#{entryDate},#{deptId},#{createTime},#{updateTime})")
    public void insert(Emp emp);
}
 | 
 
- 第一个是删除操作,@Delete里面写SQL语句,d=#{id}是Mybatis的占位符
使用Integer是因为int不支持不输入就是null,与SQL语句不吻合
该删除操作删除的是指定id对象
写正常的insert语句,然后每个占位符都是JavaBean里面的,注意驼峰命名法
插入操作的形参是JavaBean对象
Test类的写法
|  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
 | class SpringBootTest03CrudApplicationTests {
    @Autowired
    EmpMapper empMapper;
    @Test
    public void testDelete() {
        int a = empMapper.delete(17);
        System.out.println(a);
    }
    public void testInsert() {
        //构造员工对象
        Emp emp = new Emp();
        emp.setUsername("Tom7");
        emp.setName("汤姆3");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntryDate(LocalDate.of(2000,1,1));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);
        //执行新增员工信息操作
        empMapper.insert(emp);
        System.out.println(emp.getId());
    }
}
 | 
 
在测试类中,先用抽象父类创建对象,然后使用依赖注入@Autowire,等价于EmpMapper empMapp=new EmpMapperA(),把和数据库连接好的bean对象传过来,这样就可以对数据库或者xml等数据载体进行操作了。
删除操作
插入操作
insert方法要将创建好的对象初始化后使用empMapper.insert(emp)来插入
如果直接输出emp.getId()是没有结果的,在定义接口的时候使用注解@Options
| 1
 | @Options(useGeneratedKeys = true,keyProperty = "
 | 
 
这样就可以返回Id了
使用LocalDateTime.now()这个方法最后的返回值符合MySQL的date格式
修改操作
mapper中的代码
| 1
2
3
 | @Update("update emp set username =#{username},name=#{name},gender=#{gender},image=#{image}," +
            "job=#{job},entrydate=#{entryDate},dept_id=#{deptId},update_time=#{updateTime} where id=#{id}")
    public void update(Emp emp);
 | 
 
Test中的代码
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 | public void testUpdate() {
        Emp emp = new Emp();
        emp.setId(1);
        emp.setUsername("Tom12");
        emp.setName("汤姆1");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntryDate(LocalDate.of(2000,1,1));
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);
        empMapper.update(emp);
    }
 | 
 
查询操作
mapper中的代码
| 1
2
 | @Select("select * from emp where id=#{id}")
    public Emp selectById(Integer id);
 | 
 
Test中的代码
| 1
2
3
4
5
6
 | public void testSelect() {
        Integer id=12;
        Emp emp= new Emp();
        emp=empMapper.selectById(id);
        System.out.println(emp);
    }
 | 
 
这里是根据id来对数据查询,但是在注入对象empMapper对应的代理对象赋值的时候,数据库中的下划线命名法和java中的驼峰命名法冲突,导致后面使用驼峰命名法的字段赋值失败
- 这时候可以在application.properties中输入camel+Tab
| 1
2
 | #Mybatis的驼峰命名法映射开关打开
mybatis.configuration.map-underscore-to-camel-case=true
 | 
 
这时候所有输出就对味了
查询操作ProMax:模糊查询
对员工姓名进行模糊查询,对应的SQL语句是
| 1
 | select * from emp where name like concat('%',#{name},'%') and gender=#{gender} and entrydate between #{begin} and #{end} order by update_time desc
 | 
 
其中‘%张%’的意思是其中有一个字是张就行了,前面和后面都有字也在查询范围里面,张无忌,我是张三等名字都可以被查询到,模糊查询要用关键词like,时间范围可以用between
但是在@Select注解中不能直接这么写,‘%#{name}%’,其中#{name}不能放到引号里面,因为#{name}会在预编译期间变为?,如果是%?%那么任何一个索引都可以被查询到
| 1
2
 | @Select("select * from emp where name like concat('%',#{name},'%') and gender=#{} and " +
            "entrydate between #{begin} and #{end} order by updateTime desc")
 | 
 
| 1
2
3
4
 | public void testSelectPlus() {
        List<Emp> empList=empMapper.selectAll("张",(short)1,LocalDate.of(2010,01,01),LocalDate.of(2020,01,01));
        System.out.println(empList);
    }
 | 
 
LocalDate.of(2010,01,01)可以输入时间
Mybatis的XML写法
要想使用XML映射来实现增删改查需要在resources中添加一致包名和xml文件
注:在resources里面创建的不是软件包,是资源包,分隔符不是’.‘而是’/’,之后会自动转化为’.’,并且之后创建的xml文件要和接口文档命名一致
两种方法对比:
| 1
2
3
4
 | //条件查询注解法
    @Select("select * from emp where name like concat('%',#{name},'%') and gender=#{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> selectAll(String name, Short gender, LocalDate begin, LocalDate end);
 | 
 
| 1
2
 | //xml法
public List<Emp> selectAll(String name, Short gender, LocalDate begin, LocalDate end);
 | 
 
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
 | <?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.test.springboottest03_crud.mapper.EmpMapper">
    <select id="selectAll" resultType="com.test.springboottest03_crud.pojo.Emp">
        select * from emp where name like concat('%',#{name},'%') and gender=#{gender} and
        entrydate between #{begin} and #{end} order by update_time desc
    </select>
</mapper>
 | 
 
XML法的前面部分是固定语句,可以直接从官网复制
创建一个接口的xml语句可以先创建好接口,然后按下Alt+Enter点击最上面的选项
然后就可以在xml文件里编辑了
想通过这种方式创建得按照下面方式下载MybatisX插件
在XML文件中,SQL语句很长,可以选中所有SQL语句然后按下Ctrl+Alt+L格式化
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 | <update id="update2">
        update emp
        set username   =#{username},
            name=#{name},
            gender=#{gender},
            image=#{image},
            job=#{job},
            entrydate=#{entryDate},
            dept_id=#{deptId},
            update_time=#{updateTime}
        where id = #{id}
    </update>
 | 
 
注:Ctrl+Alt+L可能被网易云音乐或者QQ占用,需要去对应的软件中关闭此快捷键
不管哪一种方法,都要有方法体,只是说把SQL语句移到了xml文件中
可以在IDEA中下载MybatisX插件,跳转非常方便
官方提示
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
动态SQL语句
实际业务需求:
若直接写刚才的select语句很容易就搜索不到数据,因为搜索对应的值为null和无搜索条件逻辑不符,此时可以引入动态SQL语句
Select动态SQL语句
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 | <?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.test.springboottest03_crud.mapper.EmpMapper">
    <select id="selectAll" resultType="com.test.springboottest03_crud.pojo.Emp">
        select *
        from emp
        <where>
            <if test="name!=null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender!=null">
                and gender = #{gender}
            </if>
            <if test="begin!=null and end!=null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
            order by update_time desc
    </select>
</mapper>
 | 
 
其中语句可以判断是否有这个条件,如果没有则跳过这条语句。
可以动态判断是否该加and,如果搜索条件为后面两个条件,那么SQL语句开头就是and导致语法错误,但是where可以解决这个问题
Update的动态SQL语句
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 | <update id="update2">
        update emp
        <set>
        <if test="username!=null">username =#{username},</if>
        <if test="name!=null">name=#{name},</if>
        <if test="gender!=null">gender=#{gender},</if>
        <if test="image!=null">image=#{image},</if>
        <if test="job!=null">job=#{job},</if>
        <if test="entryDate!=null">entrydate=#{entryDate},</if>
        <if test="deptId!=null">dept_id=#{deptId},</if>
        <if test="updateTime!=null">update_time=#{updateTime}</if>
        </set>
        where id = #{id}
    </update>
 | 
 
这里同样用到了if来设置默认搜索条件,并且引入来判断逗号是否多余导致的SQL语句错误,与的用法一致
在上述xml写好后,修改条件就可以如下
| 1
2
3
4
5
6
7
8
9
 | public void testUpdate() {
        Emp emp = new Emp();
        emp.setId(12);
        emp.setUsername("Sam54235");
        /*emp.setName("萨姆1");
        emp.setImage("1.jpg");
        emp.setUpdateTime(LocalDateTime.now());*/
        empMapper.update2(emp);
    }
 | 
 
动态SQL—批量删除操作
一次性删除多个对象可以这样写SQL语句
| 1
 | delete from emp where id in(18,21);
 | 
 
| 1
2
3
4
5
6
7
 | //接口部分这么写
public void deleteById(List<Integer> list);
//Test类中这样写
public void deleteTest() {
        List<Integer> list = Arrays.asList(10, 11);
        empMapper.deleteById(list);
    }
 | 
 
因为要删除多个,所以此时传参以集合的方式传递,并且后面集合的名称要和xml中的一致
在xml中需要这么写
| 1
2
3
4
5
6
 | <delete id="deleteById">
        delete from emp where id in
        <foreach collection="list" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
 | 
 
其中foreach操作可以遍历传递过来的集合list,然后拼凑出所要的sql语句
其中collection是集合的名称,item是告诉sql语句这时候要按照什么进行删除,separator是SQL语句的分隔符,open和close分别是开始和结尾的字符#{id}通过占位符来加入数据,最后就可以形成(10,11)这样的语句,和之前的delete from emp where id in结合起来就是完整的SQL语句
SQL代码复用
在企业中直接使用select * from emp速度没有全参访问速度快
| 1
2
 | select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
        from emp
 | 
 
| 1
2
3
4
 | <sql id="commonSelect">
        select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
        from emp
</sql>
 | 
 
这时候可以使用动态SQL语句sql来封装SQL代码
id就是以后调用的时候的名称
要调用的时候就这样写
| 1
2
3
4
 | <select id="selectAll" resultType="com.test.springboottest03_crud.pojo.Emp">
        <include refid="commonSelect"/>
        /**/
</select>
 | 
 
简易Web网站开发
前端部分已经写好,我们只用对照产品经理写的API文档接口来写后端程序即可
创建springboot项目,勾选springweb依赖,lombok依赖,mybaties和MySQL依赖
在application.properties中配置文件
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 | spring.application.name=SpringBootProject01
#?????
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#??????url
spring.datasource.url=jdbc:mysql://localhost:3306/springboottest
#?????????
spring.datasource.username=root
#????????
spring.datasource.password=123456
#??mybatis???, ????????
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#??mybatis??????????? a_column ------> aCloumn
mybatis.configuration.map-underscore-to-camel-case=true----> aCloumn
 | 
 
配置MySQL信息,MySQL用户名,密码,还有Mybatis的驼峰命名法转蛇形命名法
内容是查询所有部门,要求这里是Get请求,可以使用@GetMapping
使用三层架构,分别是DeptController,DeptService,DeptMapper
|  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
 | @Slf4j
@RestController
@RequestMapping("/depts")
public class DeptController {
    Dept dp=new Dept();
    @Autowired
    private DeptService deptService;
    /*@RequestMapping(value = "/depts",method = RequestMethod.GET)*/
    @GetMapping()
    public Result list(){
        log.info("查询所有部门数据");
        List<Dept> depts= deptService.list();
        return Result.success(depts);
    }
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id){
        log.info("删除所选部门数据");
        deptService.delete(id);
        return Result.success();
    }
    @PostMapping()
    public Result save(@RequestBody Dept dept){
        log.info("添加部门{}", dept);
        deptService.save(dept);
        return Result.success();
    }
    @GetMapping("/{id}")
    public Result select(@PathVariable Integer id){
        log.info("根据ID{}查询部门", id);
        dp=deptService.select(id);
        return Result.success(dp);
    }
    @PutMapping()
    public Result update(@RequestBody Dept dept){
        log.info("修改部门{}", dept);
        deptService.update(dept);
        return Result.success();
    }
}
 | 
 
这是DeptController的代码有@RequestMapping("/depts)后可以在后面定义类似GetMapping("/depts/{id}")时直接省略前面的/depts
Result类可以以统一格式把数据上传到前端,并且是JSON格式(这是个工具类,直接导入pojo包下即可)
|  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
 | package com.test.springbootproject01.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Integer code;//响应码,1 代表成功; 0 代表失败
    private String msg;  //响应信息 描述字符串
    private Object data; //返回的数据
    //增删改 成功响应
    public static Result success(){
        return new Result(1,"success",null);
    }
    //查询 成功响应
    public static Result success(Object data){
        return new Result(1,"success",data);
    }
    //失败响应
    public static Result error(String msg){
        return new Result(0,msg,null);
    }
}
 | 
 
Service层用来处理数据,需要用到注解@Service
|  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
 | @Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    public List<Dept> list(){
        List<Dept> deptList = deptMapper.list();
        return deptList;
    }
    public void delete(Integer id){
        deptMapper.delete(id);
    }
    public void save(Dept dept){
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.save(dept);
    }
    @Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.update(dept);
    }
    @Override
    public Dept select(Integer id) {
        return deptMapper.list1(id);
    }
}
 | 
 
最后到Mapper层用来和数据库对接,Mapper可以用XML来和数据库对接,也可以使用注解的方式,这里演示全用xml格式
| 1
2
3
4
5
6
7
8
9
 | @Mapper
public interface DeptMapper {
/*    @Select("select * from springboottest.dept")*/
    public List<Dept> list();
    public void delete(Integer id);
    public void save(Dept dept);
    public void update(Dept dept);
    public Dept list1(Integer id);
}
 | 
 
xml文件如下
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 | <insert id="save">
        insert into springboottest.dept(springboottest.dept.name,springboottest.dept.create_time,springboottest.dept.update_time) values(#{name},#{createTime},#{updateTime})
    </insert>
    <update id="update">
        update springboottest.dept set springboottest.dept.name=#{name},springboottest.dept.update_time=#{updateTime} where springboottest.dept.id=#{id}
    </update>
    <delete id="delete">
        delete  from springboottest.dept where id=#{id}
    </delete>
    <select id="list" resultType="com.test.springbootproject01.pojo.Dept">
        select * from springboottest.dept
    </select>
    <select id="list1" resultType="com.test.springbootproject01.pojo.Dept">
        select * from springboottest.dept where id=#{id}
    </select>
 | 
 
这样Get请求从前端发送过来后,由后端Controller层接受请求,然后调用service层处理数据,然后service层再调用mapper层获取数据,最终处理完数据后返回给前端
注意到这里update接口应当先根据ID查询到对应的数据,然后再将更改后的数据发送给服务端存储
点击编辑按钮后,前端发送get请求,将查询到的数据发送到这个窗口页面上
然后我们可以对其进行修改,然后将改正后的数据通过post请求发送给后端,然后后端对这个数据进行存储,完成了一次更新操作
@PathVariable注解的使用
当前端发送数据且根据id给后端时,前端的id和后端的id不一定相同
但是数据库中的内容并不是如此
所以这里可以通过@PathVariable注解来寻找到之前数据库传过来的正确的id,格式如下
| 1
2
3
4
5
6
 | @GetMapping("/{id}")
    public Result select(@PathVariable("id") Integer id) {
        log.info("根据id{}查询数据",id);
        Emp emp1=empService.selectId(id);
        return Result.success(emp1);
    }
 | 
 
@RequestBody注解的使用
前端此时传回来的数据是JSON格式,并不能直接把这个数据转化为对象传给数据库做select或者存储,此时可以通过注解@RequestBody来转化为Java对象,格式如下
| 1
2
3
4
5
6
 | @PutMapping
    public Result update(@RequestBody Emp emp){
        log.info("{}修改数据",emp.getUsername());
        empService.update(emp);
        return Result.success();
    }
 | 
 
查询emp部分稍有麻烦
分页查询员工
根据API接口文档
前端返回的数据为当前页数和每页有多少个数据
此时后端应当给前端返回的是当前页所查询到的数据和总共数据库中有多少条数据
后面的查询很简单,可以直接用个select语句来完成
| 1
 | select count(*) from springboottest.emp
 | 
 
前面的数据得用到分页查询,条件为limit #{page},#{pageSize},
此时EmpService得设置page和pageSize
| 1
2
3
4
5
6
7
8
9
 | @Override
    public PojoBean select(String name, Short gender, LocalDate begin, LocalDate end, Integer page, Integer pageSize) {
        PojoBean pojoBean = new PojoBean();
        pojoBean.setTotal(empMapper.count());
        pojoBean.setRows(empMapper.list(name,gender,begin,end,(page-1)*pageSize,pageSize));
        System.out.println((page-1)*pageSize);
        System.out.println(pageSize);
        return pojoBean;
    }
 | 
 
在数据库中limit 0,5代表第0索引开始,且每页有5个元素,前端应该是第1页,每页有5个元素,因此索引数和前端页码对上的话,索引为(page-1)*pageSize
分页条件查询员工
此时前端可能会给出查询条件,姓名name,性别gender,入职时间,entryDate
这些条件可能给,也可能全给,也可能给部分,也可能一个都不给,可以用之前提到的动态SQL语句来解决这个问题,这种复杂的sql语句不能用注解来写,只能通过XML文件配置
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 | <select id="list" resultType="com.test.springbootproject01.pojo.Emp">
        select * from springboottest.emp
        <where>
            <if test="name!=null">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender!=null">
                and gender=#{gender}
            </if>
            <if test="begin!=null and end!=null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        limit #{page},#{pageSize}
    </select>
 | 
 
前端总共返回的数据如下
| 1
2
3
4
5
6
7
 | @GetMapping
    public Result emp(String name, Short gender, LocalDate begin, LocalDate end,
                      @RequestParam(defaultValue = "1") Integer page,
                      @RequestParam(defaultValue = "10") Integer pageSize) {
        PojoBean pb=empService.select(name,gender,begin,end,page,pageSize);
        return Result.success(pb);
    }
 | 
 
@RequestParam 注解的使用
@RequestParam注解可以让参数有默认值,这样用户不使用任何条件查询就可以查询到默认的10条记录
最终给前端因为要返回两种数据,一个是总页数,一个是查询到的员工的list集合
因此这时候创建一个PojoBean类
最后把数据封装好后以Result的标准JSON格式返回给前端
批量删除员工
前端返回的删除指令可能有多条,这时候返回来的是个数组
| 1
2
3
4
5
6
 | @DeleteMapping("/{ids}")
    public Result delete(@PathVariable("ids") Integer [] ids) {
        log.info("删除所选员工数据");
        empService.delete(ids);
        return Result.success();
    }
 | 
 
接受到前端的数据后可以去service层,然后再把数组交给Mapper层
最后的xml语句为
| 1
2
3
4
5
6
 | <delete id="delete">
        delete from springboottest.emp where springboottest.emp.id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
 | 
 
安全性与认证
API接口使用JWT(JSON Web Token)进行认证,确保请求的安全性。如果登录成功就获得一个令牌,每次访问网站都会检查jwt令牌是否有效,同时可以给jwt令牌设置有效时限。
JWT认证示例(Spring Security集成JWT):
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
public class JwtTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            // Validate and parse JWT token here
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}
 | 
 
SpringBoot手搓jwt令牌认证
在使用前要在pom.xml中引入jwt的依赖
| 1
2
3
4
5
 |         <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
 | 
 
这是jwt令牌的工具类直接CV到pojo包下即可,key是秘钥,Time是令牌有效期,过期自动登出网站
|  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
 | package com.test.springbootproject01.pojo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtHelper {
    private String key = "Lucius";
    private Integer Time=3600*1000;
    public String getJwt(Claims claims){
        String jwt= Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256,key)
                .setExpiration(new Date(System.currentTimeMillis()+Time))
                .compact();
        return jwt;
    }
    public Claims parseJwt(String jwt){
        Claims claims=Jwts.parser()
                //输入秘钥
                .setSigningKey(key)
                //给jwt令牌解码
                .parseClaimsJws(jwt)
                //获取claims对象
                .getBody();
        return claims;
    }
}
 | 
 
为了让没有jwt令牌的用户无法访问网站,我们得使用拦截器,下面是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
39
40
41
42
43
44
45
46
47
48
49
50
 | package com.test.springbootproject01.interceptor;
import ...
@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override//目标方法运行前执行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1获取请求路径
        String url = request.getRequestURI();
        log.info("拦截到请求:{}",url);
        //如果是登录请求,放行
        if(url.equals("/login")){
            log.info("登录放行");
            return true;
        }
        //2判断是否登录
        String jwt=request.getHeader("token");
        if(jwt==null){
            log.info("未登录,拦截");
            Result error=Result.error("NOT_LOGIN");
            String notlogin= JSONObject.toJSONString(error);
            response.getWriter().write(notlogin);
            //返回false不放行
            return false;
        }
        JwtHelper jwtHelper=new JwtHelper();
        //3判断jwt是否合法
        //解析jwt令牌时,如果解析失败,抛出异常,捕获异常,返回错误信息,如果解析成功,就可以放行
        try {
            jwtHelper.parseJwt(jwt);
        } catch (Exception e) {
            log.info("jwt无效");
            Result error=Result.error("NOT_LOGIN");
            String notlogin=JSONObject.toJSONString(error);
            response.getWriter().write(notlogin);
            return false;
        }
        log.info("jwt有效");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}
 | 
 
正常写的话需要实现HandlerInterceptor接口中的preHandle方法,这个方法是在调用controller方法前执行的,在后端未向前端发送数据时拦截检查jwt令牌,jwt令牌的逻辑请看注释
写一个类名为WebConfig,然后配置拦截器的信息
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 | package com.test.springbootproject01.config;
import ...
@Configuration//@Configuration注解表示当前类是一个配置类
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    //注入拦截器对象
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    //注册/添加拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor)
                //添加拦截器拦截路径
                .addPathPatterns("/**")
                //除了/login以外的路径都要被拦截
                .excludePathPatterns("/login");
    }
}
 | 
 
然后回到登录的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
 | package com.test.springbootproject01.Controller;
import ...
@Slf4j
@RestController
@RequestMapping("/login")
public class LoginController {
    @Autowired
    private EmpService empService;
    @PostMapping
    public Result Login(@RequestBody Emp emp){
        log.info("{}请求登录",emp);
        Emp emp1=empService.login(emp);
        //如果查有此人就开始准备制作令牌
        if(emp1!=null){
            JwtHelper jh=new JwtHelper();
            Claims claims=new DefaultClaims();
            claims.put("id",emp1.getId());
            claims.put("username",emp1.getUsername());
            claims.put("password",emp1.getPassword());
            log.info("请求人用户名:{}",emp.getUsername());
            log.info("请求人密码{}",emp.getPassword());
            String jwt=jh.getJwt(claims);
            return Result.success(jwt);
        }
        return Result.error("NOT_LOGIN");
    }
}
 | 
 
AOP 面向切面/方法编程
要在使用AOP之前先引入依赖
| 1
2
3
4
 | <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
 | 
 
一个简单的AOP入门示例
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 | package com.test.springbootproject01.AOP;
import ...
@Slf4j
@Component
@Aspect
public class TimeAspect {
    @Around("execution(* com.test.springbootproject01.Service.*.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //方法启动时间
        long startTime = System.currentTimeMillis();
        //执行方法
        Object result = joinPoint.proceed();
        //方法结束时间
        long endTime = System.currentTimeMillis();
        log.info(joinPoint.getSignature()+"方法执行时间为"+(endTime - startTime) + "ms");
        return result;
    }
}
 | 
 
| 1
 | @Around("execution(* com.test.springbootproject01.Service.*.*(..))")
 | 
 
execution是用来提示后面是切入点,第一个*指的是返回值为任意类型,com.test.springbootproject01.Service第二个是指任何类,第三个是任何方法,(..)表示匹配任何数量和类型的参数
Nginx的反向代理
后端部署在服务器上默认占用8080端口,前端若也要在服务器上部署,最好不要也选择8080,此时就要用到反向代理。
打开
nginx配置界面、
然后修改这里的代码
listen代表前端服务器占用的端口
location /api/ 块说明
| 1
2
3
 | location /api/ {
proxy_pass http://localhost:8080/emprequest/;
}
 | 
 
- location指令(- /api/路径情况):
这里的- location /api/表示匹配所有以- /api/开头的客户端请求 URI。例如,像- http://localhost:100/api/user、- http://localhost:100/api/order这样的请求都会进入到这个- location块中进行后续处理。
- proxy_pass指令:
用于设置反向代理,即将匹配到- /api/开头的请求转发到指定的后端服务器地址及路径上。在这里,它会把请求转发到- http://localhost:8080/emprequest/。具体来说,比如前端页面发起了一个- http://localhost:100/api/some-api的请求,Nginx 会把这个请求去掉- /api/这部分前缀后,转发到- http://localhost:8080/emprequest/some-api这个路径上,让运行在- 8080端口的后端服务器去处理对应的请求,然后后端服务器返回的响应结果又会通过 Nginx 再传递回发起请求的客户端(比如浏览器)。
总体来讲,这段 Nginx 配置定义了一个监听在- 100端口的服务器,针对根路径请求会查找并返回- html目录下的相关文件,而针对以- /api/开头的请求则会将其代理转发到本地- 8080端口下的特定路径上让后端服务进行处理。
此时前端的代码为
以后设计接口最好这样搞
TODO标签代表还没做完的事,后面可以查看TODO标签对没写完的代码进行完善