SpringBoot Note [vayenxiao]

概念理解

什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,
对比其他javaweb框架的好处,官方说是简化开发,约定大于配置,

Spring Boot的主要优点:
	- 为所有Spring开发者更快的入门
	- 开箱即用,提供各种默认配置来简化项目配置 
	- 内嵌式容器简化Web项目
	- 没有冗余代码生成和XML配置的要求

Spring开发变的越来越笨重,大量的XML(SpringMVC)文件,繁琐的配置,复杂的部署流程,
整合第三方技术时 难度大等,导致开发效率低下 
SpringBoot是一个用来简化Spring应用的初始化创建和开发的框架,简化配置,实现快速开发

新建boot项目

选择spring initalizr(如果没有该选项,settings—Plugins 搜索 Spring Assistant)
不需要的mvn相关文件删掉

勾选依赖
Spring Boot DevTools:? 
Spring Web:启动器
Spring Configuration Processor : 针对提示“Spring Boot Configration AnnotationProcessor not configured”

Maven配置

<!-- 配置本地仓库路径 配置aliyun镜像来源 -->
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                              http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository>C:\Environment\apache-maven-3.6.3\repo363</localRepository>
  <interactiveMode>true</interactiveMode>
  <offline>false</offline>
  <pluginGroups></pluginGroups>
  <proxies></proxies>
  <servers></servers>

  <mirrors>
    <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>

  <profiles></profiles>
</settings>

<!-- Idea中配置 -->
setting 搜索maven
改为本地maven路径、改为本地settings.xml文件、改为本地仓库路径

???启动类需要增加一个依赖

 父项目是spring-boot-starter-parent,需要继承很多东西
<parent> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-starter-parent</artifactId> 
	<version>2.0.3.RELEASE</version> 
</parent>

父项目的父项目是spring-boot-dependencies,用来管理SpringBoot应用中底层依赖的版本
<parent> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-dependencies</artifactId> 
	<version>2.0.3.RELEASE</version> 
	<relativePath>../../spring-boot-dependencies</relativePath> 
</parent>

启动器
<!-- web场景启动器 包括:tomcat等等 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
测试类
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

构建工具也加上,用来打包
	<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

设置banner和port

https://www.bootschool.net/ascii
拷贝生成的字符到一个文本文件中,并且将该文件命名为banner.txt
将banner.txt拷贝到项目的resources目录中:
重新查看效果。

Spring Boot项目使用一个全局的配置文件application.properties或者是application.yml
在文件中添加:
server.port=8081
或者yml的
server:
 port: 8081

???运行原理探究

https://www.cnblogs.com/hellokuangshen/p/12450327.html

初步结论:

SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作;
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
它会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;

???自动配置原理

https://edu.51cto.com/center/course/lesson/index?id=290344
https://www.bilibili.com/video/BV1PE411i7CV?p=6
https://www.bilibili.com/video/av75233634?p=7

yml

语法

空格要求极高,不过不使用yml,properties也可以

# 对空格要求极高
server:
 port: 8081
 servlet: 
  context-path: /test   # 表示项目所有访问路径增加前缀/test

address: 陕西省西安市
# 在java中就可以使用@Value("${address}")就可以取到值,类型会自动转换
content: "地址是${address}"
# 也可以这样引用其他变量,

# 对象
student:
  name: Alice
  age: 24

student2: {
    
    name: bob, age: 24} # 行内写法

pets:
  - cat
  - dog
  - pig
pets2: [cat, dog, pig]

还可以注入对象的值

person:
  name: mata
  age: 24
  happy: true
  birth: 2009/09/09
  maps: {
    
    k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: wangcai
    age: 14

创建Dog和Person实体类

@Data
@Component
public class Dog {
    
    
    private  String name;
    private int age;
}

@Data
@Component
@ConfigurationProperties(prefix="person") 
//默认从yml全局配置文件中获取对应的yml值
//默认的全局文件文件名必须是application.yml或者application.properties
public class Person {
    
    
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<String> lists;
    private Dog dog;
}
然后可以测试类进行测试
@SpringBootTest
class HelloworldApplicationTests {
    
    
	@Autowired
	Person p;
	@Test
	void contextLoads() {
    
    
		System.out.println(p);
	}
}

加载指定的配置文件

注释掉该注解 @configurationProperties
替换为 @PropertySource(value = “classpath:person.properties”)
在resources目录下新建一个person.properties文件,内容如下
name=kuangshen

@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
    
    
    @Value("${name}")
    private String name;  
}

