springboot学习笔记

目录

一、入门案例

1、创建的控制器操作

2、在Idea中隐藏指定文件/文件夹

3、SpringBoot基础配置

二、基础配置

1、修改配置   

2、springboot的三种配置文件格式

3、配置文件的优先级

4、 yml语法规则

5、yml数据读取

6、yaml文件中的数据引用

7、将整个数据封装为Environment

8、读取ymal当中引用型数据

三、整合第三方技术

1、整合junit

 2、整合mybatis(spring_04_mybatis)

3、整合mybatis-plus

四、springboot整合ssmp的案例

1.导入的jar包依赖

2、配置application.yml文件

3.创建pojo类

 4.创建dao映射接口

5、测试我们的映射dao接口的方法

(1)分页查询::

   (2)按条件查询

(3)其他方法

6.业务层service接口

7.业务层service接口的实现类

8.业务层快速开发

9、控制表现层的开发 controller

10.前端页面的实现

五、运维实用篇

1、SpringBoot程序的打包与运行

 2、配置临时属性

 3.配置文件4级分类

4、自定义配置文件

5、多环境开发

(1)配置

 (2)多环境开发分组管理

 (3)多环境的开发控制

6、日志文件

7、日志输出格式与控制

六、热部署功能

七、配置高级

1、绑定第三方的bean

 2、松散绑定

 3、常量计量单位

 4、使用属性校验

八、测试专题   

1、加载测试临时属性

2、加载测试专用属性

3、使用测试类启动web环境

4、测试匹配信息

(1)匹配响应状态

(2)匹配响应体

(3)响应头信息匹配

5、业务层测试事务回滚

6、测试用例设置随机数

九、数据层解决方案

1、内置数据源

2、jdbcTemplate持久化技术

3、内置数据库 h2

(2) 数据库redis,springbot整合redis

(3)springboot整合mogodb

十、缓存

1、springboot内置缓存技术

2、使用内置缓存技术实现验证码

3、将实现验证码改换成ehcache技术

4、使用redis  整合redis

5、整合Memcached技术

6、使用jetcacha

(1)使用纯远程redis缓存

(2)本地缓存

7、j2cache缓存框架

十一、定时

(1)springboot整合Quartz定时任务

(2)整合task

(3)springboot整合javamail发送邮件

十二、mq产品

(1)购物订单案例,消息发送

(2)SpringBoot整合ActiveMQ

(3)SpringBoot整合RocketMQ


一、入门案例

1、创建的控制器操作

@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping
    public String getById(){
        System.out.println("springboot getById");
        return "springboot getById";
    }

}

2、在Idea中隐藏指定文件/文件夹

因为我们所创建的工程是使用别人的模板,有很多很多个包,有些包不需要使用,我们就可以将文件夹隐藏,从而更加简洁

. Idea中隐藏指定文件或指定类型文件
   1. 【Files】→【Settings】
   2. 【Editor】→【File Types】→【Ignored Files and Folders】
   3. 输入要隐藏的名称,支持*号通配符
   4. 回车确认添加

3、SpringBoot基础配置

  1.在工作空间赋值对应的工程,并修改工程名称。

  2.只留下src 喝pox.xml文件 

3.使用的时候,直接复制模板,将pox.xml文件打开将artifactId与新工程名字相同

4.在idea当中将工程导入进去,刷新maven

二、基础配置

SpringBoot默认配置文件是application.properties

1、修改配置   

  直接name=value的形式  

例子::: #服务器的端口配置
                     server.port=8081

                  #设置日志相关

                  logging.level.root=debug

2、springboot的三种配置文件格式

