Springboot笔记

文章目录

SpringBoot的pom配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>spring-boot</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	
		<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>
	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

SpringBoot的常用注解

@RestController //需要返回json数据的时候加注解

@RestController//相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面,返回到请求体中。

@RequestMapping(“list”)

@GetMapping(“list”)
@PostMapping(“list”)

@GetMapping = @RequestMapping(method = RequestMethod.GET) 
@PostMapping = @RequestMapping(method = RequestMethod.POST) 
@PutMapping = @RequestMapping(method = RequestMethod.PUT) 
@DeleteMapping = @RequestMapping(method = RequestMethod.DELETE)
@ComponentScan扫那些注解
{
	@Component		//泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
	@Repository		//mapper层,dao层,用于标注数据访问组件
	@Service		//Service层,用于标注业务层组件
	@Controller		//Controller层,用于标注控制层组件
}

注意:应⽤启动的位置,Application启动类位置

在使用@Autowired时,首先在容器中查询对应类型的bean
    如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据
    如果查询的结果不止一个,那么@Autowired会根据名称来查找。

这时在启动容器后,在容器中有两个UserRepository类型的实例,一个名称为userRepository,另一个为userJdbcImps。在UserService中
  
@Autowired
private UserRepository userRepository;

如果这里想要装载userJdbcImps的实例,除了将字段userRepository名称改成userJdbcImps外,可以提供了一个@Qualifier标记,来指定需要装配bean的名称,代码这样写
1 @Autowired
2 @Qualifier("userJdbcImps")
3 private UserRepository userRepository;

SpringBoot的启动的三种形式

当启动类和controller在同⼀类中时,在该类上添加注解@Controller即可;

当启动类和controller分开时,启动类要放在根⽬录下,启动类上只需要注解
@SpringBootApplication;

当启动类和controller分开时,如果启动类在⾮根⽬录下,需要在启动类中增加注解
@ComponentScan,并配置需要扫描的包名,如(basePackages = )
@ComponentScan(basePackages ={“net.xdclass.controller”,“net.xdclass.service”})


SpringBoot的@SpringBootApplication

@SpringBootApplication 作⽤: ⽤于标记是SringBoot应⽤,⾥⾯包含多个⼦注解,即
@SpringBootApplication =
@Configuration+@EnableAutoConfiguration+@ComponentScan
(下⾯的⽬前只需简单理解即可,想深⼊的同学,后续可以看专⻔的Spring原理课程深⼊)

@Configuration: 主要标注在某个类上,⽤于spring扫描注⼊,⼀般结合@Bean使⽤

@EnableAutoConfiguration: 启⽤Spring的⾃动加载配置,⾃动载⼊应⽤程序所需的所有Bean

@ComponentScan:告诉spring扫描包的范围,默认是Applocation类所在的全部⼦包,可以指定
其他包

@ComponentScan({“net.xdclass.package1”,“net.xdclass.package2”})


SpringBoot的静态资源访问

同个⽂件的加载顺序,静态资源⽂件 Spring Boot 默认会挨个从

META/resources >

resources >

static >

public

⾥⾯找是否存在相应的资源,如果有则直接返回,不在默认加载的⽬录,则找不到 默认配置

spring.resources.static-locations = classpath:/METAINF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

SpringBoot的接⼝返回⼯具类

/**
 * 接⼝返回⼯具类
 */
public class JsonData {
 private int code;
 private Object data;
 private String msg;
 public int getCode() {
 return code;
 }
 public void setCode(int code) {
 this.code = code;
 }
 public Object getData() {
 return data;
 }
 public void setData(Object data) {
 this.data = data;
 }
 public String getMsg() {
 return msg;
 }
 public void setMsg(String msg) {
 this.msg = msg;
 }
 public JsonData(){
 }
 public JsonData(int code, Object data){
 this.code = code;
 this.data = data;
 }
 public JsonData(int code, Object data, String msg){
 this.code = code;
 this.data =data;
 this.msg = msg;
 }
 public static JsonData buildSuccess(Object data){
 return new JsonData(0,data);
 }
 public static JsonData buildError(String msg){
 return new JsonData(-1,"",msg);
 }
 public static JsonData buildError(String msg,int code){
 return new JsonData(code,"",msg);
 }
}

SpringBoot的使用@RequestBody接收前端json

前端json

var val = {id: 1, name: "小明"};
$.ajax({
    url: "/getJson",
    dataType: "JSON",
    type: "post",
    contentType: 'application/json;charset=UTF-8',
    data: JSON.stringify(val),
    success: function (msg) {
        console.log(msg)
    }
})

后端

@PostMapping("/getJson")
@ResponseBody
public Map<String,Object> getJsonVal(@RequestBody Map<String,Object> user) {
    System.out.println("user = " + user.get("id"));
    System.out.println("user = " + user.get("name"));
    return user;
}
在这里插入代码片
@PostMapping("/getJson")
@ResponseBody
public User getJsonVal(@RequestBody User user) {
    return user;
}

SpringBoot的配置Jackson处理字段

JavaBean序列化为Json,
性能:Jackson > FastJson > Gson > Json-lib 同个结构
Jackson、FastJson、Gson类库各有优点,各有⾃⼰的专⻓
空间换时间,时间换空间

jackson处理相关⾃动
指定字段不返回:@JsonIgnore
指定⽇期格式:@JsonFormat(pattern = “yyyy-MM-dd hh:mm:ss”,locale = “zh”,timezone = “GMT+8”)
空字段不返回:@JsonInclude(Include.NON_NULL)
指定别名:@JsonProperty

开发功能:视频创建时间返回⾃定义格式;过滤⽤户敏感信息
序列化和反序列化操作

//序列化操作对象转换成json
 ObjectMapper objectMapper = new ObjectMapper();
 String jsonStr = objectMapper.writeValueAsString(list);
 System.out.println(jsonStr);
 //反序列化操作吧json转换成对象
 List<Video> temp = objectMapper.readValue(jsonStr,List.class);

SpringBoot的热部署

pom⽂件添加依赖包

 <dependency>
 	<groupId>org.springframework.boot</groupId>
 	<artifactId>spring-boot-devtools</artifactId>
 	<optional>true</optional>
 </dependency>

 <build>
	<plugins>
		<plugin>
 			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
 			<configuration>
 				<fork>true</fork><!--必须添加这个配置-->
 			</configuration>
 		</plugin>
 	</plugins>
 </build>
(1)File-Settings-Compiler-Build Project automatically
(2)ctrl + shift + alt + / ,选择Registry,勾上 Compiler autoMake allow when app running

SpringBoot的注解配置⽂件映射属性和实体类

⽅式⼀
1、Controller上⾯配置 @PropertySource({“classpath:resource.properties”})
2、增加属性 @Value("${test.name}") private String name;
⽅式⼆:实体类配置⽂件
1、添加 @Component 注解;
2、使⽤ @PropertySource 注解指定配置⽂件位置;
3、使⽤ @ConfigurationProperties 注解,设置相关属性;
4、必须 通过注⼊IOC对象Resource 进来 , 才能在类中使⽤获取的配置⽂件值。
@Autowired private ServerSettings serverSettings;

例⼦:
@Configuration
@ConfigurationProperties(prefix="test")
@PropertySource(value="classpath:resource.properties")
public class ServerConstant {
}

常⻅问题:

1、配置⽂件注⼊失败,Could not resolve placeholder
解决:根据springboot启动流程,会有⾃动扫描包没有扫描到相关注解,
默认Spring框架实现会从声明@ComponentScan所在的类的package进⾏扫描,来⾃动注⼊,
因此启动类最好放在根路径下⾯,或者指定扫描包范围
spring-boot扫描启动类对应的⽬录和⼦⽬录

2、注⼊bean的⽅式,属性名称和配置⽂件⾥⾯的key⼀⼀对应,就⽤加@Value 这个注解
如果不⼀样,就要加@value("${XXX}")

SpringBoot的单元测试

引⼊相关依赖

<!--springboot程序测试依赖,如果是⾃动创建项⽬默认添加-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
 	<groupId>junit</groupId>
 	<artifactId>junit</artifactId>
 	<version>4.12</version>
 	<scope>test</scope>
</dependency>

配置相关注解

@RunWith(SpringRunner.class) //底层⽤junit SpringJUnit4ClassRunner
@SpringBootTest(classes={XdclassApplication.class})//启动整个springboot⼯程
public class SpringBootTests { }
@before //资源的加载初始化
@Test	//逻辑
@After	//回收利用

TestCase.assertEquals(0,自定义参数);判断是否相等(不等报错)

TestCase.assertTrue(自定义参数.size()>0);判断是否为true(不是就报错)


SpringBoot的MockMvc调⽤api层接⼝

 @Autowired
 private MockMvc mockMvc;
 
 @Test
 public void testVideoListApi()throws Exception{
 
 MvcResult mvcResult =
 mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/pub/video/list"))
 .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
 
 int status = mvcResult.getResponse().getStatus();
 System.out.println(status);
 //会乱码
 //String result = mvcResult.getResponse().getContentAsString();
 
 // 使⽤下⾯这个,增加 编码 说明,就不会乱码打印
 String result =
 mvcResult.getResponse().getContentAsString(Charset.forName("utf-8"));
 
 System.out.println(result);
 }

SpringBoot的⾃定义全局异常返回⻚⾯

Springboot2.X怎么在项⽬中配置全局异常

类添加注解
@ControllerAdvice,如果需要返回json数据,则⽅法需要加@ResponseBody
@RestControllerAdvice, 默认返回json数据,⽅法不需要加@ResponseBody

⽅法添加处理器
捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)

返回⾃定义异常界⾯,需要引⼊thymeleaf依赖(⾮必须,如果是简单的html界⾯则不⽤)

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

resource⽬录下新建templates,并新建error.html

ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html");
modelAndView.addObject("msg", e.getMessage());
return modelAndView;
//@RestControllerAdvice//异常处理器的控制,标记这个是一个异常处理类                    //默认返回json数据
@ControllerAdvice
public class CustomExtHandler {

//    @ExceptionHandler(value = Exception.class)//处理那一类的异常				   //默认返回json数据
//    JsonData handlerException(Exception e, HttpServletRequest request){		  //默认返回json数据
//        return JsonData.buildError("服务端出问题了",-2);						   //默认返回json数据
//    };																	//默认返回json数据

    @ExceptionHandler(value = Exception.class)//处理那一类的异常
    Object handlerException(Exception e, HttpServletRequest request){
        ModelAndView modelAndView=new ModelAndView();
        //设置返回的页面
        modelAndView.setViewName("error.html");
        //设置信息
        modelAndView.addObject("msg",e.getMessage());
        return modelAndView;
    };
}


SpringBoot的过滤器

使⽤Servlet3.0的注解进⾏配置步骤

启动类⾥⾯增加 @ServletComponentScan,进⾏扫描
新建⼀个Filter类,implements Filter,并实现对应的接⼝
@WebFilter 标记⼀个类为filter,被spring进⾏扫描
urlPatterns:拦截规则,⽀持正则
控制chain.doFilter的⽅法的调⽤,来实现是否通过放⾏
不放⾏,web应⽤resp.sendRedirect("/index.html") 或者 返回json字符串

    private void renderJson(HttpServletResponse response, String json) {
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");//返回json
        try (PrintWriter writer = response.getWriter()) {
            writer.print(json);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

SpringBoot的使用Servlet的注解⾃定义原⽣Servlet

@WebServlet(name = "userServlet", urlPatterns = "/api/v1/test/customs")
class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.write("this is my custom servlet");
        writer.flush();
        writer.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

SpringBoot的RequestMapping和WebServlet的区别

@WebServlet注解用于标注在一个继承了HttpServlet类之上,属于类级别的注解

一般用于需要有HttpServletRequest请求中带了参数,或者需要filter等场景,这个是Servlet实现的路径映射逻辑

@RequestMapping 注解可以在控制器类的级别和/或其中的方法的级别上使用 处理普通的URI请求,或者http请求,这个是spring实现的DispatcherServlet的路径映射逻辑

后面你就会了解到注解的作用,其实没什么特别的,主要是便于程序的标识,能够在反射中使用这些注解,如果包含这些注解信息,就去做不同的处理


SpringBoot的监听器

监听器:应⽤启动监听器,会话监听器,请求监听器
作⽤

ServletContextListener 应⽤启动监听
HttpSessionLisener 会话监听
ServletRequestListener 请求监听

常⽤的监听器 ServletContextListener、HttpSessionListener、ServletRequestListener)

@WebListener
public class RequestListener implements ServletRequestListener {
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("======contextDestroyed========");
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("======contextInitialized========");
    }
}

SpringBoot的拦截器

新版本配置拦截器 implements WebMvcConfigurer

/**
 * 拦截器配置类
 * @author shkstrat
 * @create 2020-11-26 19:19
 */
@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册配置和拦截器拦截的路径
        registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/pri/**");
        //第二个拦截器
        registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/pri/**");

        WebMvcConfigurer.super.addInterceptors(registry);
    }

    @Bean
    public LoginIntercepter getLoginInterceptor(){
        return new LoginIntercepter();
    }
}

⾃定义拦截器 HandlerInterceptor
preHandle:调⽤Controller某个⽅法之前,用的多,返回false的话不执行下面的操作
postHandle:Controller之后调⽤,视图渲染之前,如果控制器Controller出现了异常,则不会执⾏此⽅法
afterCompletion:不管有没有异常,这个afterCompletion都会被调⽤,⽤于资源清理

public class LoginIntercepter implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("LoginIntercepter preHandle ========");
        return HandlerInterceptor.super.preHandle(request,response,handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("LoginIntercepter postHandle ========");
        HandlerInterceptor.super.postHandle(request,response,handler,modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("LoginIntercepter request ========");
        HandlerInterceptor.super.afterCompletion(request,response,handler,ex);
    }
}

拦截器不⽣效常⻅问题:

是否有加@Configuration
- 拦截路径是否有问题 ** 和 *
- 拦截器最后路径⼀定要 /** 如果是⽬录的话则是 /*/

