SpringBoot笔记

SpringBoot笔记

SpringBoot整合SpringData JPA

  • SpringBoot整合SpringData JPA依赖导入

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
  • SpringBoot连接数据库配置,使用properties文件的方式(数据库版本MySQL 8.0)

    spring.datasource.url=jdbc:mysql://localhost:3306/blog_system?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    spring.datasource.username=root
    spring.datasource.password=123456
    #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    

    上面注释了spring.datasource.driver-class-name配置,是因为在SpringBoot 2中默认提供了MySql的数据库连接驱动类 “com.mysql.cj.jdbc.Driver”

  • 准备测试环境

    • 创建数据库和表

      CREATE DATABASE blog_system;
      
      DROP TABLE IF EXISTS t_article;
      CREATE TABLE t_article (
       id INT(11) NOT NULL AUTO_INCREMENT,
       title VARCHAR(50) NOT NULL COMMENT '文章标题',
       content LONGTEXT COMMENT '文章具体内容',
       created DATE NOT NULL COMMENT '发表时间',
       modified DATE DEFAULT NULL COMMENT '修改时间',
       categories VARCHAR(200) DEFAULT '默认分类' COMMENT '文章分类',
       tags VARCHAR(200) DEFAULT NULL COMMENT '文章标签',
       allow_comment TINYINT(1) NOT NULL DEFAULT '1' COMMENT '是否允许评论',
       thumbnail VARCHAR(200) DEFAULT NULL COMMENT '文章缩略图',
       PRIMARY KEY (id)
      ) ENGINE=INNODB DEFAULT CHARSET=utf8;
      
    • 准备数据

      INSERT INTO t_article VALUES ('1', '2019新版Java学习路线图','Java学习路线图具体内容
      具体内容具体内容具体内容具体内容具体内容具体内容','2019-10-10', NULL, '默认分类',
      '‘2019,Java,学习路线图', '1', NULL);
      INSERT INTO t_article VALUES ('2', '2019新版Python学习线路图','据悉,Python已经入驻
      小学生教材,未来不学Python不仅知识会脱节,可能与小朋友都没有了共同话题~~所以,从今天起不要再
      找借⼝,不要再说想学Python却没有资源,赶快行动起来,Python等你来探索' ,'2019-10-10',
      NULL, '默认分类', '‘2019,Java,学习路线图', '1', NULL);
      INSERT INTO t_article VALUES ('3', 'JDK 8——Lambda表达式介绍',' Lambda表达式是JDK
      8中一个重要的新特性,它使一个清晰简洁的表达式来表达一个接口,同时Lambda表达式也简化了对集合
      以及数组数据的遍历、过滤和提取等操作。下面,本篇文章就对Lambda表达式进行简要介绍,并进行演示
      说明' ,'2019-10-10', NULL, '默认分类', '‘2019,Java,学习路线图', '1', NULL);
      INSERT INTO t_article VALUES ('4', '函数式接口','虽然Lambda表达式可以实现匿名内部类的
      功能,但在使用时却有一个局限,即接口中有且只有一个抽象方法时才能使用Lamdba表达式代替匿名内部
      类。这是因为Lamdba表达式是基于函数式接口实现的,所谓函数式接口是指有且仅有一个抽象方法的接
      口,Lambda表达式就是Java中函数式编程的体现,只有确保接口中有且仅有一个抽象⽅法,Lambda表达
      式才能顺利地推导出所实现的这个接口中的方法' ,'2019-10-10', NULL, '默认分类',
      '‘2019,Java,学习路线图', '1', NULL);
      INSERT INTO t_article VALUES ('5', '虚拟化容器技术——Docker运行机制介绍','Docker是一
      个开源的应用容器引擎,它基于go语言开发,并遵从Apache2.0开源协议。使用Docker可以让开发者封装
      他们的应用以及依赖包到一个可移植的容器中,然后发布到任意的Linux机器上,也可以实现虚拟化。
      Docker容器完全使用沙箱机制,相互之间不会有任何接口,这保证了容器之间的安全性' ,'2019-10-10',
       NULL, '默认分类', '‘2019,Java,学习路线图', '1', NULL);
      
      
    • 创建实体类,并配置和数据表的映射

      package com.springboot.lean.pojo;
      
      import lombok.Data;
      
      import javax.persistence.*;
      import java.util.Date;
      
      @Entity
      @Table(name = "t_article")
      @Data
      public class Article {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Integer id;
          private String title;
          @Lob
          private String content;
          private Date created;
          private Date modified;
          private String categories;
          private String tags;
          @Column(name = "allow_comment")
          private Integer allowComment;
          private String thumbnail;
      
      }
      

      因为实体中allowComment字段和数据库表中的allow_comment不一致,所以需要手动映射。@Data注解是提供get,set,toString等方法的,使用该注解需要导入以下依赖

      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      
      

      当然后也可以不使用该注解,自己提供get,set方法

    • 创建dao层

      package com.springboot.lean.repository;
      
      import com.springboot.lean.pojo.Article;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface ArticleRepository extends JpaRepository<Article,Integer> {
      }
      
    • 创建Service接口层

      package com.springboot.lean.service;
      
      import com.springboot.lean.pojo.Article;
      import org.springframework.data.domain.Page;
      
      import java.util.List;
      
      public interface ArticleService {
      
          public List<Article> selectList();
      
          public Page<Article> page(int pageNum, int pageSize);
      }
      
    • 创建Service实现层

      package com.springboot.lean.service.impl;
      
      import com.springboot.lean.pojo.Article;
      import com.springboot.lean.repository.ArticleRepository;
      import com.springboot.lean.service.ArticleService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.data.domain.Page;
      import org.springframework.data.domain.PageRequest;
      import org.springframework.data.domain.Pageable;
      import org.springframework.stereotype.Service;
      
      import java.util.List;
      
      @Service
      public class ArticleServiceImpl implements ArticleService {
      
          @Autowired
          private ArticleRepository articleRepository;
      
          @Override
          public List<Article> selectList() {
              return articleRepository.findAll();
          }
      
          @Override
          public Page<Article> page(int pageNum, int pageSize) {
      
              Pageable pageable = PageRequest.of(pageNum, pageSize);
              Page<Article> articles = articleRepository.findAll(pageable);
              return articles;
          }
      }
      
    • 在properties文件中, 配置JPA打印SQL

      ##配置JPA打印SQL
      spring.jpa.show-sql=true
      
    • 单元测试~

SpringBoot单元测试

  • 使用SpringBoot的单元测试,需要导入以下依赖

    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    
  • 测试代码实例

    package com.springboot.lean.springboothomework;
    
    import com.springboot.lean.pojo.Article;
    import com.springboot.lean.repository.ArticleRepository;
    import org.junit.jupiter.api.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import javax.annotation.security.RunAs;
    import java.util.Optional;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    class SpringbootHomeworkApplicationTests {
    
        @Autowired
        private ArticleRepository articleRepository;
        @Test
        void contextLoads() {
            Optional<Article> byId = articleRepository.findById(1);
            Article article = byId.get();
            System.out.println(article);
        }
    }
    
  • 测试结果
    在这里插入图片描述

SpringBoot整合MyBatis

  • 编写配置文件信息
    • 在properties文件中配置数据库连接信息,同整合SpringData JPA
注解方式整合MyBatis
  • 注解方式方式整合MyBatis

    • 编写数据库表对应的实体类Article类

      package com.learn.springoot.pojo;
      
      import lombok.Data;
      
      import java.util.Date;
      
      @Data//用于提供get,set等方法,可以不用自己提供get,set等方法
      public class Article {
          private Integer id;
          private String title;
          private String content;
          private Date created;
          private Date modified;
          private String categories;
          private String tags;
          private Integer allowComment;
          private String thumbnail;
      }
      
      
    • 创建对应的Mapper,ArticleMapper

      package com.learn.springoot.mapper;
      
      import com.learn.springoot.pojo.Article;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Select;
      
      @Mapper
      public interface ArticleMapper {
      
          @Select("select * from t_article where id = #{id}")
          public Article findById(Integer id);
      }
      
      

      @Mapper注解表示该类是一个Mabatis的Mapper文件,并保证能够被SpringBoot自动扫描到Spring容器中。

    • 编写测试方法

      package com.learn.springoot;
      
      import com.learn.springoot.mapper.ArticleMapper;
      import com.learn.springoot.pojo.Article;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      @SpringBootTest
      class MybatisSpringbootApplicationTests {
      
          @Autowired
          private ArticleMapper articleMapper;
      
          @Test
          void contextLoads() {
              Article byId = articleMapper.findById(1);
              System.out.println(byId);
          }
      
      }
      
      
    • 测试结果
      在这里插入图片描述

      从上面的图中可以看到,allowComment字段的值为null,这是因为,在Article实体类中,属性名和数据库表中的字段名不一致导致的,数据库表中,我们是以下划线的方式命名的,在实体类中是以驼峰的方式命名的,因此,在这里,在配置文件中开启mybatis的驼峰命名匹配配置

      #### 开启驼峰命名匹配
      mybatis.configuration.map-underscore-to-camel-case=true
      

      然后再次测试
      在这里插入图片描述

      现在allowComment字段的值就查找出来了

    • @MapperScan

      上面我们编写ArticleMapper时,使用了@Mapper注解,这种方式可以快速的标记,但是当有多个Mapper文件时,我们需要重复的添加@Mapper注解,为了解决这个麻烦,我们可以使用@MapperScan注解,直接在SpringBoot应用的启动类上添加@MapperScan(“xxx”)注解

    • @MapperScan测试

      • 首先注释ArticleMapper上的@Mapper注解
      package com.learn.springoot.mapper;
      
      import com.learn.springoot.pojo.Article;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Select;
      
      //@Mapper
      public interface ArticleMapper {
      
          @Select("select * from t_article where id = #{id}")
          public Article findById(Integer id);
      }
      
      
      
      • 在启动类上添加@MapperScan注解
      package com.learn.springoot;
      
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      
      @SpringBootApplication
      @MapperScan(basePackages = "com.learn.springoot.mapper")
      public class MybatisSpringbootApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(MybatisSpringbootApplication.class, args);
          }
      
      }
      
      • 执行测试方法结果
        在这里插入图片描述

    @Mapper注解和@MapperScan注解都能在让Spring扫描到Mapper文件,但是使用@MapperScan时,必须指定要扫描的包

