瑞吉外卖学习总结

一、静态资源映射

1、介绍:

静态资源,例如 JS、CSS 和 HTML 等需要配置静态资源的映射才能被访问到。()

2、实现方式

  • webjars方式

引入依赖

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

SpringBoot对于静态资源放置的位置,是有静态资源映射规则,在SpringBoot中,SpringMVC的web配置都在 org.springframework.boot.autoconfigure.web.servlet 包下的 WebMvcAutoConfiguration 这个配置里面,我们找到addResourceHandlers这个方法,该方法用于处理webjars方式的静态资源映射。

  @Override
  protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    super.addResourceHandlers(registry);
    if (!this.resourceProperties.isAddMappings()) {
      logger.debug("Default resource handling disabled");
      return;
    }
    ServletContext servletContext = getServletContext();
    addResourceHandler(registry, "/webjars/**","classpath:/META-INF/resources/webjars/");
    addResourceHandler(registry,this.mvcProperties.getStaticPathPattern(),registration -> {
    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (servletContext != null) {
          registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
        }
    });
  }
  • 继承WebMvcConfigurationSupport类
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 添加静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 表示通过 ip:port/profile/** 能访问服务器D://upload目录下的所有文件
        // addResourceHandler是指你想在url请求的路径
        // addResourceLocations是存放的真实路径
        registry.addResourceHandler("/profile/**").addResourceLocations("file:D://upload");
        // 表示通过 ip:port/backend/** 能访问根目录/backend目录下的所有文件
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        // 重写父类方法默认映射关系,如果不需要可以不写
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}
​
  • 在application.properties中配置
# 自定义静态资源访问路径,可以指定多个,之间用逗号隔开
spring.resources.static-locations=classpath:/file1/,classpath:/file2
​

二、过滤器

1、介绍

过滤器 Filter 由 Servlet 提供,基于函数回调实现链式对网络请求与响应的拦截与修改

Filter 的生命周期

init(): 初始化Filter 实例,Filter 的生命周期与 Servlet 是相同的,也就是当 Web 容器(tomcat)启动时,调用 init() 方法初始化实例,Filter只会初始化一次。需要设置初始化参数的时候,可以写到init()方法中。 doFilter(): 业务处理,拦截要执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现 destroy() : 销毁实例,关闭容器时调用 destroy() 销毁 Filter 的实例。

2、实现方式

  • @WebFilter注解

通过 @WebFilter 注解,将类声明为 Bean 过滤器类,在启动类添加注解 @ServletComponentScan ,让 Spring 可以扫描到。

/**
 * 检查用户是否完成登录
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    //在当前的作用域中创建了一个名为PATH_MATCHER的常量,并将其赋值为AntPathMatcher类的一个新实例。
    //是Spring框架中的一个工具类,用于匹配和操作Ant样式的路径模式。它可以用于实现路径匹配、路径变量提取等功能
    public static final AntPathMatcher PATH_MATCHER =new AntPathMatcher();
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request =(HttpServletRequest) servletRequest;
        HttpServletResponse response =(HttpServletResponse) servletResponse;
        //1、获取本次请求uri,将不需要的拦截的放行
        String requestURI = request.getRequestURI();
        log.info("拦截到请求:{}",requestURI);
        //定义不需要处理的路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "front/**"
        };
        //2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        //3、如果不需要处理,则直接放行
        if(check){
            log.info("本次请求{}不需要处理",requestURI);
//            放行
            filterChain.doFilter(request,response);
            return;
        }
        //4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee")!=null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
//            放行
            filterChain.doFilter(request,response);
            return;
        }
        log.info("用户未登录");
//        5、如果未登录则返回登录结果(分析前端代码)通过输出流方式向客户端响应数据
//        response.getWriter()返回的是一个字符输出流(PrintWriter),
//        通过调用其write()方法可以将字符串写入到HTTP响应的输出流中。
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }
​
    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */
    public boolean check(String[]urls,String requestURI){
        for (String url:urls){
            boolean match = PATH_MATCHER.match(url,requestURI);
            if (match){
                return true;
            }
        }
        return false;
    }
​
​
​
@SpringBootApplication
@ServletComponentScan
public class Application {
​
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
​
}
​

三、拦截器

1、介绍

 拦截器是在servlet执行之前执行的程序(这里就是controller代码执行之前),它主要是用于拦截用户请求并作相应的处理,比如说可以判断用户是否登录,做相关的日志记录,也可以做权限管理。

 SpringBoot中的拦截器实现和spring mvc 中是一样的,它的大致流程是,先自己定义一个拦截器类,并将这个类实现一个HandlerInterceptor类,或者是继承HandlerInterceptorAdapter,都可以实现拦截器的定义。然后将自己定义的拦截器注入到适配器中,也有两种方式,一种是实现WebMvcConfigurer接口,一种是继承WebMvcConfigurerAdapter