场景:权限控制、⽤户登录状态控制等

和Filter过滤器的区别

Filter和Interceptor⼆者都是AOP编程思想的体现,功能基本都可以实现

拦截器功能更强⼤些,Filter能做的事情它都能做

Filter在只在Servlet前后起作⽤,⽽Interceptor够深⼊到⽅法前后、异常抛出前后等

filter依赖于Servlet容器即web应⽤中,⽽Interceptor不依赖于Servlet容器所以可以运⾏在多种环境。

在接⼝调⽤的⽣命周期⾥,Interceptor可以被多次调⽤,⽽Filter只能在容器初始化时调⽤⼀次。

Filter和Interceptor的执⾏顺序

过滤前->拦截前->action执⾏->拦截后->过滤后

补充知识点

如何配置不拦截某些路径?
registry.addInterceptor(new
LoginIntercepter()).addPathPatterns("/api/v1/pri/**")
//配置不拦截某些路径,⽐如静态资源
.excludePathPatterns("/**/*.html","/**/*.js");

SpringBoot的Freemarker

前端使用el表达式获取

相关maven依赖

        <!-- 引⼊freemarker模板引擎的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

Freemarker基本配置

# 是否开启thymeleaf缓存,本地为false,⽣产建议为true
 spring.freemarker.cache=false
 spring.freemarker.charset=UTF-8
 spring.freemarker.allow-request-override=false
 spring.freemarker.check-template-location=true

 #类型
 spring.freemarker.content-type=text/html
 spring.freemarker.expose-request-attributes=true
 spring.freemarker.expose-session-attributes=true

 #⽂件后缀
 spring.freemarker.suffix=.ftl
 #路径
 spring.freemarker.template-loader-path=classpath:/templates/

建⽴⽂件夹

1)src/main/resources/templates/fm/user/
2)建⽴⼀个index.ftl
3)user⽂件夹下⾯建⽴⼀个user.html

SpringBoot的ModelMap

ModelMap对象主要用于传递控制方法处理数据到结果页面

//通过以下方法向页面传递参数: 
addAttribute(String key,Object value); //modelMap的方法

ModelMap modelMap=new ModelMap();
modelMap.addAttribute("abc",abcdefg);

SpringBoot的ModelAndView

ModelAndView modelAndView=new ModelAndView();

ModelAndView对象有两个作用:

作用一: 设置转向地址,如下所示(这也是ModelAndView和ModelMap的主要区别)

//ModelAndView view = new ModelAndView("path:ok"); (举例一,我用下面那种)
//设置返回的页面
modelAndView.setViewName("error.html");

作用二 :用于传递控制方法处理结果数据到结果页面,也就是说我们把需要在结果页面上需要的数据放到ModelAndView对象中即可,他的作用类似于request对象的setAttribute方法的作用,用来在一个请求过程中传递处理的数据。通过以下方法向页面传递参数:

//addObject(String key,Object value); 
//设置信息
modelAndView.addObject("msg",e.getMessage());

SpringBoot的Thymeleaf

前端页面接收

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<p th:text="${setting.paySecret}"></p>

相关maven依赖

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

thymeleaf基础配置

 #开发时关闭缓存,不然没法看到实时⻚⾯
 spring.thymeleaf.cache=false
 spring.thymeleaf.mode=HTML5
 #前缀
 spring.thymeleaf.prefix=classpath:/templates/
 #编码
 spring.thymeleaf.encoding=UTF-8
 #类型
 spring.thymeleaf.content-type=text/html
 #名称的后缀
 spring.thymeleaf.suffix=.html

建⽴⽂件夹

1)src/main/resources/templates/tl/
2)建⽴⼀个index.html

注意:$表达式只能写在th标签内部
快速⼊⻔:https://www.thymeleaf.org/doc/articles/standarddialect5minutes.html


SpringBoot的整合定时任务和异步任务

SpringBoot使⽤注解⽅式开启定时任务
启动类⾥⾯ @EnableScheduling开启定时任务,⾃动扫描
定时任务业务类 加注解 @Component被容器扫描
定时执⾏的⽅法加上注解 @Scheduled(fixedRate=2000) 定期执⾏⼀次

cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒
crontab ⼯具 https://tool.lu/crontab/
fixedRate: 定时多久执⾏⼀次(上⼀次开始执⾏时间点后xx秒再次执⾏;)
fixedDelay: 上⼀次执⾏结束时间点后xx秒再次执⾏

@Component
public class VideoOrderTask {
    //每2秒执行一次
    @Scheduled(fixedRate = 2000)
    public void sum() {
        //正常的从数据库中查询
        System.out.println(LocalDateTime.now() + "当前交易额=" + Math.random());
    }
}

启动类⾥⾯使⽤@EnableAsync注解开启功能,⾃动扫描
定义异步任务类并使⽤@Component标记组件被容器扫描,异步⽅法加上@Async

@Component
@Async//标记为异步
public class AsyncTask {
    public void task1(){
        System.out.println("task2");
    }
}

要把异步任务封装到类⾥⾯,不能直接写到Controller
增加Future 返回结果 AsyncResult(“task执⾏完成”);
如果需要拿到结果 需要判断全部的 task.isDone()