XML方式整合Mybatis
  • 使用XML文件的方式整合Mybatis

    • 创建XML映射文件

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.learn.springoot.mapper.ArticleMapper">
          <select id="findById" resultType="com.learn.springoot.pojo.Article">
              select * from t_article where id=#{id}
          </select>
      </mapper>
      
    • 配置XML映射文件。使用XML的方式整合mybatis时,SpringBoot并不知道,所以无法扫描到该自定义编写的XML配置文件,因此还必须在全局配置文件application.properties中,配置MyBatis映射文件的路径,同时添加实体类别名映射路径(非必须)

      ### 配置映射文件路径
      mybatis.mapper-locations=classpath:com/learn/springoot/mapper/*.xml
      ### 配置实体类别名路径
      mybatis.type-aliases-package=com.learn.springoot.pojo
      
    • 注释掉之前ArticleMapper中的注解查询

      package com.learn.springoot.mapper;
      
      import com.learn.springoot.pojo.Article;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Select;
      
      //@Mapper
      public interface ArticleMapper {
      
          //    @Select("select * from t_article where id = #{id}")
          public Article findById(Integer id);
      }
      
      
    • 单元测试
      在这里插入图片描述

SpringBoot整合Redis

  • SpringBoot整合Redis需要导入如下依赖

    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-data-redis</artifactId> 
    </dependency>
    
  • 编写实体类

    package com.learn.springoot.domain;
    
    import org.springframework.data.redis.core.index.Indexed;
    
    public class Address {
    
        @Indexed
        private String city;
        @Indexed
        private String country;
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        public String getCountry() {
            return country;
        }
    
        public void setCountry(String country) {
            this.country = country;
        }
        
        @Override
        public String toString() {
            return "Address{" +
                    "city='" + city + '\'' +
                    ", country='" + country + '\'' +
                    '}';
        }
    }
    
    
    package com.learn.springoot.domain;
    
    import org.springframework.data.annotation.Id;
    import org.springframework.data.redis.core.RedisHash;
    import org.springframework.data.redis.core.index.Indexed;
    
    @RedisHash("person")//指定实体对象在Redis数据库中的存储空间
    public class Person {
    
        @Id//标识实体类主键
        private String id;
        @Indexed//标识属性在Redis数据库中生成二级索引
        private String firstname;
        @Indexed
        private String lastname;
    
        private Address address;
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getFirstname() {
            return firstname;
        }
    
        public void setFirstname(String firstname) {
            this.firstname = firstname;
        }
    
        public String getLastname() {
            return lastname;
        }
    
        public void setLastname(String lastname) {
            this.lastname = lastname;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "id='" + id + '\'' +
                    ", firstname='" + firstname + '\'' +
                    ", lastname='" + lastname + '\'' +
                    ", address=" + address +
                    '}';
        }
    }
    
    

    实体类中,针对面向Redis数据库的数据操作,设置了几个主要的注解,注解说明如下:

    • @RedisHash(“person”):用于指定操作实体类对象在Redis数据库中的存储空间,此处表示,针对Person实体类的的数据操作都存储在Redis中名为person的存储空间下

    • @Id:用于标识实体类主键。在Redis中,会默认生成字符串形式的HashKey作为唯一的实体对象的Id,也可以在数据存储时手动设置Id值

    • @Indexed:用于标识对应属性在Redis中生存二级索引,属性名就是二级索引名称,方便进行数据条件查询

    • 编写Repository接口。SpringBoot针对Redis在内的一些常用数据库,提供了自动化配置,可以通过实现Repository接口,简化对数据的增删改查操作

      package com.learn.springoot.repository;
      
      import com.learn.springoot.domain.Person;
      import org.springframework.data.repository.CrudRepository;
      
      import java.util.List;
      
      public interface PersonRepository extends CrudRepository<Person, String> {
          
          List<Person> findByAddress_City(String city);
      }
      
      
    • Redis数据库连接配置

      ### Redis服务器地址
      spring.redis.host=127.0.0.1
      ### Redis服务端口
      spring.redis.port=6379
      ### Redis服务连接密码(默认为空)
      spring.redis.password=
      
    • 编写测试方法

      package com.learn.springoot;
      
      import com.learn.springoot.domain.Address;
      import com.learn.springoot.domain.Person;
      import com.learn.springoot.mapper.ArticleMapper;
      import com.learn.springoot.pojo.Article;
      import com.learn.springoot.repository.PersonRepository;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import java.util.List;
      
      @SpringBootTest
      class MybatisSpringbootApplicationTests {
      
          @Autowired
          private PersonRepository personRepository;
      
          @Test
          public void savePerson() {
              Person person = new Person();
              person.setFirstname("张");
              person.setLastname("三");
              Address address = new Address();
              address.setCity("北京");
              address.setCountry("中国");
              person.setAddress(address);
              personRepository.save(person);
          }
      
          @Test
          public void findPerson() {
      
              List<Person> personList = personRepository.findByAddress_City("北京");
              for (Person person : personList) {
                  System.out.println(person);
              }
          }
      }
      
      

      首先执行savePerson方法,然后执行findPerson方法,最后执行结果如下图:
      在这里插入图片描述

    • 通过Redis可视化工具Redis-Desktop-Manager查看数据信息
      在这里插入图片描述

    执行savePerson方法后,数据保存成功,在可视化工具中,可以看到有一些二级索引,这些都是通过在属性上标注了@Indexed注解后,自动生成的,便于条件查询,如上测试中的findByAdress_City方法,就是通过二级索引address.city来进行查找的

SpringBoot整合Thymeleaf

  • 导入Thymeleaf依赖和Web依赖

    		<!--thymeleaf 依賴-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <!--WEB 依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
    
  • 在全局配置文件中配置Thymeleaf模板的一些参数

    ### 配置是否启用模板缓存
    spring.thymeleaf.cache=false
    

    在这里只配置了是否启用缓存,其他的使用的默认配置就可以了

  • Thymeleaf的静态资源访问

    开发web应用是难免会使用静态资源,Springboot设置了默认的静态资源访问路径

    • classpath:/META-INF/resources/
    • classpath:/resources/
    • classpath:/static/
    • classpath:/public/

    在上面四个目录下存放静态资源,可以直接访问

  • 编写controller类

    package com.learn.springboot.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.util.Calendar;
    
    @Controller
    public class LoginController {
    
        @GetMapping("/toLogin")
        public ModelAndView toLogin(){
            ModelAndView andView = new ModelAndView();
            andView.setViewName("login");
            andView.addObject("currentYear", Calendar.getInstance().get(Calendar.YEAR));
            return andView;
        }
    }
    
    
  • 编写Html页面

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
        <title>用户登录界面</title>
        <link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
        <link th:href="@{/login/css/signin.css}" rel="stylesheet">
    </head>
    <body class="text-center">
    <!--  用户登录form表单 -->
    <form class="form-signin">
        <h1 class="h3 mb-3 font-weight-normal">请登录</h1>
        <input type="text" class="form-control" required="" autofocus="">
        <input type="password" class="form-control" required="">
        <div class="checkbox mb-3">
            <label>
                <input type="checkbox" value="remember-me">记住我 
            </label>
        </div>
        <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button><
        <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2019</span>-<span
                th:text="${currentYear}+1">2020</span></p>
    </form>
    </body>
    </html>
    
    
    
    • 效果展示
      在这里插入图片描述

      上面html文件中使用了,从后台传到页面的值,currentYear。通过使用th:text标签实现了文本的展示

Thymeleaf国际化配置

配置国际化页面
  • 编写国际化多语言配置文件

    在项目的类路径下resouce下创建名称为i8n的文件夹,并在该文件夹中添加多语言国际化配置文件,login.properties,login_zh_CN.properties,login_en_US.properties文件

    • login.properties
    login.tip=请登录
    login.username=用户名
    login.password=密码
    login.button = 登录
    login.rememberme =记住我
    
    • login_zh_CN.properties
    login.tip=请登录
    login.username=用户名
    login.password=密码
    login.button = 登录
    login.rememberme =记住我
    
    • login_en_US.properties
    login.tip=Please sign in
    login.username=Username
    login.password=Password
    login.button = Login
    login.rememberme =Remember me
    
    

    login.properties为默认的配置文件,login_zh_CN.properties是中文国际化配置文件,zh_en_US.properties是英文国际化配置文件

    在SpringBoot中,默认的国际化配置文件路径为resource下的message.properties,其他语言国际化配置文件的名称必须严格按照“文件的前缀名 语言代码 国家代码.properties”的形式命名

    我们现在是在resouce下的i18n文件夹下存放了国际化配置文件,因此需要在项目的全局的配置文件中进行国际化文件基础名配置,才能引用自定义的国际化配置文件

    • 编写配置文件
    ### 配置国际化文件基础名
    spring.messages.basename=i18n.login
    

    i18n是相对于resouce文件夹,login表示多语言文件前缀名。如果是按照Springboot的默认识别机制,在resouce下编写message.properties国际化文件,就不需要配置该项。

    • 测试配置结果

      修改login.html文件

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
          <title>用户登录界面</title>
          <link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
          <link th:href="@{/login/css/signin.css}" rel="stylesheet">
      </head>
      <body class="text-center">
      <!--  用户登录form表单 -->
      <form class="form-signin">
          <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
          <input type="text" class="form-control"
                 required="" autofocus="" th:placeholder="#{login.username}">
          <input type="password" class="form-control"
                 required="" th:placeholder="#{login.password}">
          <div class="checkbox mb-3">
              <label>
                  <input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
              </label>
          </div>
          <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
          <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2019</span>-<span
                  th:text="${currentYear}+1">2020</span></p>
      </form>
      </body>
      </html>
      
      

      页面展示效果
      在这里插入图片描述

      在login.html文件中,我们使用了Thymeleaf的标签替换了文本值,重新加载后,页面内容正常显示,说明配置成功

  • 定制区域信息解析器

    在完成上面的配置后,就可以正式在前端页面结合thymeleaf模板相关属性进行国际化语言设置和展示了,不过这种方式默认使用的是请求头中的语言信息(浏览器的语言信息),自动进行语言切换,如果要使用手动切换语言,则需要定制区域解析器

    定义区域解析器

    package com.learn.springboot.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.util.StringUtils;
    import org.springframework.web.servlet.LocaleResolver;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Locale;
    
    @Configuration
    public class MyLocalResolver implements LocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest httpServletRequest) {
            //获取手动切换语言的请求参数  zh_CN
            String language = httpServletRequest.getParameter("language");
            //获取请求头中自动传递的语言参数
            String header = httpServletRequest.getHeader("Accept-Language");
            Locale local = null;
            //
            if (!StringUtils.isEmpty(language)) {
                String[] s = language.split("_");
                local = new Locale(s[0], s[1]);
            } else {
                //Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
                String[] split = header.split(",");
                //zh-CN
                String l = split[0];
                String[] s = l.split("_");
                local = new Locale(s[0], s[1]);
            }
            return local;
        }
    
        @Override
        public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
        }
    
        /**
         * 将自定义的区域解析器注册到Spring容器中
         *
         * @return
         */
        @Bean
        public MyLocalResolver localResolver() {
            return new MyLocalResolver();
        }
    }
    
    
    
  • login页面修改

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
    <title>用户登录界面</title>
    <link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
    <link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!--  用户登录form表单 -->