2.自定义拦截器

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("进入拦截器了");
        //中间写逻辑代码,比如判断是否登录成功,失败则返回false
        return true;
    }
​
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        //
        System.out.println("controller 执行完了");
    }
​
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("我获取到了一个返回的结果:"+response);
        System.out.println("请求结束了");
    }
}

代码说明:

  1. 自定义的拦截器可以实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。

  2. 重写三个方法,当然也可以只实现一个最重要的preHandle方法。

  3. preHandle方法:此方法会在进入controller之前执行,返回Boolean值决定是否执行后续操作。

  4. postHandle方法:此方法将在controller执行之后执行,但是视图还没有解析,可向ModelAndView中添加数据(前后端不分离的)。

  5. afterCompletion方法:该方法会在整个请求结束(请求结束,但是并未返回结果给客户端)之后执行, 可获取响应数据及异常信息。

3.拦截器注入适配器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")//拦截所有的路径
                .excludePathPatterns("/LoginController/login");
    }
}

四、异常处理

1、介绍

@RestControllerAdvice是什么 @RestControllerAdvice是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。

@RestControllerAdvice的特点:

通过@ControllerAdvice注解可以将对于控制器的全局配置放在同一个位置。 注解了@RestControllerAdvice的类的方法可以使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上。 @RestControllerAdvice注解将作用在所有注解了@RequestMapping的控制器的方法上。 @ExceptionHandler:用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。 @InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model中。 @ModelAttribute:本来作用是绑定键值对到Model中,当与@ControllerAdvice配合使用时,可以让全局的@RequestMapping都能获得在此处设置的键值对

2、实现方式

  • 直接处理

/**
 * 注解参数 annotations 指定了需要处理的控制器的类型。
 * annotations = {} 表示该全局异常处理类将会处理使用了
 * {}内注解的控制器类。
 */
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class GlobalExceptionHandler {
/**
 * 异常处理方法
 *
 */
//ExceptionHandler()当遇到()内里面的异常时执行下面方法
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
    log.info(ex.getMessage());
    return R.error("失败了‘");
}
}
​
  • 自定义异常

    //自定义业务异常
    public class CustomException extends RuntimeException{
        public CustomException(String message){
            super(message);
        }
    }
       /**
     * 注解参数 annotations 指定了需要处理的控制器的类型。
     * annotations = {} 表示该全局异常处理类将会处理使用了
     * {}内注解的控制器类。
     */
    @RestControllerAdvice(annotations = {RestController.class, Controller.class})
    @Slf4j
    public class GlobalExceptionHandler {
       @ExceptionHandler(CustomException.class)
        public R<String> exceptionHandler(CustomException ex){
            log.info(ex.getMessage());
            return R.error(ex.getMessage());
        }
        }

五、Mybatis-plus分页查询

1、介绍:

在实现分页查询时,MyBatis-Plus要求你添加一个分页拦截器(PaginationInterceptor),这是因为分页查询涉及到对SQL语句的修改和重写,以实现正确的分页效果。分页拦截器是MyBatis-Plus提供的一个组件,它会拦截执行的SQL语句,并根据指定的分页参数,修改SQL语句以获取指定范围的数据。

2、实现方式

  • 先配置分页拦截器

@Configuration
public class MybatisPlusConfig {
@Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
  • 再实现分页功能

    /**
     * 分页
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    public R<Page> page(int page,int pageSize,String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page, pageSize);
        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
        //添加过滤条件
        // StringUtils.isNotEmpty(name)判断那么是否为空,如果为空则不执行查询语句,如果不为空则执行查询语句
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //mybatis-plus自带的分页
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
​
    }

六、处理长整型精度丢失问题

  • 设置消息转换器

​
/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {
​
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
​
    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
​
        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
​
​
        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
​
                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
​
        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}
​

  • 将消息转化器设置在配置类中生效

WebMvcConfig

    /**
     * 扩展mvc框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//        创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//        设置对象转换器
        messageConverter.setObjectMapper(new JacksonObjectMapper());
//        将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0,messageConverter);
    }

七、公共字段填充

基于mybatis-plus

  • 在实体类中设置自动填充策略

    @TableField(fill = FieldFill.INSERT)//插入时自动填充
    private LocalDateTime createTime;
​
    @TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新时自动填充
    private LocalDateTime updateTime;
​
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
​
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
  • 基于线程获取id

/**
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
​
    /**
     * 获取并设置id
     * @param id
     */
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }
​
    /**
     * 获取id值
     * @return
     */
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}
  • 编辑元数据对象处理器