public Future<String> task3() {
        try {
            Thread.sleep(4000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("task3");
        return new AsyncResult<String>("task3");
}
-----------------------------------------------------------------------------------------------------
        Future<String> task3 = asyncTask.task3();
        Future<String> task4 = asyncTask.task4();
        for (; ; ) {
            if (task4.isDone() && task3.isDone()) {
                try {
                    String task3Result = task3.get();
                    System.out.println(task3Result);
                    String task4Result = task4.get();
                    System.out.println(task4Result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } finally {
                    break;
                }
            }
        }

Mybatis回顾基础使⽤原⽣jdbc访问数据库

原⽣jdbc访问数据库步骤
加载JDBC驱动程序
创建数据库的连接
创建preparedStatement
执⾏SQL语句
处理结果集
关闭JDBC对象资源
Springboot项⽬测试原⽣JDBC连接
加载依赖包

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

Main函数访问代码实操

public static void main(String[] args) throws Exception {
        /*
        加载JDBC驱动程序
        创建数据库的连接
        创建preparedStatement
        执⾏SQL语句
        处理结果集
        关闭JDBC对象资源
        */
        Class.forName("com.mysql.cj.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/xdclass?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT";
        String username = "root";
        String password = "123";
        //获取连接对象,并连接数据库
        Connection connection =DriverManager.getConnection(url, username, password);
        //获取语句对象
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from video");
        while (resultSet.next()) {
            System.out.println("视频标题:"+resultSet.getString("title"));
        }
        statement.close();
    }

Mybatis

新建⼀个测试的maven项⽬
依赖地址: https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.4
核⼼流程: https://mybatis.org/mybatis-3/zh/getting-started.html
每个基于 MyBatis 的应⽤都是以⼀个 SqlSessionFactory 的实例为核⼼
SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得
SqlSessionFactoryBuilder 可以从 XML 配置⽂件或⼀个预先配置的 Configuration 实例来构
建出 SqlSessionFactory 实例
⼯⼚设计模式⾥⾯ 需要获取SqlSession ,⾥⾯提供了在数据库执⾏ SQL 命令所需的所有⽅

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!-- 使⽤JDBC链接mysql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <!-- 日志依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.6</version>
        </dependency>
    </dependencies>

配置mybatis-config.xml

resources下config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <!--标识-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/xdclass
?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/VideoMapper.xml"/>
    </mappers>
</configuration>

配置VideoMapper.xml(配置sql文件)

<?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">
<!--
namespace:名称空间,一般需要保持全局唯一,最好是和dao层java接口一致,
可以映射sql语句到对应的方法名称和参数、返回类型
mybatis是使用接口动态代理
-->
<mapper namespace="net.xdclass.online_class.dao.VideoMapper">
    <!--
    statement sql
    id:当前mapper下唯一
    resultType:sql查询结果集的封装
    id为方法名,resultType返回类型 -->
    <select id="selectById" resultType="net.xdclass.online_class.domain.Video">
        select * from video where id = #{video_id}
    </select>
</mapper>

获取参数中的值
注意 :取java对象的某个值,属性名⼤⼩写要⼀致

#{value} : 推荐使⽤, 是java的名称
${value} : 不推荐使⽤,存在sql注⼊⻛险

编写代码 获取 SqlSession,以xml⽅式读取数据库

public class SqlSessionDemo {
    public static void main(String[] args) throws IOException {
        String resource="config/mybatis-config.xml";
        //读取配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //构建工厂,工厂设计模式,构建session工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取Session
        try (SqlSession sqlSession=sqlSessionFactory.openSession()){
            //通过反射拿到这个类
            VideoMapper videoMapper = sqlSession.getMapper(VideoMapper.class);
            Video video = videoMapper.selectById(44);
            System.out.println(video.toString());
            List<Video> videoList = videoMapper.selectList();
            System.out.println(videoList.toString());
        }
    }
}

通过注解读取(如果sql简单,没有过多的表关联,则⽤注解相对简单)

/**
 * 查询全部视频列表
 * @return
 */
 @Select("select * from video")
 List<Video> selectList();

Mybatis使⽤流程

创建mybatis-config.xml 全局的配置⽂件
创建XXXMapper.xml配置⽂件
创建SqlSessionFactory
⽤SqlSessionFactory创建SqlSession对象
⽤SqlSession执⾏增删改查CRUD

内置的⽇志⼯⼚提供⽇志功能, 使⽤log4j配置打印sql,添加依赖

        <!-- 日志依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.6</version>
        </dependency>

在应⽤的classpath中创建名称为 log4j.properties 的⽂件

log4j.rootLogger=ERROR, stdout

log4j.logger.net.xdclass=DEBUG
#trace内容会打印更多
#log4j.logger.net.xdclass=TRACE

#细化到打印某个mapper
#log4j.logger.net.xdclass.online_class.dao.VideoMapper.selectById=TRACE

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

Mybatis查询视频列表和参数别名的使⽤

默认参数查询
单个参数,可以使⽤别名,也可以使⽤默认的名称,默认名称的话可以随意
使⽤参数别名
模糊查询
mysql⾃带函数使⽤

    <select id="selectByPointAndTitleLike" resultType="net.xdclass.online_class.domain.Video">
        select * from video where point=#{point} and title like concat('%', #{title},'%')
    </select>
    
    //对应的方法
    List<Video> selectByPointAndTitleLike(@Param("point") double point, @Param("title") String title);

讲解Mybatis配置驼峰字段到java类

数据库字段是下划线,java属性是驼峰,怎么查询映射上去?
⽅法⼀: select cover_img as coverImg from video // 多字段怎么办

Mybatis⾃带配置( 加在⽂件顶部)

    <!--下划线⾃动映射驼峰字段-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

取参数值,具体某个字段的类型,从java类型映射到数据库类型

例⼦ #{title, jdbcType=VARCHAR}
注意:
多数情况不加是正常使⽤,但是如果出现报错:⽆效的列类型,则是缺少jdbcType;
只有当字段可为NULL时才需要jdbcType属性
常⻅的数据库类型和java列席对⽐

JDBC Type 	Java Type
CHAR 		String
VARCHAR 	String
LONGVARCHAR String
NUMERIC 	java.math.BigDecimal
DECIMAL 	java.math.BigDecimal
BIT 		boolean
BOOLEAN 	boolean
TINYINT 	byte
SMALLINT 	short
INTEGER 	INTEGER
INTEGER 	int
BIGINT 		long
REAL 		float
FLOAT 		double
DOUBLE 		double
BINARY 		byte[]
VARBINARY 	byte[]
LONGVARBINARY byte[]
DATE 		java.sql.Date
TIME 		java.sql.Time
TIMESTAMP 	java.sql.Timestamp
CLOB 		Clob
BLOB 		Blob
ARRAY 		Array
DISTINCT 	mapping of underlying type
STRUCT 		Struct
REF 		Ref
DATALINK 	java.net.URL

Mybatis插⼊语法的使⽤和如何获得⾃增主键

<!--
    statement sql
    id:当前mapper下唯一
    resultType:sql查询结果集的封装
    id为方法名,resultType返回类型
    parameterType:入参类型
    -->

新增⼀条视频记录

    <insert id="add" parameterType="net.xdclass.online_class.domain.Video">
        INSERT INTO `video` ( `title`, `summary`, `cover_img`, `price`,`create_time`, `point`)
        VALUES
        (#{title,jdbcType=VARCHAR},#{summary,jdbcType=VARCHAR},#{coverImg,jdbcType=VARCHAR},#{price,jdbcType=INTEGER},#{createTime,jdbcType=TIMESTAMP},#{point,jdbcType=DOUBLE});
    </insert>

如何获得插⼊的⾃增主键
①如果希望在xml映射器中执行添加记录之后返回主键ID,则必须在xml映射器中明确设置useGeneratedKeys参数值为true。

<!-- keyProperty对应的是javabean,keyColumn对应的是数据库中,自增属性的数据库id赋给javabean里的id -->

<insert id="add" parameterType="net.xdclass.online_class.domain.Video"
useGeneratedKeys="true" keyProperty="id" keyColumn="id" >

Mybatis实战foreach批量插⼊语法之视频批量插⼊

foreach: ⽤于循环拼接的内置标签,常⽤于 批量新增、in查询等常⻅

包含以下属性:
 collection:必填,值为要迭代循环的集合类型,情况有多种(不是属性名而是类型)
 ⼊参是List类型的时候,collection属性值为list
 ⼊参是Map类型的时候,collection 属性值为map的key值

 item:每⼀个元素进⾏迭代时的别名
 index:索引的属性名,在集合数组情况下值为当前索引值,当迭代对象是map时,这个值是map的key
 open:整个循环内容的开头字符串
 close:整个循环内容的结尾字符串
 separator: 每次循环的分隔符

例子

    <!--批量插⼊-->
    <insert id="addBatch"
            parameterType="net.xdclass.online_class.domain.Video"
            useGeneratedKeys="true" keyProperty="id" keyColumn="id">
        INSERT INTO `video` ( `title`, `summary`, `cover_img`, `price`,`create_time`, `point`)
        VALUES
        <foreach collection="list" item="video" separator=",">
            (#{video.title,jdbcType=VARCHAR},
            #{video.summary,jdbcType=VARCHAR},
            #{video.coverImg,jdbcType=VARCHAR},
            #{video.price,jdbcType=INTEGER},
            #{video.createTime,jdbcType=TIMESTAMP},
            #{video.point,jdbcType=DOUBLE})
        </foreach>
    </insert>

Mybatis更新和删除

update 语法更新视频对象

    <update id="updateVideo" parameterType="net.xdclass.online_class.domain.Video">
        UPDATE video
        set
        title = #{title,jdbcType=VARCHAR},
        summary = #{summary,jdbcType=VARCHAR},
        cover_img = #{coverImg,jdbcType=VARCHAR},
        price = #{price,jdbcType=INTEGER},
        c_id = #{cId,jdbcType=INTEGER},
        point = #{point,jdbcType=INTEGER},
        learn_base = #{learnBase,jdbcType=VARCHAR},
        learn_result = #{learnResult,jdbcType=VARCHAR},
        total_episode = #{totalEpisode,jdbcType=INTEGER},
        update_time = now()
        WHERE
        id = #{id}
    </update>

Mybatis选择性更新标签使⽤

if test标签介绍
if 标签可以通过判断传⼊的值来确定查询条件,test 指定⼀个OGNL表达式
常⻅写法

//当前字段符合条件才更新这个字段的值
<if test='title != null and id == 87 '> title = #{title}, </if>
<if test="title!=null"> title = #{title}, </if>

代码(⾥⾯包含⼀个惨痛教训,⼀定要看pojo类⾥⾯的是基本数据类型,还是包装数据类型)

    <update id="updateVideoSelective" parameterType="net.xdclass.online_class.domain.Video">
        update video
        <!-- prefix前缀,suffixOverrides去除后缀 -->
        <trim prefix="set" suffixOverrides=",">
            <if test="title != null ">
                title = #{title,jdbcType=VARCHAR},
            </if>
            <if test="summary != null ">
                summary = #{summary,jdbcType=VARCHAR},
            </if>
            <if test="coverImg != null ">
                cover_img = #{coverImg,jdbcType=VARCHAR},
            </if>
            <if test="price != 0 ">
                price = #{price,jdbcType=INTEGER},
            </if>
            <if test="createTime !=null ">create_time = #
                {createTime,jdbcType=TIMESTAMP},
            </if>
            <!-- 特别注意: ⼀定要看pojo类⾥⾯的是基本数据类型,还是包装数据类型-->
            <if test="point != null ">
                point = #{point,jdbcType=DOUBLE},
            </if>
        </trim>
        where
        id = #{id}
    </update>

Mybatis删除语法和转义字符使⽤

delete删除语法(类型为map)

需求:删除某个时间段之后 且⾦额⼤于 10元的数据

//map里的方法
int deleteByCreateTimeAndPrice(Map<String,Object> map);
<!--    <delete id="deleteByCreateTimeAndPrice" parameterType="java.util.Map">-->
    <delete id="deleteByCreateTimeAndPrice" parameterType="Map">
        delete from video where create_time <![CDATA[ > ]]> #{createTime} and price <![CDATA[ >= ]]> #{price}
    </delete>

为什么要转义字符:
由于MyBatis的sql写在XML⾥⾯, 有些sql的语法符号和xml⾥⾯的冲突

⼤于等于 <![CDATA[ >= ]]>
⼩于等于 <![CDATA[ <= ]]>

Mybatis配置⽂件mybatis-config.xml常⻅属性

configuration(配置)
 properties(属性)
 settings(设置)
 typeAliases(类型别名)
 typeHandlers(类型处理器)
 objectFactory(对象⼯⼚)
 plugins(插件,少⽤)
 environments(环境配置,不配多环境,基本在Spring⾥⾯配置)
 environment(环境变量)
 transactionManager(事务管理器)
 dataSource(数据源)
 databaseIdProvider(数据库⼚商标识)
 mappers(映射器)

Mybatis的查询类别名typeAlias的使⽤

typeAlias
类型别名,给类取个别名,可以不⽤输⼊类的全限定名

mybatis-config.xml中

    <typeAliases>
        <typeAlias type="net.xdclass.online_class.domain.Video" alias="Video"></typeAlias>
    </typeAliases>

VideoMapper.xml中可以将原来的全类名改成自己设置的别名

<!--    <select id="selectById" parameterType="java.lang.Integer" resultType="net.xdclass.online_class.domain.Video">-->
    <select id="selectById" parameterType="java.lang.Integer" resultType="Video">
        select * from video where id = #{videoId,jdbcType=INTEGER}
    </select>

如果有很多类,是否需要⼀个个配置? 不⽤⼀个个配置,使⽤包扫描即可

    <typeAliases>
        <!--        <typeAlias type="net.xdclass.online_class.domain.Video" alias="Video"></typeAlias>-->
        <!--        扫描这个包的全部类以类名为别名-->
        <package name="net.xdclass.online_class.domain"/>
    </typeAliases>

本身就内置很多别名,⽐如Integer、String、List、Map 等


Mybatis的sql⽚段的使⽤

根据业务需要,⾃定制要查询的字段,并可以复⽤

    <sql id="base_video_field">
        id,title,summary,cover_img
    </sql>

    <select id="selectById" parameterType="java.lang.Integer" resultType="Video">
        select
        <include refid="base_video_field"/>
        from video where
        id = #{video_id,jdbcType=INTEGER}
    </select>
    
    <select id="selectListByXML" resultType="Video">
        select
        <include refid="base_video_field"/>
        from video
    </select>

MyBatis复杂Sql查询

Mybatis的SQL语句返回结果有两种
resultType
查询出的字段在相应的pojo中必须有和它相同的字段对应,或者基本数据类型
适合简单查询
resultMap
需要⾃定义字段,或者多表查询,⼀对多等关系,⽐resultType更强⼤
适合复杂查询

    <resultMap id="VideoResultMap" type="Video">
        <!--
            id 指定查询列的唯⼀标示
            column 数据库字段的名称
            property pojo类的名称
        -->
        <id column="id" property="id" jdbcType="INTEGER"></id>
        <result column="video_tile" property="title" jdbcType="VARCHAR" />
        <result column="summary" property="summary" jdbcType="VARCHAR" />
        <result column="cover_img" property="coverImg" jdbcType="VARCHAR" />
    </resultMap>

    <select id="selectBaseFieldByIdWithResultMap" resultMap="VideoResultMap">
        select id , title as video_tile, summary, cover_img from video where id = #{video_id}
    </select>

Mybatis复杂对象映射配置ResultMap的association(一对一)

association: 映射到POJO的某个复杂类型属性,⽐如订单order对象⾥⾯包含 user对象

<?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="net.xdclass.online_class.dao.VideoOrderMapper">
    <resultMap id="VideoOrderResultMap" type="VideoOrder">
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="out_trade_no" property="outTradeNo"/>
        <result column="create_time" property="createTime"/>
        <result column="state" property="state"/>
        <result column="total_fee" property="totalFee"/>
        <result column="video_id" property="videoId"/>
        <result column="video_title" property="videoTitle"/>
        <result column="video_img" property="videoImg"/>
        <!--
            association 配置属性⼀对⼀
            property 对应videoOrder⾥⾯的user属性名
            javaType 这个属性的类型
        -->
        <association property="user" javaType="User">
            <id property="id" column="user_id"/>
            <result property="name" column="name"/>
            <result property="headImg" column="head_img"/>
            <result property="createTime" column="create_time"/>
            <result property="phone" column="phone"/>
        </association>
    </resultMap>
    <!--⼀对⼀管理查询订单, 订单内部包含⽤户属性-->
    <select id="queryVideoOrderList" resultMap="VideoOrderResultMap">
        select
        o.id id,
        o.user_id ,
        o.out_trade_no,
        o.create_time,
        o.state,
        o.total_fee,
        o.video_id,
        o.video_title,
        o.video_img,
        u.name,
        u.head_img,
        u.create_time,
        u.phone
        from video_order o left join user u on o.user_id = u.id
    </select>
</mapper>


Mybatis的ResultMap复杂对象⼀对多查询结果映射之collection(一对多)

property 填写pojo类中集合类属性的名称
ofType 集合⾥⾯的pojo对象

    <resultMap id="UserOrderResultMap" type="User">
    <!--        <id property="id" column="id"/> 可以让没有订单的用户有id值-->
        <id property="id" column="user_id"/>
        <result property="name" column="name"/>
        <result property="headImg" column="head_img"/>
        <result property="createTime" column="create_time"/>
        <result property="phone" column="phone"/>
        <!--
        property 填写pojo类中集合类属性的名称
        ofType 集合⾥⾯的pojo对象
        -->
        <collection property="videoOrders" ofType="VideoOrder">
            <!--配置主键,管理order的唯⼀标识-->
            <id column="order_id" property="id"/>
            <result column="user_id" property="userId"/>
            <result column="out_trade_no" property="outTradeNo"/>
            <result column="create_time" property="createTime"/>
            <result column="state" property="state"/>
            <result column="total_fee" property="totalFee"/>
            <result column="video_id" property="videoId"/>
            <result column="video_title" property="videoTitle"/>
            <result column="video_img" property="videoImg"/>
        </collection>
    </resultMap>
    <select id="queryUserOrder" resultMap="UserOrderResultMap">
        select
        u.id,
        u.name,
        u.head_img,
        u.create_time,
        u.phone,
        o.id order_id,
        o.out_trade_no,
        o.user_id,
        o.create_time,
        o.state,
        o.total_fee,
        o.video_id,
        o.video_title,
        o.video_img
        from user u left join video_order o on u.id = o.user_id
    </select>

代码

            // resultmap association关联查询
            VideoOrderMapper videoOrderMapper = sqlSession.getMapper(VideoOrderMapper.class);
            //resultmap collection测试
            List<User> users = videoOrderMapper.queryUserOrder();
            System.out.println(users.toString());

Mybatis⼀级缓存介绍和验证

什么是缓存
程序经常要调⽤的对象存在内存中,⽅便其使⽤时可以快速调⽤,不必去数据库或者其他持久化设备中查询,主要就是提⾼性能
Mybatis⼀级缓存
简介:⼀级缓存的作⽤域是SQLSession,同⼀个SqlSession中执⾏相同的SQL查询(相同的SQL和参数),第⼀次会去查询数据库并写在缓存中,第⼆次会直接从缓存中取基于PerpetualCache 的 HashMap本地缓存默认开启⼀级缓存
失效策略:当执⾏SQL时候两次查询中间发⽣了增删改的操作,即insert、update、delete等操作commit后会清空该SQLSession缓存; ⽐如sqlsession关闭,或者清空等


Mybatis⼆级缓存

Mybatis⼆级缓存
简介:⼆级缓存是namespace级别的,多个SqlSession去操作同⼀个namespace下的Mapper的sql语句,多个SqlSession可以共⽤⼆级缓存,如果两个mapper的namespace相同,(即使是两个mapper,那么这两个mapper中执⾏sql查询到的数据也将存在相同的⼆级缓存区域中,但是最后是每个Mapper单独的名称空间)

​ 基于PerpetualCache 的 HashMap本地缓存,可⾃定义存储源,如 Ehcache/Redis等

​ 默认是没有开启⼆级缓存

​ 操作流程:第⼀次调⽤某个namespace下的SQL去查询信息,查询到的信息会存放该mapper对应的⼆级缓存区域。 第⼆次调⽤同个namespace下的mapper映射⽂件中,相同的sql去查询信息,会去对应的⼆级缓存内取结果

​ 失效策略:执⾏同个namespace下的mapepr映射⽂件中增删改sql,并执⾏了commit操作,会清空该⼆级缓存

​ 注意:实现⼆级缓存的时候,MyBatis建议返回的POJO是可序列化的, 也就是建议实现Serializable接⼝

​ 缓存淘汰策略:会使⽤默认的 LRU 算法来收回(最近最少使⽤的)

​ 如何开启某个⼆级缓存 mapper.xml⾥⾯配置

        <!--开启mapper的namespace下的⼆级缓存-->
        <!--
        eviction:代表的是缓存回收策略,常⻅下⾯两种。
        (1) LRU,最近最少使⽤的,⼀处最⻓时间不⽤的对象
        (2) FIFO,先进先出,按对象进⼊缓存的顺序来移除他们
        flushInterval:刷新间隔时间,单位为毫秒,这⾥配置的是100秒刷新,如果不配置它,当SQL被执⾏的时候才会去刷新缓存。
        size:引⽤数⽬,代表缓存最多可以存储多少个对象,设置过⼤会导致内存溢出
        readOnly:只读,缓存数据只能读取⽽不能修改,默认值是false
        -->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
全局配置:
<settings>
<!--这个配置使全局的映射器(⼆级缓存)启⽤或禁⽤缓存,全局总开关,这⾥关闭,mapper中开启了也没⽤-->
 <setting name="cacheEnabled" value="true" />
</settings>

如果需要控制全局mapper⾥⾯某个⽅法不使⽤缓存,可以配置 useCache=“false”

    <select id="selectById" parameterType="java.lang.Integer" resultType="Video" useCache="false">
        select
        <include refid="base_video_field"/>
        from video where id = #
        {video_id,jdbcType=INTEGER}
    </select>

⼀级缓存和⼆级缓存使⽤顺序
优先查询⼆级缓存-》查询⼀级缓存-》数据库


Mybatis的懒加载

什么是懒加载: 按需加载,先从单表查询,需要时再从关联表去关联查询,能⼤⼤提⾼数据库性
能,并不是所有场景下使⽤懒加载都能提⾼效率
Mybatis懒加载: resultMap⾥⾯的association、collection有延迟加载功能

        <!--全局参数设置-->
        <settings>
            <!--延迟加载总开关-->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!--将aggressiveLazyLoading设置为false表示按需加载,默认为true-->
            <setting name="aggressiveLazyLoading" value="false"/>
        </settings>
    <!--懒加载-->
    <resultMap id="VideoOrderResultMapLazy" type="VideoOrder">
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="out_trade_no" property="outTradeNo"/>
        <result column="create_time" property="createTime"/>
        <result column="state" property="state"/>
        <result column="total_fee" property="totalFee"/>
        <result column="video_id" property="videoId"/>
        <result column="video_title" property="videoTitle"/>
        <result column="video_img" property="videoImg"/>
        <!--
            select: 指定延迟加载需要执⾏的statement id
            column: 和select查询关联的字段
        -->
        <association property="user" javaType="User" column="user_id" select="findUserByUserId"/>
    </resultMap>
    <!--⼀对⼀管理查询订单, 订单内部包含⽤户属性 ,懒加载-->
    <select id="queryVideoOrderListLazy" resultMap="VideoOrderResultMapLazy">
        select
        o.id id,
        o.user_id ,
        o.out_trade_no,
        o.create_time,
        o.state,
        o.total_fee,
        o.video_id,
        o.video_title,
        o.video_img
        from video_order o
    </select>

    <select id="findUserByUserId" resultType="User">
        select * from user where id=#{id}
    </select>
            //测试懒加载
            VideoOrderMapper videoOrderMapper = sqlSession.getMapper(VideoOrderMapper.class);
            List<VideoOrder> videoOrderList = videoOrderMapper.queryVideoOrderListLazy();
            //课程⾥⾯演示是6条订单记录,但是只查询3次⽤户信息,是因为部分⽤户信息⾛了⼀级缓存
            for (VideoOrder videoOrder:videoOrderList){
                System.out.println(videoOrder.getVideoTitle());
                System.out.println(videoOrder.getUser().getName());
            }

Mybatis的Innodb与myisam事务管理形式

使⽤JDBC的事务管理
使⽤ java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())

使⽤MANAGED的事务管理
MyBatis⾃身不会去实现事务管理,⽽让程序的容器如(Spring, JBOSS)来实现对事务的管理

    <environments default="development">
        <!--标识-->
        <environment id="development">
            <!--<transactionManager type="MANAGED"/>-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/xdclass
?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>

事务⼯⼚TransactionFactory 的两个实现类
JdbcTransactionFactory->JdbcTransaction
ManagedTransactionFactory->ManagedTransaction
注意:如果不是web程序,然后使⽤的事务管理形式是MANAGED, 那么将没有事务管理功能


区别项Innodbmyisam
事务⽀持不⽀持
锁粒度⾏锁,适合⾼并发表锁,不适合⾼并发
是否默认默认⾮默认
⽀持外键⽀持外键不⽀持
适合场景读写均衡,写⼤于读场景,需要事务读多写少场景,不需要事务
全⽂索引可以通过插件实现, 更多使⽤ElasticSearch⽀持全⽂索引

重点:MyISAM不⽀持事务,如果需要事务则改为innodb引擎 更改数据库的表⾥⾯的引擎


Mybatis事务控制实战

为什么原先没进⾏commit操作,也可以插⼊成功?
因为原先是myisam引擎,没有事务,直接插⼊成功
检查数据库的引擎 ,改为innodb
多个表video/chapter/episode/user/video_order
video_banner
案例实战

事务管理记得改为这个mybatis-config.xml
<transactionManager type="JDBC"/>

事务管理形式 MANAGED,设置⾮⾃动提交,然后注释 commit, 依旧可以保存成功
不⽤重点关注,公司开发项⽬的事务控制基本是交给Spring,或者使⽤分布式事务

//        SqlSession sqlSession = sqlSessionFactory.openSession(true);  //自定提交
//        SqlSession sqlSession = sqlSessionFactory.openSession(false);//非自定提交

JDK8的新特性自动关闭

try (SqlSession sqlSession = sqlSessionFactory.openSession()) {

}

SpringBoot的配置扫描@MapperScan

application.properties

spring.datasource.url=jdbc:mysql://localhost/ResidentialPropertySystem?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#映射表
#配置mapper.xml文件存放路径
mybatis.mapper-locations=classpath:mapper/*.xml

mapper.conntest.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.example.residentialsystem.test.Conntest">
    <!--开启mapper的namespace下的⼆级缓存-->
    <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.example.residentialsystem.domain.User">
        select
        id,username,password
        from t_user where
        id = #{id,jdbcType=INTEGER}
    </select>
</mapper>

pom.xml

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.2.0</version>
        </dependency>

test.Conntest

@Repository
public interface Conntest {
    User selectById(int id);
}

ResidentialsystemApplication配置扫描类

@SpringBootApplication
@ServletComponentScan
@MapperScan("com.example.residentialsystem.test")
public class ResidentialsystemApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResidentialsystemApplication.class, args);
    }
}

Spring5的Maven + Spring5创建项⽬

核心的包
Spring Context
Spring Core
Spring Beans

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.3.1</version>
    </dependency>

添加配置⽂件applicationContext.xml
添加bean配置
如果id不写为包名的最后一个

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean name="video" class="com.example.sp.domain.Video"
          scope="prototype">
        <property name="title" value="tom"/>
        <property name="id" value="23"/>
    </bean>
</beans>

applicationContext.xml配置⽂件
bean标签 id属性:指定Bean的名称,在Bean被别的类依赖时使⽤
name属性:⽤于指定Bean的别名,如果没有id,也可以⽤name
class属性:⽤于指定Bean的来源,要创建的Bean的class类,需要全限定名


IOC的容器

什么是IOC Inverse of Control(控制反转)是⼀种设计思想 将原本在程序中⼿动创建对象的流程,交由Spring框架来管理
核⼼:把创建对象的控制权反转给Spring框架,对象的⽣命周期由Spring统⼀管理
把spring ioc 当成⼀个容器,⾥⾯存储管理的对象称为Bean,类实例
案例实操 配置⽂件⾥⾯定义⼀个bean,通过代码去获取

<bean name="video" class="net.xdclass.sp.domain.Video">
     <property name="id" value="9"/>
     <property name="title" value="Spring 5.X课程" />
</bean>
------------------------------------------------------------------------------------------------------
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Video video = (Video)applicationContext.getBean("video");
System.out.println(video.getTitle());

IOC的DI依赖注⼊

什么是DI Dependency Injection ,依赖注⼊
IOC容器在运⾏期间,动态地将对象某种依赖关系注⼊到对象之中,⽐如视频订单对象,依赖⽤视频对象
scope="prototype"设置多例,scope="singleton"默认不是多例

    <bean id="video" class="com.example.sp.domain.Video" scope="prototype">
        <property name="title" value="tomdfj"/>
        <property name="id" value="23"/>
    </bean>

    <bean id="videoOrder" class="com.example.sp.domain.VideoOrder">
        <property name="id" value="8"></property>
        <property name="outTradeNo" value="123456"></property>
        <!--        依赖注入-->
        <property name="video" ref="video"></property>
    </bean>

prototype: 多例,调⽤getBean⽅法创建不同的对象,会频繁的创建和销毁对象造成很⼤的开销
其他少⽤ (作⽤域 只在 WebApplicationContext)
request :每个Http请求都会创建⼀个新的bean
session: 每个Http Session请求都会创建⼀个新的bean
global session(基本不⽤)


IOC的spring的常⻅的注⼊⽅式

使⽤set⽅法注⼊

<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton">
 <property name="id" value="9"/>
 <property name="title" value="Spring 5.X课程" />
</bean>

使⽤带参的构造函数注⼊(有带参就不能运行原来的set方法注入)

    <bean id="video" class="com.example.sp.domain.Video" scope="prototype">
         通过带参构造器 
        <constructor-arg name="title" value="课堂一"></constructor-arg>
    </bean>

POJO类型注⼊(property 没有使⽤value属性,⽽是使⽤了ref属性)

        <bean id="video" class="com.example.sp.domain.Video" scope="prototype">
            <property name="title" value="tomdfj"/>
            <property name="id" value="23"/>
        </bean>
-----------------------------------------------------------------------------------------------------
    <bean id="videoOrder" class="com.example.sp.domain.VideoOrder">
        <property name="id" value="8"></property>
        <property name="outTradeNo" value="123456"></property>
        <!--        依赖注入-->
        <property name="video" ref="video"></property>
    </bean>

注意: 类的构造函数重写的时候,⼀定要保留空构造函数!!!


IOC的spring的常⻅的注⼊⽅式-List-Map

    <bean id="video" class="com.example.sp.domain.Video">
        <!-- list类型注入 -->
        <property name="chapterList">
            <list>
                <value>第一章</value>
                <value>第二章</value>
                <value>第三章</value>
            </list>
        </property>
        <property name="videoMap">
            <map>
                <entry key="1" value="key1的第一章"></entry>
                <entry key="2" value="key2的第二章"></entry>
                <entry key="3" value="key3的第三章"></entry>
            </map>
        </property>
    </bean>
-----------------------------------------------------------------------------------------------------
public class Video {
    private int id;
    private String title;
    private List<String> chapterList;
    private Map<Integer, String> videoMap;
    //省略get和set
}

IOC的Bean之间的依赖和继承

bean继承:两个类之间⼤多数的属性都相同,避免重复配置,通过bean标签的parent属性重⽤已 有的Bean元素的配置信息 继承指的是配置信息的复⽤,和Java类的继承没有关系

    <bean name="video" class="com.example.sp.domain.Video" scope="singleton">
        <property name="title" value="tomdfj"/>
        <property name="id" value="23"/>
    </bean>

    <bean name="video2" class="com.example.sp.domain.Video2" scope="singleton" parent="video">
        <property name="summary" value="这个是summary"></property>
    </bean>

属性依赖: 如果类A是作为类B的属性, 想要类A⽐类B先实例化,设置两个Bean的依赖关系

    <bean name="video" class="com.example.sp.domain.Video" scope="singleton">
        <property name="title" value="tomdfj"/>
        <property name="id" value="23"/>
    </bean>
-----------------------------------------------------------------------------------------------------
        <!-- 设置两个bean的关系,video要先于videoOreder实例化 -->
    <bean id="videoOrder" class="com.example.sp.domain.VideoOrder" depends-on="video">
        <property name="id" value="8"></property>
        <property name="outTradeNo" value="123456"></property>
        <property name="video" ref="video"></property>
    </bean>

IOC的Bean的⽣命周期的init和destroy⽅法

    <bean name="video" class="com.example.sp.domain.Video" scope="singleton" init-method="init" destroy-method="destroy">
        <property name="title" value="tomdfj"/>
        <property name="id" value="23"/>
    </bean>
-----------------------------------------------------------------------------------------------------
public static void main(String [] args){
	ApplicationContext context = new
	ClassPathXmlApplicationContext("applicationContext.xml");
	//回调函数调用destroy-method="destroy"
	((ClassPathXmlApplicationContext) context).registerShutdownHook();
 }
-----------------------------------------------------------------------------------------------------
//Video中自定义的方法
     public void init(){
        System.out.println("video类 init 方法被调用");
    }

    public void destroy(){
        System.out.println("video类 destory 方法被调用");
    }

IOC的后置处理器 BeanPostProcessor

什么是BeanPostProcessor
是Spring IOC容器给我们提供的⼀个扩展接⼝
在调⽤初始化⽅法前后对 Bean 进⾏额外加⼯,ApplicationContext 会⾃动扫描实现了
BeanPostProcessor的 bean,并注册这些 bean 为后置处理器
是Bean的统⼀前置后置处理⽽不是基于某⼀个bean
执⾏顺序

Spring IOC容器实例化Bean
调⽤BeanPostProcessor的postProcessBeforeInitialization⽅法
调⽤bean实例的初始化⽅法
调⽤BeanPostProcessor的postProcessAfterInitialization⽅法

注意:接⼝重写的两个⽅法不能返回null,如果返回null那么在后续初始化⽅法将报空指针异常或
者通过getBean()⽅法获取不到bean实例对象

public class CustomBeanPostProcessor implements BeanPostProcessor, Ordered {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("CustomBeanPostProcessor postProcessBeforeInitialization beanName="+beanName);
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("CustomBeanPostProcessor postProcessAfterInitialization beanName="+beanName);
        return bean;
    }

    public int getOrder() {
        return 1;
    }
}

可以注册多个BeanPostProcessor顺序
在Spring机制中可以指定后置处理器调⽤顺序,通过BeanPostProcessor接⼝实现类实现
Ordered接⼝getOrder⽅法,该⽅法返回整数,默认值为 0优先级最⾼,值越⼤优先级越低

调用在后置处理器,applicationContext.xml配置

    <bean class="com.example.sp.processor.CustomBeanPostProcessor"/>

IOC的bean⾃动装配Autowire 属性

属性注⼊
前⾯学过属性注⼊,set⽅法、构造函数等,属于⼿⼯注⼊
有没办法实现⾃动装配注⼊?
Spring⾃动注⼊
使⽤元素的 autowire 属性为⼀个 bean 定义指定⾃动装配模式
autowire设置值
no:没开启
byName: 根据bean的id名称,注⼊到对应的属性⾥⾯(通过名称来找IOC容器中id相同的bean)
byType:根据bean需要注⼊的类型,注⼊到对应的属性⾥⾯
如果按照类型注⼊,存在2个以上bean的话会抛异常(类型相同的bean)
expected single matching bean but found 2
constructor: 通过构造函数注⼊,需要这个类型的构造函数

//通过构造函数注入在VideoOrder下需要加构造函数
    public VideoOrder(Video video) {
        this.video=video;
    }
    <!--    <bean id="videoOrder" class="com.example.sp.domain.VideoOrder" autowire="byType">-->
    <!--    <bean id="videoOrder" class="com.example.sp.domain.VideoOrder" autowire="byName">-->
    <bean id="videoOrder" class="com.example.sp.domain.VideoOrder" autowire="constructor">
        <property name="id" value="8"></property>
        <property name="outTradeNo" value="123456"></property>
    </bean>

AOP的⾯向切⾯编程

什么是AOP
Aspect Oriented Program ⾯向切⾯编程
在不改变原有逻辑上增加额外的功能,⽐如解决系统层⾯的问题,或者增加新的功能
场景
权限控制
缓存
⽇志处理
事务控制
AOP思想把功能分两个部分,分离系统中的各种关注点
核⼼关注点
业务的主要功能
横切关注点
⾮核⼼、额外增加的功能
好处
减少代码侵⼊,解耦
可以统⼀处理横切逻辑
⽅便添加和删除横切逻辑

横切关注点
对哪些⽅法进⾏拦截,拦截后怎么处理,这些就叫横切关注点
⽐如 权限认证、⽇志、事物
通知 Advice
在特定的切⼊点上执⾏的增强处理,有5种通知,后⾯讲解
做啥? ⽐如你需要记录⽇志,控制事务 ,提前编写好通⽤的模块,需要的地⽅直接调⽤
连接点 JointPoint
要⽤通知的地⽅,业务流程在运⾏过程中需要插⼊切⾯的具体位置,
⼀般是⽅法的调⽤前后,全部⽅法都可以是连接点
只是概念,没啥特殊
切⼊点 Pointcut
不能全部⽅法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那⼏个你想要的⽅法
在程序中主要体现为书写切⼊点表达式(通过通配、正则表达式)过滤出特定的⼀组JointPoint连接点
过滤出相应的 Advice 将要发⽣的joinpoint地⽅
切⾯ Aspect
通常是⼀个类,⾥⾯定义 切⼊点+通知 , 定义在什么地⽅; 什么时间点、做什么事情
通知 advice指明了时间和做的事情(前置、后置等)
切⼊点 pointcut 指定在什么地⽅⼲这个事情
web接⼝设计中,web层->⽹关层->服务层->数据层,每⼀层之间也是⼀个切⾯,对象和对象,⽅法和⽅法之间都是⼀个个切⾯
⽬标 target
⽬标类,真正的业务逻辑,可以在⽬标类不知情的条件下,增加新的功能到⽬标类的链路上
织⼊ Weaving
把切⾯(某个类)应⽤到⽬标函数的过程称为织⼊
AOP代理
AOP框架创建的对象,代理就是⽬标对象的加强
Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理


AOP的⾥⾯Advice通知的讲解

@Before前置通知
在执⾏⽬标⽅法之前运⾏
@After后置通知
在⽬标⽅法运⾏结束之后
@AfterReturning返回通知
在⽬标⽅法正常返回值后运⾏
@AfterThrowing异常通知
在⽬标⽅法出现异常后运⾏
@Around环绕通知
在⽬标⽅法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,⽇志等都是环绕通知,注意编程中核⼼是⼀个ProceedingJoinPoint,需要⼿动执⾏ joinPoint.procced()

常⻅例⼦
⽤户下单
核⼼关注点:创建订单
横切关注点:记录⽇志、控制事务
⽤户观看付费视频
核⼼关注点:获取播放地址
横切关注点:记录⽇志、权限认证

JoinPoint连接点: addOrder/ findOrderByld/ delOrder/ updateOrder
PointCut切入点:过滤出那些joinpoint中哪些目标函数进行切入
Advice通知:在切入点中的函数上执行的动作,如记录日志、权限校验等
Aspect切面:有切入点和通知组合而成,定义通知应用到那些切入点
Weaving 织入︰把切面的代码应用到目标函数的过程

接⼝业务流程例⼦

//⽬标类 VideoOrderService; ⾥⾯每个⽅法都是连接点,;切⼊点是CUD类型的⽅法,R读取的不作为切⼊点
//CRDU全称:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
VideoOrderService {
    //新增订单
    addOrder() { }
    //查询订单
    findOrderById() { }
    //删除订单
    delOrder() { }
    //更新订单
    updateOrder() { }
}
-----------------------------------------------------------------------------------------------------
//权限切⾯类 = 切⼊点+通知
PermissionAspects {

    //切⼊点 定义了什么地⽅
    @Pointcut("execution(public intnet.xdclass.sp.service.VideoOrderService.*(..))")
    public void pointCut() { }

    //before 通知 表示在⽬标⽅法执⾏前切⼊, 并指定在哪个⽅法前切⼊
    //什么时候,做什么事情
    @Before("pointCut()")
    public void permissionCheck() {
        System.out.println("在 xxx 之前执⾏权限校验");
    }
    
    .....
}
-----------------------------------------------------------------------------------------------------
//⽇志切⾯类 = 切⼊点+通知
LogAspect {

    //切⼊点 定义了什么地⽅
    @Pointcut("execution(public intnet.xdclass.sp.service.VideoOrderService.*(..))")
    public void pointCut() { }
    
    //after 通知 表示在⽬标⽅法执⾏后切⼊, 并指定在哪个⽅法前切⼊
    //什么时候,做什么事情
    @After("pointCut()")
    public void logStart() {
        System.out.println("在 xxx 之后记录⽇志");
    }
    
    .....
}

AOP的切⼊点表达式

切⼊点表示式
除了返回类型、⽅法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)

访问修饰符                    返回值类型(必填) 包和类 ⽅法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?namepattern(param-pattern) throws-pattern?)

@Pointcut(“execution(public int net.xdclass.sp.service.VideoOrderService.*(…))”)
常⻅匹配语法
*:匹配任何数量字符 单个;
…:匹配任何数量字符,可以多个,在类型模式中匹配任何数量⼦包;在⽅法参数模式中匹配任何数量参数

() 匹配⼀个不接受任何参数的⽅法
(..) 匹配⼀个接受任意数量参数的⽅法
(*) 匹配了⼀个接受⼀个任何类型的参数的⽅法
(*,Integer) 匹配了⼀个接受两个参数的⽅法,其中第⼀个参数是任意类型,第⼆个参数必须
是Integer类型

常⻅例⼦
任意公共⽅法

execution(public * *(..))

任何⼀个名字以“save”开始的⽅法

execution(* save*(..))

VideoService接⼝定义的任意⽅法(识别)

execution(* net.xdclass.service.VideoService.*(..))

在service包中定义的任意⽅法(识别)

execution(* net.xdclass.service.*.*(..))

匹配 service 包,⼦孙包下所有类的所有⽅法(识别)

execution(* net.xdclass.service..*.*(..))

AOP的静态代理

静态代理
优点
代理使客户端不需要知道实现类是什么,怎么做的,⽽客户端只需知道代理即可
⽅便增加功能,拓展业务逻辑
缺点
代理类中出现⼤量冗余的代码,⾮常不利于扩展和维护
如果接⼝增加⼀个⽅法,除了所有实现类需要实现这个⽅法外,所有代理类也需要实现此⽅法。增加了代码维护的复杂度

public class StaticProxyPayServiceImpl implements PayService{

    private PayService payService;

    public StaticProxyPayServiceImpl(PayService payService) {
        this.payService = payService;
    }

    public String callback(String outTradeNo) {
        System.out.println("StaticProxyPayServiceImpl callback begin");
        String result=payService.callback(outTradeNo);
        System.out.println("StaticProxyPayServiceImpl callback end");
        return result;
    }

    public int save(int userId, int productId) {
        System.out.println("StaticProxyPayServiceImpl save end");
        int id=payService.save(userId,productId);
        System.out.println("StaticProxyPayServiceImpl save end");
        return id;
    }
}

AOP的JDK动态代理

动态代理
在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
JDK动态代理与静态代理⼀样,⽬标类需要实现⼀个代理接⼝,再通过代理对象调⽤⽬标⽅法
实操:
InvocationHandler接口内部方法:

定义⼀个java.lang.reflect.InvocationHandler接⼝的实现类,重写invoke⽅法

//Object proxy:被代理的对象
//Method method:要调⽤的⽅法
//Object[] args:⽅法调⽤时所需要参数
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws hrowable;
}
class JdkProxy implements InvocationHandler {
    //⽬标类
    private Object targetObject;

    //获取代理对象
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        //第一个参数类加载器,第二个参数接口,第三个参数目标类实现的接口,第三个方法被拦截时执行那个InvocationHandler的方法(由于该类实现了InvocationHandler接口并重写invoke所以this)
        //绑定关系,也就是和具体的哪个实现类关联
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        try {
            System.out.println("通过JDK动态代理调⽤ " + method.getName() + ", 打印⽇志 begin");
            result = method.invoke(targetObject, args);
            System.out.println("通过JDK动态代理调⽤ " + method.getName() + ", 打印⽇志 end");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

调用

        //JDK动态代理
        JdkProxy jdkProxy=new JdkProxy();

        //获取代理类对象
        PayService payServiceProxy = (PayService)jdkProxy.newProxyInstance(new PayServiceImpl());

        //调用目标方法
        payServiceProxy.callback("abc");

AOP的CGLib动态代理

​ 什么是动态代理
​ 在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
​ CgLib动态代理的原理是对指定的业务类⽣成⼀个⼦类,并覆盖其中的业务⽅法来实现代理

public class CglibProxy implements MethodInterceptor {
    //目标类
    private Object targetObject;

    //绑定关系
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;

        Enhancer enhancer = new Enhancer();
        //设置代理类的⽗类(⽬标类)
        enhancer.setSuperclass(this.targetObject.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建⼦类(代理对象)
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        //调用对应的方法
        try {
            System.out.println("通过CGLib动态代理调⽤ " + method.getName() + ", 打印⽇志 begin");
            result = methodProxy.invokeSuper(o, args);
            System.out.println("通过CGLib动态代理调⽤ " + method.getName() + ", 打印⽇志 end");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }
}

调用

        //CGLib动态代理
        CglibProxy cglibProxy=new CglibProxy();
        PayService payService = (PayService)cglibProxy.newProxyInstance(new PayServiceImpl());
        //调用目标类方法
        payService.callback("abc");

AOP的CGLib动态代理和JDK动态代理总结

动态代理与静态代理相⽐较,最⼤的好处是接⼝中声明的所有⽅法都被转移到调⽤处理器⼀个集中的⽅法中处理,解耦和易维护

两种动态代理的区别:
JDK动态代理:要求⽬标对象实现⼀个接⼝,但是有时候⽬标对象只是⼀个单独的对象,并没有实现任何的接⼝,这个 时候就可以⽤CGLib动态代理
CGLib动态代理,它是在内存中构建⼀个⼦类对象从⽽实现对⽬标对象功能的扩展
JDK动态代理是⾃带的,CGlib需要引⼊第三⽅包
CGLib动态代理基于继承来实现代理,所以⽆法对final类、private⽅法和static⽅法实现代理

Spring AOP中的代理使⽤的默认策略:
如果⽬标对象实现了接⼝,则默认采⽤JDK动态代理
如果⽬标对象没有实现接⼝,则采⽤CgLib进⾏动态代理
如果⽬标对象实现了接口,程序⾥⾯依旧可以指定使⽤CGlib动态代理


AOP的配置⽇志打印基础准备

需求分析:针对Videoservice接⼝实现⽇志打印
三个核⼼包
spring-aop:AOP核⼼功能,例如代理⼯⼚
aspectjweaver:简单理解,⽀持切⼊点表达式
aspectjrt:简单理解,⽀持aop相关注解
定义service接⼝和实现类
定义横切关注点
引⼊相关包

        <!-- SpringAOP配置⽇志核心的包 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.11</version>
        </dependency>
    <!-- maven仓库改为阿⾥云 -->
    <repositories>
        <repository>
            <id>maven-ali</id>

            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </snapshots>
        </repository>
    </repositories>

添加schema

       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"
       xmlns:aop="http://www.springframework.org/schema/aop"

配置bean和aop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"
       xmlns:aop="http://www.springframework.org/schema/aop">

    <bean id="timeHandler" class="com.example.sp.aop.TimeHandler"/>
    <bean id="videoService" class="com.example.sp.service.VideoServiceImpl"/>

    <!--aop配置-->
    <aop:config>
        <!-- 横切关注点 -->
        <aop:aspect id="timeAspect" ref="timeHandler">
            <!-- 定义切入点表达式 -->
            <aop:pointcut id="allMethodLogPointCut" expression="execution(* com.example.sp.service.VideoService.*(..))"/>
            <!-- 配置前置后置通知 -->
            <aop:before method="printBefore" pointcut-ref="allMethodLogPointCut"/>
            <aop:after method="printAfter" pointcut-ref="allMethodLogPointCut"/>
        </aop:aspect>
    /aop:config>
</beans>

IOC的Xml配置转换到注解配置

讲解Spring使⽤xml和注解的优缺点
spring的使⽤⽅式有两种 xml配置和注解
有些公司只⽤其中⼀种,也有公司xml 配置与注解配置⼀起使⽤
注解的优势:配置简单,维护⽅便
xml的优势:单修改xml时不⽤改源码,不⽤重新编译和部署
结论: 看团队开发规范进⾏选择,没有强调⼀定⽤哪个 更多的是xml+注解配合使⽤,⽐如spring整合mybatis

开启注解配置和包扫描

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //扫描指定的包,包括子包
        context.scan("com.example");
        //里面完成初始化操作,核心方法
        context.refresh();

        VideoService videoService = (VideoService)context.getBean("VideoService");

        videoService.findById(2);
        //没有取名的时候默认类名的小写
        Video video = (Video)context.getBean("video");
        video.init();
    }

IOC的xml和注解对⽐

常⽤注解
bean定义
xml⽅式:
注解⽅式:@Component 通⽤组件 细分: @Controller (⽤于web层) @Service (⽤于service层) @Repository (⽤于dao仓库层)
bean取名
xml⽅式:通过id或者name
注解⽅式:@Component(“XXXX”)
bean注⼊
xml⽅式:通过
注解⽅式:类型注⼊@Autowired 名称注⼊@Qualifier
bean⽣命周期
xml⽅式:init-method、destroy-method
注解⽅式:@PostConstruct初始化、@PreDestroy销毁
bean作⽤范围
xml⽅式:scope属性
注解⽅式:@scope注解(默认是单例 ) //@Scope(“prototype”)设置多例


IOC的@Configuration和@Bean注解定义第三⽅bean

@Configuration标注在类上,相当于把该类作为spring的xml配置⽂件中的,作⽤为:配置spring容器(应⽤上下⽂)
@bean注解:⽤于告诉⽅法产⽣⼀个Bean对象,然后这个Bean对象交给Spring管理,Spring将会将这个Bean对象放在⾃⼰的IOC容器中
注意点:SpringIOC容器管理⼀个或者多个bean,这些bean都需要在@Configuration注解下进⾏创建

@Configuration
public class AppConfig {
    //使⽤@bean注解,表明这个bean交个spring 进⾏管理
    //如果没有指定名称,默认采⽤ ⽅法名 + 第⼀个字⺟⼩写 作为bean的名称
    //initMethod初始化方法(init)为该类定义的方法,destroyMethod销毁方法(destroy)为该类定义的方法
    @Bean(name = "videoOrderName" ,initMethod = "init",destroyMethod = "destroy")
    @Scope
    public VideoOrder videoOrder(){
        return new VideoOrder();
    }
}

IOC的@PropertySource加载配置⽂件

@PropertySource,指定加载配置⽂件
配置⽂件映射到实体类
使⽤@Value映射到具体的java属性

@Configuration
@PropertySource(value = {"classpath:config.properties"})
public class CustomConfig {
    @Value("${server.host}")
    private String host;
    @Value("${server.port}")
    private String port;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }
}

AOP的注解基础准备

声明切⾯类 @Aspect(切⾯): 通常是⼀个类,⾥⾯可以定义切⼊点和通知
配置切⼊点和通知

@Component
//告诉spring,这个⼀个切⾯类,⾥⾯可以定义切⼊点和通知
@Aspect
public class LogAdvice {
    //切入点表达式
    @Pointcut("execution(* com.example.sp.service.VideoServiceImpl.*(..))")
    public void aspect() {

    }

    //前置通知
    @Before("aspect()")
    public void beforeLog(JoinPoint joinPoint) {

    }

    //后置通知
    @After("aspect()")
    public void afterLog(JoinPoint joinPoint) {
        System.out.println("LogAdvice afterLog");
    }
}

开启SpringAOP注解配置(二)

@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy//开启了spring对aspect的支持
public class AnnotationAppConfig {

}

main下加载(一)

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
        VideoService videoService = (VideoService) context.getBean("VideoService");
        videoService.findById(1);
    }
}

效果测试
LogAdvice下前后通知(三)

@Component
//告诉spring,这个⼀个切⾯类,⾥⾯可以定义切⼊点和通知
@Aspect
public class LogAdvice {
    //切⼊点表达式,也可以直接在通知上编写切⼊点表达式
    @Pointcut("execution(* com.example.sp.service.VideoServiceImpl.*(..))")
    public void aspect() {

    }

    //前置通知
//    @Before("execution(* com.example.sp.service.VideoServiceImpl.*(..))")
    @Before("aspect()")
    public void beforeLog(JoinPoint joinPoint) {
        System.out.println("LogAdvice beforeLog");
    }

    //后置通知
    @After("aspect()")
    public void afterLog(JoinPoint joinPoint) {
        System.out.println("LogAdvice afterLog");
    }
}

配置环绕通知:打印⽅法请求耗时时间
环绕通知获取⽬标⽅法和参数
LogAdvice下环绕通知(三)

    /**
     * 环绕通知
     *
     * @param joinPoint
     */
    @Around("aspect()")
    public void arround(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget().getClass().getName();
        System.out.println("调⽤者="+target);
        //⽬标⽅法签名
        System.out.println("调⽤⽅法="+joinPoint.getSignature());
        //通过joinPoint获取参数(值)
        Object [] args = joinPoint.getArgs();
        System.out.println("参数="+args[0]);

        long start = System.currentTimeMillis();
        System.out.println("环绕通知 环绕前===============");
        //执行连接点的方法
        try {
            ((ProceedingJoinPoint)joinPoint).proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        long end =System.currentTimeMillis();
        System.out.println("环绕通知 环绕后===============");
        System.out.println("方法前后总耗时 time="+(end-start)+"ms");
    }

SpringBoot的整合打通Mysql数据库控制事务

事务:多个操作,要么同时成功,要么失败后⼀起回滚
具备ACID四种特性
Atomic(原⼦性)
Consistency(⼀致性)
Isolation(隔离性)
Durability(持久性)
编程式事务管理:

* 代码中调⽤beginTransaction()、commit()、rollback()等事务管理相关的⽅法,通过TransactionTempalte⼿动管理事务(⽤的少)

声明式事务管理:

 * 通过AOP实现,可配置⽂件⽅式或者注解⽅式实现事务的管理控制(⽤的多)

事务管理本质
本质是对⽅法前后进⾏拦截,底层是建⽴在 AOP 的基础之上
在⽬标⽅法开始之前创建或者加⼊⼀个事务,在执⾏完⽬标⽅法之后根据执⾏情况提交或者回滚事务

Spring事务的传播属性和隔离级别
事物传播⾏为介绍:
如果在开始当前事务之前,⼀个事务上下⽂已经存在,此时有若⼲选项可以指定⼀个事务性⽅法的执⾏⾏为
@Transactional(propagation=Propagation.REQUIRED) 如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) 不为这个⽅法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务
@Transactional(propagation=Propagation.MANDATORY) 必须在⼀个已有的事务中执⾏,否则抛出异常
@Transactional(propagation=Propagation.NEVER) 必须在⼀个没有的事务中执⾏,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) 如果其他bean调⽤这个⽅法,在其他bean中声明事务,那就⽤事务.如果其他bean没有声明事务,那就不⽤事务.
@Transactional(propagation=Propagation.NESTED) 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏; 如果当前没有事务,则该取值等价于Propagation.REQUIRED。

事务隔离级别: 是指若⼲个并发的事务之间的隔离程度
@Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读,不可重复读) 基本不使⽤
@Transactional(isolation = Isolation.READ_COMMITTED) 读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE) 串⾏化
MYSQL: 默认为REPEATABLE_READ级别

快速创建SpringBoot+Spring+Mybatsi项⽬
https://start.spring.io/
连接打通数据库

spring.datasource.url=jdbc:mysql://localhost/ResidentialPropertySystem?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#使⽤阿⾥巴巴druid数据源,默认使⽤⾃带的
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

//注意表一定是InnoDB类型

多表操作,通过@Transactional控制事务,面向切面的回滚事物
启动类加注解 @EnableTransactionManagement			(启动类中添加)
业务类 加 @Transactional			(service,可以抛出异常触发事物回滚)

新版SSM课程总结
SpringBoot
Mybatis
Spring
综合项⽬实战规划
后端 SpringBoot + Spring5 + Mybatis + Maven +IDEA +MySql + JWT + Guava + Jmeter5.X
前端 Vue + Vuex + Axios + CubeUI
阿⾥云ECS CentOS7 + Nginx + 域名绑定


SpringBoot的后端项⽬框架搭建

在线创建 https://start.spring.io/
添加依赖
springboot核⼼包

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<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>

mybaits依赖

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

mysql驱动 (注意需要去掉runtime,否则报错)

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<!--			<scope>runtime</scope>-->
		</dependency>

通⽤⼯具包

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

跨域身份验证解决⽅案 Json web token包

       <!-- JWT相关 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

⾼性能缓存组件

        <!--⾼性能缓存组件的guava依赖包-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>

配置Mybatis连接Mysql数据库


server.port=8080

#==============================数据库相关配置========================================
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/localhost?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123

#使用阿里巴巴druid数据源,默认使用自带的
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true
#配置扫描
mybatis.mapper-locations=classpath:mapper/*.xml

#配置xml的结果别名
mybatis.type-aliases-package=com.example.online.domain

创建VideoMapper.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.example.online.mapper.VideoMapper">
    <select id="listVideo" resultType="Video">
        select  * from video
    </select>
</mapper>

配置扫描mapper路径

@SpringBootApplication
@MapperScan("com.example.online.mapper")
public class OnlineApplication {
	public static void main(String[] args) {
		SpringApplication.run(OnlineApplication.class, args);
	}
}

实现热部署

        <!--        热部署依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

    <!--springboot打包的依赖-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork><!--必须添加这个配置-->
                </configuration>
            </plugin>
        </plugins>
    </build>

SpringBoot的三表关联查询映射

    <resultMap id="VideoDetailResultMap" type="Video">

        <id column="id" jdbcType="INTEGER" property="id"/>
        <result column="title" jdbcType="VARCHAR" property="title"/>
        <result column="summary" jdbcType="VARCHAR" property="summary"/>
        <result column="cover_img" jdbcType="VARCHAR" property="coverImg"/>
        <result column="price" jdbcType="INTEGER" property="price"/>
        <result column="point" jdbcType="DOUBLE" property="point"/>
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>

        <!--映射-->
        <collection property="chapterList" ofType="Chapter">
            <id column="chapter_id" jdbcType="INTEGER" property="id"/>
            <result column="chapter_title" jdbcType="VARCHAR" property="title"/>
            <result column="ordered" jdbcType="INTEGER" property="ordered"/>
            <result column="chapter_create_time" jdbcType="TIMESTAMP" property="createTime"/>
            <!--映射-->
            <collection property="episodeList" ofType="Episode">
                <id column="episode_id" jdbcType="INTEGER" property="id"/>
                <result column="num" jdbcType="INTEGER" property="num"/>
                <result column="episode_title" jdbcType="VARCHAR" property="title"/>
                <result column="episode_ordered" jdbcType="INTEGER" property="ordered"/>
                <result column="play_url" jdbcType="VARCHAR" property="playUrl"/>
                <result column="free" jdbcType="INTEGER" property="free"/>
                <result column="episode_create_time" jdbcType="TIMESTAMP" property="createTime"/>
            </collection>
        </collection>
    </resultMap>
    
    <select id="findDetailById" resultMap="VideoDetailResultMap">

        select
        v.id, v.title,v.summary,v.cover_img,v.price,v.point,v.create_time,
        c.id as chapter_id, c.title as chapter_title, c.ordered,c.create_time as chapter_create_time,
        e.id as episode_id,e.num, e.title as episode_title,e.ordered as episode_ordered,e.play_url,e.free,e.create_time as episode_create_time
        from video v
        left join chapter c on v.id=c.video_id
        left join episode e on c.id= e.chapter_id
        where v.id = #{video_id}
        order by c.ordered,e.num asc
    </select>

SpringBoot⾃定义异常开发和配置

public class XDException extends RuntimeException {
    private Integer code;
    private String msg;
    public XDException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
@ControllerAdvice//全局异常处理
public class CustomExceptionHandler {
    //记录日志
    private final static Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
    @ExceptionHandler(value = Exception.class)//处理那类的异常
    @ResponseBody//响应数据给前端
    public JsonData handle(Exception e) {
        logger.error("[ 系统异常 ]{}", e);//如果除了异常则记录
        if (e instanceof XDException) {//如果属于自定义异常(XDException为我们自己设置的异常)
            XDException xdException = (XDException) e;
            //扔出自定义的jsondata的code值和msg
            return JsonData.buildError(xdException.getCode(), xdException.getMsg());
        } else {
            return JsonData.buildError("全局异常,未知错误");//全局异常则扔出错误信息因为code已被定义
        }
    }
}

SpringBoot的MD5加密⼯具类和JWT登录

MD5加密⼯具类封装

    public static String MD5(String data) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }

            return sb.toString().toUpperCase();
        } catch (Exception exception) {
        }
        return null;
    }

随机头像⽣成

    private static final String [] headImg = {
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/12.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/11.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/13.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/14.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/15.jpeg"
    };

登录校验解决⽅案
单机tomcat应⽤登录检验
sesssion保存在浏览器和应⽤服务器会话之间
⽤户登录成功,服务端会保存⼀个session,当然客户端有⼀个sessionId
客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId

分布式应⽤中session共享
		真实的应⽤不可能单节点部署,所以就有个多节点登录session共享的问题需要解决
		tomcat⽀持session共享,但是有⼴播⻛暴;⽤户量⼤的时候,占⽤资源就严重,不推荐
		使⽤redis存储token:
				服务端使⽤UUID⽣成随机64位或者128位token,放⼊redis中,然后返回给客户端并存储在cookie中
				⽤户每次访问都携带此token,服务端去redis中校验是否有此⽤户即可

什么是JWT
JWT 是⼀个开放标准,它定义了⼀种⽤于简洁,⾃包含的⽤于通信双⽅之间以 JSON 对象的形式安全传递信息的⽅法。 可以使⽤ HMAC 算法或者是 RSA 的公钥密钥对进⾏签名
简单来说: 就是通过⼀定规范来⽣成token,然后可以通过解密算法逆向解密token,这样就可以获取⽤户信息

    {
        id:888,
        name:'⼩D',
        expire:10000
    }
    funtion 加密(object, appsecret){
        xxxx
        return base64( token);
    }
    function 解密(token ,appsecret){
        xxxx
        //成功返回true,失败返回false
    }

优点
⽣产的token可以包含基本信息,⽐如id、⽤户昵称、头像等信息,避免再次查库
存储在客户端,不占⽤服务端的内存资源
缺点
token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如⽤户权限,密码等
如果没有服务端存储,则不能做登录失效处理,除⾮服务端改秘钥

加⼊相关依赖

        <!-- JWT相关 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

封装⽣产token⽅法

/**
 * Jwt工具类
 * 注意点:
 * 1、生成的token, 是可以通过base64进行解密出明文信息
 * 2、base64进行解密出明文信息,修改再进行编码,则会解密失败
 * 3、无法作废已颁布的token,除非改秘钥
 */
public class JWTUtils {
    /**
     * 过期时间,一周
     */
    //private  static final long EXPIRE = 60000 * 60 * 24 * 7;
    private static final long EXPIRE = 1;

    /**
     * 加密秘钥
     */
    private static final String SECRET = "xdclass.net168";

    /**
     * 令牌前缀
     */
    private static final String TOKEN_PREFIX = "xdclass";

    /**
     * subject
     */
    private static final String SUBJECT = "xdclass";

    /**
     * 根据用户信息,生成令牌
     *
     * @param user
     * @return
     */
    public static String geneJsonWebToken(User user) {
        String token = Jwts.builder().setSubject(SUBJECT)
                .claim("head_img", user.getHeadImg())
                .claim("id", user.getId())
                .claim("name", user.getName())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SignatureAlgorithm.HS256, SECRET).compact();

        token = TOKEN_PREFIX + token;
        return token;
    }

    /**
     * 校验token的方法
     *
     * @param token
     * @return
     */
    public static Claims checkJWT(String token) {
        try {
            final Claims claims = Jwts.parser().setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();

            return claims;
        } catch (Exception e) {
            return null;
        }
    }
}

SpringBoot的截器开发与ObjectMapper

登录拦截器

public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 进入到controller之前的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            String accesToken = request.getHeader("token");
            if (accesToken == null) {
                accesToken = request.getParameter("token");
            }

            if (StringUtils.isNotBlank(accesToken)) {
                Claims claims = JWTUtils.checkJWT(accesToken);
                if (claims == null) {
                    //告诉登录过期,重新登录
                    sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
                    return false;
                }

                //放在参数里controller才能取到
                Integer id = (Integer) claims.get("id");
                String name = (String) claims.get("name");

                request.setAttribute("user_id", id);
                request.setAttribute("name", name);

                return true;
            }
        }catch (Exception e){}
        sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
        return false;
    }
    
    /**
     * 响应json数据给前端,序列化json数据返回json数据的方法
     * @param response
     * @param obj
     */
    public static void sendJsonMessage(HttpServletResponse response, Object obj){
        try{
            ObjectMapper objectMapper = new ObjectMapper();
            response.setContentType("application/json; charset=utf-8");//设置响应的类型
            PrintWriter writer = response.getWriter();
            writer.print(objectMapper.writeValueAsString(obj));//ObjectMapper将java对象转换为JSON对象
            writer.close();
            response.flushBuffer();//刷新
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }
}