<form class="form-signin">
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">请登录</h1>
    <input type="text" class="form-control"
           required="" autofocus="" th:placeholder="#{login.username}">
    <input type="password" class="form-control"
           required="" th:placeholder="#{login.password}">
    <div class="checkbox mb-3">
        <label>
            <input type="checkbox" value="remember-me"> [[#{login.rememberme}]]
        </label>
    </div>
    <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.button}">登录</button>
    <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2019</span>-<span
            th:text="${currentYear}+1">2020</span></p>
    <!--新增手动切换语言-->
    <a class="btn btn-sm" th:href="@{/toLogin(l='zh_CN')}">中文</a>
    <a class="btn btn-sm" th:href="@{/toLogin(l='en_US')}">English</a>
</form>
</body>
</html>

SpringBoot热部署

我们在开发过程中,修改了代码后要重新部署之后才能看到效果,但是这样重新部署造成了大量的时间的浪费,很影响我们的开发效率,因此热部署很有必要

  • SpringBoot的热部署需要引入如下依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
  • 导入了依赖后,需要设置开发工具自动编译,这里使用是IDEA
    在这里插入图片描述

  • 设置了自动编译之后,还需要进行以下一步操作

    在项目任意界面使用组合快捷键“Ctrl+Shift+Alt+/”打开Registry,勾选选上其中的compiler.automake.allow.when.app.running
    在这里插入图片描述

  • 热部署测试

 	@GetMapping("/hello")
    public String hello() {
        return "hello world!";
    }

在这里插入图片描述

  • 修改测试代码,在不重新启动项目的情况下重新访问连接
	@GetMapping("/hello")
    public String hello() {
        return "hello springboot!";
    }

在这里插入图片描述

  • 热部署配置成功~

SpringBoot缓存管理

环境准备
  • 数据环境使用上面整合SpringdData JPA时的Artice
  • Article实体
package com.learn.springboot.pojo;


import javax.persistence.*;
import java.util.Date;

/**
 * @author leim
 * @data 2020/8/27 22:35
 * @descript
 */
@Entity
@Table(name = "t_article")
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String title;
    @Lob
    private String content;
    private Date created;
    private Date modified;
    private String categories;
    private String tags;
    @Column(name = "allow_comment")
    private Integer allowComment;
    private String thumbnail;
	//省略get,set方法
}

  • ArticleRepository