文件占位符

person:
  name: qinjiang${
    
    random.uuid} # 随机uuid
  age: ${
    
    random.int}
  happy: false
  birth: 2020/03/15
  maps: {
    
    k1: v1,k2: v2}
  lists:
    - code
    - girl
    - music
  dog:
    # 引用yml中person.hello的值,如果不存在就用:后面的值,即Bob,然后拼接上_旺财
    name: ${
    
    person.hello:Bob}_旺财  # 若中文出现乱码,将idea中File Encodings改为UTF-8 
    age: 1

@ConfigurationProperties与@Value区别

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:yml中写的last-name,这个和lastName是一样的, -后面跟着的字母默认被转为大写。
3、JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性,jsr303有很多,网上可以查到。

@Component //注册bean 
@ConfigurationProperties(prefix = "person") 
@Validated //数据校验 
	public class Person {
    
     
	@Email(message="邮箱格式错误") //自定义报错信息为:name必须是邮箱格式 
	private String name; 
	@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 对象是否符合正则表达式的规则 

}

4、复杂类型封装,yml中可以封装对象,使用value就不支持

结论:
1,配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
2,如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
3,如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

HelloWorld

这三个包必须和启动类同级,这样就可以扫描到程序中

@RestController //@Controller
public class HelloController {
    
    

    @RequestMapping({
    
    "/hello","/hello2"}) //可接受数组形式的多个值
    public String hello() {
    
    
        return "Hello World";
    }
    //http://localhost:port/hello   //http://localhost:port/hello2
}

配置文件优先级

1、spring boot项目中同时存在application.properties和application.yml文件时,两个文件都有效,
但是application.properties的优先级会比application.yml高。

2、配置文件所在目录不同优先级也不同。
    最高级别:处于外层config目录下
    第二级别:与最外层同级别
    第三级别:resource - config 目录下
    第四级别:resource目录下

热部署

<!-- 添加spring-boot-devtools的包,true必须加上 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

方式一:Build对象项目(或者build对应的文件也可以)
方式二: setting - Build,Execution - Compiler - 打勾 Compiler-Build Project automatically
        Ctrl+Shift+Alt 搜索Registry  打勾 Compiler.autoMake.allow.when.app.running

两种方式都是重新编译文件,只不过一个手动一个自动

多环境配置

开发环境、生产环境、测试环境等随时切换,所以准备多个配置文件

方式一:profiles

​ 在application.properties文件中指定要激活的配置:spring.profiles.active=dev

​ 然后新建各环境对应的文件:application-dev.properties application-test.yml

方式二:文档块 application.properties举例:

# 第一个文档块,指定文档块id为testing,此时文档块234中3就激活,port就成了8082
server: 
 port: 8080
spring: 
 profiles: 
  active: test

# 第2个文档块
---
spring: 
 profiles: develop 
server: 
 port: 8081 

# 第3个文档块
---
spring: 
 profiles: test
server: 
 port: 8082
 
# 第4个文档块
---
spring: 
 profiles: product 
server: 
 port: 8083

加载外部配置文件

方式1,使用@PropertySource加载properties属性文件

//entity
@Component // 将当前Bean添加到容器中
@ConfigurationProperties(prefix = "user") //在配置文件中匹配user属性,与@PropertySource没有前后顺序
@PropertySource(value={
    
    "classpath:user.properties"}) // 加载外部的属性文件,value=可以省略
public class User {
    
    
    String name;
    int age;
    String address;
    getter setter toString ... 
}

//user.propeties
user.name=Faker
user.age=21
user.address=false