loginInterceptor 拦截器注册和路径校验配置

@Configuration//表示这个是一个配置
public class InterceptorConfig implements WebMvcConfigurer {

    @Bean//添加到ioc注解中
    LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //拦截全部,addInterceptor添加拦截器,addPathPatterns添加拦截的路径
        registry.addInterceptor(loginInterceptor()).addPathPatterns("/api/v1/pri/*/*/**")
                //不拦截哪些路径   斜杠一定要加
                .excludePathPatterns("/api/v1/pri/user/login","/api/v1/pri/user/register");

        WebMvcConfigurer.super.addInterceptors(registry);

    }
}

SpringBoot的缓存

添加依赖

        <!--⾼性能缓存组件的guava依赖包-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>

封装api

    private Cache<String, Object> tenMinuteCache = CacheBuilder.newBuilder()
            //设置缓存初始⼤⼩,应该合理设置,后续会扩容
            .initialCapacity(10)
            //最⼤值
            .maximumSize(100)
            //并发数设置
            .concurrencyLevel(5)
            //缓存过期时间,写⼊后10分钟过期
            .expireAfterWrite(600, TimeUnit.SECONDS)
            //统计缓存命中率
            .recordStats()
            .build();

    public Cache<String, Object> getTenMinuteCache() {
        return tenMinuteCache;
    }

    public void setTenMinuteCache(Cache<String, Object> tenMinuteCache) {
        this.tenMinuteCache = tenMinuteCache;
    }