package com.learn.springboot.repository;

import com.learn.springboot.pojo.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

public interface ArticleRepository extends JpaRepository<Article, Integer> {

    /**
     * 修改文章的分类
     *
     * @param categories
     * @param id
     */
    @Transactional
    @Modifying
    @Query(value = "update t_article set categories =?1 where id=?2", nativeQuery = true)//nativeQuery表示使用原生SQL查询
    public void updateArticle(String categories, Integer id);
}

  • 编写Service
package com.learn.springboot.service;

import com.learn.springboot.pojo.Article;
import com.learn.springboot.repository.ArticleRepository;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Optional;

@Service
@Transactional
public class ArticleService {

    @Autowired
    private ArticleRepository articleRepository;

    public Article findById(Integer id) {
        Optional<Article> byId = articleRepository.findById(id);
        if (byId != null) {
            return byId.get();
        }
        return null;
    }
}

  • 编写controller
package com.learn.springboot.controller;

import com.learn.springboot.pojo.Article;
import com.learn.springboot.repository.ArticleRepository;
import com.learn.springboot.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    @GetMapping("findById")
    public Article findById(Integer id) {
        return articleService.findById(id);
    }

}
  • 连接数据库后进行测试,测试结果
    在这里插入图片描述

现在是没有使用任何的缓存的,所以每次请求都会进行一次数据库查询

