文章目录
SpringBoot
简介
Spring框架是Java平台上的一种开源应用框架,提供具有控制反转特性的容器。Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
官网:https://spring.io/projects/spring-boot
官网提供了一个快速生成的网站!IDEA集成了这个这个网站!
- 可以在官网直接下载后,导入idea开发
- 直接使用idea创建一个springboot项目
原理初探
自动配置
结论:springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,导入了对应的start,就有对应的启动了,有了启动器,我们自动装配就会生效,配置成功!
pom.xml
- spring-boot-dependencies:核心依赖在父工程!
- 我们在写或者引入引入一些springboot依赖的时候,不需要指定版本,就因为有这些版本仓库
启动器:springboot的启动场景
<!--启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
- 比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
- springboot会将所有的功能场景,都编程一个个的启动器
- 我们要使用什么功能就只需要找到对应的启动器就可以了
主程序
//@SpringBootApplication:标注这个类是一个springboot的应用
@SpringBootApplication
public class FirstspringbootApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(FirstspringbootApplication.class, args);
}
}
注解
-
@SpringBootApplication:标注这个类是一个springboot的应用
-
@SpringBootConfiguration:springboot的配置 @Configuration:spring配置类 @Component:说明这也是一个spring的组件 @EnableAutoConfiguration : 自动配置 @AutoConfigurationPackage : 自动配置包 @Import({Registrar.class}) : 自动配置‘包注册’ //获取所有的配置 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
-
获取候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
META-INF/spring.factories:自动配置的核心文件
结论:springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,导入了对应的start,就有对应的启动了,有了启动器,我们自动装配就会生效,配置成功!
- springboot在启动的时候,从类路径下/META-INF/spring.factores获取指定的值
- 将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置!
- 整合javaEE,解决方案和自动配置的东西在:spring-boot-autoconfigure-2.6.3.jar这个文件下
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,javaConfig!
- 有了自动配置类,免去了手动编写配置文件的工作!
SpringApplication
- 推断应用的类型是普通的项目还是Web项目
- 查找并加载所有可用初始化器,设置到initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
JavaConfig @Configuration @Bean
关于springboot:
- 自动装配
- run()
SpringBoot:配置文件
springboot使用一个全局的配置文件,配置文件名称是固定的
- application.properties
- 语法结构:key=value
- application.yml
- 语法结构:key:空格 value
配置文件的作用:修改Springboot自动配置的默认值,因为springboot 在底层都自动配置好了;
YAML
YAML是"YAML Ain’t a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
标记语言
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,对比yaml和xml
yalm配置:
server:
port: 8081
xml配置:
<server>
<port>8081</port>
</server>
语法
# 普通的key-value
# 对空格的要求十分高!
#注入到配置类中
name: azmat
#对象
teacher:
name: azmat
age: 22
#行内写法,大括号代表对象
student: {name: azmat,age: 3}
# 数组
pets:
- cat
- dog
- pig
#中括号代表数组
petsName: [cat,dog,pig]
yaml可以直接给实体类赋值
- @ConfigurationProperties 作用:
将配置文件中配置的每一个属性的值,映射到这个组件中。告诉spingboot将本类中的所有属性和配置文件中相关的配置进行绑定。
参数prefix = “person” :将配置文件中的person下面的所有属性一一对应。
(只有这个组件是容器中的组件才能使用容器提供的@ConfigurationProperties 功能)
person.java
@Component //为了让这类扫描到,注册bean
@ConfigurationProperties(prefix = "person")
//javaConfig 绑定我们配置文件的值,可以采取这些方法!
//加载指定的配置文件
//@PropertySource("classpath:test.properties")
public class Person {
//SPEL表达式取出配置文件的值
// @Value("${name}") //从配置文件中取值
private String name;
// @Value("#{20+2}") //#{SPEL} Spring表达式
private Integer age;
// @Value("true") // 字面量
private boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
public Person(){
}
public Person(String name, Integer age, boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public boolean isHappy() {
return happy;
}
public void setHappy(boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
application.yaml
person:
name: azmat${random.uuid}
age: ${random.int}
happy: false
birth: 1999/11/27
maps: {k1: v1,k2: v2}
hello: haayy
lists:
- code
- music
- boy
dog:
name: ${person.hello:hello}_pp
age: 3
Test
@SpringBootTest
class FirstspringbootApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
结果
另外:配置文件除了yaml还有properties,properties配置文件在写中文的时候会有乱码,我们需要去IDEA中设置编码格式为UTF-8
解决报错
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
yaml和properties的区别
松散绑定:这个什么意思呢?比如我的yml中写的last-name,这个和lastName是一样的,-后面跟着的字母默认是大写的。这就松散绑定
总结:
- 配置yml和配置properties都可以获取到值,强烈推荐yml
- 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下@value
- 如果我们专门编写了一个JavaBean来和配置文件进行映射,就直接使用@configurationProperties
JSR303校验
- JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
需要导入依赖
<!--JSR303校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
运行结果 :default message [不是一个合法的电子邮件地址];
使用数据校验,可以保证数据的正确性;
常见参数
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
对环境配置
多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
多配置文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
yaml的多文档块
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles:
default: #配置环境的名称
---
server:
port: 8082
spring:
profiles:
default: test #配置环境的名称
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
配置文件加载位置
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
官方外部配置文件说明参考文档
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;
#配置项目的访问路径
server.servlet.context-path=/azmat
自动配置原理
精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解:@Conditional
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true
Positive matches:(自动配置类启用的:正匹配)
egative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
SpringBoot Web开发
要解决的问题:
- 导入静态资源……
- 首页
- 所有页面的静态资源都需要使用thymeleaf接管
- Link URL Expressions: @{…}
- jsp,模板引擎Thymeleaf
- 装配扩展SpringMVC
- 增删改查
- 拦截器
- 国际化!
Thymeleaf 模板引擎
前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?
SpringBoot推荐你可以来使用模板引擎:
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf。
简介
Thymeleaf是一个流行的模板引擎,该模板引擎采用Java语言开发,模板引擎是一个技术名词,是跨领域跨平台的概念,在Java语言体系下有模板引擎
使用thymeleaf,只需要导入对应的依赖就可以!
<!-- Thymeleaf模板引擎-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
关闭thymeleaf的缓存,默认是true
#关闭thymeleaf的缓存
spring.thymeleaf.cache=false
HTML放在templates目录下!
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
命名空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
测试:
Controller
//在templates目录下的所有页面,只能通过controller来跳转!
//这个需要模板引擎的支持! thymeleaf
@Controller
public class HelloController {
@GetMapping("/test")
public String test(Model model) {
model.addAttribute("msg", "<h1>hello SpringBoot</h1>");
model.addAttribute("users", Arrays.asList("nur", "abdu++", "alem", "HK", "azmat"));
return "test";
}
}
templates下的HTML
<!DOCTYPE html>
<!--命名空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有HTML元素都可以被thymeleaf替换接管:th:元素名-->
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
<hr>
<!--<h3 th:each="user:${users}" th:text="${user}"></h3>-->
<!--第二种方式-->
<h3 th:each="user:${users}">[[ ${user} ]]</h3>
</body>
</html>
结果:
装配扩展SpringMVC
SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
ContentNegotiatingViewResolver 内容协商视图解析器
自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。
我们可以自己给容器中去添加一个视图解析器, 这个类就会帮我们自动的将它组合进来;
1、在我们的主程序中去写一个视图解析器;
//如果、想diy一些指定花的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配
//扩展springMVC
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//viewResolvers 实现了视图解析器接口的类,我们就可以把它看做视图解释器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义了一个自己的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
2、给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中
修改SpringBoot的默认配置
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
扩展使用SpringMVC 官方文档如下:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;
//扩展springMVC,官方建议这样去做!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/azmat").setViewName("test"); //跳转test.html页面
}
}
国际化
有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要国际化了!
国际化:internationalization 缩写:i18n
页面国际化
- 需要配置i18n文件
- 项目中进行按钮自动切换,需要自定义一个组件LocaleResolver
- Message Expressions: #{…}
中使用springboot 国际化 Resource Bundle不显示问题:新版中(2021.2.3版idea中)是没有这个可视化界面的、在idea的Plugins中下载一个 Resource Bundle Editor
login.properties :默认
login.btn = 登录
login.password = 密码
login.remember = 记住我
login.tip = 请登录
login.username = 用户名
英文
login.btn = Sign in
login.password = Password
login.remember = Remember me
login.tip = Please sign in
login.username = Username
中文
login.btn = 登录
login.password = 密码
login.remember = 记住我
login.tip = 请登录
login.username = 用户名
我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;
#我们的配置文件的真实位置
spring.messages.basename=i18n.login
配置页面国际化值
去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。页面测试:
配置国际化解析
在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!
我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!
修改一下前端页面的跳转连接:
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
自定义一个组件LocaleResolver、写一个处理的组件类!
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求中的语言参数
String language = request.getParameter("l");
Locale locale = Locale.getDefault(); //如果没有就使用默认的;
//如果请求连接携带了国际化的参数
if(!ObjectUtils.isEmpty(language)){
//zh_CN
String[] split=language.split("_");
//国家地区
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
为了区域化信息能够生效,需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//自定义的国际化
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
员工管理系统
登录拦截器
登录功能实现
LoginController
@Controller
public class LoginController {
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session){
//用户名放入session
session.setAttribute("loginUser",username);
//具体的业务
if (!ObjectUtils.isEmpty(username) && "123456".equals(password)){
//重定向
return "redirect:/main.html";
}else {
//告诉用户,登录失败
model.addAttribute("msg","用户名或者密码有错误 !");
return "index";
}
}
}
HTML页面
给用户提示错误
<!--如果msg的值为空,则不显示-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
//返回true放心,返回false不放心
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功之后,应该有用户的session;
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser == null) { //没有登录
request.setAttribute("msg", "没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
} else {
return true;
}
}
}
在我们自己的MvcConofig下重写;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")//拦截
.excludePathPatterns("/index", "/index.html", "/", "/user/login","/css/**","/js/**","/img/**");//放心,非拦截
}
展示员工列表
提取公共页面
th:fragment="sidebar"
<div th:replace="~{commons/commons::sidebar}"></div>
如果要传递参数, 可以直接使用()参数,接受判断即可
<!--传递参数给组件-->
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
判断
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
列表循环展示
员工Controller
@Controller
public class EmployeeController {
@Resource
EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model) {
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps", employees);
return "emp/list";
}
}
Employee
/**
* 员工
*/
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //0:女 1:男
private Department department;
private Date birth;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
//默认创建日期
this.birth = new Date();
}
}
EmployeeDao
/**
* 员工
*/
@Repository
public class EmployeeDao {
//模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
//员工有所属的部门
@Resource
private DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "AA", "2271427740@qq.com", 1, new Department(101, "教学部")));
employees.put(1002, new Employee(1002, "BB", "2271427740@qq.com", 1, new Department(102, "市场部")));
employees.put(1003, new Employee(1003, "CC", "2900928132@qq.com", 0, new Department(103, "教研部")));
employees.put(1004, new Employee(1004, "DD", "2271427740@qq.com", 1, new Department(104, "运营部")));
employees.put(1005, new Employee(1005, "EE", "1334534999@qq.com", 0, new Department(105, "后勤部")));
}
//主键自增!
private static Integer initId = 1006;
//增加员工
public void save(Employee employee) {
if (employee.getId() == null) {
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
//查询全部员工信息
public Collection<Employee> getAll(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
//删除员工通过id
public void delete(Integer id){
employees.remove(id);
}
}
Department
/**
* 部门
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
DepartmentDao
/**
* 部门dao
*/
@Repository
public class DepartmentDao {
//模拟数据库中的数据
private static Map<Integer, Department> departments = null;
static {
departments = new HashMap<Integer, Department>();//创建一个部门表
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
//获得所有部门信息
public Collection<Department> getDepartments(){
return departments.values();
}
//通过id得到部门
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
HTML页面
测试
添加员工实现
首先在list.html
页面增添一个增加员工
按钮,点击该按钮时发起一个请求/emp
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a></h2>加员工</a></h2>
然后编写对应的controller,处理点击添加员工
的请求
@GetMapping("/emp")
ublic String toAddPage(Model model) {
//查出所有部门的信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
return "emp/add"; //返回到添加员工页面
}
修改其中表格为:
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" name="lastName" class="form-control" placeholder="lastname:zsr">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="email:xxxxx@qq.com">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<!--注意这里的name是department.id,因为传入的参数为id-->
<!--controller接收的是一个Employee,所以我们需要提交的是其中的一个属性-->
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.departmentName}"
th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<!--springboot默认的日期格式为yy/MM/dd-->
<input type="text" name="birth" class="form-control" placeholder="birth:yyyy/MM/dd">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
在
add.html
页面,当我们填写完信息,点击添加
按钮,应该完成添加返回到list
页面,展示新的员工信息;因此在add.html
点击添加
按钮的一瞬间,我们同样发起一个请求/add
,与上述提交按钮
发出的请求路径一样,但这里发出的是post
请求
然后编写对应的controller,同样在EmployeeController
中添加一个方法addEmployee
用来处理点击添加按钮
的操作
@PostMapping("/emp")
public String addEmployee(Employee employee) {
System.out.println("save=>"+employee);
employeeDao.save(employee); //调用底层业务方法保存员工信息
return "redirect:emps"; //从定向首页
}
springboot默认的日期格式为yy/MM/dd
,我们在properties里面进行配置日期格式化
#时间日期格式化
spring.mvc.format.date=yyyy-MM-dd
修改员工信息
点击编辑标签时,应该跳转到编辑页面update.html(我们即将创建)进行编辑
因此首先将list.html页面的编辑标签添加href属性,实现点击请求/update/id号到编辑页面
<a class="btn btn-sm btn-primary" th:href="@{/update/{id}(id=${emp.getId()})}">编辑
然后编写对应的controller,在EmployeeController
中添加一个方法update
用来处理list
页面点击编辑
按钮的操作,返回到update.html
编辑员工页面,我们即将创建
//restful风格接收参数
@GetMapping("/update/{id}")
public String toUpdateEmp(@PathVariable("id") Integer id, Model model) {
//查出原来的数据
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp", employee);
//查出所有的部门信息,添加到departments中,用于前端接收
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
//返回到编辑员工页面
return "emp/update";
}
修改提交controller
@PostMapping("/updateEmp")
public String updateEmp(Employee employee) {
employeeDao.save(employee);
return "redirect:/emps";
}
删除员工信息
点击
删除
标签时,应该发起一个请求,删除指定的用户,然后重新返回到list
页面显示员工数据
<a class="btn btn-sm btn-danger" th:href="@{/delete/{id}(id=${emp.getId()})}">删除</a>
然后编写对应的controller,处理点击删除
按钮的请求,删除指定员工,重定向到/emps
请求,更新员工信息
//删除员工信息
@GetMapping("/delete/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {
employeeDao.delete(id);
return "redirect:/emps";
}
404页面指定
只需要在
templates
目录下新建一个error
包,然后将404.html
放入其中,报错SpringBoot就会自动找到这个页面
我们可以启动程序测试,随便访问一个不存在的页面
注销操作
提取出来的公共commons
页面,顶部导航栏处中的标签添加href
属性,实现点击发起请求/user/logout
然后编写对应的controller,处理点击注销
标签的请求,在LoginController
中编写对应的方法,清除session,并重定向到首页
//注销
@RequestMapping("/user/logout")
public String logout(HttpSession session){
session.invalidate();
return "redirect:/index";
}
SpringBoot整合JDBC
创建SpringBoot项目
首先创建一个springboot项目,勾选组件时勾选
JDBC API
和MySQL Driver
项目创建完成之后,发现导入了以下两个启动器,这就是SpringBoot和JDBC整合所需要的两个依赖
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--MySQL启动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
.yaml 中填写数据库相关的信息:
spring:
datasource:
username : root
password: 1127
url: jdbc:mysql://localhost:3307/mybatis?&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
com.mysql.jdbc.Driver
是 mysql-connector-java 5中的com.mysql.cj.jdbc.Driver
是 mysql-connector-java 6 中的特性,相比5多了一个时区设置:serverTimezone
注意:(使用username
作为变量名)、而是用name
的就报错 Access denied for user 'Administrator'@'localhost' (using password: YES)
编写测试
上述配置完成后,就可以直接使用了,因为SpringBoot已经默认帮我们进行了自动配置
我们接下来去测试类测试,SpringBoot提供了默认的数据源,我们只需要注入到spring容器中即可使用
@Test
void contextLoads() throws SQLException {
//查看一下默认的数据源:com.zaxxer.hikari.HikariDataSource
System.out.println(dataSource.getClass());
//获得数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//xxx Template : springboot已经配置好模板bean,拿来即用 CRUD
//关闭
connection.close();
}
运行测试,出现如下结果即代表数据库连接成功
可以看到SpringBoot默认的数据源是class com.zaxxer.hikari.HikariDataSource
,即hikari
Hikari
快速,简单,可靠,spring boot2.0 已经将 HikariCP 做为了默认的数据源链接池,在官网测试中秒杀一切其他数据源,比如 commons-dbcp,tomcat,c3po,druid等
CRUD操作数据库
SpringBoot中,不需要使用第三方第数据库操作框架,如
MyBatis
等,Spring本身对原生的JDBC做了轻量级的封装:JdbcTemplate
1.JDBCTemplate简介
JdbcTemplate
是Spring对JDBC的封装,目的是使JDBC更加易于使用。
JdbcTemplate
是Spring的一部分,处理了资源的建立和释放;帮助我们避免一些常见的错误,比如忘了总要关闭连接。- 它运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果。
- 数据库操作的所有 CRUD 方法都在 JdbcTemplate 中
- Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了
JdbcTemplate
放在了容器中,只需注入即可使用 - JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的
JdbcTemplateConfiguration
类
JdbcTemplate
主要提供以下几类方法:
execute
方法:可以用于执行任何SQL语句,一般用于执行DDL语句;update
方法及batchUpdate
方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;query
方法及queryForXXX
方法:用于执行查询相关语句;call
方法:用于执行存储过程、函数相关语句;
CRUD测试
在主程序同级目录下新建一个包
controller
,其中新建JDBCController
类,执行数据库相关操作首先导入
web场景启动器
,导入web模块正常运行所依赖的组件<!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
@RestController
public class JDBCController {
@Resource
JdbcTemplate jdbcTemplate;
//查询数据库的所有信息
//没有实体类,数据中的东西,怎么获取? Map
@GetMapping("/userList")
public List<Map<String, Object>> userList() {
String sql = "select * from user";
List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);
return list_maps;
}
//增
@GetMapping("/addUser")
public String addUser() {
String sql = "insert into user(id,name,pwd) values (0,'艾孜','1127')";
jdbcTemplate.update(sql);//自动提交事务
return "add-ok";
}
//改
@GetMapping("/updateUser/{id}")
public String updateUser(@PathVariable("id") int id) {
String sql = "update user set name=?,pwd=? where id=" + id;
//封装
Object[] objects = new Object[2];
objects[0] = "aize";
objects[1] = "191127";
jdbcTemplate.update(sql,objects);//自动提交事务
return "update-ok";
}
//删
@GetMapping("/deleteUser/{id}")
public String deleteUser(@PathVariable("id") int id) {
String sql = "delete from user where id=?";
jdbcTemplate.update(sql, id);//自动提交事务
return "delete-ok";
}
}
SpringBoot整合Druid数据源
1.Druid简介
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid是一个关系型数据库连接池,它是阿里巴巴的一个开源项目。Druid支持所有JDBC兼容的数据库,包括Oracle、MySQL、Derby、PostgreSQL、SQL Server、H2等。Druid在监控、可扩展性、稳定性和性能方面具有明显的优势。通过Druid提供的监控功能,可以实时观察数据库连接池和SQL查询的工作情况。使用Druid连接池,在一定程度上可以提高数据库的访问性能。
Druid
是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。Druid
可以很好的监控 DB 池连接和 SQL 的执行情况,天然继承监控功能Druid
已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
部分基本参数
com.alibaba.druid.pool.DruidDataSource
基本配置参数如下:
配置 | 缺省值 | 说明 |
---|---|---|
name | 配置这个属性的意义在于:如果存在多个数据源,监控的时候可以通过名字来区分开来;如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 连接数据库的url,不同数据库不一样 | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应driverClassName(建议配置) |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1. Destroy线程会检测连接的间隔时间 2. testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat、日志用的filter:log4j、防御sql注入的filter:wall | |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
2.使用Durid数据源
1.导入依赖
<!--Druid-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
2.切换数据源
在springboot配置文件中通过
spring.datasource.type
指定数据源
spring:
datasource:
username : root
password: 1127
url: jdbc:mysql://localhost:3307/mybatis?&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
启动上述测试类进行测试、可以看到数据源切换成功
3.设置数据源属性
我们在基础的配置属性之外,可以添加
Druid
数据源的专有配置属性
spring:
datasource:
username : root
password: 1127
url: jdbc:mysql://localhost:3307/mybatis?&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters
# stat:监控统计
# log4j:日志记录(需要导入log4j依赖)
# wall:防御sql注入
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
其中,监控统计拦截器配置了log4j
需要导入其依赖
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
4.使添加属性生效
为了使上述配置的druid特有数据源属性生效,我们创建自己的DruidDataSource并绑定配置文件中的属性参数,添加到容器中,而不再使用SpringBoot自动生成的Druid数据源
在主程序同级目录下新建一个config
包,在其中新建DruidConfig
类
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource") //将全局配置文件中前缀为spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
@Bean //将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
public DataSource druidDateSource() {
return new DruidDataSource();
}
}
5. 配置Druid后台监控Servlet
Druid
数据源具有监控的功能,并内置提供了一个StatViewServlet
用于展示Druid的监控信息,包括
- 提供监控信息展示的html页面
- 提供监控信息的JSON API
开启Druid的监控功能,可以在应用运行期间,通过监控提供的多维度数据来分析使用数据库的运行情况,从而可以调整程序设计,以达到优化数据库访问性能的目的
接下来定义一个后台监控器,在DruidConfig
中新建一个方法statViewServlet()
- 首先要创建一个
servle
的bean:springboot中内置了servlet容器,我们要使用servlet,只需要用其提供的ServletRegistrationBean
注册一个servlet注入到spring容器中即可,不再需要web.xml
的配置
ServletRegistrationBean<StatViewServlet> servlet = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
ServletRegistrationBean
的有参构造函数中:
-
第一个参数为传入的servlet:这里传入Druid内置的
StatViewServlet
; -
第二个参数可以设置映射的路径:这里我们使用
/druid/*
就是以前我们在
web.xml
中配置的servlet-mapping
定义的请求路径
这里设置一些初始化参数:
loginUsername
:StatViewServlet提供后台管理页面的登录用户名
loginPassword
:StatViewServlet提供后台管理页面的登录密码
allow
:设置允许访问的人
这些参数可以在StatViewServlet的父类ResourceServlet
中找到
最后返回servlet
对象即可
完整代码
//配置Druid监控管理后台的Servlet;
//因为springboot 内置了servlet 容器,所以么有web.xml,替代方法:ServletRegistrationBean
@Bean //注册到bean中
public ServletRegistrationBean registrationBean() {
ServletRegistrationBean<StatViewServlet> servlet = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
///初始化参数设置:后台登录的账号密码、允许访问者
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername", "admin");//登录key 是固定的loginUsername loginPassword
initParameters.put("loginPassword", "123456");//key名固定,这些参数可以在StatViewServlet的父类ResourceServlet中找到
//设置允许访问的人:这里表示只有本机可以访问(后面参数为空则所有人都可以访问)
initParameters.put("allow", "localhost");
//禁止谁能访问
//initParameters.put("azmat","192.16.166.104");
servlet.setInitParameters(initParameters); //设置初始化参数
return servlet;
}
测试:配置完成后,重启主程序,访问localhost:8080/druid
,会跳转到http://localhost:8080/druid/login.html
页面,这就是Druid
内置的展示后台管理监控的页面,然后输入我们刚才设置的用户名和密码,点击Sign in
进行登录
查询所有用户;然后查看SQL监控
,就可以看到我们刚刚执行的操作
3.配置Druid监控过滤器filter
同servlet一样,springboot中内置了filter容器,我们要使用filter,只需要用其提供的
FilterRegistrationBean
注册一个filter注入到spring容器中即可,不再需要web.xml
的配置
同样在DruidConfig
中新增一个方法webStatFilter()
-
首先要创建一个
filter
的beanFilterRegistrationBean filter = new FilterRegistrationBean(); filter.setFilter(new WebStatFilter());
ServletRegistrationBean
的有参构造函数中:传入的参数是filter:这里传入用于用于配置Web和Druid数据源之间的管理关联监控统计的
WebStatFilter()
; -
然后我们就可以执行filter的相关操作,同样可以看到很多方法
这里同样设置初始化参数:
exclusions
:表示需要排除一些不必要的url请求
其他参数可以在WebStatFilter
中看到
最后返回filter即可
完整代码
//配置Druid监控之web监控的filter
//注册到bean中
@Bean
public FilterRegistrationBean webStateFilter() {
//创建一个filter
FilterRegistrationBean filter = new FilterRegistrationBean();
filter.setFilter(new WebStatFilter());
//初始化参数设置
Map<String, String> initParameters = new HashMap<>();
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
initParameters.put("exclusions",".js,*.css,/druid/*");
//设置初始化参数
filter.setInitParameters(initParameters);
//添加过滤规则:/*表示过滤所有请求
filter.setUrlPatterns(Arrays.asList("/*"));
//返回filter
return filter;
}
SpringBoot整合MyBatis
1.导入依赖
首先新建一个springboot项目,勾选组件时勾选
Spring Web
、JDBC API
、MySQL Driver
然后导入以下整合依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
2.编写数据库配置信息
在springboot配置文件中配置数据库信息
spring.datasource.username=root
spring.datasource.password=1127
spring.datasource.url=jdbc:mysql://localhost:3307/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#整合mybatis
mybatis.type-aliases-package=com.example.springboot03mybatis.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml //resources下面必须一层一层建立
3.测试连接数据库是否成功
@SpringBootTest
class Springboot03MybatisApplicationTests {
@Resource
DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}
4.编写pojo实体类
在主程序同级目录下新建
pojo
包,其中新建User
实体类(使用了lombok)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
5.编写mapper接口
在主程序同级目录下新建
mapper
包,其中新建UserMapper
接口
//这个注解表示了这是一个mybatis 的 mapper 类
//启动类上加上@MapperScan("com.example.springboot03mybatis.mapper") 第二种方式
@Mapper
@Repository //也可以用@Component
public interface UserMapper {
List<User> queryUserList();
int deleteUser(int id);
}
6编写mapper.xml
在resources目录下新建
src/main/resources/com/example/springboot03mybatis/mapper
包,其中新建mapper
包,再在其中新建mapper.xml
必须一层一层建立,否则报错
spring boot Invalid bound statement (not found): com.example.springboot03mybatis.mapper.
<?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.springboot03mybatis.mapper.UserMapper">
<select id="queryUserList" resultType="com.example.springboot03mybatis.pojo.User">
select *
from user
</select>
<select id="queryUserById" resultType="User">
select *
from user where id=#{id};
</select>
<insert id="addUser" parameterType="User">
insert into user (id,name,pwd)
values (#{id},#{name},#{pwd});
</insert>
<update id="deleteUser" parameterType="int">
delete
from user
where id=#{id};
</update>
</mapper>
然后需要到springboot核心配置文件application.properties
中绑定mapper.xml文件
#整合mybatis
#别名
mybatis.type-aliases-package=com.example.springboot03mybatis.pojo
#中绑定mapper.xml文件
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
7.编写controller
在主程序同级目录下新建
controller
包,在其中新建UserController
@RestController
public class UserController {
@Resource
private UserMapper userMapper;
@GetMapping("/queryUserList")
public List<User> queryUserList(){
List<User> users = userMapper.queryUserList();
for (User user : users) {
System.out.println(user);
}
return users;
}
@GetMapping("/deleteUser")
public String deleteUser(@RequestParam("id")Integer id){
userMapper.deleteUser(id);
return "ok";
}
}
SpringSecurity(安全)
简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Spring Security是针对Spring项目的安全框架。也是springboot底层安全模块默认的技术选型,他可以实现强大的web安全控制、对于安全控制,需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理!
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
重点的类
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是“认证”和“授权”
“认证”(Authentication)
身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。
“授权”(Authorization)
授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。
这个概念是通用的,而并不是在Spring Security中存在!
配置Security
导入依赖
<!--security-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
编写
Spring Security
配置类config文件下面创建
SecurityConfig
一个配置类,然后继承WebSecurityConfigurerAdapter类
以下测试
//AOP :拦截器
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页面只有对应有权限的人才能访问
//请求授权的规则~
http.authorizeHttpRequests()
//链式编程
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认回到登录页面,需要开启登录的页面
//http.formLogin();
//定制登录
http.formLogin().loginPage("/toLogin").usernameParameter("name").passwordParameter("pwd").loginProcessingUrl("/login");
//注销.开启了注销功能,跳到首页
//http.logout().logoutUrl("/");
http.logout().logoutSuccessUrl("/"); //默认保存两周
http.csrf().disable();//禁用csrf功能
//开启记住我功能、cookie
http.rememberMe().rememberMeParameter("remember"); //自定义接收前端的参数
}
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//密码编码:PasswordEncoder
//这些数据正常应该从数据库中读取
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
前端注意点
controller层
异步任务,定时任务,邮件任务
在我们的工作中,常常会用到异步处理任务,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。还有一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息。还有就是邮件的发送,微信的前身也是邮件服务呢?这些东西都是怎么实现的呢?其实SpringBoot都给我们提供了对应的支持,我们上手使用十分的简单,只需要开启一些注解支持,配置一些配置文件即可。
异步任务
比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
首选当前目录下创建创建
service
目录下AsyncService
,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;
@Service
public class AsyncService {
//告诉spring这是一个异步的方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在处理...");
}
}
然后在
controller
实现
@RestController
public class AsyncController {
@Resource
AsyncService asyncService;
@RequestMapping("/hello")
public String hello(){
asyncService.hello(); //停止三秒,转圈~
return "ok";
}
}
启动类SpringBootApplication上添加
@EnableAsync
//开启异步注解功能
@EnableAsync //开启异步注解功能
@SpringBootApplication
public class Springboot06TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot06TestApplication.class, args);
}
}
定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
- TaskExecutor接口
- TaskScheduler接口
两个注解:
- @EnableScheduling
- @Scheduled
以下测试
1、创建一个ScheduledService
hello方法定时执行,目前20 0/1 12 * * ?
每天12点里的每个一分钟执行一次
@Service
public class ScheduledService {
//在一个特定的时间执行这个方法~ Timer
//cron 表达式~
//秒 分 时 日 月 周几~ 0-7: 代表每一天 ?:不确定
/*
20 27 12 * * ? 每天12点27分20 执行一次
20 0/5 11,12 * * ? 每天11点12点,每个五分钟执行一次
20 0/1 12 * * ? 每天12点里的每个一分钟执行一次
0 15 10 ? 1-6 没有月的周一到周六 10点15分钟执行一次
*/
@Scheduled(cron = "20 0/1 12 * * ?")
public void hello() {
System.out.println("hello,你被执行了~");
}
}
2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling
开启定时任务功能
@EnableAsync //开启异步注解功能
@EnableScheduling //开启定时功能的注解
@SpringBootApplication
public class Springboot06TestApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot06TestApplication.class, args);
}
}
cron表达式:
百度百科:计划任务,是任务在约定的时间执行已经计划好的工作,这是表面的意思。在Linux中,我们经常用到 cron 服务器来完成这项工作。cron服务器可以根据配置文件约定的时间来执行特定的任务。
http://www.bejson.com/othertools/cron/
常用表达式
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
邮件任务
-
邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持
-
邮件发送需要引入spring-boot-start-mail
-
SpringBoot 自动配置MailSenderAutoConfiguration
-
定义MailProperties内容,配置在application.yml中
-
自动装配JavaMailSender
-
测试邮件发送
测试:
1、引入pom依赖
<!--javax.mail-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2、查看自动配置类:MailSenderAutoConfiguration
、这个类中没有注册bean
然后我们去看下配置文件,JavaMailSenderImpl
3、配置application.properties
spring.mail.username=2271427740@qq.com
spring.mail.password=qq授权码
spring.mail.host=smtp.qq.com
#qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
4、Spring单元测试
@SpringBootTest
class Springboot06TestApplicationTests {
@Resource
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
//一个简单的邮件~
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setSubject("Aize您好!!");
mailMessage.setText("加油!!");
mailMessage.setTo("2271427740@qq.com");
mailMessage.setFrom("2271427740@qq.com");
mailSender.send(mailMessage);
}
@Test
void contextLoads2() throws MessagingException {
//一个复杂的邮件~
MimeMessage mimeMailMessage =mailSender.createMimeMessage();
//组装~
MimeMessageHelper helper=new MimeMessageHelper(mimeMailMessage,true);
//正文
helper.setSubject("艾孜你好呀~plus");
helper.setText("<p style='color:red'>继续加油学习java~</p>",true);
//附件
helper.addAttachment("82.jpg",new File("G:\\82.jpg"));
helper.setTo("2271427740@qq.com");
helper.setFrom("2271427740@qq.com");
mailSender.send(mimeMailMessage);
}
}