- properties格式(默认配置文件格式)
- yml格式(主流
- yaml格式

- application.properties(properties格式)

```properties
server.port=80
```

- application.yml(yml格式)

```YML
server:
  port: 81
```

- application.yaml(yaml格式)

```yaml
server:
  port: 82
```

3、配置文件的优先级

application.properties  >  application.yml  >  application.yaml

**总结**

1. 配置文件间的加载优先级    properties(最高)>  yml  >  yaml(最低)
2. 不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留 

4、 yml语法规则


   *大小写敏感
   *属性层级关系使用多行描述,每行结尾使用冒号结束
   *使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
   *属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
   * #号 表示注释
2. 注意属性名冒号后面与数据之间有一个**空格**
3. 字面值、对象数据格式、数组数据格式

下面是yml数据格式

5、yml数据读取

、、、application.yml
users:
  - name: Tom
    age: 4
  - name: Jerry
    age: 5

country: china

、、、java

    //读取单一数据
    @Value("${country}")
    private String country1;

    //读取对象数据
    @Value("${users[1].name}")
    private String name;
    @Value("${users[1].age}")
    private Integer age;



6、yaml文件中的数据引用

   使用的也是${}

```YAML
center:
	dataDir: D:/usr/local/fire/data
    tmpDir: D:/usr/local/fire/tmp
    logDir: D:/usr/local/fire/log
    msgDir: D:/usr/local/fire/msgDir
```
  如果你在书写yaml数据时,经常出现如下现象,比如很多个文件都具有相同的目录前缀
​  这个时候你可以使用引用格式来定义数据,其实就是搞了个变量名,然后引用变量了,格式如下:

```YAML
baseDir: /usr/local/fire
center:
    dataDir: ${baseDir}/data
    tmpDir: ${baseDir}/tmp
    logDir: ${baseDir}/log
    msgDir: ${baseDir}/msgDir
```

7、将整个数据封装为Environment

       yml页面当中的数据可以看 5、yml数据读取

  //如果一个一个获取数据太麻烦,可以直接将所有数据存储到Environment,然后通过它使用get方法拿数据,记得加上注释@Autowired(自动装配)

、、、、java
    @Autowired
    private Environment environment;

 @GetMapping
    public String getById(){
        System.out.println("age==>>"+ environment.getProperty("user[1].age"));
        return "springboot base configuration";
    }

8、读取ymal当中引用型数据

@ConfigurationProperties(prefix = "datasource")

 指定我们提取的是那一组数据

、、、yml
#创建类,用于封装下面的数据
#由spring帮我们去加载数据到对象当中,一定要告诉spring加载这组信息
#使用时候从spring当中直接获取
datasource:
  driver: com.mysql.cj.jdbc.Driver
  url: jdbc:mysql://localhost/springboot
  username: root
  password: Xx20011128

、、、创建一个pojo类

//1.定义数据模型封装yaml文件当中对应的数据
//2.定义为spring管控的Bean
@Component
//3.指定需要加载的数据,将我们yml当中定义的名字放在这里提取
@ConfigurationProperties(prefix = "datasource")
public class MyDataSource {

、、、控制器当中
 @Autowired
    private MyDataSource myDataSource;


    @GetMapping
    public String getById(){
        System.out.println(myDataSource);
        return "springboot base configuration";
    }

三、整合第三方技术

1、整合junit

  这个就是我们测试类,如果测试类不在我们启动类的包或者子包当中,我们就添加classes来指定配置类

```JAVA
  //在注解@SpringBootTest中添加classes属性指定配置类
@SpringBootTest(classes = Springboot04JunitApplication.class)
class Springboot04JunitApplicationTests {
    //注入你要测试的对象
    @Autowired
    private BookDao bookDao;
    @Test
    void contextLoads() {
        //执行要测试的对象对应的方法
        bookDao.save();
        System.out.println("two...");
    }
}
```

 2、整合mybatis(spring_04_mybatis)

  (1).将我们需要使用的jar加上

(2)yml配置文件

     重点:这里面有一个问题,玩的端口号不设置称3306启动就会报错

```yaml
#2.配置相关信息
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db
    username: root
    password: root
```

 (3)pojo类

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

(4)映射dao接口

这里面使用的mybatis注解配置,


@Mapper
public interface BookDao {
    @Select("select * from tbl_book where id = #{id} ")
    Book selectById(Integer id);
}

(5)测试程序

@SpringBootTest
class Spring04MybatisApplicationTests {
    @Autowired
    private BookDao bookDao;

    @Test
    void contextLoads() {
        Book book = bookDao.selectById(1);
        System.out.println(book);
    }

}

3、整合mybatis-plus

(1)添加依赖,在将module添加mysql包创建出来后,我们将mybatis-plus的jar依赖导入进去

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.2</version>
		</dependency>

(2)yml配置文件

#2.配置相关信息
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_tb?serverTimezone=UTC
    username: root
    password: Xx20011128


#设置所有表的通用前缀名称为tbl_,然后继承了BaseMapper的dao接口类,
#就会再查询数据的时候加上前缀,因为我们数据库设置的是tbl_book

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_

(3)创建pojo类

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

(4)映射dao接口

最重要的就是这里 我们继承 BaseMapper<Book> ,指定表的类型,然后就会帮我们将数据库的一些增删改查的方法写出来

//如果是整合mybatis-plus的话,我们这里的方法不需要写具体的方法操作了
//mybatis-plus帮助我们写了
@Mapper
public interface BookDao extends BaseMapper<Book> {
}

(5)测试方法

@SpringBootTest
class Spring05MybatisPlusApplicationTests {

	@Autowired
	private BookDao bookDao;

	@Test
	void contextLoads() {
		System.out.println(bookDao.selectById(3));
	}

}

四、springboot整合ssmp的案例

1.导入的jar包依赖

  model哪里选择spring web ,mysql Driver

手动导入的有::前面不加mybatis是因为我们使用的mybatis-plus,所以不需要添加

		<!--mybatis-plus的jar包依赖-->这个永华简化Mapper操作
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.2</version>
		</dependency>

		<!--druid的jar依赖文件-->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.2.18</version>
		</dependency>

		<!--lombok-->这个给是方便我们实例化类
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

2、配置application.yml文件

(1)开启日志文件

3.创建pojo类

  使用lombok的话,我们直接添加@Data属性

 4.创建dao映射接口

//这个我们使用的是mybatis-plus,可以简化Mapper文件,
//只需要继承BaseMapper<这里写我们pojo类>
@Mapper
public interface BookDao extends BaseMapper<Book> {

}

5、测试我们的映射dao接口的方法

(1)分页查询::

分页查询获取查询数据,直接用创建的 IPage 对象使用get方法获取

  测试包BookDaoTest下
 

   //分页查询,分页查询的话,它不能直接使用,我们需要在创捷一个MybatisPlus的拦截器,
    @Test
    void testGetPage(){
        //	其中selectPage方法需要传入一个封装分页数据的对象,
        //可以通过new的形式创建这个对象,当然这个对象也是MyBatisPlus提供的,别选错包了。
        //创建此对象时需要指定两个分页的基本数据
        IPage page = new Page(1, 5);
        bookDao.selectPage(page,null);
        //分页方法返回的就是page,我们要想获取查询数据,直接page。get的获取
        System.out.println(page.getCurrent());

    }


拦截器,我们创建config文件,专门存放配置类信息,在里面创建MP的配置配置类

//这个是我们mybatisPlus的配置文件
@Configuration//添加注解,让它成为配置文件
public class MPConfig {

    因为这个是配置文件,整体上还是spring的程序,
    要将对象创建出来交给spring的ioc管理
    配置拦截器,这个拦截器就是帮助我们动态的拼接limit语句,帮助我们完成分页查询
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }



}
   (2)按条件查询

     这个记住一点,LambdaQueryWrapper是用来封装条件的对象,

    QueryWrapper的话可能我们查询的条件name会打错,所以我们一般使用第一种情况

   第一个是按条件分页查询,第二个是我们普通的按条件查询所有

 //按条件查询
    @Test
    void testGetBy(){
        String name = "spring";
        //这个是要传入值的对象,可以封装我们的条件
        LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
        //因为我们给条件传值的时候为null会将null作为值传入进去,所有LambdaQueryWrapper对象提供了这个值name!=null
        //将这个值传入进去就,如果为为空,我们就不将条件拼接进去
        lqw.like(name!=null,Book::getName,name);
        bookDao.selectList(lqw);
    }
(3)其他方法
@SpringBootTest
public class BookDaoTest {//测试我们dao下面的方法
    @Autowired
    private BookDao bookDao;

    @Test
    public void testSelectById(){
        System.out.println(bookDao.selectById(2));
    }

    @Test
    public void testSelectAll(){
        System.out.println(bookDao.selectList(null));
    }

    @Test
    public void testSave(){
        Book book = new Book();
        book.setName("测试1");
        book.setType("测试2");
        book.setDescription("测试3");
        bookDao.insert(book);
    }

    @Test
    void testDeleteById(){
        bookDao.deleteById(2);
    }

6.业务层service接口

一般service接口的名称定义为业务名称,与dao当中的名称有所区分



public interface BookService {

    Boolean save(Book book);

    Boolean delete(Integer id);

    Boolean update(Book book);

    Book getById(Integer id);

    List<Book> getAll();

    //分页查询,两个参数,当前页码,每页显示数目
    IPage<Book> getPage(int currentPage,int pageSize);

}

7.业务层service接口的实现类


@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public Boolean save(Book book) {
        int count = bookDao.insert(book);
        return count > 0;
    }

    @Override
    public Boolean delete(Integer id) {
        int count = bookDao.deleteById(id);
        return count > 0;
    }

    @Override
    public Boolean update(Book book) {
        int count = bookDao.updateById(book);
        return count > 0;
    }

    @Override
    public Book getById(Integer id) {
        return bookDao.selectById(id);
    }

    @Override
    public List<Book> getAll() {
        return bookDao.selectList(null);
    }

    //分页查询
    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        IPage page = new Page(currentPage,pageSize);
        return bookDao.selectPage(page,null);
    }
}

8.业务层快速开发

直接写我们的业务层接口继承IService<T>,然后接口实现类继承业务通用实现类。

注意::要记住的就是ServiceImpl<M,T>一般就是ServiceImpl<BookDao,Book> ,M就是dao层的业务接口

service接口

public interface IBookService extends IService<Book> {

    boolean saveBook(Book book);

    boolean modify(Book book);

    boolean delete(Integer id);

    //分页
    IPage<Book> getPage(int currentPage, int pageSize);

    //分页加条件查询
    IPage<Book> getPage(int currentPage, int pageSize,Book book);

}

service接口的实现

@Service//把这个纳入ioc容器管理
public class IBookServiceImpl extends ServiceImpl<BookDao,Book> implements IBookService {
    //要记住的就是ServiceImpl<M,T>一般就是ServiceImpl<BookDao,Book> ,M就是dao层的业务接口

    @Autowired
    private BookDao bookDao;

    @Override
    public boolean saveBook(Book book) {
        return bookDao.insert(book)>0;
    }

    @Override
    public boolean modify(Book book) {
        int count = bookDao.updateById(book);
        return count >0;
    }

    @Override
    public boolean delete(Integer id) {
        int count = bookDao.deleteById(id);
        return count>0;
    }