默认的缓存管理
  • ringboot提供了默认的缓存管理,我i们只需要在项目启动类上添加,@EnableCaching注解,就可以开启SpringBoot基于注解的缓存支持
package com.learn.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching//开启基于注解的缓存支持
public class RedisSpringbbotApplication {

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

}
  • @Cacheable注解对数据操作方法进行缓存管理。将@Cacheable注解添加到Service中的查询方法上,对查询的结果进行缓存
	@Cacheable(cacheNames = "article")
    public Article findById(Integer id) {
        Optional<Article> byId = articleRepository.findById(id);
        if (byId != null) {
            return byId.get();
        }
        return null;
    }

在@Cacheable注解中使用了它的属性cacheNames,该属性的作用就是将查询方法的结果缓存到SpringBoot默认缓存中,名称为article的命名空间中,对应唯一的缓存标识,即缓存数据对应的主键key,默认为方法参数的id

  • 测试访问
    在这里插入图片描述
    在这里插入图片描述

可以看到,在配置了Springboot的默认缓存后,我们重复进行查询请求,查询出来了结果,但是数据库只进行了一次SQL查询语句,说明项目开启的缓存支持已经生效。

  • 底层结构
    • SpringBoot默认缓存的装配使用的是SimpleCacheConfiguration,它使用的cacheManager是ConCurrentMapCacheManager,使用ConcurrentMap当底层数据结构,按照Cache的名称,查询出Cache(ConcurrentMapCache),每一个Cache中存在多个key-value
