注解
@Conditional():spring提供的条件注解
@ConditionalOnProperty(perfix="pan"):springboot提供的条件注解,判断是否property文件是否含有该前缀,使用前缀是pan的参数
@ConditionalOnClass(name={"com.pan.A"}):springboot提供的条件注解,判断是否有该类,如果有就实现有该注解的实现类,没有的话,就不实现该实现类
一.application.properties容器配置
#修改服务的端口号,不一定是修改Tomcat的端口号,是修改系统容器的端口号,因为有可能使用了Jetty/Undertow容器
server.port=8888
#配置项目的上下文路径(项目名)
server.servlet.context-path=/s#配置tomcat的日志目录,日志位置
server.tomcat.accesslog.directory=logs
#开启tomcat的访问日志
server.tomcat.accesslog.enabled=true
#配置tomcat的Get请求参数的编码格式,设置URL地址栏编码格式,防止url地址栏乱码,默认就是utf-8
#如果换成了jetty或者undertow容器,这个配置就不会生效了
server.tomcat.uri-encoding=UTF-8
二.多环境切换(profiles使用方式)
方式一:
1.假设有两种不同开发环境
2.在application.yaml中调用profiles,多环境配置
#激活dev的配置文件,以这个配置文件来启动
#不管是什么配置环境,如果配置是一样的,就直接在application.yaml中输写就好了。
#最终使用的是当前配置文件+引入的(激活的会覆盖公共的)
spring:
profiles:
active: dev
方式二:yaml文件提供的方法
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
---
server:
port: 8083
spring:
profiles: test
三.springboot的配置文件
1.properties文件来定义数据
情况一:在application.properties文件中直接写,因为在运行服务器的时候,首先调用的是application配置文件中的配置信息
book.name=潘权荣
book.price=88
book.args[0]=ok1
book.args[1]=ok2
book.person.name=欧克
book.person.age=18
book.list[0].name=okboy
book.list[0].age=10
@Component
//该注解是会自动去spring容器中查找前缀是book的属性
@ConfigurationProperties(prefix = "book")
public class Book {
private String name;
private Integer price;
private String[] args;
//新发现,当类不是被实例化的时候可以不需要@Component,不需要注入到spring容器中,因为只是引用数据类型
private Person person;
private List<Person> list;
情况二:在别的properties文件下调用上面的数据
@Component
//导入properties文件,因为不是写在配置文件上的,Spring容器不会自动去找的
@PropertySource("classpath:book.properties")
//该注解是会自动去spring容器中查找前缀是book的属性
@ConfigurationProperties(prefix = "book")
public class Book {
private String name;
private Integer price;
private String[] args;
//新发现,当类不是被实例化的时候可以不需要@Component,不需要注入到spring容器中,因为只是引用数据类型
private Person person;
private List<Person> list;
2.yaml
特征:和properties文件不一样的是书写方式,更规范了,写在application.yaml文件中
book:
name: 潘权荣
price: 88
args:
- ok
- niubi
person:
name: 潘权荣
age: 18
list:
- name: okboy
age: 19
- name: niuok
age: 18
四.引擎模板
1.Freemarker页面模板
和jsp输写很相似,但是语法不太一样,老牌模板引擎,支持非web环境下使用
jsp中jstl的循环
<c:forEach items="集合,支持EL表达式" var = "集合中的元素"
varStatus="元素的状态对象">${元素状态对象:count}</c:forEach>
<table border="1px" >
<tr>
<td>用户名</td>
<td>用户密码</td>
<td>用户生日</td>
</tr>
//list相当于foreach,u相当于items="u",us相当于var="us"
<#list u as us>
<tr>
<td>${us.id}</td>
<td>${us.username}</td>
<td>${us.brithday?string("yyyy-MM-dd")}</td>
</tr>
</#list>
</table>
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2.freemarker配置
#配置freemarker模板路径,类似于jsp配置的prefix,默认位置是classpath:templates/
spring.freemarker.template-loader-path=classpath:templates/
#模板文件的后缀
spring.freemarker.suffix=.ftlh
#是否将session中的属性自动加入到model中,这样freemarker页面就可以渲染出session中的数据
spring.freemarker.expose-session-attributes=true
#是否将request中的属性自动加入到model中,这样freemarker页面就可以渲染出request中的数据
spring.freemarker.expose-request-attributes=true
#当session和model的数据同名时,是否覆盖model
spring.freemarker.allow-session-override=true
#当request和model的数据同名时,是否覆盖model
spring.freemarker.allow-request-override=true
#是否开启缓存
spring.freemarker.cache=true
spring.freemarker.charset=UTF-8
#是否检查模板的位置,在项目运行时就检查,避免后面运行页面时才说没有这个位置
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
#开启freemarker
spring.freemarker.enabled=true
3.常用指令
1.?? 判断是否为空
??表示我们之前写的hello!=null,用来判断是否为空的
<#if hello??>
${"不是为空哦"}
</#if>
${hello!"为空就显示我"}
2.定义一个变量
注意:?是调用方法
<#--定义变量-->
<#assign a=99>
${a}<br>
<#--以百分号的形式-->
${a?string.percent}
<#--以美元的形式-->
${a?string.currency}
3.显示字符
注意:r是可以自动帮转义,多一个斜杠
${"hello,潘权荣"}
${"D:\\user\\ok"}
<#--自动帮转义,多一个\斜杠-->
${r"D:\user\ok"}
4.boolean
<#--无法显示boolean,只能通过字符串的形式-->
<#assign b=false>
${b?string("true","false")}
5.noparse不解析
<#--里面的内容可以不需要解析-->
<#noparse></#noparse>
6.if/else
<#--当后端传过来的数据u不等于空,并且元素大于0的时候才显示-->
<#if u?? && (u?size>0)>
<table border="1px" >
<tr>
<td>用户名</td>
<td>用户密码</td>
<td>用户生日</td>
</tr>
<#list u as us>
<#-- 只想获得五条数据,以满足就停止-->
<#if (us_index>5)>
<#break>
</#if>
<tr>
<td>${us.id}</td>
<td>${us.username}</td>
<td>${us.brithday?string("yyyy-MM-dd")}</td>
</tr>
</#list>
</table>
</#if>
4.#import不同ftlh数据引用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<#import './book.ftlh' as com>
<#--com是文件的别名,book是引用该文件里的取名,{}里的是将里面的数据传输到book.ftlh文件的别名里,然后根据别名的属性进行赋值-->
<@com.book{"name":"潘权荣的快乐时光","price":88}></@com.book>
</body>
</html>
2.thymeleaf页面模板
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.常用指令
@Controller
public class UserController {
@GetMapping("/users")
public String users(Model model, HttpSession session) {
session.setAttribute("name","王五");
List<User> us = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User u = new User();
u.setId(i);
u.setUsername("lisi:" + i);
u.setAge(99);
us.add(u);
}
model.addAttribute("us", us);
Map<String, String> book = new HashMap<>();
book.put("name", "三国演义");
book.put("author", "罗贯中");
model.addAttribute("book", book);
model.addAttribute("imgUrl", "https://wx4.sinaimg.cn/mw2000/0015RU9ely1h71b1pi8x2j651c3s0tzv02.jpg");
model.addAttribute("imgAlt", "awaaaaa");
model.addAttribute("imgTitle", "bbbbbbbbbb");
model.addAttribute("myhtml", "<div style='color: red;background-color: antiquewhite'>hello thymeleaf</div>");
return "users";
}
}
1.循环
<table border="1">
<tr>
<td>id</td>
<td>用户名</td>
<td>用户年龄</td>
<td>是否奇数行</td>
</tr>
<tr th:each="u,s : ${us}">
<td th:text="${u.id}"></td>
<td th:text="${u.username}"></td>
<td th:text="${u.age}"></td>
<td th:text="${s.odd}"></td>
</tr>
</table>
2.map中读取
方式一:
<div>
<div th:text="${book.name}"></div>
<div th:text="${book.author}"></div>
</div>
方式二
<div th:object="${book}">
<div th:text="*{name}"></div>
<div th:text="*{author}"></div>
</div>
3.赋变量值
<img th:src="${imgUrl}" th:alt="${imgAlt}" th:title="${imgTitle}">
4.session中获取值
<div th:text="${#session.getAttribute('name')}"></div>
5.渲染变量,不是单纯输出
<div th:utext="${myhtml}"></div>
<div>[(${myhtml})]</div>
单纯输出
<div>[[${myhtml}]]</div>
6.特殊
//上下文路径
<div th:text="${#servletContext.getContextPath()}"></div>
//访问路径
<div th:text="${#execInfo.getProcessedTemplateName()}">
其他页面
7.获取其他页面
<div th:replace="links.html"></div>
8.获取其他页面的单独路径
<div th:replace="~{links.html::baidu}"></div>
3.配置
# 是否开启缓存
spring.thymeleaf.cache=true
# 提前检查模板是否存在
spring.thymeleaf.check-template=true
# 提前检查模板位置是否存在
spring.thymeleaf.check-template-location=true
# 模板位置
spring.thymeleaf.prefix=classpath:templates/
server.servlet.context-path=/aa
3.jsp页面模板
1.依赖
<!--这是支持 JSP 的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
freemarker和thymeleaf的页面默认写在resources/templates下,而jsp只能在src/main/webapp/jsp,就是一定要在webapp下
2.jsp的视图解析器配置
方式一:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp().prefix("/jsp/")
.suffix(".jsp");
}
}
方式二:
# 配置 JSP 视图的前后缀
spring.mvc.view.prefix=/jsp/
spring.mvc.view.suffix=.jsp
4.Freemarker,thymeleaf,jsp的整合
step01-导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--这是支持 JSP 的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
step02-判断是否存在jsp页面
public class MyJspView extends InternalResourceView {
/**
* 这个方法用来检查 JSP 视图文件是否真正存在,默认情况下,这个方法返回值总是为 true
* @param locale
* @return
* @throws Exception
*/
@Override
public boolean checkResource(Locale locale) throws Exception {
File file = new File(this.getServletContext().getRealPath("/") + getUrl());
return file.exists();
}
}
step03-增加jsp的视图解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp()
.prefix("/jsp/")
.suffix(".jsp")
//指定 JSP 使用的视图解析器
.viewClass(MyJspView.class);
registry.order(1);
}
}
注意:因为freemarker优先级和thymeleaf的优先级是一样高的,但因为freemarker的jar包在上面所以要先执行,jsp的优先级是最低的。freemarker判断resources/templates有没有自己的资源,如果没有就不理了,但thymeleaf判断有没有页面,如果有,即便不是自己的html,也会自动加载。所以为了能够整合jsp,就得将jsp设置最高优先级,还得判断jsp有没有相关的页面。
五.Json
1.jackson
不用导入依赖,因为springboot默认导入了json依赖
step01-实体类
public class User {
private Integer id;
private String username;
//方法一:用注解配置json
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
private Date birthday;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
step02-controller
@RestController
public class UserController {
@GetMapping("/u")
public User getUserById() {
User u = new User();
u.setId(99);
u.setUsername("zhangsan");
u.setBirthday(new Date());
return u;
}
}
step03-config配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
//方法三,要继承WebMvcConfigurer
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//方式一
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
ObjectMapper om = new ObjectMapper();
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
((MappingJackson2HttpMessageConverter) converter).setObjectMapper(om);
}
}
//方式二
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper om = new ObjectMapper();
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converter.setObjectMapper(om);
converters.add(0,converter)
}
//方法四
@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper om = new ObjectMapper();
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converter.setObjectMapper(om);
return converter;
}
//方法二
// @Bean
// ObjectMapper objectMapper() {
// ObjectMapper om = new ObjectMapper();
// om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
// return om;
// }
}
2.gson
step01-要换一下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
step02-config
@Configuration
public class WebConfig {
@Bean
GsonBuilder gsonBuilder() {
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat("yyyy-MM-dd");
builder.setPrettyPrinting();
return builder;
}
}
3.fastson
step01-依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.14</version>
</dependency>
step02-config
@Configuration
public class WebConfig {
@Bean
FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
converter.setDefaultCharset(Charset.forName("UTF-8"));
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setDateFormat("yyyy-MM-dd");
converter.setFastJsonConfig(fastJsonConfig);
return converter;
}
}
六.系统任务(类似于监听器)
1.实现CommandLineRunner
step01-第一个系统启动任务
/**
* 系统启动任务:当系统启动的时候,会自动触发执行的代码
*
* 如果存在多个系统启动任务,则可以通过 @Order 注解来设置不同启动任务的优先级
*
*/
@Component
@Order(1)
public class MyCommandLineRunner01 implements CommandLineRunner {
/**
* 系统启动时,执行的方法
* @param args 这个参数其实就是系统启动时候 main 方法的参数
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner01 = " + Arrays.toString(args));
}
}
step02-第二个系统启动任务
/**
* 系统启动任务:当系统启动的时候,会自动触发执行的代码
*
* 如果存在多个系统启动任务,则可以通过 @Order 注解来设置不同启动任务的优先级
*
*/
@Component
@Order(2)
public class MyCommandLineRunner02 implements CommandLineRunner {
/**
* 系统启动时,执行的方法
* @param args 这个参数其实就是系统启动时候 main 方法的参数
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner02 = " + Arrays.toString(args));
}
}
step03
2.实现ApplicationRunner
/**
* 另外一种系统启动任务的定义方式
* <p>
* 系统启动的时候,传递的参数有两种形态:
* <p>
* 1. java -jar xxx.jar zhangsan lisi wangwu
* <p>
* 上面这种,参数不带 key,CommandLineRunner 主要处理这种不带 key 的参数
* <p>
* 2. java -jar xxx.jar --name=zhangsan --age=99
* <p>
* 这种是参数带 key 的
* <p>
* ApplicationRunner 则两种形式的参数都可以处理
*/
@Component
@Order(-1)
public class MyApplicationRunner implements ApplicationRunner {
/**
* 系统启动时候,这个方法会被触发
*
* @param args 这个参数是一个对象,对象可以描述更多信息
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) throws Exception {
//获取没有 key 的参数,这个就类似于 CommandLineRunner
List<String> nonOptionArgs = args.getNonOptionArgs();
System.out.println("nonOptionArgs = " + nonOptionArgs);
//获取有 key 的参数的所有 key
Set<String> optionNames = args.getOptionNames();
for (String optionName : optionNames) {
List<String> optionValues = args.getOptionValues(optionName);
System.out.println(optionName + "--->" + optionValues);
}
//获取所有的参数,无论有 key 无 key
String[] sourceArgs = args.getSourceArgs();
System.out.println("sourceArgs = " + Arrays.toString(sourceArgs));
}
}
七.静态资源的访问
如果是跟web相关的,都可以去访问WebMvcAutoConfiguration看看
1.自定义静态资源的访问路径和文件路径
方式一
#静态资源的路径
spring.web.resources.static-locations=classpath:/pan/
#访问的路径
spring.mvc.static-path-pattern=/1/**
方式二:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//addResourceHandler是浏览器访问的路径
registry.addResourceHandler("/pan/**")
//addResourceLocations静态资源访问的路径
.addResourceLocations("/1/");
}
}
八.异常处理机制
1.spring提供的处理异常
方式一:实现HandlerExceptionResolver
@Component
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView("01");
mv.addObject("error", ex.getMessage());
return mv;
}
}
方式二:使用注解@RestControllerAdvice(强调,出现异常就立马执行)和@ExceptionHandler
@RestControllerAdvice
public class GlobalException2{
@ExceptionHandler(ArithmeticException.class)
//异常抛出的时候Http的状态码是200的,当你需要反应的是500,需要设置 HTTP 响应状态码为 500
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String resolveException(Exception ex) {
return "error->"+ex.getMessage();
}
}
2.springboot提供的处理异常
方式一:继承DefaultErrorAttributes,是自定义异常数据(传输的是数据),如果没有配置DefaultErrorViewResolver,就会访问的是springboot自带的
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> map = super.getErrorAttributes(webRequest, options);
//加多一条数据
map.put("msg", "出错啦");
return map;
}
}
还要再核心配置文件中启动这四个参数,前端才会显示
#spring.thymeleaf.prefix=classpath:qf/
# 是否在默认的异常数据中包含 exception 属性
server.error.include-exception=true
# http://localhost:8080/hello?message=false
server.error.include-message=on_param
server.error.include-binding-errors=always
server.error.include-stacktrace=always
方式二:继承DefaultErrorViewResolver,是springboot底层实现静态文件error的方法(传输的是视图界面)
@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
/**
* Create a new {@link DefaultErrorViewResolver} instance.
*
* @param applicationContext the source application context
* @param resources resource properties
* @since 2.4.0
*/
public MyErrorViewResolver(ApplicationContext applicationContext, WebProperties webProperties) {
super(applicationContext, webProperties.getResources());
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView mv = new ModelAndView("qf/02",model,status);
return mv;
}
}
页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>02</h1>
<table border="1">
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>exception</td>
<td th:text="${exception}"></td>
</tr>
<tr>
<td>msg</td>
<td th:text="${msg}"></td>
</tr>
</table>
</body>
</html>
九.springboot整合jdbctemplate
step01-jdbc的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
step02-在springboot的核心配置文件中加入数据源
spring.datasource.url=jdbc:mysql:///java2207?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
step03-测试
@SpringBootTest
class JdbctemplateApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
for (User user : list) {
System.out.println("user = " + user);
}
}
@Test
public void test01() {
int i = jdbcTemplate.update("insert into user (username,age) values (?,?);", "wangwu", 100);
System.out.println("i = " + i);
}
}
十.springboot整合mybatis
springboot整合不需要像之前spring整合一样需要配置datasource,sqlSessionFactoryBean,MapperScannerConfigurer等类,只需要在配置文件中加入数据源
step01-依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
Spring 官方提供的 starter,命名模式一般都是 spring-boot-starter-xxx
第三方的 starter,命名模式一般都是 xxx-spring-boot-starter
-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
#如果mapper接口和mapper.xml放在同一个包下的话,就得配置资源的路径
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
step02-springboot的配置文件
#直接在配置数据源,然后springboot会自动配置SqlSessionFactoryBean
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///test02?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
#springboot默认的是mapper接口和mapper.xml放在同一个包下,如果不在同一个包下,就得配置下,否则无法找到xml文件
#<mappers>
# <!--执行该xml文件的路径 -->
# <mapper resource="UserMapper.xml"/>
#<!-- 指定Mapper所在的包,配置完成后,会自动找到Mapper接口,然后根据接口名称找到对应的xml文件-->
#<!-- <package name="com.pan.dao"/>-->
# </mappers>
mybatis.mapper-locations=classpath*:/mapper/*.xml
#实体类的别名
mybatis.type-aliases-package=com.qfedu.mybatis_demo.model
step03-识别mapper接口
相当于
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer">
<property name="basePackage" value="com.qf.mapper"></property>
</bean>
方式一:单个识别,在mapper接口中加@Mapper
@Mapper
public interface AccountMapper {
List<Account> getAllAccount();
}
方式二:整个mapper包扫描,在启动类中加@MapperScan(basePackages = "com.pan.mybatis.mapper")
@SpringBootApplication
@MapperScan(basePackages = "com.pan.mybatis.mapper")
public class MybatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisDemoApplication.class, args);
}
}
十一.springData-jpa( Java Persistence API )
JPA 即Java Persistence API。
JPA 是一个基于O/R映射的标准规范(目前最新版本是JPA 2.1 )。所谓规范即只定义标准规则(如注解、接口),不提供实现,软件提供商可以按照标准规范来实现,而使用者只需按照规范中定义的方式来使用,而不用和软件提供商的实现打交道。
JPA的出现有两个原因:
简化现有Java EE和Java SE应用的对象持久化的开发工作;
Sun希望整合对ORM技术,实现持久化领域的统一。JPA 的主要实现有Hibernate、EclipseLink 和OpenJPA 等,这也意味着我们只要使用JPA 来开发,无论是哪一个开发方式都是一样的。
一句话总结:直接通过一个实体类来创建数据库表
1.实体类生成数据库表
step01-依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
step02-配置文件
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///test02?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
#jpa的配置,告诉springboot是mysql数据库
spring.jpa.database-platform=mysql
spring.jpa.database=mysql
#在控制台打印出来自动生成的sql
spring.jpa.show-sql=true
#自动的根据项目的实体类来生成表,
#update:表示每一次启动的时候,就去数据库中查看,如果有更新,就修改
#create:表示每一次创建的时候,都先销毁,在重新创建
#create-drop:表示当项目启动时创建,结束时就销毁
spring.jpa.hibernate.ddl-auto=update
#配置数据库方言,我们一般使用的是InnoDB,默认的是MyISAM存储引擎,所以要配置一下
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
step03-在实体类中设置
/**
* @Entity(name = "t_user"):告诉java,通过该实体类上创建一个数据库表,类名就是表名,也可以通过name来指定表名称,这个数据库的表名为t_user
*/
@Entity(name = "t_user")
public class User {
//@Id:该注解的含义是,这是一个主键
@Id
//Generated:产生,strategy策略
//@GeneratedValue(strategy = GenerationType.IDENTITY):配置主键自增
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// @Column(name = "name")定义在数据库表中该属性的名称
@Column(name = "name",unique = true)
private String username;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
step04-执行启动类
2.crun表中的数据
1.jpa提供的sql
step01-dao层继承JpaRepository
//泛型第一个参数是实体类对象,第二个参数是主键的类型
public interface UserDao extends JpaRepository<User,Integer> {
}
step02-直接使用userdao提供的方法
@Autowired
UserDao userDao;
@Test
void test04(){
userDao.deleteById(1);
}
@Test
void test03(){
List<User> all = userDao.findAll();
System.out.println(all);
//参数是页码和每一页的记录数,注意的是,页码是从0开始读取
PageRequest page = PageRequest.of(1, 2);
Page<User> pa = userDao.findAll(page);
//是否是第一页
System.out.println("pa.isFirst() = " + pa.isFirst());
//是否是最后一页
System.out.println("pa.isLast() = " + pa.isLast());
//当前页码
System.out.println("pa.getNumber() = " + pa.getNumber());
//当前页的记录数
System.out.println("pa.getNumberOfElements() = " + pa.getNumberOfElements());
//查询到的数据
System.out.println("pa.getContent() = " + pa.getContent());
//总页数
System.out.println("pa.getTotalPages() = " + pa.getTotalPages());
//总记录数
System.out.println("pa.getTotalElements() = " + pa.getTotalElements());
}
/**
* 更新的时候,先查询后更新
*/
@Test
void text02(){
Optional<User> op = userDao.findById(1);
User user = op.get();
user.setUsername("欧克");
userDao.saveAndFlush(user);
}
@Test
void text01(){
User user = new User();
user.setId(2);
user.setUsername("欧克1");
//如果该数据没有就插入,如果有就更新,如果写漏了一个,就直接赋值为null
userDao.saveAndFlush(user);
}
//save可以是加入,也可以是修改,以id来进行修改
@Test
void contextLoads() {
User user = new User();
user.setUsername("潘权荣");
user.setAddress("聚龙聚");
userDao.save(user);
}
2.jpa提供的方法名
step01-在dao层里写方法名
//泛型第一个参数是实体类对象,第二个参数是主键的类型
public interface UserDao extends JpaRepository<User,Integer> {
//通过姓氏和地址来查询
List<User> findAllByUsernameStartingWithAndAddressIs(String username,String address);
}
step02-测试
@Autowired
UserDao userDao;
@Test
void test11(){
List<User> all = userDao.findAllByUsernameStartingWithAndAddressIs("潘", "茂名");
for (User user : all) {
System.out.println("user = " + user);
}
}
3.自定义sql
step01-在dao层自定义sql
注意:查询只需要提供@Query(value="sql语句",nativeQuery = true)就行了;增删改还需要@Modifying
public interface StudentDao extends JpaRepository<Student,Integer> {
/**
* 根据id来修改name,会有参数,填入参数是按顺序的
* 有两种方法:name=?1 where id=?2,这种不需要@Param,直接表示第一个参数和第二个参数
* name=:name where id=:id,这种需要@Param
* @param name
* @param sid
* @return
*/
@Query(value = "update student set name=:name where sid=:sid",nativeQuery = true)
//如果涉及到数据的更新(包含修改,删除,增加)还得加以下注解
@Modifying
int updateName(@Param("name") String name,@Param("sid") Integer sid);
/**
* jpql:jpa
* hql
* 当springdata没有给予我们特殊的方法时,我们可以自定义sql来使用
* nativeQuery = true:表示使用我们写的原生sql来查询,false表示不是原生的sql,而是JPQL,查询对象的
* @return
* 查询最大的id用户
*/
@Query(value = "select * from student where sid=(select max(sid) from student);",nativeQuery = true)
Student maxIdStudent();
//根据姓氏查询
List<Student> findStudentByNameStartingWith(String username);
//根据name开头和性别
List<Student> findStudentByNameStartingWithAndGenderEquals(String username,String gender);
}
step02-测试自定义的sql语句
注意:增删改需要增加事务@Transactional,否则会报没有事务的异常,还需要加取消自动回滚@Rollback(false),默认会自动回滚是因为在测试的时候避免脏读
@Autowired
StudentDao studentDao;
@Test
//加在单元测试上的事务,执行成功之后会自动回滚,在执行修改操作的时候,需要加事务,否则会报没有事务的异常
@Transactional
//表示单元测试的时候,不用自动回滚
@Rollback(false)
void test08(){
int i = studentDao.updateName("潘权荣", 6);
System.out.println(i);
}
@Test
void test07(){
Student student = studentDao.maxIdStudent();
System.out.println(student);
3.多表关联
1.一对一或者多对一
注意:一对一或者多对一都是在该实体类生成的表中多一个字段,它会默认生成一个表名_id的字段,如果想要自定义的需要通过@JoinColumn(name = "cid",referencedColumnName = "cid")和加多一个属性
step01-student01实体类关联clazz实体类
@Entity(name = "t_student")
public class Student01 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer sid;
private String username;
private String address;
//一对一,cascade = CascadeType.ALL:在student表进行数据处理的时候,顺便将clazz表中的数据也进行一并处理了,
// CascadeType.REFRESH,刷新
@OneToOne(cascade = CascadeType.REFRESH)
// @ManyToOne(cascade = CascadeType.ALL)
/**
* name中的cid指的是clazz中的cid属性
* referencedColumnName中的cid是当前类cid属性
*/
@JoinColumn(name = "cid",referencedColumnName = "cid")
private Clazz clazz;
/**
* 自定义一个字段来维护Student01和clazz的一对一关系
* 由于这个 cid 属性将来是外键(用来关联 clazz 的),所以,这个 cid 属性是不需要进行插入操作和更新操作的
*/
@Column(insertable = false,updatable = false)
private Integer cid;
}
clazz类
@Entity(name = "t_clazz")
public class Clazz {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer cid;
private String name;
//一对多,CascadeType.MERGE存储,CascadeType.PERSIST:持久化
@OneToMany(cascade = CascadeType.PERSIST,fetch = FetchType.EAGER)
private List<Student01> students;
}
step02-测试
@Autowired
Student01Dao student01Dao;
@Autowired
ClazzDao clazzDao;
//设置在一个表中添加数据,另一个表也能跟着加,但是如果与clazz的数据相同的就没必要添加了
@Test
void test09(){
Student01 student01 = new Student01();
student01.setAddress("聚龙居");
student01.setUsername("潘权荣");
// Clazz clazz = new Clazz();
Clazz clazz = clazzDao.findById(1).get();
clazz.setName("博一");
student01.setClazz(clazz);
student01Dao.save(student01);
}
2.一对多
注意:一对多会产生多一个中间表来关联两个表的数据
step01-Clazz关联Student01
@Entity(name = "t_clazz")
public class Clazz {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer cid;
private String name;
//一对多,CascadeType.MERGE存储,CascadeType.PERSIST:持久化
@OneToMany(cascade = CascadeType.PERSIST,fetch = FetchType.EAGER)
private List<Student01> students;
}
Student01类
@Entity(name = "t_student")
public class Student01 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer sid;
private String username;
private String address;
}
step02-测试
@Autowired
ClazzDao clazzDao;
@Test
void test10(){
Clazz clazz = new Clazz();
clazz.setName("饿呢");
List<Student01> list=new ArrayList<>();
Student01 s1 = new Student01();
s1.setAddress("上海");
s1.setUsername("荣");
s1.setClazz(clazz);
Student01 s2 = new Student01();
s2.setClazz(clazz);
s2.setUsername("欧克");
s2.setAddress("北京");
list.add(s1);
list.add(s2);
clazz.setStudents(list);
clazzDao.save(clazz);
}
注意:一对一,多对一,一对多可以同时存在
十二.springData-Jpa-Rest
rest是基于Jpa进行开发的,无法在mybatis中使用
step01-依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
step02-配置文件
spring.datasource.url=jdbc:mysql:///jpa_demo?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=1234
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.database-platform=mysql
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
#在地址栏中输入该参数:http://localhost:8080/api/users?page=1&size=2&sort=id,desc
# 是否在添加成功之后,返回数据
spring.data.rest.return-body-on-create=true
# 是否在更新成功之后,返回数据
spring.data.rest.return-body-on-update=true
# 给所有的请求地址加一个统一的前缀
spring.data.rest.base-path=/api
# 分页的最大限制,因为正常来说用户一般不会浏览那么多的数据,所以为了缓解服务器的压力
spring.data.rest.max-page-size=100
# 默认每页的记录数
spring.data.rest.default-page-size=10
# 默认的分页参数的页码
spring.data.rest.page-param-name=page
# 默认的分页参数每页的记录数
spring.data.rest.limit-param-name=size
# 默认的排序的参数
spring.data.rest.sort-param-name=sort
step03-User类
@Entity(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
step04-dao层
/**
path:访问地址栏的参数是http://localhost:8080/api/people?page=1&size=2&sort=id,desc,默认为小写的表名+s
*/
@RepositoryRestResource(path = "people",collectionResourceRel = "us",itemResourceRel = "u")
public interface UserDao extends JpaRepository<User, Integer> {
/**
* exported 属性表示是否暴露当前接口,如果暴露,则可以在前端请求该接口,如果该方法只是一个普通的 dao 层方法,只是在 service 层进行调用,则可以设置这个属性为 false
* @param username
* @return
* path的值会顶替方法名,到时在地址栏想要调用该方法得使用byname
*/
@RestResource(path = "byname",rel = "根据用户名查询用户")
List<User> findUserByUsernameStartingWith(@Param("username") String username);
}
十三.定时任务
* cron:cron 表达式
* https://www.matools.com/cron
* https://cron.qqe2.com/
1.springboot提供的定时任务
只需要加web依赖就🆗了
step01-需要在启动类上加@EnableScheduling
@SpringBootApplication
//开启定时任务
@EnableScheduling
public class ScheduledApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduledApplication.class, args);
}
}
step02-假设在service中弄一个定时任务
@Service
public class HelloService {
/**
* @Scheduled 定时任务注解
*
* fixedRate:定时任务执行的速率,例如设置 1s,则定时任务每隔 1s 执行一次(相当是两个定时任务启动时间间隔为 1s)
* fixedDelay:定时任务的延迟时间,例如设置 1s,表示上一个任务执行结束 1s 之后,下一个任务开始(相当于上一个定时任务结束时间和下一个定时任务启动时间之间的间隔为 1s)
* initialDelay:初始化的延迟时间,默认情况下,系统启动之后,定时任务就开始执行了,initialDelay 属性可以设置定时任务第一次执行的延迟时间
*
*
*/
@Scheduled(cron = "0/5 24 * * * ?")
public void hello() {
System.out.println(new Date());
}
}
2.quartz框架实现定时任务
step01-依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<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>
</dependencies>
step02-在启动类加定时器的注解
@SpringBootApplication
@EnableScheduling
public class QuartzApplication {
public static void main(String[] args) {
SpringApplication.run(QuartzApplication.class, args);
}
}
step03-定义两个作业
/**
* 作业的第一种方式
*/
public class MyJoy01 extends QuartzJobBean {
String username;
public void setUsername(String username) {
this.username = username;
}
/**
* 当执行定时任务的时候,当前方法会被自动触发
* @param context
* @throws JobExecutionException
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println(username+"第一种"+new Date());
}
}
/**
* 第二种方式,将类存入到Spring容器中
*/
@Component
public class MyJoy02 {
public void hello(){
System.out.println("第二种"+new Date());
}
}
step04-配置作业,配置触发器,启动quartz
**
* 1.配置作业
* 2.配置触发器
* 3.配置quartz的启动
*/
@Configuration
public class QuartzConfig {
/**
* MethodInvokingJobDetailFactoryBean只能适用于Spring容器注入的Bean的作业
* @return
*/
@Bean
MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
bean.setTargetBeanName("myJoy02");
bean.setTargetMethod("hello");
return bean;
}
/**
* 只需要写类就行了,不用写方法,因为是继承的类,调用的重写方法
* @return
*/
@Bean
JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean bean = new JobDetailFactoryBean();
bean.setJobClass(MyJoy01.class);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("username","ok");
bean.setJobDataMap(jobDataMap);
return jobDetailFactoryBean;
}
/**
* 配置触发器
*/
@Bean
SimpleTriggerFactoryBean simpleTriggerFactoryBean(){
SimpleTriggerFactoryBean Bean = new SimpleTriggerFactoryBean();
//绑定的作业
Bean.setJobDetail(jobDetailFactoryBean().getObject());
//重复次数
Bean.setRepeatCount(3);
//间隔
Bean.setRepeatInterval(3000);
//启动时间
Bean.setStartTime(new Date());
return Bean;
}
@Bean
CronTriggerFactoryBean cronTriggerFactoryBean(){
CronTriggerFactoryBean Bean = new CronTriggerFactoryBean();
Bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
Bean.setCronExpression("0/3 * * * * ?");
return Bean;
}
//开启定时任务
@Bean
SchedulerFactoryBean schedulerFactoryBean(){
SchedulerFactoryBean Bean = new SchedulerFactoryBean();
Bean.setTriggers(simpleTriggerFactoryBean().getObject(),cronTriggerFactoryBean().getObject());
return schedulerFactoryBean;
}
}
3.xxl-job定时任务,elasticjob定时任务
都是动态的,别人写的,与上面的不一样的是,这两个是可以编辑的,上面两个是项目跑起来就会启动定时任务,无法停止和编辑
十四.springboot中web组件的整合
1.拦截器的使用
//拦截器,不太懂
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
2.导入另写的配置文件
不用springboot的配置文件,自己写一个配置文件将类注入到spring容器
@ImportResource("classpath:beans.xml")
3.使servlet,filter,interceptor的注解有效
//这个用来扫描基础的 web 组件,例如 servlet,filter 以及 listener,也得加上他们对应的@WebServlet()
//在启动类中使用该注解
@ServletComponentScan
4.设置filter优先级有三种方式
/**
* SpringBoot 中注册过滤器:
*
* 1. @WebFilter+@ServletComponentScan:这种方式的优点是可以设置过滤器的拦截路径,缺点是无法设置过滤器的优先级
* 2. @Order()+@ServletComponentScan:启动类的优先级设置,直接将过滤器注册到 Spring 容器中即可:这种方式的有点是可以设置优先级,但是无法设置拦截路径(默认的拦截路径都是 /*)
* 3. 如果既要设置优先级,又要设置拦截路径,也是可以的
*/
方式一:
@WebFilter(urlPatterns = "/*")
方式二:
@Order(2)
@Component
方式三:
@Configuration
@ImportResource("classpath:beans.xml")
public class WebConfig implements WebMvcConfigurer {
//跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081");
}
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**");
}
//设置拦截规则和拦截的优先级
@Bean
FilterRegistrationBean<MyFilter01> myFilter01() {
FilterRegistrationBean<MyFilter01> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter01());
bean.setOrder(99);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
@Bean
FilterRegistrationBean<MyFilter02> myFilter02() {
FilterRegistrationBean<MyFilter02> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter02());
bean.setOrder(1);
bean.setUrlPatterns(Arrays.asList("/aa"));
return bean;
}
}
十五.springbootAop多数据源配置(模仿mybatis-plus的源码)
思路:当系统需要获取一个数据源的时候,会自动去AbstractRoutingDataSource中获取数据源,相当于数据源的一个路由中介。
我们将所有的数据源提前准备好,然后我们想用哪个数据源,可以写在service层方法的自定义注解中,这时,当service层的方法被调用的时候,就会被自动的拦截下来,拦截下来之后,我们获取到目标方法上的注解的值,将其存入到ThreadLocal中,下一步系统去AbstractRoutingDataSource中查找数据源的时候,就从ThreadLocal中获取到数据源的名称。
step01-依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--aop面向切面的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--这是spring中druid的依赖,如果不行可以去maven中找springboot给予druid的依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<!--mysql驱动的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
step02-配置文件,配置多个数据源
在此之前先将实体User,mapper,service写好
spring:
datasource:
#初始大小
initSize: 10
#最大限制
maxSize: 100
#连接池中的最小空闲连接数
minIde: 8
#自定义的key,用来存储多个数据源
ds:
#主
master:
username: root
password: 1234
url: jdbc:mysql:///test03?serverTimezone=Asia/Shanghai
#辅
slave:
username: root
password: 1234
url: jdbc:mysql:///test04?serverTimezone=Asia/Shanghai
step03-构造配置文件中ds的封装类
//配置文件的前缀输写
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private Integer initSize;
private Integer maxSize;
private Integer minIde;
//ds是以key-value的形式输写的
private Map<String, Map<String, String>> ds;
//这个方法是将初始化大小,最大限制,最小空闲数直接赋值给创建的DruidDataSource,为了让initSize,maxSize,minIde生效
public DataSource build(DruidDataSource druidDataSource) {
druidDataSource.setInitialSize(initSize);
druidDataSource.setMinIdle(minIde);
druidDataSource.setMaxActive(maxSize);
return druidDataSource;
}
public Integer getInitSize() {
return initSize;
}
public void setInitSize(Integer initSize) {
this.initSize = initSize;
}
public Integer getMaxSize() {
return maxSize;
}
public void setMaxSize(Integer maxSize) {
this.maxSize = maxSize;
}
public Integer getMinIde() {
return minIde;
}
public void setMinIde(Integer minIde) {
this.minIde = minIde;
}
public Map<String, Map<String, String>> getDs() {
return ds;
}
public void setDs(Map<String, Map<String, String>> ds) {
this.ds = ds;
}
}
step04-加载所有的数据源,并且将数据源的属性配置好
public interface DynamicDataSourceProvider {
String DEFAULT_DATASOURCE = "master";
/**
* 加载所有的数据源
* @return
*/
Map<Object, Object> loadDatasource();
}
实现上面写的接口,并且将数据源的属性配置好,返回所有在yaml文件中配置好的数据源
@Component
//相当于将properties文件注入进来
@EnableConfigurationProperties(DruidProperties.class)
public class YamlDynamicDataSourceProvider implements DynamicDataSourceProvider {
@Autowired
DruidProperties druidProperties;
@Override
public Map<Object, Object> loadDatasource() {
//有多少个数据源map就有多大
Map<Object, Object> dataSourceMap = new HashMap<>(druidProperties.getDs().size());
try {
//获取的是ds
Map<String, Map<String, String>> ds = druidProperties.getDs();
Set<String> keySet = ds.keySet();
//循环的是主,辅
for (String key : keySet) {
//获取的是数据源的key名字,map存放的是数据源的username,password,url
Map<String, String> map = ds.get(key);
//创建一个druid数据源
DataSource dataSource = DruidDataSourceFactory.createDataSource(map);
//设置这个数据源的初始化大小,最大限制,最小空闲数
DataSource source = druidProperties.build((DruidDataSource) dataSource);
//返回所有已经配置好了的数据源
dataSourceMap.put(key, source);
}
} catch (Exception e) {
e.printStackTrace();
}
return dataSourceMap;
}
}
step05-设置ThreadLocal
当项目获取数据源的时候,获取的是同一个数据源,防止出现线程不安全的局面
/**
* 这个类里边,用来存储当前所使用的数据源的名称
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> DATA_SOURCE_THREAD_LOCAL = new ThreadLocal<>();
public static void set(String dataSource) {
DATA_SOURCE_THREAD_LOCAL.set(dataSource);
}
public static String get() {
return DATA_SOURCE_THREAD_LOCAL.get();
}
public static void remove() {
DATA_SOURCE_THREAD_LOCAL.remove();
}
}
step06-自定义注解
/**
* 自定义注解,这个注解将来加在 service 层的方法上,用来标记一个方法在执行的过程中,使用哪个数据源
* 这个注解也可以加在类上面,表示类中的所有方法使用某一个数据源
* 如果方法和类上面都有该注解,那么就以方法上的注解为准
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
//这个属性用来指定具体的数据源,如果不指定,只加一个注解,表示使用 master 这个数据源
String value() default DynamicDataSourceProvider.DEFAULT_DATASOURCE;
}
step07-设置数据源切面,获取注解上的数据源,将数据源放入到ThreadLocal
/**
* 数据源切面,专门用来解析 @DataSource 注解
*/
@Component
//切面=切点+通知
@Aspect
@EnableAspectJAutoProxy
public class DataSourceAspect {
/**
* 切点
* 如果类或者方法上有 @DataSource 注解,那么该类中的方法或者目标方法都会被拦截下来
* @annotation(com.qfedu.dynamic_datasource.annotation.DataSource):如果方法在执行过程中,上面有@DataSource注解就会被拦截下来
* @within(com.qfedu.dynamic_datasource.annotation.DataSource):方法的类上有@DataSource注解也会被拦截下来
*/
@Pointcut("@annotation(com.qfedu.dynamic_datasource.annotation.DataSource)||@within(com.qfedu.dynamic_datasource.annotation.DataSource)")
public void pointcut() {
}
/**
* 通知
* @param pjp
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
DataSource dataSource = getDataSourceAnnotation(pjp);
if (dataSource != null) {
//获取注解中关于数据源的标记,值
String value = dataSource.value();
//设置数据源在ThreadLocal中
DataSourceContextHolder.set(value);
//执行具体的方法,在方法执行的过程中,系统会需要用到数据源,此时就自动去 DataSourceContextHolder 里边查找当前请求的数据源
try{
return pjp.proceed();
}finally{
//执行完成后,移除数据
DataSourceContextHolder.remove();
}
}
}
private DataSource getDataSourceAnnotation(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
//spring提供的工具:查找指定方法上有没有 @DataSource 这个注解
DataSource ds = AnnotationUtils.findAnnotation(methodSignature.getMethod(), DataSource.class);
if (ds != null) {
return ds;
}
//如果方法上没有,查看类上有没有@DataSource注解
return AnnotationUtils.findAnnotation(methodSignature.getDeclaringType(),DataSource.class);
}
}
step08-数据源路由工具(继承AbstractRoutingDataSource)
当系统需要获取一个数据源的时候,会自动去AbstractRoutingDataSource中获取数据源,相当于数据源的一个路由中介。这时,我们需要将自定义注解中指定的数据源获取过来
@Component
public class MyRoutingDataSource extends AbstractRoutingDataSource {
//重新定义一个
private YamlDynamicDataSourceProvider yamlDynamicDataSourceProvider;
//构造方法
public MyRoutingDataSource(YamlDynamicDataSourceProvider yamlDynamicDataSourceProvider) {
this.yamlDynamicDataSourceProvider = yamlDynamicDataSourceProvider;
//设置所有的数据源
super.setTargetDataSources(yamlDynamicDataSourceProvider.loadDatasource());
//设置默认的数据源,如果 service 中的某一个方法上,防止没有@DataSOurce 注解,那么需要一个默认的数据源
super.setDefaultTargetDataSource(yamlDynamicDataSourceProvider.loadDatasource().get(DynamicDataSourceProvider.DEFAULT_DATASOURCE));
super.afterPropertiesSet();
}
/**
* 这个方法就是用来返回数据源的,当 service 中的方法需要使用数据源的时候,就会自动调用这个方法去获取数据源
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.get();
}
}
十六.热加载-devtools
面试高频:双亲委派,涉及的是该类由哪个类加载器去加载
devtools实现了项目的热加载,也就是项目修改了代码之后,不需要重启,就可以自动加载到最新的代码
step01-依赖
<!--
devtools实现了项目的热加载,也就是项目修改了代码之后,不需要重启,就可以自动加载到最新的代码
是将项目中所有的类分为两大类:
1.不变的类(主要是各种依赖的类,第三方依赖)
2.变化的类(主要是自己写的代码)
两种类对应不同的类加载器,springboot种提供了两种不同类型的类加载器:
1.Based Classloader:加载不变的类 2.restart Classloader:加载变化的类
当使用了devtools那么热更新的时候,不会去重新加载不变的类,只会去重新加载变化的类
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<!--不具备传播性-->
<optional>true</optional>
</dependency>
step02-点击编译按钮
十七.服务端数据校验(JSR303数据校验)
数据校验是将前端传过来的数据进行格式的校验,为了避免数据库出现脏数据,保障数据的完整性
这个是springmvc里面的功能
step01-依赖validation
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<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>
</dependencies>
step02-定义一个实体类
public class User {
private Integer id;
@NotNull//表示该属性不能为空,如果为空就报错
@Size(min = 2,max = 32)//指定字符串的长度
private String username;
private String address;
@Past//当前或者是过去的时间,不可以是一个未来的时间
private Date birthday;
//指定年龄的范围
@Max(150)
@Min(0)
private Integer age;
@NotBlank//表示NotNull和NoEmpty,不能为空,不能为空字符串
@Email
private String email;
}
step03-接口
@RestController
public class UserController {
@PostMapping("/user")
public void addUser(@RequestBody @Validated User user){
System.out.println("user="+user);
}
}
step04-全局异常
//全局异常捕获,当出现校验格式不正确的时候,抛出的不是异常,而是文字
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String,String> dataInvalicadate(MethodArgumentNotValidException e){
Map<String,String> map=new HashMap<>();
map.put("status","500");
map.put("message",e.getMessage());
return map;
}
}
step05-可以定制异常信息
step06-制定分组校验,因为有的时候修改的接口不需要id一定要填写,那么就可以进行分组校验
十八.swagger(重点)
在我们开发的时候,前端有一个团队,后端也有一个团队,为了让彼此都知道自己做的哪一步。
step01-依赖,只需要加springfox也就是swagger依赖就行
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
这个依赖中实际上包含了两方面的内容:
1. 自动生成接口的 JSON 描述
2. 提供一个可视化的 UI 页面去渲染 JSON
该3.0.0的不支持springboot3.0的
-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
在springboot配置文件中,加一个配置,改为ant风格路径匹配符
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
step02-在启动类上加两个swagger的注解
/**
* 自动生成接口文档,有一个标准组织 OpenAPI,这是业界的 API 文档标准,这个标准目前有两大实现:
*
* 1. SpringFox:Swagger
* 2. SpringDoc
*/
@SpringBootApplication
//开启 Swagger
@EnableSwagger2
@EnableOpenApi
public class SwaggerApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerApplication.class, args);
}
}
以上就配置完了
step03-上图可以看到swagger自定义的basic-error-controller,我们可以通过配置类将它撤掉
@Configuration
public class SwaggerConfig {
@Bean
Docket docket() {
return new Docket(DocumentationType.OAS_30)
//这里是配置网站的基本信息
.apiInfo(new ApiInfoBuilder()
.title("我的XXX项目在线接口文档")
//标题后面的版本号
.version("v3.0")
.description("这是我的项目描述")
.contact(new Contact("zhangsan", "www.baidu.com", "zhangsan@qq.com"))
.build())
.select()
//表示,只显示我自己写的接口,basic-error-controller不需要扫了
.apis(RequestHandlerSelectors.basePackage("com.qfedu.swagger.controller")).build();
}
}
step04-写一个自己定义的接口
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户ID")
private Integer id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("用户年龄")
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
@RestController
//自定义controller的描述
@Api(tags = "用户管理相关的接口")
public class UserController {
@PostMapping("/user")
//自定义方法的描述
@ApiOperation("添加用户接口")
//给参数自定义中文描述, defaultValue默认值,required是否必填
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名", defaultValue = "zhangsan", required = true),
@ApiImplicitParam(name = "age", value = "用户年龄", defaultValue = "99", required = true)
})
public User addUser(String username, Integer age) {
User user = new User();
user.setUsername(username);
user.setAge(age);
return user;
}
@DeleteMapping("/user/{id}")
public void deleteUserById(@PathVariable Integer id) {
System.out.println("id = " + id);
}
@PutMapping("/user")
//如果前端传的是一个对象的话,那么就需要在实体类上标注一些注解,来指定给前端一些提示
public void updateUser(@RequestBody User user) {
System.out.println("user = " + user);
}
}
十九.SpringDoc
与swagger的页面是一样的,但兼容性比swagger好
step01-依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//springDOC只需要这个依赖就可以运行了,比swagger少了启动类的注解
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webmvc-core</artifactId>
<version>1.6.9</version>
</dependency>
//加这个依赖的目的是为了让swagger和springDoc合并
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
step02-注解并写一个接口
@RestController
//@Api(tags = "用户相关的接口")
@Tag(name = "用户相关的接口")
public class UserController {
// @ApiOperation("根据id查询用户")
//required = true 表示参数必填,注意,这个必填只是在生成 Swagger 文档的时候起作用,并不能代替 @RequestParam(required = true)
// @ApiImplicitParam(name = "id", value = "用户ID", defaultValue = "100")
@GetMapping("/user")
public User getUserById(@RequestParam(defaultValue = "100", required = true) Integer id) {
User u = new User();
u.setId(id);
return u;
}
// @ApiOperation("添加用户的接口")
// @ApiImplicitParams({
// @ApiImplicitParam(name = "username", value = "用户名"),
// @ApiImplicitParam(name = "address", value = "用户地址")
// })
@PostMapping("/user")
public User addUser(String username, String address) {
User user = new User();
user.setUsername(username);
user.setAddress(address);
return user;
}
@DeleteMapping("/user/{id}")
// @ApiImplicitParam(name = "id", value = "用户id")
public void deleteById(@PathVariable Integer id) {
System.out.println("id = " + id);
}
@PutMapping("/user")
@ApiResponses({
@ApiResponse(responseCode = "500", description = "服务器内部错误")
})
public void update(@RequestBody User user) {
}
}
二十.日志体系
二十一.邮件发送
邮件发送的协议就是通过SMTP:简单邮件传输协议,和http的地位是一样的,端口号为25
1.发送一封简单邮件
step01-依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
step02-配置文件
spring.mail.protocol=smtp
# 邮件编码格式
spring.mail.default-encoding=UTF-8
# smtp 服务器地址
spring.mail.host=smtp.qq.com
# smtp 服务器没有加密的端口号
spring.mail.port=587
spring.mail.username=1510161612@qq.com
#这个是在开启smtp时生成的密钥
spring.mail.password=tqdvusknvyjkgjef
spring.mail.properties.mail.debug=true
step03-发送
@SpringBootTest
class MailApplicationTests {
@Autowired
JavaMailSender sender;
@Value("${spring.mail.username}")
String from;
@Test
void contextLoads() {
//创建一封简单邮件
SimpleMailMessage msg = new SimpleMailMessage();
msg.setSubject("这是我的邮件主题");
//邮件发送人
msg.setFrom(from);
msg.setText("这是我的邮件正文");
//这是邮件的收件人
msg.setTo("1470249098@qq.com");
//这是邮件的抄送人
// msg.setCc();
//这个是密抄
// msg.setBcc("");
msg.setSentDate(new Date());
sender.send(msg);
}
}
2.模板发送邮件
step01-邮件的模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>欢迎 <span th:text="${username}"></span> 加入千锋大家庭!</div>
<div>
您的入职信息如下:
<table border="1">
<tr>
<td style="color: red;font-weight: bold">职位名称</td>
<td th:text="${position}"></td>
</tr>
<tr>
<td style="color: red;font-weight: bold">薪水</td>
<td th:text="${salary}"></td>
</tr>
</table>
</div>
</body>
</html>
step02-测试
@SpringBootTest
class MailApplicationTests {
//这是spring提供的类
@Autowired
JavaMailSender sender;
@Value("${spring.mail.username}")
String from;
/**
* 这个是 Thymeleaf 提供的一个页面渲染工具,可以将一个 Thymeleaf 模板渲染成 HTML 字符串
*/
@Autowired
TemplateEngine templateEngine;
@Test
public void test01() throws MessagingException {
Context ctx = new Context();
ctx.setVariable("username", "张三");
ctx.setVariable("position", "Java工程师");
ctx.setVariable("salary", 12000);
//获取渲染之后的邮件字符串
String mail = templateEngine.process("mail", ctx);
//创建一封复合邮件(带 HTML 样式的邮件)
MimeMessage mimeMessage = sender.createMimeMessage();
//helper 只是一个配置辅助工具,最终所有的属性都是配置给 mimeMessage 的
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("这是我的邮件主题");
helper.setFrom(from);
helper.setText(mail, true);
helper.setTo("1470249098@qq.com");
helper.setSentDate(new Date());
helper.addAttachment("我的附件.jpg", new File("C:\\Users\\baize\\Pictures\\weixin\\p02.jpg"));
sender.send(mimeMessage);
}
}
二十二.starter的自定义
step01-依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.4</version>
</dependency>
//下面的好像不用写
<dependency>
<version>2.7.4</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
step02-自定义注解
/**
* 将来在方法中,可以使用这个注解,加了这个注解的方法,在运行时,会自动打印出来方法的执行时间
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimeLog {
}
step03-切面
@Configuration
@Aspect
@EnableAspectJAutoProxy
//条件注解,如果用户在 application.properties 中配置了 time.log.enabled=false,则这个切面就不生效,如果配置了 time.log.enabled=true 或者什么都没有配置,则这个切面生效
@ConditionalOnProperty(prefix = "time.log", name = "enabled", havingValue = "true", matchIfMissing = true)
public class TimeAspect {
private static final Logger logger = LoggerFactory.getLogger(TimeAspect.class);
@Pointcut("@annotation(com.qfedu.timelogspringbootstarter.annotation.TimeLog)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
//执行目标方法
Object proceed = pjp.proceed();
long endTime = System.currentTimeMillis();
//获取方法的完整名称(从类开始写)
String methodName = pjp.getSignature().toLongString();
logger.info("{} 方法执行耗时 {} 毫秒", methodName, (endTime - startTime));
return proceed;
}
}
step04-在spring-factories中做配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.qfedu.timelogspringbootstarter.aop.TimeAspect
step05-install
step06-在别的项目中引入该依赖
step07-调用该注解
总结:starter就是提前将配置类写好,然后根据条件注解动态的生效,或者不生效,当导入依赖的时候,就去meta-inf下面的spring-factories文件,找到自动配置类
二十三.应用监控
方式一:手动配置,再配置类下完成
方式二:springboot提供的admin监控
step01-服务端依赖
step02-在启动类上加入注解@EnableAdminServer
step03-写一个客户端,加入该依赖
step04-在客户端写服务端的IP和端口,将监控放到服务端中
step05-服务端监控显示
另外,当出现异常的时候,可以再监控服务端里面设置一下邮件发送给负责人