    @Override
    public IPage<Book> getPage(int currentPage, int pageSize) {
        //如果只是分页查询这样就够了
       IPage<Book> page = new Page<Book>(currentPage,pageSize);
        return bookDao.selectPage(page,null);
    }

    //分页查询+条件查询
    @Override
    public IPage<Book> getPage(int currentPage, int pageSize,Book book) {
        IPage<Book> page = new Page<Book>(currentPage,pageSize);
        //这个是要传入值的对象,可以封装我们的条件
        LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
        //讲条件添加进去
        lqw.like(book.getType()!=null,Book::getType,book.getType());
        lqw.like(book.getName()!=null,Book::getName,book.getName());
        lqw.like(book.getDescription()!=null,Book::getDescription,book.getDescription());
        bookDao.selectPage(page,lqw);
        return page;
    }

}

9、控制表现层的开发 controller

  但是我们一般使用的是统一格式的方法,不然前端拿到的数据比较乱

//@RestController
@RequestMapping("/books")
public class BookController2 {//表现层,这里的表现层数据没有统一格式
    //@Autowired
    private IBookService iBookService;

    @GetMapping
    public List<Book> GetAll(){
        return iBookService.list();
    }

    @GetMapping("/{id}")
    public Book GetById(@PathVariable("id") Integer id){
        return iBookService.getById(id);
    }

    @PostMapping
    public Boolean save(@RequestBody Book book){
        return iBookService.save(book);
    }

    @DeleteMapping("/{id}")
    public Boolean delete(@PathVariable("id") Integer id){
        return iBookService.removeById(id);
    }

    @PutMapping
    public Boolean update(@RequestBody Book book){
        return iBookService.modify(book);
    }

    //分页查询
    @GetMapping("/{current}/{size}")
    public IPage<Book> getPage(@PathVariable("current") int current,@PathVariable("size")int size){
        return iBookService.getPage(current,size,null);
    }

}

10、控制表形层统一数据

使用这种格式 flag表示操作成功,false表示出现异常,date用来存储数据

 在controller当中创建工具类utils,存放我们统一数据的类

@Data//使用lombok构建实体类方法
public class R {//表现层数据一致性处理
    private Boolean flag;
    private Object data;
    private String msg;

    public R(){}
    public R(Boolean flag){
        this.flag=flag;
    }

    public R(Boolean flag, Object data) {
        this.flag = flag;
        this.data = data;
    }


    public R(Boolean flag, Object data, String msg) {
        this.flag = flag;
        this.data = data;
        this.msg = msg;
    }
}

控制器业务层代码,使用统一格式的方法


@RestController
@RequestMapping("/books")
public class BookController {//表现层,这里的数据统一了格式
    @Autowired
    private IBookService iBookService;

    @GetMapping
    public R GetAll(){
        return new R(true,iBookService.list());
    }

    @GetMapping("/{id}")
    public R GetById(@PathVariable("id") Integer id){
        return new R(true,iBookService.getById(id));
    }

    @PostMapping
    public R save(@RequestBody Book book) throws IOException {
        //return new R(iBookService.saveBook(book));
        //这里模拟一个异常操作,当添加的数据name=123的时候报错
        if(book.getName().equals("123")){
            throw new IOException();
        }
        boolean flag = iBookService.saveBook(book);
        return new R(flag,null,flag ? "添加成功o.O":"添加失败O.o");
    }

    @DeleteMapping("/{id}")
    public R delete(@PathVariable("id") Integer id){
        return new R(iBookService.delete(id));
    }

    @PutMapping
    public R update(@RequestBody Book book){
        return new R(iBookService.modify(book));
    }

    //分页查询,这里将book作为提交查询数据一起传输过去
    @GetMapping("/{currentPage}/{pageSize}")
    public R getPage(@PathVariable("currentPage") int currentPage,@PathVariable("pageSize")int pageSize,Book book){


        IPage<Book> page = iBookService.getPage(currentPage, pageSize,book);
        //解决分页功能bug
        //如果当前的页码值大于最大页码值,那么就应该把最大页码之赋值给最当前页码值
        if(currentPage>=page.getPages())
           page=iBookService.getPage((int) page.getPages(), pageSize,book);

        return new R(true,page);
    }

}

uils包下创建处理异常的类ProjectException,如果出现异常的话,直接返回数据,msg里面存储的状态为数据出错

@RestControllerAdvice
//springmvc的异常处理器
public class ProjectException {

    @ExceptionHandler(Exception.class)
    public R doOrderException(Exception ex){
        //出现异常可以写记录日志啊,发消息给运维啊,发邮件给开发人员啊等等
        ex.printStackTrace();
        return new R(false,null,"系统错误o.O");
    }
}

10.前端页面的实现

   这里,我直接导入老师的前端资源,然后跟着配套写。

 写的话先找到页面模块在哪里,跟着点击事件设置的下一步操作去写

<!DOCTYPE html>

<html>

<head>

    <!-- 页面meta -->

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title>基于SpringBoot整合SSM案例</title>

    <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">

    <!-- 引入样式 -->

    <link rel="stylesheet" href="../plugins/elementui/index.css">

    <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">

    <link rel="stylesheet" href="../css/style.css">

</head>

<body class="hold-transition">

<div id="app">

    <div class="content-header">

        <h1>图书管理</h1>

    </div>

    <div class="app-container">

        <div class="box">

            <div class="filter-container">
                <el-input placeholder="图书类别" v-model="pagination.type" style="width: 200px;" class="filter-item"></el-input>
                <el-input placeholder="图书名称" v-model="pagination.name" style="width: 200px;" class="filter-item"></el-input>
                <el-input placeholder="图书描述" v-model="pagination.description" style="width: 200px;" class="filter-item"></el-input>
                <el-button @click="getAll()" class="dalfBut">查询</el-button>
                <el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
            </div>

            <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>

                <el-table-column type="index" align="center" label="序号"></el-table-column>

                <el-table-column prop="type" label="图书类别" align="center"></el-table-column>

                <el-table-column prop="name" label="图书名称" align="center"></el-table-column>

                <el-table-column prop="description" label="描述" align="center"></el-table-column>

                <el-table-column label="操作" align="center">

                    <template slot-scope="scope">

                        <el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>

                        <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>

                    </template>

                </el-table-column>

            </el-table>

            <!--分页组件-->
            <div class="pagination-container">

                <el-pagination
                        class="pagiantion"

                        @current-change="handleCurrentChange"

                        :current-page="pagination.currentPage"

                        :page-size="pagination.pageSize"


                        layout="total, prev, pager, next, jumper"

                        :total="pagination.total">

                </el-pagination>

            </div>

            <!-- 新增标签弹层 -->

            <div class="add-form">

                <el-dialog title="新增图书" :visible.sync="dialogFormVisible">

                    <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">

                        <el-row>

                            <el-col :span="12">

                                <el-form-item label="图书类别" prop="type">

                                    <el-input v-model="formData.type"/>

                                </el-form-item>

                            </el-col>

                            <el-col :span="12">

                                <el-form-item label="图书名称" prop="name">

                                    <el-input v-model="formData.name"/>

                                </el-form-item>

                            </el-col>

                        </el-row>


                        <el-row>

                            <el-col :span="24">

                                <el-form-item label="描述">

                                    <el-input v-model="formData.description" type="textarea"></el-input>

                                </el-form-item>

                            </el-col>

                        </el-row>

                    </el-form>

                    <div slot="footer" class="dialog-footer">

                        <el-button @click="cancel()">取消</el-button>

                        <el-button type="primary" @click="handleAdd()">确定</el-button>

                    </div>

                </el-dialog>

            </div>

            <!-- 编辑标签弹层 -->

            <div class="add-form">

                <el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">

                    <el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right" label-width="100px">

                        <el-row>

                            <el-col :span="12">

                                <el-form-item label="图书类别" prop="type">

                                    <el-input v-model="formData.type"/>

                                </el-form-item>

                            </el-col>

                            <el-col :span="12">

                                <el-form-item label="图书名称" prop="name">

                                    <el-input v-model="formData.name"/>

                                </el-form-item>

                            </el-col>

                        </el-row>

                        <el-row>

                            <el-col :span="24">

                                <el-form-item label="描述">

                                    <el-input v-model="formData.description" type="textarea"></el-input>

                                </el-form-item>

                            </el-col>

                        </el-row>

                    </el-form>

                    <div slot="footer" class="dialog-footer">

                        <el-button @click="updateCancel()">取消</el-button>

                        <el-button type="primary" @click="handleEdit()">确定</el-button>

                    </div>

                </el-dialog>

            </div>

        </div>

    </div>