整合Redis缓存实现
SpringBoot支持的缓存组件

​ 在SpringBoot中数据的缓存管理存储依赖于Spring框架Cache相关的org.springframework.cache.Cache和org.springframework.cache.CacheManager缓存管理器接口。如果程序中没有定义CacheManager的Bean或者是名为cacheResovler的CacheResovler缓存解析器,SpringBoot将尝试选择并开启以下缓存组件(按照指定的顺序):

  • Generic
  • JCache (JSR-107) (EhCache、Hazelcast、infinispan等)
  • EhCache2.x
  • Hazelcast
  • infinispan
  • Couchbase
  • Redis
  • Caffeine
  • Simple

在项目中,如果添加了某个缓存的管理器组件(Redis)后,SpringBoot会选择并启用对应的缓存管理器。如果项目中同时添加了多个缓存组件,且没有指定缓存管理器和缓存解析器(CacheResovler活着CacheManager),那么SpringBoot会按照上述的顺序,在添加的多个缓存中,优秀启用指定的缓存组件进行管理。

在没有添加任何缓存管理组件时,使用@EnableCaching开启了缓存管理后,Spring会默认按照上诉的顺序,查找有效的缓存组件进行缓存管理,如果没有任何的缓存组件,会默认使用最后一个Simple缓存组件,进行缓存管理。Simple组件是SpringBoot默认的缓存管理组件,所以在没有任何第三方缓存组件的情况下,可以实现内存中的缓存管理。