​
/**
 * 元数据对象处理器
 */
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {
    /**
     * 插入操作自动填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段填充[insert]...");
        log.info(metaObject.toString());
​
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser", BaseContext.getCurrentId());//通过线程获取的id
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
​
    }
​
    /**
     * 更新操作自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段更新[update]...");
        log.info(metaObject.toString());
        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());//通过线程获取的id
    }
}

八、文件的上传和下载

  • 在yml配置文件设置通用路径,方便日后修改。

reggie:
  path: E:\瑞吉外卖\image\
  • 上传文件实现

    @Value("${reggie.path}")
    private String basePath;
​
    //文件上传
    @PostMapping("/upload")
    //MultipartFile file就是文件上传参数,file必须和浏览器发来的name一致
    public R<String> upload(MultipartFile file) {
​
        //file 是一个临时文件,需要转存到指定位置,否则请求完成后临时文件会删除
        log.info("file:{}", file.toString());
​
        //原始文件名
        String originalFilename = file.getOriginalFilename();
        //截取后缀名
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        //使用uuid重新生成文件名,防止文件名重复
        String fileName = UUID.randomUUID().toString() + suffix;
​
        //创建一个目录对象
        File dir = new File(basePath);
        //判断当前目录是否存在
        if (!dir.exists()) {
            //目录不存在则创建
            dir.mkdirs();
        }
        try {
            //指定文件位置(主要就是这里)
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.success(fileName);
    }
  • 下载文件实现

    @GetMapping("/download")
    public void download(String name, HttpServletResponse response) {
​
        try {
            //输入流通过输入流读文件
            FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
            //输出流,通过输出流将文件写回浏览器,在浏览器中显示图片
            ServletOutputStream outputStream = response.getOutputStream();
            response.setContentType("image/jpeg");
​
            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = fileInputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
                outputStream.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

八、双表增删改

增删改方式类似,这里就写一个增加为例

  • 设置一个Dto用于联系双表

@Data
public class DishDto extends Dish {
​
    private List<DishFlavor> flavors = new ArrayList<>();
​
    private String categoryName;
​
    private Integer copies;
}
  • 在Service实现层处理

    @Autowired
    private DishFlavorService dishFlavorService;
​
    /**
     * 新增菜品,同时保存对应的口味数据
     *
     * @param dishDto
     */
    @Transactional
    //通过dishdto将数据分别保存到两个表中
    public void saveWithFlavor(DishDto dishDto) {
        //保存菜品的基本信息到菜品表dish
        this.save(dishDto);
        //此时我们还未获取到DishFlavors表中的dishId。那么我们可以通过dto中的列表来传入
        //获取到菜品id,这里是dish表中的id
        Long dishId = dishDto.getId();//菜品id
​
        //菜品口味,将dish表中的id传给flavors表
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishId);
            return item;
            //这里是以列表返回
        }).collect(Collectors.toList());
​
        //将id和用户输入的数据保存菜品口味数据到菜品口味表dish_flavor
        // saveBatch传列表
        dishFlavorService.saveBatch(flavors);
​
    }
  • 在controller层调用

@PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());
        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }
  • 注意多变查询要增加事务处理

运行类中添加@EnableTransactionManagement注解

实现类上添加@Transactional注解

九、实现双表分页查询

根据id查询方式与其类似就不写了

1、使用dto关联两表数据

@Data
public class DishDto extends Dish {
​
    private List<DishFlavor> flavors = new ArrayList<>();
​
    private String categoryName;
​
    private Integer copies;
}

2、在controller层中进行数据处理

    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name) {
​
        //构造分页构造器对象
        Page<Dish> pageInfo = new Page<>(page, pageSize);
        Page<DishDto> dishDtoPage = new Page<>();
​
        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤条件
        queryWrapper.like(name != null, Dish::getName, name);
        //添加排序条件
        queryWrapper.orderByDesc(Dish::getUpdateTime);
​
        //执行分页查询
        dishService.page(pageInfo, queryWrapper);
        /**
         * 经过分页后缺少一个菜品分类的字段
         * 这里是获取这个字段
         *最终分页自动获取records内容
         */
        //对象拷贝(注意这里只是拷贝的属性,并不是具体数据内容)
        //BeanUtils.copyProperties(源,拷贝到的对象,忽略的字段);records是内容的列表
        //records这里忽略的原因是dishDto中的内容属性和dish中的不同,不能直接拷过去
        BeanUtils.copyProperties(pageInfo, dishDtoPage, "records");
        //把分页中的内容传回给dish列表
        List<Dish> records = pageInfo.getRecords();
        //通过流的形式遍历刚获得的内容,处理完成后用DishDto列表接收
        List<DishDto> list = records.stream().map((item) -> {
            //创建DishDto对象
            DishDto dishDto = new DishDto();
            //将内容先拷贝过去给这个DishDto对象
            BeanUtils.copyProperties(item, dishDto);
            //获取分类id,两表具有的相同字段分类id,可通过分类id来查询数据
            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);
            //如果不为空通过查询的信息来获取菜品分类,将菜品分类名传给DishDto
            if (category != null) {
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            return dishDto;
            //将内容收集成列表
        }).collect(Collectors.toList());
​
        dishDtoPage.setRecords(list);
​
​
        return R.success(dishDtoPage);
    }