</div>

</body>

<!-- 引入组件库 -->

<script src="../js/vue.js"></script>

<script src="../plugins/elementui/index.js"></script>

<script type="text/javascript" src="../js/jquery.min.js"></script>

<script src="../js/axios-0.18.0.js"></script>

<script>
    var vue = new Vue({
        el: '#app',
        data:{
            dataList: [],//当前页要展示的列表数据
            dialogFormVisible: false,//添加表单是否可见
            dialogFormVisible4Edit:false,//编辑表单是否可见
            formData: {},//表单数据
            rules: {//校验规则
                type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
                name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
            },
            pagination: {//分页相关模型数据
                currentPage: 1,//当前页码
                pageSize:10,//每页显示的记录数
                total:0,  //总记录数
                type:"",  //条件查询数据
                name:"",
                description:""
            }
        },

        //钩子函数,VUE对象初始化完成后自动执行
        created() {
            //调用查询全部数据
            this.getAll();
        },

        methods: {
            //列表
/*            getAll() {
                //发送异步请求
                axios.get("/books").then((res)=>{
                    this.dataList=res.data.data;  //第一个data是拿到我们后端R表现层数据一致性处理的数据,第二个data是拿到r里面的数据
                })
            },*/

            getAll(){
                //条件查询数据,因为是get请求,我们需要做字符串的拼接
                param = "?query";
                param += "&type=" + this.pagination.type;
                param += "&name=" + this.pagination.name;
                param += "&description=" + this.pagination.description;

                //分页查询
                axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize + param).then((res)=>{
                    this.dataList=res.data.data.records; //将查询到的数据给到表单数据存储当中
                    //下面的是分页查询的页数,多少条,一共多少条
                    this.pagination.currentPage=res.data.data.current;
                    this.pagination.pageSize=res.data.data.size;
                    this.pagination.total=res.data.data.total;
                })
            },

            //切换页码
            handleCurrentChange(currentPage) {
                //修改页码值为当前选中的页码值
                this.pagination.currentPage=currentPage;
                //修改页码后执行查询
                this.getAll();
            },


            //弹出添加窗口
            handleCreate() {
                //因为我们将添加表单设置为不可见,所以这里设置点击新建我们将弹窗改为true,让它弹出来
                this.dialogFormVisible=true;
                //一般清空数据设置在打开弹窗最为妥当
                this.resetForm();
            },

            //重置表单
            resetForm() {
                //将添加弹窗数据清空
                this.formData={};
            },

            //添加
            handleAdd () {
                //这个this.formData就是将我们弹出的添加窗口当中的数据添加打包传参
                //如果不添加操作失败,我们就不关闭弹窗
                axios.post("/books",this.formData).then((res)=>{
                    if(res.data.flag){
                        //1.操作成功,将弹窗关闭
                        this.dialogFormVisible=false;
                        this.$message.success(res.data.msg);
                    }else {
                        this.$message.error(res.data.msg);
                    }

                }).finally(()=>{
                    //2.添加了数据,重新刷新页面
                    this.getAll();
                })

            },

            //添加弹窗取消
            cancel(){
                this.dialogFormVisible=false;//关闭添加创库弹窗
                this.$message.info("您取消了当前操作")
            },

            // 删除
            handleDelete(row) {
                //删除操作我们得设置一个提醒,万一用户手抖点错了就恢复不了,所以设置一个弹窗
                this.$confirm("此操作将会删除当前数据,是否继续","删除弹窗提示",{type:"info"}).then(()=>{
                    //弹窗点击确定
                    axios.delete("/books/"+row.id).then((res)=>{
                        //判断是否删除成功
                        if(res.data.flag){
                            this.$message.success("删除成功")
                        }else {
                            this.$message.error("删除失败")
                        }
                    }).finally(()=>{
                        //不管成功与否,都重新刷新页面,调用查询所有
                        this.getAll();
                    });
                }).catch(()=>{
                    //弹窗点击取消
                    this.$message.info("您取消了删除操作");
                });


            },

            //弹出编辑窗口
            handleUpdate(row) {
                //将我们编辑的哪一个数据查询编辑窗口表单当中
                axios.get("/books/"+row.id).then((res)=>{
                    //出现1异常的情况
                    if(res.data.flag && res.data.data!=null){
                        this.formData=res.data.data
                        //将编辑窗口改为true
                        this.dialogFormVisible4Edit=true;
                    }else{
                        this.$message.error("数据同步失败,自动刷新");
                    }
                }).finally(()=>{
                    this.getAll();
                })
            },

            //修改
            handleEdit() {
                axios.put("/books",this.formData).then((res)=>{
                    //判断是否操作成功
                    if(res.data.flag){
                        this.$message.success("操作成功");
                        this.dialogFormVisible4Edit=false;
                    }else{
                        this.$message.error("操作失败");
                    }
                }).finally(()=>{
                    this.getAll();
                })
            },

            //编辑弹窗取消
            updateCancel(){
                this.dialogFormVisible4Edit=false;//关闭添加创库弹窗
                this.$message.info("您取消了当前操作")
            },



        }
    })

</script>

</html>

五、运维实用篇

1、SpringBoot程序的打包与运行

 运行项目:java后面那个就是打包后的文件名字

   下面的是我们检查端口的操作::

 2、配置临时属性

 3.配置文件4级分类

(1)一级::在我们编程的resources当中的:application.xml是给我们程序员用的

(2)二级:: 在编程的resources当中创建config包下的application.yml是给项目经理用的,而皮质等级高于前面

(3)三级::打包后的jar文件同级目录下的application.yml,

(4)四级::打包后的jar文件同级目录下的config,config包里面的application

等级越高,优先级越高,相同配置会覆盖前面的配置,不同的配置,会联合一起使用

4、自定义配置文件

 这个是根据名字,也可以根据路径  --spring.config.location=classpath:/ebank.yml

5、多环境开发

(1)配置

yml文件当中,不同的环境用 --- 分隔开

#应用环境,当前在使用的环境
spring:
  profiles:
    active: dev

#设置环境
---
#生产环境
spring:
  profiles: pro
server:
  port: 80
---
#开发环境
spring:
  profiles: dev
server:
  port: 81

---
#测试环境
spring:
  profiles: test
server:
  port: 82

第二种方式

 (2)多环境开发分组管理

include的话,我们的主配置文件会在最后加载,而group的话就是先加载主配置文件,再加载指定的附属配置文件

 

 (3)多环境的开发控制

            pox.xml文件当中的配置

**maven中设置多环境(使用属性方式区分环境)**