基于注解的Redis缓存实现
  • 添加Spring Data Redis依赖启动器。
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

当我们添加了Redis的依赖启动器后,Springboot会使用RedisCacheConfiguration当作生效的配置类,进行缓存相关的自动配置,容器使用的缓存管理器是RedisCacheManager,这个缓存管理器创建的Cache为RedisCache,进而操作Redis进行数据缓存

  • Redis缓存配置
### Redis服务器地址
spring.redis.host=127.0.0.1
### Redis服务端口
spring.redis.port=6379
### Redis服务连接密码(默认为空)
spring.redis.password=

  • 编写Service类
package com.learn.springboot.service;

import com.learn.springboot.pojo.Article;
import com.learn.springboot.repository.ArticleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
@Transactional
public class ArticleService {

    @Autowired
    private ArticleRepository articleRepository;

    @Cacheable(cacheNames = "article")
    public Article findById(Integer id) {
        Optional<Article> byId = articleRepository.findById(id);
        if (byId != null) {
            return byId.get();
        }
        return null;
    }

    @CachePut(cacheNames = "article", key = "#result.id")
    public Article updateArticleCategories(Article article) {
        articleRepository.updateArticle(article.getCategories(), article.getId());
        return article;
    }

    @CacheEvict(cacheNames = "article")
    public void deleteArticle(Integer id) {
        articleRepository.deleteById(id);
    }
}

  • 编写Controller
package com.learn.springboot.controller;

import com.learn.springboot.pojo.Article;
import com.learn.springboot.repository.ArticleRepository;
import com.learn.springboot.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    @GetMapping("findById")
    public Article findById(Integer id) {
        return articleService.findById(id);
    }

    @GetMapping("/updateArticle")
    public Article updateArticle(String categories, Integer id) {
        Article byId = articleService.findById(id);
        byId.setCategories(categories);
        return articleService.updateArticleCategories(byId);
    }

    @GetMapping("deleteById")
    public void deleteById(Integer id) {
        articleService.deleteArticle(id);
    }
}

  • 测试

    • 访问findById?id=1,进行了一次数据查询

    • 再次访问,数据库未进行SQL查询

    • 访问updateArticle?categories=java&id=1

    • 数据库执行了一次update SQL语句

    • 再次访问findById?id=1

    • 数据库未执行查询语句,返回结果中,categories字段的值变为了java

    • 查看RedisDesktopManager,有一条缓存信息
      在这里插入图片描述

    • 访问deleteByid?id=1

    • 数据库执行了 delete SQL语句

    • 再次访问findById?id=1,返回结果报错,因此已经没有这个数据了

    • 查看数据库中,无此条数据,查看RedisDesktopManager,里面已经没有缓存的数据信息了

    至此,SpringBoot基于注解的Redis的缓存实现已经完成了。

    总结:

    ​ SpringBoot整合Redis实现基于注解的的缓存管理,只需要添加Redis的相关依赖,然后使用@EnableCaching,开启基于注解的缓存支持,然后使用对应的注解@Cacheable,@CachePut,@CacheEvict就可以了。

    另外,可以在全局配置文件中,添加Redis缓存的失效时间

    ### 配置Redis缓存的失效时间,5分钟,单位ms
    spring.cache.redis.time-to-live=300000
    