//test
@SpringBootTest
class DemoApplicationTests {
    
    
	@Autowired
	User u;
	@Test
	void contextLoads() {
    
    
		System.out.println(u);
	}
}

方式2:@ImportResource

将方式1中User的注解替换为@ImportResource({
    
    "classpath:xxx.xml"})
    
//entity
@Component // 将当前Bean添加到容器中
@ImportResource({
    
    "classpath:spring.xml"})
public class User {
    
    
    String name;
    int age;
    String address;
    getter setter toString ... 
}

//新建xxx.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="u" class="com.baidu.studyboot.entity.User">
        <property name="name" value="江苏省"/>
        <property name="age" value="23"/>
        <property name="address" value="大寨路78号"/>
    </bean>
</beans>

???方式3:使用注解方式添加组件

@Configuration // 标注在类上,表示这是一个配置文件,相当于以前的spring xml配置文件
public class MyConfig {
    
    

    @Bean // 标注在方法上,向容器中添加组件,将方法的返回值添加到到容器中,将方法名作为组件id
    public Address address(){
    
    
        Address address = new Address();
        address.setProvince("江苏");
        address.setCity("苏州");
        return address;
    }

}

@Value

实体类中使用@Value给字段赋值,当然实体类需要加上@Component

@Value("${server.port}")
public String port;

server.port=80123
    
若属性port使用了static会无法赋值,为null

@ConfigurationProperties

@ConfigurationProperties(prefix = "user") //在默认配置文件中匹配user属性,即application.properties

@SpringBootApplication

是一个复合注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解。

- @SpringBootConfiguration:标注当前类是配置类,这个注解继承自@Configuration。又继承自@Component,
- 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。

- @EnableAutoConfiguration:是自动配置的注解,这个注解会根据我们添加的组件jar来完成一些默认配置,
- 我们做微服时会添加spring-boot-starter-web这个组件jar的pom依赖,这样配置会默认配置springmvc 和tomcat。
- 开启自动配置功能,SpringBoot会自动完成许多配置,简化了以前的繁琐的配置。

- @ComponentScan:默认扫描当前包及其子包下被@Component,@Controller,@Service,@Repository注解标记的类并纳入到
spring容器中进行管理。等价于context:component-scan的xml配置文件中的配置项。也可以(“com.baidu.xxx”)自行指定扫描范围。

大多数情况下,这3个注解会被同时使用,基于最佳实践,这三个注解就被做了包装,成为了@SpringBootApplication注解。

@MapperScan

spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis接口的扫描。

它和@mapper注解是一样的作用,不同的地方是扫描入口不一样。@mapper需要加在每一个mapper接口类上面。
所以大多数情况下,都是在规划好工程目录之后,通过@MapperScan注解配置路径完成mapper接口的注入。

添加mybatis相应组建依赖之后。就可以使用该注解。

@PathVariable

//@PathVariable可以用来映射URL中的占位符到目标方法的参数中
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id){
    
    
     System.out.println("testPathVariable:"+id);
     return SUCCESS;
}

@Controller @RequestMapping

@Controller 表明这个类是一个控制器类,和@RequestMapping来配合使用拦截请求,如果不在method中注明请求的方式,
默认是拦截get和post请求。这样请求会完成后转向一个视图解析器。但是在大多微服务搭建的时候,前后端会做分离。
所以请求后端只关注数据处理,后端返回json数据的话,需要配合@ResponseBody注解来完成。

这样一个只需要返回数据的接口就需要3个注解来完成,大多情况我们都是需要返回数据。也是基于最佳实践,所以将这三个注解进一步整合。

@RestController 是@Controller 和@ResponseBody的结合,一个类被加上@RestController 注解,
数据接口中所有方法就都不再需要添加@ResponseBody。更加简洁。

1) 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,
配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
且每一个方法都不需要加 @ResponseBody

2) 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