```xml
<profiles>
    <profile>
        <id>env_dev</id>
        <properties>
            <profile.active>dev</profile.active>
        </properties>
        <activation>
            <activeByDefault>true</activeByDefault>		<!--默认启动环境-->
        </activation>
    </profile>
    <profile>
        <id>env_pro</id>
        <properties>
            <profile.active>pro</profile.active>
        </properties>
    </profile>
</profiles>
```

**SpringBoot中读取maven设置值**

```yaml
spring:
	profiles:
    	active: @profile.active@
```

注意:如果pom.xml文件当中设置更改了另一个环境为true,但是运行的时候没有更改成功,这个问题是因为idea缓存的原因,你直接再meven当中手动compile一下就可以,所以一般都是在你更新pom文件的时候就compile

6、日志文件

    private static final Logger log= LoggerFactory.getLogger(BookController.class);

    @GetMapping
    public String toIndex(){
        log.info("info...");
        log.warn("warn...");
        log.error("error...");
        return "log日志文件";
    }



- TRACE:运行堆栈信息,使用率低
- DEBUG:程序员调试代码使用
- INFO:记录运维过程数据
- WARN:记录运维过程报警数据
- ERROR:记录错误堆栈信息
- FATAL:灾难信息,合并计入ERROR

 yml配置文件当中更改日志级别

使用lombok快速创建日志对象

  

、、、添加依赖
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


、、、给类加上@Slf4j注解,就可以直接使用了
@Slf4j
@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping
    public String toIndex(){
        log.info("info...");
        log.warn("warn...");
        log.error("error...");
        return "log日志文件";
    }
}

7、日志输出格式与控制

```yaml
logging:
	pattern:
    	console: "%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"
```

六、热部署功能

    springboot_09_hot_deploy

1、手动启动热部署

 (2)、热部署的范围

```yaml
spring:
  devtools:
    restart:
      # 设置不参与热部署的文件或文件夹
      exclude: static/**,public/**,config/application.yml
```

七、配置高级

 (springboot_10_configuration)

1、绑定第三方的bean

 2、松散绑定

注意::松散绑定就是你在配置文件yml当中,可以使用很多种命名方式,我们最常用的就是烤肉串模式,前面使用@configurationProperties获取数据直接全小写就可以了

 3、常量计量单位

、、、java
@Data
@Component
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    private Duration serverTime;//这个没有绑定单位,所以我们再yml当中的数据带上单位
    @DataSizeUnit(DataUnit.MEGABYTES)//再这里绑定单位就不用再yml当中带单位了
    private DataSize dataSize;
}

、、、yaml
servers:
  serverTime: 10m
  data-size: 100

 时间的话再yml配置文件当中默认是ms,所以不好写,datasize是容量大小,再配置文件当中是单位是by,不方便使用,我们可以再定义pojo类的时候给他加上单位

 4、使用属性校验

<!--1.导入JSR303规范-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>
<!--使用hibernate框架提供的校验器做实现-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

  这里不用配置版本的原因是我们的springboot里面有 

@Data//lombokchuachua快速创建pojo类
@Component//注册bean
@ConfigurationProperties(prefix = "servers")//这个是yml当中提取数据
//开启校验功能
@Validated
public class ServerConfig {
    private String ip;
    @Max(value = 400,message = "最大值不能超过400")//给我们属性添加范围最大值
    private int port;
}

总结:开启Bean属性校验功能一共3步:

导入JSR303与Hibernate校验框架坐标、

使用@Validated注解启用校验功能、

使用具体校验规则规范数据校验格式

八、测试专题   

    (springboot_11_text)

1、加载测试临时属性

@SpringBootTest(properties = {"test.post=testValue1"})//使用临时参数测试程序
@SpringBootTest(args = {"--test.post=testValue2"})
//使用args来,这个的话需要加--,和我们之前运输jar的时候设置临时参数一样
class Springboot11TextApplicationTests {

    @Value("${test.post}")
    private String msg;

    @Test
    void contextLoads() {
        System.out.println(msg);
    }

}

2、加载测试专用属性

、、、配置类
@Configuration
public class MsgConfig {

    @Bean
    public String msg(){
        return "MsgConfig";
    }
}


、、、测试里面导入并且使用
@SpringBootTest
@Import(MsgConfig.class)
public class MsgConfigTest {

    @Autowired
    private String msg;

    @Test
    void testConfiguration(){
        System.out.println(msg);
    }

}

3、使用测试类启动web环境

、、、控制器
@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping
    public String toIndex(){
        return "books";
    }

}

、、、测试类
//1.这个就是配置虚拟端口,后面的表明端口是随机的
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//2.开启虚拟mvc调用
@AutoConfigureMockMvc
public class WebTest {

    @Test
    //3。注入虚拟mvc调用对象
    void indexTest(@Autowired MockMvc mvc) throws Exception {
        //4.创建虚拟请求,当前访问/books
        MockHttpServletRequestBuilder  builder = MockMvcRequestBuilders.get("/books");
        //5.执行请求
        ResultActions actions = mvc.perform(builder);
    }

4、测试匹配信息

(1)匹配响应状态
//这个就是配置虚拟端口,后面的表明端口是随机的
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟mvc调用
@AutoConfigureMockMvc
public class WebTest {


    @Test
    void testStatus(@Autowired MockMvc mvc) throws Exception {
        //创建虚拟请求
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        //执行请求
        ResultActions actions = mvc.perform(builder);
        //定义本次调用的预期值
        StatusResultMatchers status = MockMvcResultMatchers.status();
        //预计本次调用时成功的状态200
        ResultMatcher ok = status.isOk();
        //添加预计值到本次调用过程中进行匹配
        actions.andExpect(ok);
    }

}
(2)匹配响应体
- 响应体匹配(非json数据格式)

  ```JAVA
  @Test
  void testBody(@Autowired MockMvc mvc) throws Exception {
      MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      ResultActions action = mvc.perform(builder);
      //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
      //定义本次调用的预期值
      ContentResultMatchers content = MockMvcResultMatchers.content();
      ResultMatcher result = content.string("springboot2");
      //添加预计值到本次调用过程中进行匹配
      action.andExpect(result);
  }
  ```

- 响应体匹配(json数据格式,开发中的主流使用方式)

  ```JAVA
  @Test
  void testJson(@Autowired MockMvc mvc) throws Exception {
      MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      ResultActions action = mvc.perform(builder);
      //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
      //定义本次调用的预期值
      ContentResultMatchers content = MockMvcResultMatchers.content();
      ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}");
      //添加预计值到本次调用过程中进行匹配
      action.andExpect(result);
  }
  ```
(3)响应头信息匹配
- 响应头信息匹配