基于API的Redis缓存实现
  • 使用API进行业务数据缓存管理

    • Service编写
    package com.learn.springboot.service;
    
    import com.learn.springboot.pojo.Article;
    import com.learn.springboot.repository.ArticleRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cache.annotation.CacheEvict;
    import org.springframework.cache.annotation.CachePut;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Optional;
    
    @Service
    @Transactional
    public class ApiArticleService {
    
        @Autowired
        private ArticleRepository articleRepository;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        public Article findById(Integer id) {
            Object o = redisTemplate.opsForValue().get("comment_" + id);
            if (o != null) {
                return (Article) o;
            } else {
                Optional<Article> byId = articleRepository.findById(id);
                if (byId != null) {
                    return byId.get();
                }
            }
            return null;
        }
    
        public Article updateArticleCategories(Article article) {
            articleRepository.updateArticle(article.getCategories(), article.getId());
            //数据库更新完成后,更新缓存
            redisTemplate.opsForValue().set("comment_" + article.getId(), article);
            return article;
        }
    
        public void deleteArticle(Integer id) {
            articleRepository.deleteById(id);
            redisTemplate.delete("comment_" + id);
        }
    
    }
    
    
    • Controller编写
    package com.learn.springboot.controller;
    
    import com.learn.springboot.pojo.Article;
    import com.learn.springboot.service.ApiArticleService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/api")
    public class ApiArticleController {
    
    
        @Autowired
        private ApiArticleService articleService;
    
        @GetMapping("findById")
        public Article findById(Integer id) {
            return articleService.findById(id);
        }
    
        @GetMapping("/updateArticle")
        public Article updateArticle(String categories, Integer id) {
            Article byId = articleService.findById(id);
            byId.setCategories(categories);
            return articleService.updateArticleCategories(byId);
        }
    
        @GetMapping("deleteById")
        public void deleteById(Integer id) {
            articleService.deleteArticle(id);
        }
    }
    
    
    • 测试~

    注意:使用基于注解方式的Redis缓存实现,缓存的实体(Article)需要实现序列化接口Serializable

自定义Redis序列化机制

​ 在上面完成的实现Redis进行缓存管理中,我们在RedisDesktopManager中查看缓存数据时,发现看到是一些看不明白的序列化信息,这个是因为Redis默认使用的是Jdk序列化方式,不便于使用可视化工具进行查看和管理。

针对基于注解的和基于API的redis缓存实现中的序列化进行介绍,并自定义JSON格式的数据序列化方式进行数据缓存管理

  • 自定义 RedisTemplate
package com.learn.springboot.cofig;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean//在没有配置bean的名称时,方法名必须是redisTemplate
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        //使用Json序列化方式,序列化的对象
        Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jsonRedisSerializer.setObjectMapper(om);

        redisTemplate.setDefaultSerializer(jsonRedisSerializer);

        return redisTemplate;
    }
}

  • 测试
    在这里插入图片描述

重新访问/api/findById,查看可视化工具,现在Redis中的数据已经使用了Json的数据格式进行存储了,说明自定义的RedisTemplate生效

  • 自定义RedisCacheManager

    上面实现了自定义RedisTemplate,实现了基于API的的json序列化方式管理缓存,但是这种方式对基于注解的Redis缓存来说是无效的。因为在SpringBoot2.X版本中,RedisCacheManager是单独进行构建的,因此对RedisTemplate进行了自定义序列化方式之后,无法覆盖RedisCacheManager内部的序列化机制,因此想要基于注解的Redis缓存管理,也使用自定义的序列化机制,需要自定义RedisCacheMananger

    @Bean//完成基于注解的Redis缓存管理RedisCacheManager的定制后,实体类可以不用实现序列化接口
        public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
            RedisSerializer<String> keySerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer(Object.class);
            //解决查询缓存转换异常问题
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            valueSerializer.setObjectMapper(om);
    
            //定制数据缓存序列化方式及时生效
            //设置缓存有效期 1天
            RedisCacheConfiguration redisConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1))    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer)).disableCachingNullValues();
    
            RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory).cacheDefaults(redisConfiguration).build();
            return redisCacheManager;
        }
    
    

    测试:
    在这里插入图片描述

在使用自定义的RedisCacheManager时,实体类可以不需要实现序列化接口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值