列子:轮播图接⼝加⼊缓存的存储

public class CacheKeyManager {
    /**
     * 首页轮播图缓存key
     */
    public static final String INDEX_BANNER_KEY = "index:banner";
}

例子:轮播图接⼝加⼊缓存

    public List<VideoBanner> listBanner() {
        try{
            //先从缓存中找,找不到使用jdk8的回调函数从数据库中找
            Object cacheObj= baseCache.getTenMinuteCache().get(CacheKeyManager.INDEX_BANNER_KEY, () -> {
                //缓存中没找到,从数据库中查找
                List<VideoBanner> bannerList = videoMapper.listVideoBanner();
                System.out.println("从数据库中找轮播图列表");
                //这里是返回给cacheObj,由于缓存中没有所有返回给缓存
                return bannerList;
            });
            //如果缓存中有则转换成list返回
            if(cacheObj instanceof List){
                System.out.println("缓存中找轮播图列表");
                List<VideoBanner> bannerList = (List<VideoBanner>) cacheObj;
                return bannerList;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

SpringBoot的压⼒测试⼯具Jmeter

快速下载 https://jmeter.apache.org/download_jmeter.cgi
⽂档地址:http://jmeter.apache.org/usermanual/get-started.html

bin:核⼼可执⾏⽂件,包含配置
 jmeter.bat: windows启动⽂件(window系统⼀定要配置显示⽂件拓展名)
 jmeter: mac或者linux启动⽂件
 jmeter-server:mac或者Liunx分布式压测使⽤的启动⽂件
 jmeter-server.bat:window分布式压测使⽤的启动⽂件
 jmeter.properties: 核⼼配置⽂件
extras:插件拓展的包
lib:核⼼的依赖包

Jmeter语⾔版本中英⽂切换
控制台修改 menu -> options -> choose language
配置⽂件修改
bin⽬录 -> jmeter.properties
默认 #language=en
改为 language=zh_CN

添加->threads->线程组(控制总体并发)

线程数:虚拟⽤户数。⼀个虚拟⽤户占⽤⼀个进程或线程
准备时⻓(Ramp-Up Period(in seconds)):全部线程启动的时⻓,⽐如100个线程,20秒,则
表示20秒内 100个线程都要启动完成,每秒启动5个线程
循环次数:每个线程发送的次数,假如值为5,100个线程,则会发送500次请求,可以勾选永远循环

线程组->添加-> Sampler(采样器) -> Http (⼀个线程组下⾯可以增加⼏个Sampler)

名称:采样器名称
注释:对这个采样器的描述
web服务器:
 默认协议是http
 默认端⼝是80
 服务器名称或IP :请求的⽬标服务器名称或IP地址
路径:服务器URL

查看测试结果

线程组->添加->监听器->察看结果树

新增聚合报告:线程组->添加->监听器->聚合报告(Aggregate Report)

lable: sampler的名称
Samples: ⼀共发出去多少请求,例如10个⽤户,循环10次,则是 100
Average: 平均响应时间
Median: 中位数,也就是 50% ⽤户的响应时间
90% Line : 90% ⽤户的响应不会超过该时间 (90% of the samples took no more than
this time. The remaining samples at least as long as this)
95% Line : 95% ⽤户的响应不会超过该时间
99% Line : 99% ⽤户的响应不会超过该时间
min : 最⼩响应时间
max : 最⼤响应时间
Error%:错误的请求的数量/请求的总数
Throughput: 吞吐量——默认情况下表示每秒完成的请求数(Request per Second) 可类⽐为
qps、tps
KB/Sec: 每秒接收数据量

SpringBoot的跨域配置

@CrossOrigin//在Controller层上写

或者

public class CorsInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //表示接受任意域名的请求,也可以指定域名
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));

        //该字段可选,是个布尔值,表示是否可以携带cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");

        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");

        response.setHeader("Access-Control-Allow-Headers", "*");


        //这里可以不加,但是其他语言开发的话记得处理options请求
        /**
         * 非简单请求是对那种对服务器有特殊要求的请求,
         * 比如请求方式是PUT或者DELETE,或者Content-Type字段类型是application/json。
         * 都会在正式通信之前,增加一次HTTP请求,称之为预检。浏览器会先询问服务器,当前网页所在域名是否在服务器的许可名单之中,
         * 服务器允许之后,浏览器会发出正式的XMLHttpRequest请求
         */
        if(HttpMethod.OPTIONS.toString().equals(request.getMethod())){
            return true;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

拦截器配置类添加拦截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Bean
    LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }
    @Bean
    CorsInterceptor corsInterceptor(){
        return new CorsInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 拦截全部路径,这个跨域需要放在最上面
         */
        registry.addInterceptor(corsInterceptor()).addPathPatterns("/**");

        registry.addInterceptor(loginInterceptor()).addPathPatterns("/api/v1/pri/*/*/**")
                //不拦截哪些路径   斜杠一定要加
                .excludePathPatterns("/api/v1/pri/user/login","/api/v1/pri/user/register");
                
        WebMvcConfigurer.super.addInterceptors(registry);

    }
}