  ```JAVA
  @Test
  void testContentType(@Autowired MockMvc mvc) throws Exception {
      MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
      ResultActions action = mvc.perform(builder);
      //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
      //定义本次调用的预期值
      HeaderResultMatchers header = MockMvcResultMatchers.header();
      ResultMatcher contentType = header.string("Content-Type", "application/json");
      //添加预计值到本次调用过程中进行匹配
      action.andExpect(contentType);
  }
  ```

5、业务层测试事务回滚

只要注解@Transactional出现的位置存在注解@SpringBootTest,
springboot就会认为这是一个测试程序,无需提交事务,所以也就可以避免事务的提交。

6、测试用例设置随机数

九、数据层解决方案

1、内置数据源

- HikariCP(默认)
- Tomcat提供DataSource
- Commons DBCP

、、、使用druid
```YAML
spring:
  datasource:
    druid:	
   	  url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
```

、、、换成是默认的数据源HikariCP后
两种方案,第一,直接将druid方法的druid哪一行删除就可以
第二:
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
      maximum-pool-size: 50 //这个配置其他的配置

2、jdbcTemplate持久化技术

首先需要导入依赖

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency
```
**步骤④**:使用JdbcTemplate实现查询操作(实体类封装数据的查询操作)

@Test
    void testJdbcTemplate(@Autowired JdbcTemplate jdbcTemplate){
        String sql = "select * from tbl_book";
        //这个是封装数据模型,不封装模型的话,我们使用jdbcTemplate拿到的数据是map数组
        //而封装后我们拿到的就是book类型的数据
        RowMapper<Book> rm = new RowMapper<Book>() {
            @Override
            public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
                Book book = new Book();
                book.setId(rs.getInt("id"));
                book.setName(rs.getString("name"));
                book.setType(rs.getString("type"));
                book.setDescription(rs.getString("description"));
                return book;
            }
        };
        List<Book> list = jdbcTemplate.query(sql, rm);
        System.out.println(list);
    }

     如果想对JdbcTemplate对象进行相关配置,可以在yml文件中进行设定,具体如下:

```yaml
spring:
  jdbc:
    template:
      query-timeout: -1   # 查询超时时间
      max-rows: 500       # 最大行数
      fetch-size: -1      # 缓存行数

3、内置数据库 h2

导入依赖(工程为web工程)

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

yml当中的配置

spring:
  h2:
    console:
      enabled: true
      path: /h2
```

​		web端访问路径/h2,访问密码123456,如果访问失败,先配置下列数据源,
启动程序运行后再次访问/h2路径就可以正常访问了

```yaml
datasource:
  url: jdbc:h2:~/test
  hikari:
    driver-class-name: org.h2.Driver
    username: sa
    password: 123456

注意::其实我们只是换了一个数据库而已,其他的东西都不受影响。一个重要提醒,别忘了,上线时,把内存级数据库关闭,采用M据持久化方ySQL数据库作为数案,关闭方式就是设置enabled属性为false即可。

(2) 数据库redis,springbot整合redis

windows启动redis::redis-server.exe redis.windows.conf

、、依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>



、、、yml配置文件
spring:
  redis:
    host: localhost
    port: 6379


(3)springboot整合mogodb

 这个数据库一般都是数据变化很快,我们一般使用的是robo3T来操作文档库

 1.依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>

2.使用

、、、yml页面当中,url和mysql数据库一样的格式
spring:
  data:
    mongodb:
      uri: mongodb://localhost/itheima


、、、java
@SpringBootTest
class Springboot14MongodbApplicationTests {

	@Autowired
	private MongoTemplate mongoTemplate;

	@Test
	void testMongo(){
		Book book = new Book();
		book.setId(2);
		book.setName("stringboot");
		book.setType("stringboot");
		book.setDescription("stringboot");
		mongoTemplate.save(book);
	}

	@Test
	void  testFind(){
		List<Book> books = mongoTemplate.findAll(Book.class);
		System.out.println(books);
	}

}

十、缓存

   springboot_15_cache(依赖有lombok,springboot-web,mybatispuls,druid,mysql,test)

 

1、springboot内置缓存技术

、、、xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

第二步:再我们的启动程序哪里启动缓存

  重点:: 使用缓存技术,一定不能忘记再application哪里添加启用缓存

@SpringBootApplication
//启用缓存技术
@EnableCaching
public class Springboot15CacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot15CacheApplication.class, args);
    }

}

第四步,再service实现类当中使用缓存、

  注意::@Cacheable这个注解存储数据可以拿,可以存,key就是我们设置的key,key对应的值就是我们这个方法的返回值。但是如果你要是那种60秒验证码变更的那种,就需要使用@Cacheput,这个只能往缓存里放数据

    @Override
    @Cacheable(value = "cacheSpring",key = "#id")//这样就对这个方法使用了缓存,
                         //value定义的是数据存储的一个大的key,最终的结果存在于key
    public Book selectById(int id) {
        return bookDao.selectById(id);
    }

2、使用内置缓存技术实现验证码

pojo类

@Data
public class SMSCode {
    private String phone;
    private String code;
}

工具类(获取验证码,和从缓存当中提取验证码)

@Component
public class CodeUtils {
    private String[] patch = {"000000","00000","0000","000","00","0",""};

    //这个是获取验证码的
    public String generator(String smsPhone){
        //加密获取6位验证码
        int hash = smsPhone.hashCode();
        int encryption = 20011128;
        long result = hash ^ encryption;//第一次加密
        long nowtime = System.currentTimeMillis();
        result = result ^ nowtime;//第二次加密,获取系统时间
        long code = result % 1000000; //获取后六位数字
        code = code < 0 ? -code : code; //取出来的数据可能为负数,所以这里判断一下
        String strCode = code + "";
        int len = strCode.length();
        return patch[len]+strCode;  // 因为取出来的数字可能前面为0,而不是六位数,所以需要补零
    }

    //这个是从缓存当中拿验证码的,
    //需要注意的是,@Cacheable这个注解的返回值就是我们的value,如果缓存当中有数据,就会返回缓存当中的,没有数据就返回方法的返回值
    @Cacheable(value = "SMSphone",key = "#phone")
    public String get(String phone){
        return null;
    }
}

service接口和实现类

、、、接口
public interface SMSCodeService {
    public  String getCaptcha(String phone);

    public boolean ifCaptcha(SMSCode smsCode);
}


、、、实现类
@Service
public class SMSCodeServiceImpl implements SMSCodeService {

    @Autowired
    private CodeUtils codeUtils;

    //获取验证码
    @Override
//    @Cacheable(value = "SMSphone",key = "#phone")
    @CachePut(value = "SMSphone",key = "#phone")//因为我们只希望向缓存当中存储验证码,不希望第二次请求的时候拿到验证码,所以使用这个注解可以只存不取
    public String getCaptcha(String phone) {
        String smsCode = codeUtils.generator(phone);
        return smsCode;
    }

    //判断验证码
    @Override
    public boolean ifCaptcha(SMSCode smsCode) {
        //这个是拿到用户输入的code
        String code = smsCode.getCode();
        //这个是调用工具类当中的方法,拿到缓存当中数据
        String queryCode = codeUtils.get(smsCode.getPhone());
        return code.equals(queryCode);
    }
}

3、将实现验证码改换成ehcache技术

第一步:再原有的依赖文件上我们的

        <!--ehcache-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>