同样的情况,@RequestMapping(value="",method= RequestMethod.GET ),我们都需要明确请求方式。
这样的写法又会显得比较繁琐,于是乎就有了如下的几个注解。
普通风格 --->   Rest风格
@RequestMapping(value="",method = RequestMethod.GET) @GetMapping(value =“”)
@RequestMapping(value="",method = RequestMethod.POST) @PostMapping(value =“”)
@RequestMapping(value="",method = RequestMethod.PUT) @PutMapping(value =“”)
@RequestMapping(value="",method = RequestMethod.DELETE) @DeleteMapping(value =“”)

@Service

1@Service:这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个注解交给spring容器。
事务的切面也会配置在这一层。当让 这个注解不是一定要用。有个泛指组件的注解,
当我们不能确定具体作用的时候 可以用泛指组件的注解托付给spring容器。

2@Resource@Resource@Autowired一样都可以用来装配bean,都可以标注字段上,或者方法上。 
@resource注解不是spring提供的,是属于J2EE规范的注解。

两个之前的区别就是匹配方式上有点不同,@Resource默认按照名称方式进行bean匹配,@Autowired默认按照类型方式进行bean匹配。

静态资源

###################  资源处理 #####################
SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
有一个方法:addResourceHandlers 添加资源处理
通过观察,可以得知有几个候选路径(优先级从高到低):
"classpath:/META-INF/resources/" 
 "classpath:/resources/"    
 "classpath:/static/"    
 "classpath:/public/" 

如果不使用候选路径,可自定义
在application.properties中配置;
spring.resources.static-locations=classpath:/coding/,classpath:/kuang/
    
#自定义匹配规则
spring.mvc.static-path-pattern=规则
    
配置好后可通过http://localhost:8080/1.js访问静态资源(图片 js 音频等)


######################   网页图标icon   ########################
2.2.0版本以上已移除
spring.mvc.favicon.enabled=false
再放一个favicon.ico
再加入
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
    
##################  首页 ########################
底层默认固定文件名为:index.html
    
################### 访问 templates下页面,只能通过controller ##############

Thymeleaf模板引擎

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

@Controller //注意:不是RestController
public class TestController {
    
    
    @RequestMapping("/t1")
    public String test1(Model model){
    
    
        model.addAttribute("msg","Hello,Thymeleaf"); 
        model.addAttribute("users", Arrays.asList("qinjiang","kuangshen"));
        return "test"; //跳转到test.html,页面必须在templates中
    }
}

注意:themeleaf的命名空间 xmlns:th="http://www.thymeleaf.org"
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神说</title>
</head>
<body>
	<div th:text="${msg}"></div>
	<h4 th:each="user :${users}" th:text="${user}"></h4>
</body>
</html>
        
spring.thymeleaf.cache=false # 关闭引擎缓存

MVC配置原理

https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247483819&idx=1&sn=b9009aaa2a9af9d681a131b3a49d8848&scene=19#wechat_redirect

https://www.bilibili.com/video/BV1PE411i7CV?p=18

https://www.bilibili.com/video/BV1PE411i7CV?p=19

i18n

先在IDEA中统一设置properties的编码utf8
在resources资源文件下新建一个i18n目录,存放国际化配置文件
建立  login.properties  login_zh_CN.properties  login_en_US.properties
配置这个messages的路径: spring.messages.basename=i18n.login
    
在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!
AcceptHeaderLocaleResolver 这个类中有一个方法
    
修改一下前端页面的跳转连接:
<!-- 这里传入参数不需要使用 ?使用 (key=value)-->
<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>
    
写一个处理的组件类
//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {
    
    
    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
    
    
        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
        //如果请求链接不为空
        if (!StringUtils.isEmpty(language)){
    
    
            //分割请求参数
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }
    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
    
    
    }
}

为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的 MyMvcConfig 下添加bean;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
    @Bean
    public LocaleResolver localeResolver(){
    
    
        return new MyLocaleResolver();
    }
}

整合JDBC

//依赖
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-jdbc</artifactId> 
</dependency> 
    <dependency> 
    <groupId>mysql</groupId> 
    <artifactId>mysql-connector-java</artifactId> 
    <scope>runtime</scope> 
</dependency>
    
spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/databaseName?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
 