SpringBoot的搭建Swagger

http://localhost:8080/swagger-ui.html

Maven依赖如下

        <!--搭建Swagger(搭建Swagger后就不需要单独导入guava的包了)-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--搭建Swagger(搭建Swagger后就不需要单独导入guava的包了)-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

配置类

@Component
// 开启Swagger2的自动配置
@EnableSwagger2
public class SwaggerConfig {
    // 配置docket以配置Swagger具体参数
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2);
    }
}

通过apiInfo()属性配置文档信息

@Component
@Configuration
// 开启Swagger2的自动配置
@EnableSwagger2
public class SwaggerConfig {

    // 配置docket以配置Swagger具体参数
    @Bean
    public Docket docket() {
        //一个Swagger实例,通过apiinfo
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        Contact contact = new Contact("李长渊", "https://www.baidu.com/s?wd=%E4%B8%AA%E4%BA%BA%E9%93%BE%E6%8E%A5", "1311328238@qq.com");
        // public ApiInfo(String title, String description, String version, String termsOfServiceUrl, Contact contact, String ", String licenseUrl, Collection<VendorExtension> vendorExtensions) {
        return new ApiInfo("古诗文接口文档", // 标题
                "详细的描述我就不说了,懂得都懂。", // 描述
                "v1.0", // 版本
                "https://www.baidu.com/s?wd=%E7%BB%84%E7%BB%87%E9%93%BE%E6%8E%A5", // 组织链接
                contact, // 联系人信息
                "Apach 2.0 许可", // 许可
                "https://www.baidu.com/s?wd=%E8%AE%B8%E5%8F%AF%E9%93%BE%E6%8E%A5", // 许可连接
                new ArrayList<>()); // 扩展
    }
}