第二步:将我们ehcache的配置文件导入进来(ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <diskStore path="D:\sofo\ehcache" /> //这个是缓存存放的位置

    <!--默认缓存策略 -->
    <!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
    <!-- diskPersistent:是否启用磁盘持久化-->
    <!-- maxElementsInMemory:最大缓存数量-->
    <!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
    <!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码-->
    <!-- timeToLiveSeconds:最大存活时间-->
    <!-- memoryStoreEvictionPolicy:缓存清除策略-->
    <defaultCache
            eternal="false"
            diskPersistent="false"
            maxElementsInMemory="1000"
            overflowToDisk="false"
            timeToIdleSeconds="60"
            timeToLiveSeconds="60"
            memoryStoreEvictionPolicy="LRU" />

    <!--这里面的name就是我们使用缓存设置的那个value,大的环境的名字,这个可以给它进行配置-->
    <cache
            name="SMSphone"
            eternal="false"
            diskPersistent="false"
            maxElementsInMemory="1000"
            overflowToDisk="false"
            timeToIdleSeconds="10"
            timeToLiveSeconds="10"
            memoryStoreEvictionPolicy="LRU" />
</ehcache>

第三步:再环境当中将默认的cache该化成ehcache(yml文件当中)

spring:
  cache:
    type: ehcache
    ehcache:
      config: classpath:ehcache.xml  //记住这里,直接导入文件位置找不到的话,就要使用classpath导入

再就可以直接使用了,springboot好的点在于,之前使用的默认的cache,再将ehcache技术导入进来后,我们可以直接使用

4、使用redis  整合redis

**步骤①**:导入redis的坐标

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```

​		如果需要对redis作为缓存进行配置,注意不是对原始的redis进行配置,而是配置redis作为缓存使用相关的配置,隶属于spring.cache.redis节点下,注意不要写错位置了。

```yaml
spring:
  redis:
    host: localhost
    port: 6379
  cache:
    type: redis
    redis:
      use-key-prefix: false  是否使用前缀名字
      key-prefix: sms_   我们自己给它加前缀名字
      cache-null-values: false   是否应许存储空值
      time-to-live: 10s   缓存时间
```

5、整合Memcached技术

  使用这个技术需要注意的是和redis一样,需要将程序启动,而且暂时springboot内部还没有memcached,所以需要我们导入版本号

导入依赖

        <!--memcached-->
        <dependency>
            <groupId>com.googlecode.xmemcached</groupId>
            <artifactId>xmemcached</artifactId>
            <version>2.4.7</version>
        </dependency>

将memcached纳入ioc管理,配置Bean

@Configuration
public class XMemcachedConfig {//这个是我们的memcached缓存技术的配置文件

    @Bean
    public MemcachedClient getMemcachedClient() throws IOException {
        //因为MemcachedClient需要通过MemcachedClientBuilder的builder方法获取
        MemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder("localhost:11211");
        MemcachedClient memcachedClient = memcachedClientBuilder.build();
        return memcachedClient;
    }

}

使用方法

@Service
public class SMSCodeServiceImpl implements SMSCodeService {

    @Autowired
    private CodeUtils codeUtils; (这个是工具类,用来获取我们的6位数字验证码的)


    @Autowired
    private MemcachedClient memcachedClient;

    @Override
    public String getCaptcha(String phone) {
        String generator = codeUtils.generator(phone);
        try {
 //这三个参数,一个是我们的key,一个是效应时间,一个是value
            memcachedClient.set(phone,10,generator);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return generator;
    }

    @Override
    public boolean ifCaptcha(SMSCode smsCode) {
        String code = null;
        try {
             code = memcachedClient.get(smsCode.getPhone()).toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return smsCode.getCode().equals(code);
    }


}

我们可以创建一个pojo类,来存放我们需要配置的属性

 然后直接在我们给memcached配置文件里面引用就可以了

6、使用jetcacha

目前jetcache支持的缓存方案本地缓存支持两种,远程缓存支持两种,分别如下

- 本地缓存(Local)
  - LinkedHashMap
  - Caffeine
- 远程缓存(Remote)
  - Redis
  - Tair

(1)使用纯远程redis缓存
///1.添加依赖          
<!--jetcache-->
        <dependency>
            <groupId>com.alicp.jetcache</groupId>
            <artifactId>jetcache-starter-redis</artifactId>
            <version>2.6.2</version>
        </dependency>

  、、、2.yml当中配置

spring:
  main:
    allow-circular-references: true  //将循环依赖关闭

jetcache:
  remote:
    default:
      type: redis
      host: localhost
      port: 6379
      poolConfig:  //这个必须要配置,不配置的话会报错
        maxTotal: 50  


、、、3.使用需要开启jetcache
@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class Springboot16JetcacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(Springboot16JetcacheApplication.class, args);
    }

}

、、、4.使用
@Service
public class SMSCodeServiceImpl implements SMSCodeService {

    @Autowired
    private CodeUtils codeUtils;

    @CreateCache(name = "jetcache_",expire = 10,timeUnit = TimeUnit.SECONDS)
    private Cache<String,String> jetcache;

    @Override
    public String getCaptcha(String phone) {
        String generator = codeUtils.generator(phone);
        jetcache.put(phone,generator);
        return generator;
    }

    @Override
    public boolean ifCaptcha(SMSCode smsCode) {
        String code = jetcache.get(smsCode.getPhone());
        return smsCode.getCode().equals(code);
    }


}

注意:: 因为现在的版本使用纯远程有一个依赖循环的bug,所以我们需要在yml配置文件当中将循环依赖关闭

spring: 
  main:
      allow-circular-references: true

(2)本地缓存
、、、yml配置
jetcache:
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson

开启jetcache等都和前面一样,值得注意的是,我们在使用的时候可以指定使用哪一种

cacheType = CacheType.LOCAL这个是指定使用本地缓存

@Service
public class SMSCodeServiceImpl implements SMSCodeService {
    @CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL)
    private Cache<String ,String> jetCache;

    public String sendCodeToSMS(String tele) {
        String code = codeUtils.generator(tele);
        jetCache.put(tele,code);
        return code;
    }

    public boolean checkCode(SMSCode smsCode) {
        String code = jetCache.get(smsCode.getTele());
        return smsCode.getCode().equals(code);
    }
}

7、j2cache缓存框架

十一、定时

(1)springboot整合Quartz定时任务

 导入依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-quartz</artifactId>
		</dependency>

创建我们需要定时发送的工作,记住,需要让它继承QuartzJobBean

public class MYQuartz extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("你好");
    }
}

配置文件类

@Configuration
public class QuartzConfig {//触发器
   @Bean
    public JobDetail printJobDetail(){ //工作明细
//绑定具体的工作
       return JobBuilder
               .newJob(MYQuartz.class)
               .storeDurably()
               .build();
   }

    @Bean
    public Trigger printJobTrigger(){
       //这个是设置发送时间
        ScheduleBuilder schedBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
        //绑定对应的工作明细
        return TriggerBuilder
                .newTrigger()
                .forJob(printJobDetail())
                .withSchedule(schedBuilder)
                .build();
    }
}

(2)整合task

  这个是springboot自己推出的,更加简单方便

```java
@SpringBootApplication
//开启定时任务功能
@EnableScheduling
public class Springboot22TaskApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot22TaskApplication.class, args);
    }
}
```

**步骤②**:定义Bean,在对应要定时执行的操作上方,
使用注解@Scheduled定义执行的时间,执行时间的描述方式还是cron表达式

```java
@Component
public class MyBean {
    @Scheduled(cron = "0/1 * * * * ?")
    public void print(){
        System.out.println(Thread.currentThread().getName()+" :spring task run...");
    }
}

(3)springboot整合javamail发送邮件

导入springboot整合javamail的starter

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
```

第二步,yaml配置邮箱登录信息

我们的密码写的是开启smtp服务时候的给的编号

第三步:编写程序,添加发送邮件需要的信息

```java
@Service
public class SendMailServiceImpl implements SendMailService {

    //发送人
    private String from = "test@qq.com";
    //接收人
    private String to = "test@126.com";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "测试邮件正文内容";

    @Autowired
    private JavaMailSender javaMailSender;