//测试是否连接成功
@SpringBootTest
class DemoApplicationTests {
    
    
	@Autowired
	DataSource dataSource;
	@Test
	public void contextLoads() throws SQLException {
    
    
		System.out.println(dataSource.getClass());
		Connection connection =   dataSource.getConnection();
		System.out.println(connection);
		connection.close();
	}
}

// JDBCTemplate
1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),
有了连接,就可以使用原生的 JDBC 语句来操作数据库;
2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:
execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
query方法及queryForXXX方法:用于执行查询相关语句;
call方法:用于执行存储过程、函数相关语句。
    
// JDBCTemplate 测试
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
    
    

    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
    
    
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from emp");
        return maps;
    }
    
    @GetMapping("/add")
    public String addUser(){
    
    
        //插入语句,注意时间问题
        String sql = "insert into emp(last_name, email,gender,department,birth)" +
                " values ('狂神说','[email protected]',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        return "addOk";
    }

    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
    
    
        String sql = "update emp set last_name=?,email=? where id="+id;
        Object[] objects = new Object[2];
        objects[0] = "秦疆";
        objects[1] = "[email protected]";
        jdbcTemplate.update(sql,objects);
        return "updateOk";
    }

    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
    
    
        String sql = "delete from emp where id=?";
        jdbcTemplate.update(sql,id);
        return "deleteOk";
    }
    
}

整合Druid

https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247483786&idx=1&sn=f5f4ca792611af105140752eb67ce820&scene=19#wechat_redirect

https://www.bilibili.com/video/BV1PE411i7CV?p=32

整合Redis

springboot 2.x 之后spring-boot-starter-data-redis底层的 jedis 被替代为 letture
    
jedis
Spring-data-redis
Spring-boot-starter-data-Redis
三者关系是后者封装前者,jedis最原生类似jdbc
    
jedis:采用直连,多线程时不安全,使用jedis pool
lettuce:采用netty,实例在多线程中共享,不存在不安全情况

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password: 
    lettuce:
      pool:       
        max-idle: 8  # 连接池中的最大空闲连接 默认8 
        min-idle: 0   # 连接池中的最小空闲连接 默认0
        max-active: 8  # 连接池最大连接数 默认8 ,负数表示没有限制
        max-wait: -1  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
    timeout: 30000
@SpringBootTest
class DemoApplicationTests {
    
    
	@Autowired
	private RedisTemplate redisTemplate;
	@Test
	void contextLoads() {
    
    
		redisTemplate.opsForValue().set("age","24");
		// 如果 set 对象,该 entity 必须实现序列化
		System.out.println(redisTemplate.opsForValue().get("age"));
	}
}

如果有中文,会出现乱码,解决方法是增加自定义序列化配置文件

@Configuration
public class Config {
    
    
    @Autowired
    private RedisTemplate redisTemplate;
    @Bean
    public RedisTemplate redisTemplateInit() {
    
    
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        return redisTemplate;
    }
}

源码可以看出,SpringBoot自动帮我们在容器中生成了一个 RedisTemplate 和 StringRedisTemplate。

但自动配置不好用,就重新配置一个RedisTemplate

public class RedisConfig {
    
     
    @Bean @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
     		
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); 	
        template.setConnectionFactory(factory); 
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 		ObjectMapper om = new ObjectMapper(); 
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 	
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 
        jackson2JsonRedisSerializer.setObjectMapper(om); 
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 
        // key采用String的序列化方式 
        template.setKeySerializer(stringRedisSerializer); 
        // hash的key也采用String的序列化方式 
        template.setHashKeySerializer(stringRedisSerializer); 
        // value序列化方式采用jackson 
        template.setValueSerializer(jackson2JsonRedisSerializer); 
        // hash的value序列化方式采用jackson 
        template.setHashValueSerializer(jackson2JsonRedisSerializer); 
        template.afterPropertiesSet(); return template; 
    }
}

RedisUtil.java