构建Docket时通过select()方法配置怎么扫描接口。

    // 配置docket以配置Swagger具体参数
    @Bean
    public Docket docket() {
        //一个Swagger实例,通过apiinfo
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())//配置文档信息
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.poetry.controller"))
                // 配置如何通过 path过滤 即这里只扫描 请求以 /user开头的接口(注释掉了)
    		   //.paths(PathSelectors.ant("/user/**"))
                .build()//配置要扫描的接口
                ;
    }
any() // 扫描所有,项目中的所有接口都会被扫描到,为默认
none() // 不扫描接口

withMethodAnnotation(final Class<? extends Annotation> annotation)// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求

withClassAnnotation(final Class<? extends Annotation> annotation) // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口

basePackage(final String basePackage) // 根据包路径扫描接口

通过ignoredParameterTypes()方法去配置要忽略的参数

// 配置docket以配置Swagger具体参数
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
            // 配置要忽略的参数
                .ignoredParameterTypes(HttpServletRequest.class) 
                .select()
       .apis(RequestHandlerSelectors.basePackage("com.example.swaggerexample.controller")).build();
    }

配置API分组

//分组
    @Bean
    public Docket docketPoem() {
        //一个Swagger实例,通过apiinfo
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())//配置文档信息
                .groupName("古诗文接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.poetry.controller"))
                // 配置如何通过 path过滤 即这里只扫描 请求以 /poem开头的接口
                .paths(PathSelectors.ant("/poem/**"))
                .build()//配置要扫描的接口
                ;
    }

实体配置

@ApiModel("用户实体")
public class User {
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;
	// 省略getter/setter
}

@ApiModel为类添加注释
@ApiModelProperty为类属性添加注释

全局参数的配置,配置参数需要token

@Bean
    public Docket docketFunction() {
        Parameter token=new ParameterBuilder().name("token")
                .description("用户登录令牌")
                .parameterType("header")
                .modelRef(new ModelRef("String"))
                .required(true)//表示该参数是必要参数
                .build();
        List<Parameter> parameters=new ArrayList<>();
        parameters.add(token);
        //一个Swagger实例,通过apiinfo
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())//配置文档信息
                .groupName("功能性接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.poetry.controller"))
                // 配置如何通过 path过滤 即这里只扫描 请求以 /function开头的接口
                .paths(PathSelectors.ant("/function/**"))
                .build()//配置要扫描的接口
                .globalOperationParameters(parameters)//表示添加全局变量token
                ;
    }