    @Override
    public void sendMail() {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from+"(小甜甜)"); //这个后面添加了名称,那么发送邮件显示名称就会是我们添加的名称
        message.setTo(to);
        message.setSubject(subject);
        message.setText(context);
        javaMailSender.send(message);
    }
}
```

​		将发送邮件的必要信息(发件人、收件人、标题、正文)封装到SimpleMailMessage对象中
      ,可以根据规则设置发送人昵称等。

第四步,运行这个方法即可;

如果需要发送带有附件的邮件,那我们所创建的对象有所区别,但是我们的步骤相差不大

```JAVA
@Service
public class SendMailServiceImpl2 implements SendMailService {
    @Autowired
    private JavaMailSender javaMailSender;

    //发送人
    private String from = "test@qq.com";
    //接收人
    private String to = "test@126.com";
    //标题
    private String subject = "测试邮件";
    //正文
    private String context = "测试邮件正文";

    public void sendMail() {
        try {
            MimeMessage message = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message,true);		//此处设置支持附件
            helper.setFrom(to+"(小甜甜)");
            helper.setTo(from);
            helper.setSubject(subject);
            helper.setText(context);

            //添加附件
            File f1 = new File("springboot_23_mail-0.0.1-SNAPSHOT.jar");
            File f2 = new File("resources\\logo.png");

            helper.addAttachment(f1.getName(),f1);
            helper.addAttachment("最靠谱的培训结构.png",f2);

            javaMailSender.send(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 1. springboot整合javamail其实就是简化了发送邮件的客户端对象JavaMailSender的初始化过程,
      通过配置的形式加载信息简化开发过程

十二、mq产品

目前企业级开发中广泛使用的消息处理技术共三大类,具体如下:

- JMS
- AMQP
- MQTT

(1)购物订单案例,消息发送

   server层接口和实现类

、、、、接口
public interface MessageService {
    void sendMessage(String id);
    String doMessage();

}

public interface OrderService {
    void order(String id);
}

、、、实现类
@Service
public class MessageServiceImpl implements MessageService {

    private ArrayList<String> msgList = new ArrayList<String>();
    @Override
    public void sendMessage(String id) {
        msgList.add(id);
        System.out.println("已经将订单纳入待处理"+id);
    }

    @Override
    public String doMessage() {
        String id = msgList.remove(0);
        System.out.println(id+"已经处理完毕");
        return id;
    }
}

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private MessageService messageService;

    @Override
    public void order(String id) {
        //这里写处理业务的操作
        System.out.println("订单开始处理");
        //短信消息处理
        messageService.sendMessage(id);
        System.out.println("订单处理完毕");
    }
}

表现层::

@RequestMapping("/orders")
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    //发送消息
    @PostMapping("/{id}")
    public void order(@PathVariable("id")String id){
         orderService.order(id);
    }

}
------------------------
@RequestMapping("/mes")
@RestController
public class MessageController {

    @Autowired
    private MessageService messageService;

    //接受消息
    @GetMapping
    public String message(){
        String id = messageService.doMessage();
        return id;
    }
}


yaml 

(2)SpringBoot整合ActiveMQ

导入依赖坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

yml当中的配置

server:
  port: 80

spring:
  activemq:
    broker-url: tcp://localhost:61616
  jms:
    template:
      default-destination: xsh  这个是如果后面没有设置消息的存储位置,就用这个

业务层

  这里我们在message当中设置的消息的发送和接受,order当中负责消息的发送,调用message当中的消息发送方法


public interface OrderService {
    void order(String id);
}

@Service
public class OrderServiceActivemqImpl implements OrderService {
    @Autowired
    private MessageService messageServiceActivemq;
    @Override
    public void order(String id) {
        //这里写处理业务的操作
        System.out.println("订单开始处理");
        //短信消息处理
        messageServiceActivemq.sendMessage(id);
        System.out.println("订单处理完毕");
    }
}


public interface MessageService {
    void sendMessage(String id);
    String doMessage();

}
@Service
public class MessageServiceActivemqImpl implements MessageService {
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;
    @Override
    public void sendMessage(String id) {
        System.out.println("待发送短信的订单已纳入处理队列,id:"+id);
        jmsMessagingTemplate.convertAndSend("order.queue.id",id);
                                         //order.queue.id这个是指定它存放的位置
    }

    @Override
    public String doMessage() {
        String id =jmsMessagingTemplate.receiveAndConvert("order.queue.id",String.class);
                               //因为前面存储指定了位置,所以这里需要加入取数据的地址
        System.out.println("已完成短信发送业务,id:"+id);
        return id;
    }
}



 表现层:

@RequestMapping("/orders")
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    //发送消息
    @PostMapping("/{id}")
    public void order(@PathVariable("id")String id){
         orderService.order(id);
    }

}


----------
@RequestMapping("/mes")
@RestController
public class MessageController {

    @Autowired
    private MessageService messageService;

    //接受消息
    @GetMapping
    public String message(){
        String id = messageService.doMessage();
        return id;
    }
}


**步骤④**:使用消息监听器在服务器启动后,监听指定位置,当消息出现后,立即消费消息

@Component
public class MessageListener {
    @JmsListener(destination = "order.queue.id")//当这个里面有消息,就会立即执行下面的代码
    @SendTo("order.other.queue.id") //这个会将返回值作为消息发送到括号里面的位置保存起来
    public String receive(String id){
        System.out.println("已完成短信发送业务,id:"+id);
        return "new:"+id;
    }
}
 

  步骤5:切换消息模型由点对点模型到发布订阅模型,修改jms配置即可

```yaml
spring:
  activemq:
    broker-url: tcp://localhost:61616
  jms:
    pub-sub-domain: true

**总结**

1. springboot整合ActiveMQ提供了JmsMessagingTemplate对象作为客户端操作消息队列
2. 操作ActiveMQ需要配置ActiveMQ服务器地址,默认端口61616
3. 企业开发时通常使用监听器来处理消息队列中的消息,设置监听器使用注解@JmsListener
4. 配置jms的pub-sub-domain属性可以在点对点模型和发布订阅模型间切换消息模型

(3)SpringBoot整合RocketMQ

我们先启动命名服务器,后期的broker

第一步:导入依赖坐标

        <!--rocketmq-->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.1</version>
        </dependency>

 第二步:yml当中的配置

#配置RocketMQ的服务器地址
rocketmq:
name-server: localhost:9876
producer:
group: group_rocketmq #给一个组名

 第三步:消息的发送

      里面存储消息第一个填写的就是我们写的消息存储的位置

@Service
public class MessageServiceRocketmqImpl implements MessageService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public void sendMessage(String id) {
        System.out.println("已经纳入待处理订单 id:"+id);
        //这个是同步消息
//        rocketMQTemplate.convertAndSend("order_rocketmq_id",id);
        //这个是异步消息
        SendCallback callback = new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("异步消息发送成功");
            }

            @Override
            public void onException(Throwable throwable) {
                System.out.println("异步消息发送失败");
            }
        };
        rocketMQTemplate.asyncSend("order_rocketmq_id",id,callback);
    }

    @Override
    public String doMessage() {
        return null;
    }

}

第四步: 设置监听器,当有消息时候,立即消费消息

@Component
@RocketMQMessageListener(topic = "order_rocketmq_id",consumerGroup = "group_rocketmq")
//这里将组名,还有我们的消息存储的位置
public class MessageListener implements RocketMQListener<String> {//设置有消息自动消费
    @Override
    public void onMessage(String s) {
        System.out.println("已经完成消息的发送:id:"+s);
    }
}

**总结**

1. springboot整合RocketMQ使用RocketMQTemplate对象作为客户端操作消息队列
2. 操作RocketMQ需要配置RocketMQ服务器地址,默认端口9876
3. 企业开发时通常使用监听器来处理消息队列中的消息,设置监听器使用注解@RocketMQMessageListener

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值