@Component
public final class RedisUtil {
    
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
    
    
        try {
    
    
            if (time > 0) {
    
    
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
    
    
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    
    /**
     * 判断key是否存在
     */
    public boolean redisTemplate.hasKey(key)
        
    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
    
    
        if (key != null && key.length > 0) {
    
    
            if (key.length == 1) {
    
    
                redisTemplate.delete(key[0]);
            } else {
    
    
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    // ============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
    
    
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
    
    
        try {
    
    
            if (time > 0) {
    
    
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
    
    
                set(key, value);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
    
    
        if (delta < 0) {
    
    
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
    
    
        if (delta < 0) {
    
    
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
    
    
        return redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
    
    
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
    
    
        try {
    
    
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     * @param map 对应多个键值
     * @param time 时间(秒)
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
    
    
        try {
    
    
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
    
    
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param item 项
     */
    public boolean hset(String key, String item, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param item 项
     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     */
    public boolean hset(String key, String item, Object value, long time) {
    
    
        try {
    
    
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
    
    
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
    
    
        redisTemplate.opsForHash().delete(key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
    
    
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
    
    
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
    
    
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    // ============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @return
     */
    public Set<Object> sGet(String key) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     * @param time 时间(秒)
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
    
    
        try {
    
    
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     */
    public long sGetSetSize(String key) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
    
    
        try {
    
    
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================
    /**
     * 获取list缓存的内容
     */
    public List<Object> lGet(String key, long start, long end) {
    
    
        try {
    
    
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     */
    public long lGetListSize(String key) {
    
    
        try {
    
    
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     */
    public Object lGetIndex(String key, long index) {
    
    
        try {
    
    
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     * @param time 时间(秒)
     */
    public boolean lSet(String key, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param time 时间(秒)
     */
    public boolean lSet(String key, Object value, long time) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param time 时间(秒)
     */
    public boolean lSet(String key, List<Object> value) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param time 时间(秒)
     */
    public boolean lSet(String key, List<Object> value, long time) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
    
    
        try {
    
    
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }

}

整合Mybatis

//entity
@Data
@NoArgsConstructor
public class User {
    
    
    private Integer id;
    private String name;
    private String pwd;
}

//sql
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `pwd` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', 'faker', '123456');

//依赖
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
    
//兼容问题
关于mybatis-spring-boot-starter的版本需要注意:
2.1.x版本适用于:MyBatis 3.5+、Java 8+、Spring Boot 2.1+
2.0.x版本适用于:MyBatis 3.5+、Java 8+、Spring Boot 2.0/2.1
1.3.x版本适用于:MyBatis 3.4+、Java 6+、Spring Boot 1.5
    
//interface
@Mapper
//表示这是一个mybatis的mapper类
//也可以在另一个文件中使用@MapperScan=("com.xxx.xxx")
@Repository
public interface UserMapper {
    
    
    List<User> selectAll();
    int update(User user);
}

//controller
@RestController
public class UserController {
    
    
    @Autowired
    private UserMapper um;
    @RequestMapping("/selectAll")
    public List<User> selectAll(){
    
    
        List<User> list = um.selectAll();
        return list;
    }
}

//xml在resources文件夹下的mapper下,resources/mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper">
    <select id="selectAll" resultType="user">
        select * from user
    </select>
</mapper>
    
//application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:port/databaseName?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456

mybatis.type-aliases-package: com.xxx.xxx
mybatis.mapper-locations=classpath:**/*.xml

//application.yml
spring
 datasource
  driver-class-name: com.mysql.cj.jdbc.Driver
  url: jdbc:mysql://localhost:port/databaseName?serverTimezone: UTC&useUnicode=true&characterEncoding=utf-8
  username: root
  password: 123456
mybatis
 type-aliases-package: com.xxx.xxx
 mapper-locations: classpath:**/*.xml

# 注意
1,driver并不是 com.mysql.jdbc.Driver"
2,url也比较特殊
3,location有个s,当初这里费劲了1个小时

//测试
http://localhost:8080/selectAll

//maven配置资源过滤问题
<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

猜你喜欢

转载自blog.csdn.net/vayne_xiao/article/details/109624520