添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

快速入门Springboot+vue全栈开发阅读笔记

SpringBoot 入门

SpringBoot 基础配置

Web容器配置

常规配置

spring-boot-starter-web 依赖默认使用的是Tomcat作为web容器,也可以通过依赖配置排除Tomcat容器,使用其他的web容器。例如:在web的依赖处,通过 <exclusions> 包裹排除配置 spring-boot-starter-tomcat 如下图:




Https的配置

书本18页


代码读取配置文件属性

application.properties

book.name=三国演义
book.author=罗贯中
book.price=30

通过两个注释,可以读取到配置文件中的配置,赋值到实体类上

@Component
@ConfigurationProperties(prefix = "book")



Profile 发布环境的配置切换

application-{profile}.properties ,profile占位符代表运行环境,例如:

application-dev.properties

server.port=8080

application-prod.properties

server.port=80

在不同的打包环境下,会对端口配置有影响

然后,在 application.propertites 中配置

spring.profiles.active=dev //这里的值对应上面的dev和prod


SpringBoot 视图层整合

Thymeleaf

使用 spring-boot-starter-thymeleaf 添加该视图模板的依赖。

相关的配置,如下如:



加载模板视图和显示数据

1,ModelAndView对象绑定视图和数据

    @GetMapping("books")
    public ModelAndView books() {
        List<Book> books = new ArrayList<>();
        Book book1 = new Book();
        book1.setId(1);
        book1.setAuthor("罗贯中");
        book1.setName("三国演义");
        books.add(book1);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("books", books);
        modelAndView.setViewName("books");
        return modelAndView;
    }

2,创建视图

resource 目录下,创建目录 templates ,在目录下创建对应的html的视图文件

<html lang="en" xmlns:th="https://www.thymeleaf.org"> // 需要导入Thymeleaf的命名空间
 <tr th:each="book:${books}">
        <td th:text="${book.id}"></td>
        <td th:text="${book.name}"></td>
        <td th:text="${book.author}"></td>
    </tr>

使用 th: 作为前缀,后面带上需要的功能,在标签中声明搭配 ${} 美元符号和大括号,包裹数据即可

更多的Thymeleaf的用法,需要去参考文档: Thymeleaf官方文档


FreeMarker

该引擎比较古老,不记录了。可以查阅《Springboot + Vue全栈开发实战》第31页


SpringBoot 整合Web开发

返回json数据

SpringMVC 中使用 HttpMessageConverter 对json进行转化,SpringBoot中在 spring-boot-starter-web 的依赖中,已经包含了 jackson-databind 作为json处理器。

对实体类的改造如下:

public class Book {
    private Integer id;
    private String name;
    private String author;
    // 日期的json转换
    @JsonFormat




    
(pattern = "yyyy-MM-dd")
    private Date publicationDate;
    // json转化忽略
    @JsonIgnore
    private Float price;
}

在controller中直接返回对象即可,但是要搭配上注释 @ResponseBody

    @GetMapping("/book")
    // 搭配上这个注释,返回的就是json数据
    @ResponseBody
    public Book book() {
        Book book1 = new Book();
        book1.setId(1);
        book1.setAuthor("罗贯中");
        book1.setName("三国演义");
        book1.setPublicationDate(new Date());
        return book1;
    }
使用 @RestController 代替 @Controller @ResponseBody

如果不想要频繁的在方法上使用 @ResponseBody 这个注释,可以在controller类上使用 @RestController ,返回的对象会被自动的转化为json数据。

正常情况下,如果controller的类上使用的注释是 @Controller 那么就需要在返回json数据的额方法上搭配 @ResponseBody 这个注释

自定义json转换器

使用Gson

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.5.0</version>
            <exclusions>
                // 排除掉默认的json转换器
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-databind</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

如果使用Gson,在对日期进行转化的时候,需要特别注意,虽然SpringBoot对Gson也有默认提供了自动转化类,但是对日期的转化,还需要开发者自行定义,自定义方式如下:

@Configuration
public class GsonConfig {
    @Bean
    GsonHttpMessageConverter gsonHttpMessageConverter() {
        GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
        GsonBuilder builder = new GsonBuilder();
        builder.setDateFormat("yyyy-MM-dd");
        // 修饰被protected的字段将会被过滤
        builder.excludeFieldsWithModifiers(Modifier.PROTECTED);
        Gson gson = builder.create();
        converter.setGson(gson);
        return converter;
}


使用fastjson

和上面的Gson差不多,需要在pom配置文件中,引入

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

接着也需要配置自定义的json转换,将fastjson的json转换工具返回给容器

@Configuration
public class MyFastJsonConfig {
    @Bean
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd");
        config.setCharset(Charset.forName("UTF-8"));
        config.setSerializerFeatures(
                SerializerFeature.WriteClassName,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullBooleanAsFalse, // 布尔值为空,返回false
                SerializerFeature.WriteNullNumberAsZero, // 数字为空返回0
                SerializerFeature.PrettyFormat, // 漂亮打印
                SerializerFeature.WriteNullListAsEmpty, // 列表为空返回[]
                SerializerFeature.WriteNullStringAsEmpty // 字符串为空,返回""不返回null
        return converter;
}

除了上面两个步骤,相对于gson,fastjson还有多出一个步骤,,需要配置响应编码,否者返回的json会出现乱码

# fastjson 响应编码
server.servlet.encoding.force-response=true
@JSONField(serialize = false) // fastjson 忽略字段的写法,和jackson有不同

静态资源访问

配置自定义的静态资源访问过滤规则和静态资源位置,详细的请看书籍第40页


文件上传

springboot内部默认集成tomcat容器,默认集成文件上传的处理 StanderdServletMultipartResolver

所以,如果没有改用其他的web容器,那么就不需要对文件上传的处理进行额外的配置。

上传文件的部分代码如下:

@PostMapping("/upload")
    public String upload(MultipartFile uploadFile, HttpServletRequest request) {
        // 获取上传文件要保存的真实路径,这里的这个路径,可以通过配置文件配置,然后用代码读取
        String realPath = request.getSession().getServletContext().getRealPath("/uploadFile/");
        String format = dateFormat.format(new Date());
        // 通过日期来分目录保存图片
        File fold = new File(realPath + format);
        if (!fold.isDirectory()){
            fold.mkdirs();
        String oldName = uploadFile.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
            uploadFile.transferTo(new File(fold, newName));
            return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() +
                    "/uploadFile/" + format + newName;
        } catch (IOException e) {
            e.printStackTrace();
        return "上传失败";
    }

resouces/static 目录下,新建 upload.html 上传文件

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" value="请选择文件">
    <input type="submit" value="上传">
</form>

最后在 application.properties 配置文件中添加配置,由于tomcat默认的文件上传配置比较苛刻,所以需要最文件上传的配置做一些适当的修改。如下

# 单个文件大小上传限制,-1为不做限制
spring.servlet.multipart.max-file-size=-1
# 单次上传文件请求的总大小不能超过100MB
spring.servlet.multipart.max-request-size=100MB

还有一些其他的文件上传的配置,如下:

# 是否支持文件上传
spring.servlet.multipart.enabled=true
# 文件写入磁盘的阈值
spring.servlet.multipart.file-size-threshold=0
# 上传文件的临时的保存位置
spring.servlet.multipart.location=E://temp
# 是否延迟解析
spring.servlet.multipart.resolve-lazily=false

另外,如果需要做多文件上传,则只需要在 controller 的方法上,对接收文件的参数做一个修改即可,即单文件接收编程多文件的数组对象接收,例如:

    @PostMapping




    
("/uploads")
    public String uploads(MultipartFile[] uploadFiles, HttpServletRequest request) {
        // 遍历数组,然后通过调用upload的方法实现多文件的上传操作,如果搭配kotlin可以放在协程当中去处理
        return "上传成功";
    }


@ControllerAdvice 应用的全局数据处理

全局异常处理

上面的文件上传的例子,如果没有对tomcat容器的文件上传配置做修改,那么单个文件的上传大小限制为 1MB ,大部分的文件上传都会报错,那么可以通过这个注解,拦截系统的报错,做一个兜底的反馈。代码如下:

@ControllerAdvice
public class CustomExceptionHandler {
    // 方法的参数可以接收异常实例,HttpServletRequest,HttpServletResponse,Model等。返回值可以是json,可以是ModelAndView
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public void uploadException(MaxUploadSizeExceededException e,
                                HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("上传文件大小超出限制");
        out.flush();
        out.close();
}

如果通过 ModelAndView 返回错误页面,代码如下:

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ModelAndView uploadException(MaxUploadSizeExceededException e,
                                        HttpServletResponse response) throws IOException {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "Error页面,上传文件大小超出限制");
        modelAndView.setViewName("error");
        return modelAndView;
    }

templates 模板目录下,创建 error.html 文件,部分代码如下:

<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <meta charset="UTF-8">
    <title>Title</title>
</head>
    // 使用Thymeleaf框架,th
<div th:text="${msg}"></div>
</body>
</html>


添加全局数据

添加全局的固定配置数据,在所有区域内都能够访问到,代码如下:

@ControllerAdvice
public class GlobalConfig {
    @ModelAttribute(value = "user")
    public Map<String,String> userInfo() {
        HashMap<String, String> map = new HashMap<>();
        map.put("username", "罗贯中");
        map.put("gender", "男");
        return map;
}

Controller 中可以拿到,在方法的参数上,传入Model类即可,代码如下:

    @GetMapping("/hello2")
    public void hello2(Model model) { // 参数添加Model来获取全局的配置数据
        Map<String, Object> map = model.asMap();
        Set<String> set = map.keySet();
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            Object value = map.get(key);
            System.out.println(key +">>>>>"+ value);
    }


自定义错误页面

Springboot内部的错误页面定位是在 resources 目录下寻找指定错误代码的页面,如果没有找到就会直接使用 error 的页面。如果要自定义错误页面,可以通过直接在 resources/static/error 目录下创建4XX.html和5XX.html,或者指定错误码为命名的页面。

还可以通过模板引擎展示详细的错误信息,只要在 resources/templates/error 目录下,创建4xx.html或者5xx.html等html模板即可。

这里有一个优先级的问题,需要特别注意,具体响应码命名的html优先级高于4xx.html和5xx.html等等。动态页面例如 resources/templates 的优先级高于静态页面优先级。

特别注意

早期版本可能不需要配置,就可以在错误页面通过 message 字段拿到错误的具体错误详情,新版本则需要在application.properties文件中配置如下才可以访问到。

server.error.include-message=always


自定义Error的返回数据

在上面的自定义错误页面的例子中,springboot总共会返五个字段给错误页面,这五个字段主要是通过 DefaultErrorAttributes 这个类的getErrorAttributes方法提供。在没有找到自定义的ErrorAttributes时,会使用这个默认的类。因此,可以通过继承这个类,覆写方法,就能对返回的信息做一些改动。例如:

@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);
        errorAttributes.put("custommsg", "出错啦!");
        errorAttributes.remove("message");
        return errorAttributes;
}

完全自定义Error页面和Error数据

Springboot默认额处理异常的Controller是 BasicErrorController ,如果需要完全的自定义异常的处理页面和数据的话,可以通过继承这个类,覆写两个方法 errorHtml error 即可。代码如下:

@Controller
public class MyErrorController extends BasicErrorController {
    public MyErrorController(ServerProperties errorAttributes) {
        super(new DefaultErrorAttributes(), errorAttributes.getError());
    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = getErrorAttributes(request,
                getErrorAttributeOptions(request, MediaType.ALL));
        model.put("custommsg", "出错了");
        // 有异常,直接定位error错误页面
        ModelAndView modelAndView = new ModelAndView("error", model, status);
        return modelAndView;
    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        // 根据项目,所有的异常都使用特殊的统一风格json结构返回数据
        Map<String, Object>




    
 model = getErrorAttributes(request,
                getErrorAttributeOptions(request, MediaType.ALL));
        model.put("custommsg", "出错了");
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(model, status);
}


跨域请求

主要配置的工作都在后端,前端项目不需要做什么配置。后端配置跨域请求的方式有两种:

  • 在指定的请求接口上通过注解 @CrossOrigin 来实现允许的跨域访问。
  • 通过全局的配置,添加跨域的访问过滤

细粒度跨域操作

代码如下:

    @GetMapping("/hello")
    @CrossOrigin(value = "http://localhost:8082", maxAge = 1800, allowedHeaders = "*")
    public String hello() {
        return "hello spring boot!";
    }

全局配置跨域

通过继承 WebMvcConfigurer 覆写 addCrosMappings 添加跨域的过滤,代码如下

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/book/**")
                .allowedHeaders("*")
                .allowedMethods("*")
                .maxAge(1800)
                .allowedOrigins("http://localhost:8082");
}

这里需要特别注意,在使用fastjson作为解析json数据的时候,需要一个特殊的配置代码,代码如下:

        converter.setSupportedMediaTypes(Arrays.asList(
                MediaType.APPLICATION_JSON,
                MediaType.APPLICATION_JSON_UTF8));


Springboot XML文件配置

springboot基本不使用xml文件进行配置,这里略过,可查阅书籍67页。


注册拦截器

创建拦截器的方法,实现 HandlerIntercepter 接口,并通过配置 WebMvcConfigurer 实现 addInterceptors 方法,进行注册。

public class MyFirstInterceptor implements HandlerInterceptor {
}

然后将这个类注册到全局配置,继承 WebMvcConfigurer 的类当中:

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyFirstInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/hello");
    }


系统启动初始化任务执行

CommandLineRunner

Springboot项目在启动时会遍历所有 CommandLineRunner 并执行run方法,如果有多个需要执行,则会按照给定设置的优先级顺序执行。

示例代码如下:

@Component
// 按数字从小到大,排序优先执行数字小的
@Order(1)
public class MyCommandLineRunner01 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        // TODO: 2022/4/1 执行自定义的应用启动时的初始化操作
        // 上面的args参数,是从应用启动的main函数执行的SpringApplication.run(App.class, args);中传递进来的
}


ApplicationRunner

ApplicationRunner 的用法和上述的 CommandLineRunner 差不多,但是在run方法的接收参数上有一定的区别。如果想要拿到应用启动时,命令行的启动参数的话,需要做一些调整

示例代码如下:

@Component
@Order
public class MyApplicationRunner01 implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 例如执行启动命令如下:
        // Java -jar runner-0.0.1.jar --name=darren --age=18 Chinese Fujian
        // 获取启动命令的执行参数,下面拿到的参数就是[Chinese, Fujian]
        List<String> nonOptionArgs = args.getNonOptionArgs();
        // 下面拿到的是带有参数的键值中的键名集合,拿到的数据将会是:[name, age]
        Set<String> optionNames = args.getOptionNames();
        for (String name :
                optionNames) {
            // 根据提供的键值的键名拿到值,这里拿到的值依次为:darren 和 18
            List<String> optionValues = args.getOptionValues(name);
            System.out.println("value:" + optionValues.get(0));
}


兼容旧代码使用Servlet,Filter,Linsener

使用Springboot的时候,基本不会再使用到Servlet,Filter,Listener。除非还得使用到早期的旧的第三方项目时,可能会涉及到这三个模块的使用。具体的使用示例代码如下,分别介绍:

Servlet集成

示例代码如下:

// 搭配注释,供Springboot进行组件扫描
@WebServlet("/my")
public class CustomServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("name >> "+ req.getParameter("name"));
}


Filter

示例代码如下:

@WebFilter("/*")
public class CustomFilter extends HttpFilter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        super.init(filterConfig);
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        super.doFilter(request, response, chain);
    @Override
    public void destroy() {
}


Listener

示例代码如下:

@WebListener
public class CustomListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
}

Listener的兼容这里用 ServletRequestListener 为例,对于其他的Listener,例如 HttpSessionListener ServletContextListener 等等也是一样的支持。

还需要一个关键的步骤,就是要让SpringBoot扫描到这些模块,需要在Application类中使用注解 @ServletComponentScan


上述的三个模块的在接收请求访问时执行的顺序为: Listener > Filter > Servlet 也就是说,Servlet的doGet,doPost将会是最后执行,然后当请求访问结束之后,在调用Listener的 requestDestroyed() 方法。


路径映射-快速定位静态页面

使用页面模板的时候,如上面提到的 Thymeleaf ,当遇到需要使用模板界面,但是又不需要通过控制器传递参数访问的时候,就可以通过这个功能快速的直接定位到指定的页面。

WebMvcConfigurer 中配置

示例代码如下:

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login")
                .setViewName("login"); // templates目录下要真实存在这个html文件
        registry.addViewController("/index")
                .setViewName("index");
    }


面向切面编程-应用解耦合

主要的作用就是在对旧项目进行改造的时候,不需要对旧项目直接添加代码,通过切面的方式,将代码注入到旧代码中,动态的添加想要的代码。例如:需要对旧项目的代码执行效率做一个检测,就可以通过此方法,拿到想要的数据,而不用对项目代码做任何的修改。

AOP的相关概念:

Joinpoint(连接点)

某些类中可以被功能加强的方法即为连接点

Pointcut(切入点)

对Joinpoint进行拦截。例如拦截所有以insert开头的方法。

Advice(通知)

拦截到Joinpoint之后,会通过通知的方式回调。通知分为前置通知,后置通知,异常通知,最终通知和环绕通知。

Aspect(切面)

Pointcut和Advice的结合

Target(目标对象)

需要被功能增强的类,也就是被动态加强动能的类


具体操作:

引入AOP依赖

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

创建需要被增强的类

在项目中新增service包,在包内创建类 UserService 作为切面加强类。代码如下

@Service
public class UserService {
    public String getUserById(Integer id) {
        System.out.println("获取用户信息");
        return "user";
    public void deleteUserById(Integer id) {
        System.out.println("删除用户");
        return;
}

创建切面类

在切面类中,可以对拦截到的执行,进行前期中间和结束时的返回或者抛出异常的拦截操作,或者也可以修改目标方法的参数,返回值以及抛出异常的处理等等。

示例代码如下:

@Component // 这个不要漏了,关键需要让SpringBoot能够扫描到
@Aspect
public class LogAspect {
    // 筛选所有的切入点
    // 第一个*表示指定任意的返回类型
    // 第二个*表示services包下的所有类
    // 第三个*表示所有类中的所有带参数不带参数的方法,用(..)表示通配所有参数的方法
    @Pointcut("execution(* com.chendeji.chapte01_01.services.*.*(..))")
    public void pc1() {
    @Before(value = "pc1()")
    public void before(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法开始执行");
    @After(value = "pc1()")
    public void after(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行结束");
    @AfterReturning(value = "pc1()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) { // result 如果为某个指定
        // 的类型,那么只有在返回指定类型的时候,才会执行这里面的方法
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法返回值为:" + result);
    @AfterThrowing(value = "pc1()", throwing = "e")
    public void afterReturning(JoinPoint joinPoint, Exception e) { // e 如果为某个指定
        // 的类型,那么只有在返回指定类型的时候,才会执行这里面的方法
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法抛出异常了,异常为:" + e.getMessage());
    // 环绕通知的概念比较特殊,环绕通知可以允许开发者在这个通知内,修改目标方法的执行参数,返回值等等,并且还能
    // 处理目标方法抛出的异常,通过调用proceed()方法使得目标方法能够继续执行
    @Around("pc1()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
}


其他功能

自定义欢迎页

Springboot在启动后,会自动去静态文件目录下寻找 index.html 文件,如果找不到会再去动态文件目录寻找。所以,如果应用首页是静态的,可以通过在 resources/static 目录下创建 index.html 文件,如果为动态页面,则需要在 resources/templates 目录下创建该文件供访问。然后在控制器中返回逻辑视图页面,或者配置 路径映射

静态首页

直接再静态资源目录创建 index.html 文件即可

动态首页

通过控制器,获取相关信息后,返回ModelAndView,附带上数据和页面映射

代码示例:

    @GetMapping("/")
    public ModelAndView index() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("index");
        modelAndView.addObject("user", "userInfo");
        return modelAndView;
    }


自定义网站icon

favicon.ico 是浏览器选项卡左上角的图标,直接在静态资源目录下存放这个文件,即可完成网站浏览器选项卡左上角的icon配置。

这个图片,可以通过在线网站 ico转换 将一张普通图片转换成.ico图片。


去除某个自动配置

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})

也可以在 application.properties 文件中声明

# 排除某个自动配置
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration


SpringBoot整合数据持久层


整合JdbcTemplate

JdbcTemplate 是Spring提供的一套JDBC的模板框架,利用的是AOP切面的方式实现使用JDBC避免大量的重复代码。

建表建库

首先,在数据库创建库和表以及插入初始数据,代码如下:

CREATE TABLE `book` (
    `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR ( 128 ) DEFAULT NULL




    
,
    `author` VARCHAR ( 64 ) DEFAULT NULL,
    PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;
INSERT INTO `book` ( `id`, `name`, `author` )
VALUES
    ( 1, '三国演义', '罗贯中' ),
    ( 2, '水浒传', '施耐庵' );

导入第三方库

以上的sql语句可以通过软件 Navicat 进行导入,具体操作参考博客。

将需要的库导入项目:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 数据库连接池可选其他,这里直接选择阿里巴巴的druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

配置数据库链接

接着在 application.properties 文件中配置数据库的链接信息

# 数据库配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///spring_chapter05
spring.datasource.username=root
spring.datasource.password=root

创建实体类

public class BookEntity {
    private Integer id;
    private String name;
    private String author;
    private Date publicationDate;
    // 省略getter和setter
}

创建数据访问层

@Repository
public class BookDaoJdbc {
    final JdbcTemplate template;
    public BookDaoJdbc(JdbcTemplate template) {
        this.template = template;
    public int addBook(BookEntity book) {
        return template.update("insert into book(name, author) value (?,?)", book.getName(), book.getAuthor());
    public int updateBook(BookEntity book) {
        return template.update("update book set name =?,author=? where id=?", book.getName(), book.getAuthor(), book.getId());
    public int deleteBookById(Integer id) {
        return template.update("delete from book where id=?", id);
    public BookEntity getBookById(Integer id) {
        return template.queryForObject("select * from book where id=?",
                new BeanPropertyRowMapper<>(BookEntity.class), id);
    public List<BookEntity> getAllBooks() {
        return template.query("select * from book",
                new BeanPropertyRowMapper<>(BookEntity.class));
}

创建服务层和控制层

  • Service层
@Service
public class BookService {
    final BookDaoJdbc bookDao;
    public BookService(BookDaoJdbc bookDao) {
        this.bookDao = bookDao;
    public int addBook(BookEntity book) {
        return bookDao.addBook(book);
    public int updateBook(BookEntity book) {
        return bookDao.updateBook(book);
    public int deleteBookById(Integer id) {
        return bookDao.deleteBookById(id);
    public BookEntity getBookById(Integer id) {
        return bookDao.getBookById(id);
    public List<BookEntity> getAllBooks() {
        return bookDao.getAllBooks();
}
  • Controller 控制层
@RestController()
@RequestMapping("/book")
public class BookController {
    final BookService bookService;
    public BookController(BookService bookService) {
        this.bookService = bookService;
    @GetMapping("/")
    public List<BookEntity> getBook() {
        List<BookEntity> allBooks = bookService.getAllBooks();
        return allBooks;
    @PutMapping("/add")
    public int addBook(@RequestBody BookEntity book) {
        return bookService.addBook(book);
    @DeleteMapping("/delete/{id}")
    public int deleteBookById(@PathVariable Integer id) {
        return bookService.deleteBookById(id);
}

到这里,完成JdbcTemplate数据访问框架的整体代码配置


整合Mybatis

Mybatis基本屏蔽了所有的JDBC代码书写数据库操作,因此拥有不错的效率和性能上的优势,目前大部分项目中相信使用的也都是Mybatis这款优秀的数据库操作框架。

接下来,进行Mybatis的整合,步骤如下:

建库建表

数据库的建表建库参考 整合JdbcTemplate ,数据库的链接配置也是一样

导入第三方库

        <dependency><!-- 这个是Mybatis的库集合 -->
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

添加Mapper接口

这个步骤主要是和Mybatis的数据库操作配置文件创建对应的映射关系,而创建的接口类。代码如下:

@Mapper
public interface BookMapper {
    int addBook(BookEntity entity);
    int deleteBookById(Integer id);
    int updateBook(BookEntity bookEntity);
    BookEntity getBookById(Integer id);
    List<BookEntity> getAllBooks();
}

这种声明Mapper的方法就是需要在每个Mapper接口上都添加 @Mapper 注解供Springboot扫描。

还有一种方式,可以避免每个Mapper接口上都添加这个注解,就是通过 @MapperScan("com.xxx.xxx.mapper") 这个注解,在配置类上添加这个注解,指明项目的mapper类目录即可扫描到所有的Mapper接口。


创建Mapper的xml文件,具体的sql操作语句

代码如下:

这里面有些特殊的知识点需要知道:

  • id属性的值需要和Mapper接口中的方法名一致
  • 参数类型 parameterType 需要和接口方法的参数类型一致
  • #{} 用来代替接口中参数,或者实体类中的属性可以直接通过 #{实体类属性名} 来获取真实的数据
<?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.chendeji.chapte01_01.mapper.BookMapper">
    <insert id="addBook" parameterType="com.chendeji.chapte01_01.bean.BookEntity" >
        INSERT INTO book(name, author) VALUES (#{name}, #{author})
    </insert>
    <delete id="deleteBookById" parameterType="int">
        DELETE FROM book WHERE id=#{id}
    </delete>
    <update id="updateBook" parameterType="com.chendeji.chapte01_01.bean.BookEntity">
        UPDATE book set name=#{name},author=#{author} WHERE id=#{id}
    </update>
    <select id="getBookById" parameterType="int">
        SELECT * FROM book WHERE id=#{id}
    </select>
    <select id="getAllBooks" resultType="com.chendeji.chapte01_01.bean.BookEntity">
        SELECT * FROM book
    </select>
</mapper>

这一步完成之后,接着创建 Service Controller 相对应的类,这个步骤可以直接参考 整合JdbcTemplate 步骤都一样。

但是需要特别注意,如果mapper映射文件被存放在java的目录中,而不是resource目录,那么会被过滤掉,需要在pom文件中进行配置,避免被过滤

    <build>
<!--     为了能够将bean文件目录下的相对应的mapper的xml文件也能够被项目编译,需要添加java文件目录下的xml文件扫描   -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <!-- 这个filtering 需要被开启,否者无效 -->
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build> 



整合SpringData JPA

概念

首先需要了解一下什么是 JPA JPA Spring Data 是两个范畴的概念。 JPA 是一种ORM规范。Spring Data 是 Spring的一个子项目,主要作用是简化数据库的操作,通过规范的方法名来分析具体的数据库操作来减少数据库的操作代码编写量。

同时,Spring Data不仅仅支持关系型数据库,也能够支持非关系型数据库。所以,Spring Data JPA 能够简化关系型数据库的操作代码。

建库,不需要建表

引入依赖库

这里引入的库代码不是全部,如果使用maven创建项目,需要做其他配置,引入web还有starter-parent等等

        <dependency><!-- 这里为引入的jpa三方库 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>


配置数据库链接

# 基础的数据库链接配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.password=root
spring.datasource.username=root
# JPA的配置部分
# show-sql表示是否控制台打印sql执行语句
spring.jpa.show-sql=true
spring.jpa.database=mysql
# ddl-auto 表示项目启动时根据实体类更新数据库中的表,其他可选项有:create, create-drop,validate, no
spring.jpa.hibernate.ddl-auto=update
# dialect 表示数据库方言是 MySQL57Dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect


创建实体类

JPA的实体类需要使用注释 @Entity(name = "表名") ,代码如下:

@Entity(name = "t_book")
public class Book {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "book_name", nullable = false)
    private String name;
    private String author;
    private Float price;
    @Transient // Transient 表示在生成数据库表时,被这个注释的类字段,在表中不会生成字段
    private String desc;
    // getter和settter省略
}


创建数据访问层

Jpa的数据访问规范还是比较灵活的,下面的代码中,方法的命名规范完全按照JPA的规范。使得框架能够根据命名来识别开发者的数据操作意图。

代码如下:

public interface BookDao extends JpaRepository<Book, Integer> {
    // 这句的语义,查找名字为给定参数开头的作者的所有书籍
    List<Book> getBooksByAuthorStartingWith(String author);
    // 这句的语义,查找价格高于给定参数的所有书籍
    List<Book> getBooksByPriceGreaterThan(Float price);
    // 这句的语义,查找t_book表中的id最大的那本书,应该代表的意思就是最新添加的书籍
    @Query(value = "select * from t_book where id=(select max(id) from t_book)", nativeQuery = true)
    Book getMaxIdBook();
    // 这句的语义,根据给定的作者,查找id大于给定参数的所有书籍,这里面使用的`:id`和`:author`需要搭配参数中的注解 @Param 使用
    @Query("select b from t_book b where b.id>:id and b.author=:author")
    List<Book> getBooksByIdAndAuthor(@Param("author") String author, @Param("id") Integer id);
    // 这句的语义,根据给定的作者模糊名称进行模糊查询,查找id小于给定参数的所有书籍,这里面使用的`?2`和`?1`代表的意思为,第二个参数和第一个参数,相对比上面的方法中使用到 @Param 方便
    @Query("select b from t_book b where b.id<?2 and b.name like %?1%")
    List<Book> getBooksByIdAndName(String name, Integer id);
}


创建Service层和Controller层

具体参考上面的 整合JdbcTemplate 中的代码编写模式。


需要特别注意的是,Spring Data JPA的命名规范有一个规则,这里是一份表格,如下:

Keywords 方法命名举例 对应的SQL语句

等等还有很多,详细的请查阅该书籍第92页


配置多数据源

jdbc多数据源

通过 druid 能够进行多数据库的链接和监控

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

配置依赖之后,可以通过配置文件,配置多个数据库的链接。多个数据库实例,通过前面的one,two

来进行区分。

# 数据源1
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.one.url=jdbc:mysql:///chapter05-03
# 数据源2
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.username=root
spring.datasource.two.password=root
spring.datasource.two.url=jdbc:mysql:///chapter05-04

接着通过 @Configuration 配置两个数据源

/**
 * dsOne和dsTwo是方法名,实例化,各自对应相应的实例,方便后续引用
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder




    
.create().build();
    @Bean
    @ConfigurationProperties("spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
}

配置多个数据模板实例

/**
 * springboot引入了一个jdbc的依赖 默认会提供一个jdbcTemplate的实例,
 * 这里是多数据源,所以得自己提供template实例
@Configuration
public class JdbcTemplateConfig {
    @Bean
    JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne")DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    @Bean
    JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo")DataSource dataSource) {
        return new JdbcTemplate(dataSource);
}

通过注解引入数据模板,完成dao层的代码开发

    /**
     * 由于需要用到两个数据源,所以通过注入的方式引入,这里有两种方式:
     * 1,直接通过@Resource的方式引入
     * 2,通过@Autowired和@Qualifier 搭配使用引入
    @Resource(name = "jdbcTemplateOne")
    JdbcTemplate jdbcTemplateOne;
    @Autowired
    @Qualifier("jdbcTemplateTwo")
    JdbcTemplate jdbcTemplateTwo;

Mybatis 多数据源

基于JDBC的数据源的基础上,引入的三方多数据源的库,由 jdbc-starter 变为 mybatis-starter 。代码如下

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-jdbc</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

配置Mybatis的多数据源,主要从配置 SplSessionFactory 开始

@Configuration
// 这里使用@MapperScan,扫描mapper映射文件,
// 不同数据源的mapper包路径需要不一样,不然不能指定mapper到底使用哪一个数据源,
// 同时执行mapper文件使用的SqlSessionFactory实例。
@MapperScan(value = "com.chendeji.demo.mapper1", sqlSessionFactoryRef = "sqlSessionFactoryOne" )
public class MyBatisConfigOne {
    // 引入数据源
    @Autowired
    @Qualifier("dsOne")
    DataSource dataSourceOne;
    @Bean
    SqlSessionFactory sqlSessionFactoryOne() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceOne);
        return factoryBean.getObject();
    @Bean
    SqlSessionTemplate sqlSessionTemplateOne() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactoryOne());
}

相同的步骤,配置其他数据源的代码。然后通过不同的mapper的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,不同的mapper包下的mapper对应的数据源不同 -->
<mapper namespace="com.chendeji.demo.mapper1.BookMapper">
    <select id="getAllBooks" resultType="com.chendeji.demo.BookEntity">
        SELECT * FROM book
    </select>
</mapper>


JPA 多数据源

JPA的配置思路,有一部分是和Mybatis差不多的。就是数据持久层的代码,需要被放在不同的包名下,用于区分使用的数据源的不相同。

首先是配置代码:

# JPA的配置部分
# show-sql表示是否控制台打印sql执行语句
# 和之前的JPA配置不同,这里都需要加上.properties前缀
spring.jpa.properties.show-sql=true
spring.jpa.properties.database=mysql
# ddl-auto 表示项目启动时根据实体类更新数据库中的表,其他可选项有:create, create-drop,validate, no
spring.jpa.properties.hibernate.ddl-auto=update
# dialect 表示数据库方言是 MySQL57Dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect

JPA的多数据源配置代码,使用的注解和上述的两种有差别

/**
 * dsOne和dsTwo是方法名,实例化,各自对应相应的实例,方便后续引用
@Configuration
public class DataSourceConfig {
    @Bean
    @Qualifier("dataSource1") // 重要
    @Primary
    @ConfigurationProperties("spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    @Bean
    @Qualifier("dataSource2") // 重要
    @ConfigurationProperties("spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
}

单独的JPA数据库多数据源的实例配置,配置mapper文件的目录,以及配置实体类的包路径。代码如下:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.chendeji.demo.jpa.dao1",
        entityManagerFactoryRef = "entityManagerFactoryPrimary", transactionManagerRef =
        "transactionManagerPrimary")
public class JpaConfigOne {
    // 这两个注解非常重要,不要使用@Resource
    @Autowired
    @Qualifier("dataSource1")
    DataSource dataSource;
    @Autowired
    JpaProperties jpaProperties;
    @Primary
    @Bean(name = "entityManagerFactoryPrimary")
// 存在多个数据源时,优先使用这个
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBeanOne(EntityManagerFactoryBuilder builder) {
        return builder.dataSource(dataSource)
                .properties(jpaProperties.getProperties())
                .packages("com.chendeji.demo.model")// 用于指定实体类的目录
                .persistenceUnit("pu1")// 持久化单元名,不容的数据源对应不同的名称,也只有在多数据源配置下才有作用
                .build();
    // 提供事务支持,专门解决JPA事务的管理
    @Bean(name = "transactionManagerPrimary")
    PlatformTransactionManager platformTransactionManagerOne(EntityManagerFactoryBuilder builder) {
        LocalContainerEntityManagerFactoryBean factoryBeanOne =
                entityManagerFactoryBeanOne(builder);
        return new JpaTransactionManager(factoryBeanOne.getObject());
}


SpringBoot 整合Nosql

Redis 整合

redis是使用C编写的Nosql数据库,采用 key value 键值的方式存储数据,value的类型不仅限于字符串,还可以支持列表,集合,有序集合,散列等等。可以当做缓存,也可以做数据持久化数据库使用,持久化的方案目前有两种: 快照持久化 AOF 持久化。另外,redis可以搭建集群或者主从复制结构,并在高并发下具有高可用性。


安装redis

redis的运行安装,最好还是能够在服务器的环境中运行,不推荐直接在windows的环境使用。还是先弄一个虚拟机,安装一个linux系统来模拟正式的线上运行环境。首先需要先安装vs box虚拟机,并在虚拟机里安装linux服务器。

在virtual box以及linux系统安装完成后,可以开始安装redis。操作步骤如下:

下载源码包:

wget http://download.redis.io/releases/redis-4.0.10.tar.gz

安装:

tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
// 如果在执行make MALLOC=libc的时候报错了,需要先安装gcc
sudo apt install gcc
make MALLOC=libc
make install

配置redis

在安装完成之后,需要对redis的配置进行修改:

daemonize yes
# bind 127.0.0.1 //将这个注释,这行原本表示允许连接redis的地址,默认情况下只允许本地,注释掉就允许所有的外网都能够访问。
requirepass 111111
# 设置了密码就关闭掉保护模式
protected-mode no 

启动redis

# 启动
redis-server redis.conf
redis-cli -a 111111

关闭redis

SHUTDOWN
exit


Springboot 项目整合redis

pom文件引入springboot的redis支持库

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!-- 这里可能有的项目使用的redis工具不是lettuce 可能是jedis -->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>




    

                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

配置application.properties

# 使用redis库的编号,redis有16个数据库
spring.redis.database=0
# redis实例的地址
spring.redis.host=192.168.1.70
spring.redis.port=6379
spring.redis.password=123@456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
# redis 最大阻塞等待时间 -1 代表没有限制
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
# 如果使用的是lettuce,只需要将jedis,改为lettuce即可

在java代码中操作redis:

@RestController
public class BookController {
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @GetMapping("/")
    public void index() {
        ValueOperations<String, String> ops1 =
                stringRedisTemplate.opsForValue();
        ops1.set("name", "三国演义");
        String name = ops1.get("name");
        System.out.println(name);
        ValueOperations ops2 = redisTemplate.opsForValue();
        Book book = new Book();
        book.setId(1);
        book.setAuthor("曹雪芹");
        book.setName("红楼梦");
        ops2.set("book", book);
        Book result = (Book) ops2.get("book");
        System.out.println(result);
}

StringRedisTemplate RedisTemplate 的子类,键值只能是字符串。而 RedisTemplate 是可以存储对象的,其中的操作都是一样。

Redis集群整合Springboot

redis的存储实现:(待完善)

集群配置

redis的集群管理工具redis-trib.rb依赖Ruby环境,RVM可以对Ruby的版本做一个管理,所以先安装RVM

由于书中的RVM的安装已经不再适用,给出的下载地址,已经无法进行下载,需要在原来书中的基础上,加入一些配置步骤,具体如下:

首先,修改hosts文件中的DNS的IP代理

sudo vim /etc/hosts
199.232.28.133 raw.githubusercontent.com

然后,按照书中的命令输入,下载RVM

gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L get.rvm.io | bash -s stable
// 下面这句是在安装成功之后,提示的命令操作,按照系统返回的具体命令复制执行即可
source /home/chendeji/.rvm/scripts/rvm

查询所有ruby的版本

rvm list known

安装

rvm install 2.5.1

gem 安装redis 依赖

gem install redis

环境配置成功之后,需要创建一个集群的目录用于存放集群的相关的配置信息,具体步骤如下:

mkdir redisCluster
cp -f redis-4.0.10.tar.gz ./redisCluster/
cd redisCluster
tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
make MALLOC=libc
make install

安装完成之后,在 redisCluster 目录中将 redis-4.0.10/src 目录中的 redis-trib.rb 文件复制过来

cp -f ./redis-4.0.10/src/redis-trib.rb ./

在集群目录中,创建多个节点端口的目录模拟多个节点的真实IP,然后将redis-4.0.10目录中的 redis.conf 节点配置文件拷贝到端口目录中,并进行相应的修改。以端口8001为例,配置文件的修改内容为:

port 8001
# bind 127.0.0.1
cluster-enabled yes
cluster-config-file node-8001.conf
protected no
daemonize yes
requirepass 123@456
masterauth 123@456

对应的端口目录的不同配置文件配置修改完毕后,通过 redis-server 将redis实例创建,命令如下:

redis-4.0.10 目录中执行

redis-server ../8001/redis.conf
后续其他节点类似
......

完成redis实例的启动之后,需要通过修改 redis-trib.rb 文件中的配置,添加密码,由于在端口目录中的配置文件中指定,redis实例登录都需要密码,所以在 redis-trib.rb 文件中,需要修改代码如下:

@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60, :password=>"123@456")


创建集群

执行如下代码:

redis-trib.rb create --replicas 1 192.168.1.70:8001 192.168.1.70:8002 ......

replicas 表示每个主节点的slave数量。这里需要 特别注意: 初始化主节点和子节点必须成对出现,意味着创建节点的数量必须是偶数。

执行完成上述命令后,通过如下命令可以登录到某一个redis实例上:

redis-cli -p 8001 -a 123@456 -c

-p 端口, -a 密码, -c 用集群的方式登录

添加主节点

./redis-trib-rb add-node 192.168.1.70:8007 192.168.1.70:8001

新的实例被添加之前,记得需要创建实例!!

在新的实例被添加之后,由于早期哈希槽已经被之前创建的实例分配完了,这个时候就需要重旧的节点上重新划分哈希槽给新加入的节点,否则新节点将无法存储数据。通过如下命令:

./redis-trib.rb reshard 192.168.1.70:8001

执行这个命令后,需要配置三个值,分别是新节点的哈希槽分配数量,新划分出来的哈希槽要分配给哪一个实例输入该实例的id,第三个配置是指定新划分出来的哈希槽从哪一个旧实例中划分,简单的可以直接使用 all ,让所有的旧节点平均划分哈希槽总共1000个单位给新的节点。

添加从节点

./redis-trib-rb add-node --slave -master-id a29866cf551639149f53931de8430e76e1e952ba 192.168.1.70:8008 192.168.1.70:8007

-master-id 是某个主节点的id

删除子节点

./redis-trib.rb del-node 192.168.1.70:8007 a29866cf551639149f53931de8430e76e1e952ba

第一个参数为集群中任意一个实例,后面的id表示要被删除的节点id


查看所有的节点信息,通过登录某个节点,然后输入:

cluster nodes


配置Springboot

pom文件中添加如下依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

配置 application.yml

# 集群配置
spring:
  redis:
    cluster:
      nodes: 192.168.1.70:8001,192.168.1.70:8002,192.168.1.70:8003,192.168.1.70:8004,192.168.1.70:8005,192.168.1.70:8006,192.168.1.70:8007,192.168.1.70:8008
      max-redirects: 6
server:
  port: 8080
redis:
  timeout: 10000 #客户端超时时间单位是毫秒 默认是2000
  maxIdle: 300 #最大空闲数
  maxTotal: 1000 #控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性
  maxWaitMillis: 1000 #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
  minEvictableIdleTimeMillis: 300000 #连接的最小空闲时间 默认1800000毫秒(30分钟)
  numTestsPerEvictionRun: 1024 #每次释放连接的最大数目,默认3
  timeBetweenEvictionRunsMillis: 30000 #逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
  testOnBorrow: true #是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
  testWhileIdle: true #在空闲时检查有效性, 默认false
  password: 123@456 #密码

properties的配置

spring.redis.cluster.nodes=192.168.1.70:8001,192.168.1.70:8002,192.168.1.70:8003,192.168.1.70:8004,192.168.1.70:8005,192.168.1.70:8006,192.168.1.70:8007,192.168.1.70:8008
spring.redis.cluster.max-redirects=6
server.port=8080
redis.timeout=10000
redis.maxIdle=8
redis.maxTotal=8
redis.maxWaitMillis=8
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.testOnBorrow=true
redis.testWhileIdle=true
redis.password=123@456

接着,通过注入的方式,代码配置redis集群

@Configuration
public class RedisConfig {
    // TODO: 2022/4/14 参考 https://blog.csdn.net/u014748504/article/details/108188167
    @Value("${spring.redis.cluster.nodes}")
    private String clusterNodes;
    @Value("${spring.redis.cluster.max-redirects}")
    private int maxRedirects;
    @Value("${redis.password}")
    private String password;
    @Value("${redis.timeout}")
    private int timeout;
    @Value("${redis.maxIdle}")
    private int maxIdle;
    @Value("${redis.maxTotal}")
    private int maxTotal;
    @Value("${redis.maxWaitMillis}")
    private int maxWaitMillis;
    @Value("${redis.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;
    @Value("${redis.numTestsPerEvictionRun}")
    private int numTestsPerEvictionRun;
    @Value("${redis.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;
    @Value("${redis.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${redis.testWhileIdle}")
    private boolean testWhileIdle;
     * Redis连接池的配置
     * @return JedisPoolConfig
    @Bean
    public JedisPoolConfig getJedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 最大空闲数
        jedisPoolConfig.setMaxIdle(maxIdle);
        // 连接池的最大数据库连接数
        jedisPoolConfig.setMaxTotal(maxTotal);
        // 最大建立连接等待时间
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
        jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
        jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
        // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);
        // 在空闲时检查有效性, 默认false
        jedisPoolConfig.setTestWhileIdle(testWhileIdle);
        return jedisPoolConfig;
     * Redis集群的配置
     * @return RedisClusterConfiguration
    @Bean
    public RedisClusterConfiguration redisClusterConfiguration() {
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
        // Set<RedisNode> clusterNodes
        String[] serverArray = clusterNodes.split(",");
        Set<RedisNode> nodes = new HashSet<RedisNode>();
        for (String ipPort : serverArray) {
            String[] ipAndPort = ipPort.split(":");
            nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
        redisClusterConfiguration.setClusterNodes(nodes);
        redisClusterConfiguration.setMaxRedirects(maxRedirects);
        redisClusterConfiguration.setPassword(RedisPassword.of(password));
        return redisClusterConfiguration;
     * redis连接工厂类
     * @return JedisConnectionFactory
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        // 集群模式
        JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration(), getJedisPoolConfig());
        return factory;
     * 实例化 RedisTemplate 对象
     * @return RedisTemplate<String, Object>
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // Template初始化
        initDomainRedisTemplate(redisTemplate);
        return redisTemplate;
     * 设置数据存入 redis 的序列化方式 使用默认的序列化会导致key乱码
    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        // 开启redis数据库事务的支持
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        // 如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to
        // String!
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        // jackson序列化对象设置,
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // value序列化方式采用jackson
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
}

接着就可以通过注入的方式,在controller中,使用redis了。代码如下:

@Autowired
private RedisTemplate<String, Object> template;
@RequestMapping("/test")
public String test() {
   template.opsForValue().set("demo1", "hello world! 你好,redis");