十、redis的使用

1、配置redis

  • 导入坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
​
  • 配置yml文件

  redis:
      host: localhost
      port: 6379
      password:
      database: 0

2、实现redis缓存

注入redis对象

@Autowired
private RedisTemplate redisTemplate;
​

设置缓存数据和时间等信息

redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);

十一、Spring Cache

1、介绍

说白了就是redis注解

Spring cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。

CacheManager是Spring提供的各种缓存技术抽象接口。

针对不同的缓存技术需要实现不同的CacheManager:

CacheManager描述
EhCacheCacheManager使用EhCache作为缓存技术
GuavaCacheManager使用Google的GuavaCache作为缓存技术
RedisCacheManager使用Redis作为缓存技术

2、使用方法

Spring Cache常用注解

image

3、实现方式

  • 导入maven坐标

spring-boot-starter-data-redis、spring-boot-starter-cache

  • 配置yml

spring:
    cache:
        redis:
            time-to-live: 1800000#设置缓存有效期
  • 在启动类上加入@EnableCaching注解,开启缓存注解功能

  • 在Controller的方法上加入@Cacheable、@CacheEvict等注解,进行缓存操作

    eg:

    @Cacheable(value = "setmealCache",key = "#setmeal.categoryId+'_'+#setmeal.status")
    @CacheEvict(value = "setmealCache",allEntries = true)

注意要让通用实体类R实现Serializable接口(序列化),注解才能生效

十二、配置主从库

1、介绍

MysSQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台AysQL数据库(slave,即从库)从另一台MysQL数据库(master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制是MysQL数据库自带功能,无需借助第三方工具。

2、配置方法

  • 声明:主库Master,从库slave。提前准备好两台服务器,分别安装Mysql并启动服务成功

实现过程:

master将改变记录到二进制日志( binary log)

slave将master的binary log拷贝到它的中继日志(relay log)

slave重做中继日志中的事件,将改变应用到自己的数据库中

  • 主库配置方法

第一步:修改Mysq1数据库的配置文件/etc/my.cnf

[mysqld]
log-bin=mysql-bin #[必须]启用二进制日志
server-id=100 #[必须]服务器唯一ID

第二步:重启Mysql服务 systemctl restart mysqld

第三步:登录Mysql数据库,执行下面SQL(建一个用户用作管理)

GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';

注:上面SQL的作用是创建一个用户xiaoming,密码为Root@123456,并且给xiaoming用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。

第四步:登录Mysql数据库,执行下面SQL,记录下结果中File和Position的值

show master status;

image

编辑

注:上面SQL的作用是查看Master的状态,执行完此SQL后不要再执行任何操作

  • 配置-从库Slave

克隆的话记得修改uuid

第一步:修改Mysq1数据库的配置文件/etc/my.cnf

[mysqld]
server-id=101 #[必须]服务器唯一ID

第二步:重启Mysql服务 systemctl restart mysqld

第三步:登录Mysq1数据库,执行下面SQL

change master to
master_host='主库ip',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000003',master_log_pos=441;
​
start slave;

第四步:登录Mysql数据库,执行下面SQL,查看从数据库的状态show slave status;

image

  • Sharding-JDBC介绍

Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

使用Sharding-JDBC可以在程序中轻松的实现数据库读写分离。

  • 适用于任何基于JDBC的ORM框架,如: JPA, Hibernate,Mybatis, Spring JDBC Template或直接使用JDBC。

  • 支持任何第三方的数据库连接池,如:DBCP,C3PO,BoneCP, Druid, HikariCP等。

  • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。

3、实现方式

  • 导入maven坐标

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.0.0-RC1</version>
</dependency>
​

  • 在配置文件中配置读写分离规则

spring:
  shardingsphere:
    datasource:
      names:
        master,slave
      # 主数据源
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://(主库ip地址):3306/rw?characterEncoding=utf-8
        username: root
        password: 123456
      # 从数据源
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://(从ip地址):3306/rw?characterEncoding=utf-8
        username: root
        password: 123456
    masterslave:
      # 读写分离配置
      load-balance-algorithm-type: round_robin #轮询
      # 最终的数据源名称
      name: dataSource
      # 主库数据源名称
      master-data-source-name: master
      # 从库数据源名称列表,多个逗号分隔
      slave-data-source-names: slave
    props:
      sql:
        show: true #开启SQL显示,默认false
​

  • 在配置文件中配置允许bean定义覆盖配置项

spring:
    main:
        allow-bean-definition-overriding: true
​

十三、有关nginx,Yapi,Swagger,部署等详细信息可以去优化day03查看

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值