接口和参数配置
单个参数时

@Api(tags = "古诗文的相关接口")//@Api()用于类
@RestController
@RequestMapping("poem")
@CrossOrigin//跨域
public class PoemController {
    @Autowired
    private PoemService poemService;

    @ApiOperation("推荐接口")//@ApiOperation()用于方法;
    @ApiImplicitParam(
            name = "number",
            value = "设置随机返回多少条数据",
            defaultValue = "",
            example = "100",
            dataType = "int",
            paramType = "query")
    //描述参数信息,defaultValue为默认值,example为测试时输入的默认值,dataType为类型,paramType为请求类型
    @GetMapping("recommend")
    public JsonData recommend(@RequestParam("number") int number) {
        return poemService.recommend(number);
    }

多个参数时

    @ApiOperation("返回作者列表通过朝代查询,如果不输入朝代则返回全部")//@ApiOperation()用于方法;
    @ApiImplicitParams({
            @ApiImplicitParam(name = "dynasty",
                    value = "朝代",
                    example = "唐朝",
                    dataType = "String",
                    paramType = "query"),
            @ApiImplicitParam(name = "counts",
                    value = "设置一页多少条",
                    example = "100",
                    dataType = "int",
                    paramType = "query"),
            @ApiImplicitParam(name = "page",
                    value = "设置第几页",
                    example = "1",
                    dataType = "int",
                    paramType = "query"),
    })
    @GetMapping("authorListbyDynasty")
    public JsonData authorListbyDynasty(@RequestParam("dynasty") String dynasty, @RequestParam("counts") int counts, @RequestParam("page") int page) {
        int start = (page - 1) * counts;
        return poemService.authorListbyDynasty(dynasty, counts, start);
    }

当请求参数为实体类时

    @ApiOperation("注册")//@ApiOperation()用于方法;
    @PostMapping("register")
    public JsonData register(@RequestBody User user){
        return userService.register(user);
    }
//=========================================================================================
@ApiModel("用户实体")
public class User implements Serializable {
    @ApiModelProperty("用户id")
    private int id;
    @ApiModelProperty(value = "用户账号",example = "admin")
    private String uUsername;
    @ApiModelProperty(value = "用户密码",example = "admin")
    private String uPassword;
}

常用注解:

- @Api()用于类;
表示标识这个类是swagger的资源
- @ApiOperation()用于方法;
表示一个http请求的操作
- @ApiParam()用于方法,参数,字段说明;
表示对参数的添加元数据(说明或是否必填等)
- @ApiModel()用于类
表示对类进行说明,用于参数用实体类接收
- @ApiModelProperty()用于方法,字段
表示对model属性的说明或者数据操作更改
- @ApiIgnore()用于类,方法,方法参数
表示这个方法或者类被忽略
- @ApiImplicitParam() 用于方法
表示单独的请求参数
- @ApiImplicitParams() 用于方法,包含多个 @ApiImplicitParam

具体使用举例说明:
@Api()
用于类;表示标识这个类是swagger的资源
tags–表示说明
value–也是说明,可以使用tags替代



SpringBoot的实战项目

pom.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>poetry</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>poetry</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <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>
            <!--应该去掉要不然找不到增删改查注解<scope>runtime</scope>-->
        </dependency>

        <!--通用的包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- JWT相关 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>
        <!--⾼性能缓存组件的guava依赖包(搭建Swagger后就不需要单独导入guava的包了)-->
<!--        <dependency>-->
<!--            <groupId>com.google.guava</groupId>-->
<!--            <artifactId>guava</artifactId>-->
<!--            <version>19.0</version>-->
<!--        </dependency>-->
        <!--热部署依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!--搭建Swagger(搭建Swagger后就不需要单独导入guava的包了)-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--搭建Swagger(搭建Swagger后就不需要单独导入guava的包了)-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork><!--必须添加这个配置-->
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

application.properties中的配置

#修改端口
server.port=8080
spring.resources.static-locations = classpath:/METAINF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/
#=====================================================================================
spring.datasource.url=jdbc:mysql://101.201.196.108/fucking_gushiwen?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=8112
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#使用阿里巴巴druid数据源,默认使用自带的
#spring.datasource.type =com.alibaba.druid.pool.DruidDataSource
#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# mybatis 下划线转驼峰配置,两者都可以
#mybatis.configuration.mapUnderscoreToCamelCase=true
mybatis.configuration.map-underscore-to-camel-case=true
#映射mapper.xml文件存放路径
mybatis.mapper-locations=classpath:mapper/*.xml
#映射pojo
mybatis.type-aliases-package=com.example.poetry.domain
#=====================================================================================


SpringBoot的其他笔记

扫描mapper的注解

使用MapperScan之后就不用在每一个Dao上加@mapper,放在启动类中

@MapperScan("com.example.residentialsystem.dao") //扫描mapper

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 namespace="com.example.residentialsystem.dao.ResidentDao">

</mapper>

服务器打包和挂载

打包需要的配置文件

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.4.RELEASE</version>
                <configuration>
                    <executable>true</executable>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

服务器挂载到后台

nohup java -jar arcsoft_Administration-0.0.1-SNAPSHOT.jar &
arcsoft_Administration-0.0.1-SNAPSHOT.jar为jar包
再次挂后台时先杀死原来的进程再挂载
kill -9 进程号
如果忘了进程号,可以通过
ps -ef|grep arcsoft_Administration-0.0.1-SNAPSHOT.jar
或者通过端口查找
netstat -anop|grep 8088

@RequestBody和@RequestParam区别

@RequestParam
注解@RequestParam接收的参数是来自HTTP请求体或请求url的QueryString中。

RequestParam可以接受简单类型的属性,也可以接受对象类型。

@RequestParam有三个配置参数:

required 表示是否必须,默认为 true,必须。
defaultValue 可设置请求参数的默认值。
value 为接收url的参数名(相当于key值)。
//===================================================================================================
    @GetMapping("find_detail_by_id")
    public JsonData findDetailById(@RequestParam(value = "video_id",required = true)int videoId){
        Video video = videoService.findDetailById(videoId);
        return JsonData.buildSuccess(video);
    }

@RequestBody

GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。

POST请求时
@RequestBody --> JSON字符串部分
@RequestParam --> 请求参数部分


指定别名@JsonProperty

1.前端传参数过来的时候,使用这个注解,可以获取到前端与注解中同名的属性
2.后端处理好结果后,返回给前端的属性名也不以实体类属性名为准,而以注解中的属性名为准

@JsonProperty("video_id")
private int videoId;

设置年月日,时分秒

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")

HTTP请求中的header和query和body

header显而易见是请求头

query是指请求的参数,一般是指URL中?后面的参数

如http://10.6.6.6:8080/api/v1/namespaces?pretty=true中 pretty=true就是query

body是指请求体中的数据

当lib包在src下打包时要加的依赖

<!--        导入lib包内的虹软-->
<dependency>
    <groupId>com.arcsoft.face</groupId>
    <artifactId>arcsoft-sdk-face</artifactId>
    <version>3.0.0.0</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/lib/arcsoft-sdk-face-3.0.0.0.jar</systemPath>
</dependency>

Jar包fastJson的简单使用

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

传入一个对象,将对象转成JSON字符串。
例1:将Map转成JSON

1 Map<String, Object> map = new HashMap<String, Object>();
2 map.put("key1", "One");
3 map.put("key2", "Two");
4         
5 String mapJson = JSON.toJSONString(map);

输出结果:

{"key1":"One","key2":"Two"}

例3:自定义JavaBean User转成JSON。

1 User user = new User();
2 user.setUserName("李四");
3 user.setAge(24);
4         
5 String userJson = JSON.toJSONString(user);

输出结果:

{"age":24,"userName":"李四"}

可以输出格式化后的 JSON 字符串。
传入一个对象和一个布尔类型(是否格式化),将对象转成格式化后的JSON字符串。

String objJson = JSON.toJSONString(Object object, boolean prettyFormat);

例如

String listJson = JSON.toJSONString(list, true);

Jar包Hutool的使用

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.1</version>
</dependency>
package com.example.lagouservicecommon;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.convert.Converter;
import cn.hutool.core.date.*;
import cn.hutool.core.img.Img;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Date;

public class HutoolTest {
    public static void main(String[] args) {

        //Convert
        Date date = Convert.toDate("2016-02-03");//快速格式化时间
        Date date2 = Convert.toDate("2019-03-03");
        String numberToChinese = Convert.numberToChinese(54, true);//数字转中文
        String digitToChinese = Convert.digitToChinese(32.24);//叁拾贰元贰角肆分

        //DateUtil
        String now = DateUtil.now();//获取当前时间 转为String类型
        DateTime date3 = DateUtil.date();//获取当前时间为DateTime类型
        long day = DateUtil.between(date2, date, DateUnit.DAY);//计算相差多少天
        long week = DateUtil.between(date2, date, DateUnit.WEEK);//计算相差多少中
        System.out.println(DateUtil.age(date, date2));//计算年龄
        System.out.println(DateUtil.ageOfNow(date));//计算出生到现在的年龄
        System.out.println("******"+DateUtil.beginOfDay(date));
        System.out.println(week);

        //CaptchaUtil 生成验证码
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(50, 80);
        
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(80, 80);

        ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(50, 80);
        System.out.println(shearCaptcha.getCode());
        BufferedImage image = lineCaptcha.getImage();
    }
}

Jar中Lombok使用

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>

不用写get和set方法(要在类上加@Data)
注解@Slf4j的使用
声明:如果不想每次都写private final Logger logger = LoggerFactory.getLogger(当前类名.class); 可以用注解@Slf4j;

log.error("举个例子");//可以输出到日志中

Jar总导入外部包

        <!--        导入lib包内的虹软-->
        <dependency>
            <groupId>com.arcsoft.face</groupId>
            <artifactId>arcsoft-sdk-face</artifactId>
            <version>3.0.0.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/arcsoft-sdk-face-3.0.0.0.jar</systemPath>
        </dependency>
        
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                    <fork>true</fork><!--必须添加这个配置-->
                </configuration>
            </plugin>
        </plugins>
    </build>

redis缓存

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 修改项目启动类,增加注解@EnableCaching
  • 将需要缓存的对象序列化
    ce
    3.0.0.0
    system
    ${basedir}/src/lib/arcsoft-sdk-face-3.0.0.0.jar

---

### Jar包fastJson的简单使用

com.alibaba fastjson 1.2.59 ```

传入一个对象,将对象转成JSON字符串。
例1:将Map转成JSON

1 Map<String, Object> map = new HashMap<String, Object>();
2 map.put("key1", "One");
3 map.put("key2", "Two");
4         
5 String mapJson = JSON.toJSONString(map);

输出结果:

{"key1":"One","key2":"Two"}

例3:自定义JavaBean User转成JSON。

1 User user = new User();
2 user.setUserName("李四");
3 user.setAge(24);
4         
5 String userJson = JSON.toJSONString(user);

输出结果:

{"age":24,"userName":"李四"}

可以输出格式化后的 JSON 字符串。
传入一个对象和一个布尔类型(是否格式化),将对象转成格式化后的JSON字符串。

String objJson = JSON.toJSONString(Object object, boolean prettyFormat);

例如

String listJson = JSON.toJSONString(list, true);

Jar包Hutool的使用

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.1</version>
</dependency>
package com.example.lagouservicecommon;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.convert.Converter;
import cn.hutool.core.date.*;
import cn.hutool.core.img.Img;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Date;

public class HutoolTest {
    public static void main(String[] args) {

        //Convert
        Date date = Convert.toDate("2016-02-03");//快速格式化时间
        Date date2 = Convert.toDate("2019-03-03");
        String numberToChinese = Convert.numberToChinese(54, true);//数字转中文
        String digitToChinese = Convert.digitToChinese(32.24);//叁拾贰元贰角肆分

        //DateUtil
        String now = DateUtil.now();//获取当前时间 转为String类型
        DateTime date3 = DateUtil.date();//获取当前时间为DateTime类型
        long day = DateUtil.between(date2, date, DateUnit.DAY);//计算相差多少天
        long week = DateUtil.between(date2, date, DateUnit.WEEK);//计算相差多少中
        System.out.println(DateUtil.age(date, date2));//计算年龄
        System.out.println(DateUtil.ageOfNow(date));//计算出生到现在的年龄
        System.out.println("******"+DateUtil.beginOfDay(date));
        System.out.println(week);

        //CaptchaUtil 生成验证码
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(50, 80);
        
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(80, 80);

        ShearCaptcha shearCaptcha = CaptchaUtil.createShearCaptcha(50, 80);
        System.out.println(shearCaptcha.getCode());
        BufferedImage image = lineCaptcha.getImage();
    }
}

Jar中Lombok使用

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>

不用写get和set方法(要在类上加@Data)
注解@Slf4j的使用
声明:如果不想每次都写private final Logger logger = LoggerFactory.getLogger(当前类名.class); 可以用注解@Slf4j;

log.error("举个例子");//可以输出到日志中

Jar总导入外部包

        <!--        导入lib包内的虹软-->
        <dependency>
            <groupId>com.arcsoft.face</groupId>
            <artifactId>arcsoft-sdk-face</artifactId>
            <version>3.0.0.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/arcsoft-sdk-face-3.0.0.0.jar</systemPath>
        </dependency>
        
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                    <fork>true</fork><!--必须添加这个配置-->
                </configuration>
            </plugin>
        </plugins>
    </build>

redis缓存

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 修改项目启动类,增加注解@EnableCaching
  • 将需要缓存的对象序列化
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

和烨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值