这是一篇优雅的Springboot2.0使用手册

最近再研究springboot的原理��颇有收获,现在让我分享一下springboot如何使用吧~

想要解锁更多新姿势?请访问我的博客

啥是Springboot

和书上理解的不同,我认为Springboot是一个优秀的快速搭建框架,他通过maven继承方式添加依赖来整合很多第三方工具,可以避免各种麻烦的配置,有各种内嵌容器简化Web项目,还能避免依赖的干扰,它内置tomcat,jetty容器,使用的是java app运行程序,而不是传统的用把war放在tomcat等容器中运行

和JFinal的区别

JFinal是国人出品的一个web + orm 框架 ,JFinal,优点是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展。核心就是极致简洁。他没有商业机构的支持,所以宣传不到位,少有人知。

Springboot相比与JFinal最大的优点就是支持的功能非常多,可以非常方便的将spring的各种框架如springframework , spring-mvc, spring-security, spring-data-jpa, spring-cache等等集成起来进行自动化配置 ,而且生态 比较好,很多产品都对Springboot做出一定支持。

与Springcloud的区别

可以这么理解,Springboot里面包含了Springcloud,Springcloud只是Springboot里面的一个组件而已。

Springcloud提供了相当完整的微服务架构。而微服务架构,本质来说就是分布式架构,意味着你要将原来是一个整体的项目拆分成一个个的小型项目,然后利用某种机制将其联合起来,例如服务治理、通信框架等基础设施。

SpringBoot和SpringMVC区别

SpringBoot的Web组件,默认集成的是SpringMVC框架。

快速使用

要往下看的话,注意了��

  • Springboot 2.x 要求 JDK 1.8 环境及以上版本。另外,Springboot 2.x 只兼容 Spring Framework 5.0 及以上版本。
  • 为 Springboot 2.x 提供了相关依赖构建工具是 Maven,版本需要 3.2 及以上版本。使用 Gradle 则需要 1.12 及以上版本。
  • 建议用IntelliJ IDEA IntelliJ IDEA (简称 IDEA)

建立项目

我已经好久没用Eclipse了,要知道Eclipse是创建一个maven项目在引入Springboot依赖创建的。

下面我分享一下用IDEA创建Springboot的方法。

很简单,在这个界面里面就可以创建Springboot了。接下来在添加一些组件。

大功告成!

写一个DEMO

这里用我写的一个秒杀项目作为参考栗子。秒杀商城

创建一个conntroller包,编写一个样列。

package cn.tengshe789.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class SampleController {

    @RequestMapping("/hello")
    public String index() {
        return "Hello World";
    }

}

接下来在他同级包或者上一级的包内,创建一个主方法MainApplication。方法内容;

@SpringBootApplication
@EnableAsync
//@ComponentScan("cn.tengshe789.controller")
//@EnableAutoConfiguration
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

在浏览器输入http://127.0.0.1:8080/demo/hello/,就可以启动了!

SpringApplication.run

Springboot将他标识为启动类,用它启动Springboot项目

基础注解解释

@RestController

在上加上RestController 表示修饰该Controller所有的方法返回JSON格式,直接可以编写Restful接口。就相当于@Controller+@ResponseBody这种实现

@SpringBootApplication

用在启动Springboot中,相当于@ComponentScan+@EnableAutoConfiguration+@Configuration

@ComponentScan(“cn.tengshe789.controller”)

控制器扫包范围。

@EnableAutoConfiguration

他让 Spring Boot 根据咱应用所声明的依赖来对 Spring 框架进行自动配置。意思是,创建项目时添加的spring-boot-starter-web添加了Tomcat和Spring MVC,所以auto-configuration将假定你正在开发一个web应用并相应地对Spring进行设置。

配置文件

properties

规则:

1、名用大写比较规范

2、=两边别打空格

3、名值对写完后别打分号

自定义参数
name=tengshe789
多环境配置
spring.profiles.active=pre

application-dev.properties:开发环境
application-test.properties:测试环境
application-prod.properties:生产环境
修改端口号
server.port=8888 
server.context-path=/tengshe789

yaml

规则:

  1. 使用空格 Space 缩进表示分层,不同层次之间的缩进可以使用不同的空格数目,但是同层元素一定左对齐,即前面空格数目相同(不能使用 Tab,各个系统 Tab对应的 Space 数目可能不同,导致层次混乱)
  2. ‘#’表示注释,只能单行注释,从#开始处到行尾
  3. 破折号后面跟一个空格(a dash and space)表示列表
  4. 用冒号和空格表示键值对 key: value
  5. 简单数据(scalars,标量数据)可以不使用引号括起来,包括字符串数据。用单引号或者双引号括起来的被当作字符串数据,在单引号或双引号中使用C风格的转义字符
server:
  port:  8080
  context-path: /springboot

xml

Springboot官方不推荐xml,略

Web开发

一个项目用Springboot,十有八九就是用于Web开发。首先让我们看看Springboot怎么快速开发Web把

如何访问静态资源

请在resources目录下创建static文件夹,在该位置放置一个静态资源。

目录:src/main/resources/static

启动程序后,尝试访问http://localhost:8080/img.xxx/。就可以访问了。

关于渲染Web页面

在之前的快速使用的示例中,我们都是通过添加@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?

模板引擎方法

Springboot依然可以实现动态HTML,并且提供了多种模板引擎的默认配置支持,Springboot官方文档有如下推荐的模板引擎:

· Thymeleaf

· FreeMarker

· Velocity

· Groovy

· Mustache

Springboot官方建议避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性。

在Springboot中,默认的模板配置路径都时:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在各模板引擎的配置属性中查询并修改。

Thymeleaf(胸腺)

这里还是用我写的一个秒杀项目作为参考栗子。秒杀商城

POM
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-thymeleafartifactId>
    dependency>
配置文件

application.properties中添加:

#thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
# 一代填 spring.thymeleaf.mode=HTML5
spring.thymeleaf.mode=HTML
后台

在src/main/resources/创建一个templates文件夹,新网页后缀为*.html

 @RequestMapping("/to_list")
    public String list(Model model,MiaoshaUser user) {
        model.addAttribute("user", user);
        //查询商品列表
        List goodsList = goodsService.listGoodsVo();
        model.addAttribute("goodsList", goodsList);
        return "goods_list";
    }
前台

这里注意Thymeleaf语法,Thymeleaf很像HTML,不同之处在标签加了一个th前缀


<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>商品列表title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    
    <script type="text/javascript" th:src="@{/js/jquery.min.js}">script>
head>
<body>

<div class="panel panel-default" >
    <div class="panel-heading">秒杀商品列表div>
    <table class="table" id="goodslist">
        <tr><td>商品名称td><td>商品图片td><td>商品原价td><td>秒杀价td><td>库存数量td><td>详情td>tr>
        <tr  th:each="goods,goodsStat : ${goodsList}">
            <td th:text="${goods.goodsName}">td>
            <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" />td>
            <td th:text="${goods.goodsPrice}">td>
            <td th:text="${goods.miaoshaPrice}">td>
            <td th:text="${goods.stockCount}">td>
            <td><a th:href="'/goods_detail.htm?goodsId='+${goods.id}">详情a>td>
        tr>
    table>
div>
body>
html>

Freemarker(自由标记)

POM
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-freemarkerartifactId>
    dependency>
配置文件

application.properties中添加:

#Freemarker
spring.freemarker.allow-request-override=false
spring.freemarker.cache=true
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
#spring.freemarker.prefix=
#spring.freemarker.request-context-attribute=
#spring.freemarker.settings.*=
spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates/
#comma-separated list
#spring.freemarker.view-names= # whitelist of view names that can be resolved
后台

在src/main/resources/创建一个templates文件夹,新网页后缀为*.ftl

@RequestMapping("/freemarkerIndex")
    public String index(Map result) {
        result.put("nickname", "tEngSHe789");
        result.put("old", "18");
        result.put("my Blog", "HTTPS://blog.tengshe789.tech/");
        List listResult = new ArrayList();
        listResult.add("guanyu");
        listResult.add("zhugeliang");
        result.put("listResult", listResult);
        return "index";
    }
前台

<html>
<head lang="en">
<meta charset="UTF-8" />
<title>首页title>
head>
<body>
      ${nickname}
<#if old=="18">
            太假了吧哥们
      <#elseif old=="21">
            你是真的21岁
     <#else>
        其他      

      #if>      
     <#list userlist as user>
       ${user}
     #list>
body> 
html>

JSP

不建议用Springboot整合JSP,要的话一定要为war类型,否则会找不到页面.,而且不要把JSP页面存放在resources// jsp 不能被访问到

POM
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.4.RELEASEversion>
    parent>
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-tomcatartifactId>
        dependency>
      
    <dependency>
            <groupId>org.apache.tomcat.embedgroupId>
            <artifactId>tomcat-embed-jasperartifactId>
        dependency>
    dependencies>
配置文件

application.properties中添加:

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
后台

在src/main/resources/创建一个templates文件夹,新网页后缀为*.jsp

@Controller
public class IndexController {
    @RequestMapping("/index")
    public String index() {
        return "index";
    }
}

前台

略略略��

异步编程

要了解 WebFlux ,首先了解下什么是Reactive响应式(反应式)编程 ,他是一种新的编程风格,其特点是异步或并发、事件驱动、推送PUSH机制以及观察者模式的衍生。reactive应用(响应式应用)允许开发人员构建事件驱动(event-driven),可扩展性,弹性的反应系统:提供高度敏感的实时的用户体验感觉,可伸缩性和弹性的应用程序栈的支持,随时可以部署在多核和云计算架构。

Spring Webflux

Spring Boot Webflux 就是基于 Reactor 实现的。Spring Boot 2.0 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST,HTML 和 WebSocket 交互等程序的支持。一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。

Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是使用其功能性端点方式。

WebFlux 支持的容器有 Tomcat、Jetty(Non-Blocking IO API) ,也可以像 Netty 和 Undertow 的本身就支持异步容器。在容器中 Spring WebFlux 会将输入流适配成 Mono 或者 Flux 格式进行统一处理。

POM
官方实例
@RestController
public class PersonController {
    private final PersonRepository repository;

    public PersonController(PersonRepository repository) {
            this.repository = repository;
    }

    @PostMapping("/person")
    Mono create(@RequestBody Publisher personStream) {
            return this.repository.save(personStream).then();
    }

    @GetMapping("/person")
    Flux list() {
            return this.repository.findAll();
    }

    @GetMapping("/person/{id}")
    Mono findById(@PathVariable String id) {
            return this.repository.findOne(id);
    }
}
Controller层

Spring Boot 2.0 这里有两条不同的线分别是:

  1. Spring Web MVC -> Spring Data
  2. Spring WebFlux -> Spring Data Reactive

如果使用 Spring Data Reactive ,原来的 Spring 针对 Spring Data (JDBC等)的事务管理会不起作用。因为原来的 Spring 事务管理(Spring Data JPA)都是基于 ThreadLocal 传递事务的,其本质是基于 阻塞 IO 模型,不是异步的。

但 Reactive 是要求异步的,不同线程里面 ThreadLocal 肯定取不到值了。自然,我们得想想如何在使用 Reactive 编程是做到事务,有一种方式是 回调 方式,一直传递 conn :newTransaction(conn ->{})

因为每次操作数据库也是异步的,所以 connection 在 Reactive 编程中无法靠 ThreadLocal 传递了,只能放在参数上面传递。虽然会有一定的代码侵入行。进一步,也可以 kotlin 协程,去做到透明的事务管理,即把 conn 放到 协程的局部变量中去。
那 Spring Data Reactive Repositories 不支持 MySQL,进一步也不支持 MySQL 事务,怎么办?

答案是,这个问题其实和第一个问题也相关。 为啥不支持 MySQL,即 JDBC 不支持。大家可以看到 JDBC 是所属 Spring Data 的。所以可以等待 Spring Data Reactive Repositories 升级 IO 模型,去支持 MySQL。也可以和上面也讲到了,如何使用 Reactive 编程支持事务。

如果应用只能使用不强依赖数据事务,依旧使用 MySQL ,可以使用下面的实现,代码如下:

Service 层
public interface CityService {

    /**
     * 获取城市信息列表
     *
     * @return
     */
    List findAllCity();

    /**
     * 根据城市 ID,查询城市信息
     *
     * @param id
     * @return
     */
    City findCityById(Long id);

    /**
     * 新增城市信息
     *
     * @param city
     * @return
     */
    Long saveCity(City city);

    /**
     * 更新城市信息
     *
     * @param city
     * @return
     */
    Long updateCity(City city);

    /**
     * 根据城市 ID,删除城市信息
     *
     * @param id
     * @return
     */
    Long deleteCity(Long id);
}

具体案例在我参考博主的 Github

路由器类 Router

创建一个 Route 类来定义 RESTful HTTP 路由

请参考聊聊 Spring Boot 2.x 那些事儿

@Async

需要执行异步方法时,在方法上加上@Async之后,底层使用多线程技术 。启动加上需要@EnableAsync

数据访问

整合JdbcTemplate

使用这个需要spring-boot-starter-parent版本要在1.5以上

POM
<dependency>
     <groupId>org.springframework.bootgroupId>
     <artifactId>spring-boot-starter-jdbcartifactId>
dependency>
配置文件

application.properties中添加:

# jdbc模板
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
后台

创建一个Service

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void createUser(String name, Integer age) {
        jdbcTemplate.update("insert into users values(null,?,?);", name, age);
    }
}

整合Mybatis

这里用我写的一个秒杀项目作为参考栗子。秒杀商城

POM
    <dependency>
      <groupId>org.mybatis.spring.bootgroupId>
      <artifactId>mybatis-spring-boot-starterartifactId>
      <version>1.3.2version>
    dependency>
配置文件

application.properties中添加:

#mybatis
mybatis.type-aliases-package=cn.tengshe789.domain
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=3000
mybatis.mapperLocations = classpath:cn/tengshe789/dao/*.xml
后台

创建一个Dao(Mapper 代码)

@Mapper
@Component
public interface GoodsDao {

    @Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id")
    public List listGoodsVo();
}

创建service

@Service
public class GoodsService {

    @Autowired
    GoodsDao goodsDao;

    /*
     * 展示商品列表
     */
    public List listGoodsVo() {
        return goodsDao.listGoodsVo();
    }
}

Mybatis整合分页插件PageHelper

PageHelper 是一款好用的开源免费的 Mybatis 第三方物理分页插件

POM
        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelper-spring-boot-starterartifactId>
            <version>1.2.5version>
        dependency>
配置文件

application.properties中添加:

# 配置日志
logging.level.cn.tengshe789.dao=DEBUG
# Pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
pagehelper.page-size-zero=true

或者在application.yml中添加:

# 与mybatis整合
mybatis:
  config-location: classpath:mybatis.xml
  mapper-locations:
  - classpath:cn/tengshe789/dao/*.xml

# 分页配置
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql
代码
实体层面
@Data
public class User {
    private Integer id;
    private String name;
}
Dao层
public interface UserDao {
    @Select("SELECT * FROM USERS ")
    List findUserList();
}
Service层
@Service
public class UserService {
    @Autowired
    private UserMapper userDao;

    /**
     * page 当前页数
* size 当前展示的数据
*/
public PageInfo findUserList(int page, int size) { // 开启分页插件,放在查询语句上面 PageHelper.startPage(page, size); List listUser = userDao.findUserList(); // 封装分页之后的数据 PageInfo pageInfoUser = new PageInfo(listUser); return pageInfoUser; } }

整合SpringJPA

spring-data-jpa三个步骤:

  1. 声明持久层的接口,该接口继承 Repository(或Repository的子接口,其中定义了一些常用的增删改查,以及分页相关的方法)。
  2. 在接口中声明需要的业务方法。Spring Data 将根据给定的策略生成实现代码。
  3. 在 Spring 配置文件中增加一行声明,让 Spring 为声明的接口创建代理对象。配置了 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

详情:JPA官方网站

POM
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
配置文件

Springboot 默认使用hibernate作为JPA的实现 。需要在application.properties中添加:

# hibernate
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.max-idle=200
spring.datasource.tomcat.initialSize=20
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
代码
Domain
@Data
@Entity(name = "users")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "name")
    private String name;
    @Column(name = "age")
    private Integer age;
}

注解的意思:

@Entity会被spring扫描并加载,

@Id注解在主键上

@Column name="call_phone" 指该字段对应的数据库的字段名,如果相同就不需要定义。数据库下划线间隔和代码中的驼峰法视为相同,如数据库字段create_time等价于Java类中的createTime,因此不需要用@Column注解。

Dao层

此时需要继承Repository接口~

public interface UserDao extends JpaRepository<User, Integer> {
}
Controller
@RestController
public class IndexController {
    @Autowired
    private UserDao userDao;

    @RequestMapping("/jpaFindUser")
    public Object jpaIndex(User user) {
        Optional userOptional = userDao.findById(user.getId());
        User result = userOptional.get();
        return reusltUser == null ? "没有查询到数据" : result;
    }
}

多数据源

很多公司都会使用多数据库,一个数据库存放共同的配置或文件,另一个数据库是放垂直业务的数据。所以说需要一个项目中有多个数据源

这玩意原理很简单,根据不同包名,加载不同数据源。

配置文件

application.properties中添加:

# datasource1
spring.datasource.test1.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.test1.jdbc-url =jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8
spring.datasource.test1.username = root
spring.datasource.test1.password = 123456
# datasource2
spring.datasource.test2.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.test2.jdbc-url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
spring.datasource.test2.username = root
spring.datasource.test2.password = 123456
代码
添加配置

数据库1的

//DataSource01
@Configuration // 注册到springboot容器中
@MapperScan(basePackages = "tech.tengshe789.test01", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSource1Config {

    /**
     * @methodDesc: 功能描述:(配置test1数据库)
     * @author: tEngSHe789
     */
    @Bean(name = "test1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test1")
    @Primary
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * @methodDesc: 功能描述:(test1 sql会话工厂)
     */
    @Bean(name = "test1SqlSessionFactory")
    @Primary
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //加载mapper(不需要)
        bean.setMapperLocations(
         new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
        return bean.getObject();
    }

    /**
     * 
     * @methodDesc: 功能描述:(test1 事物管理)
     */
    @Bean(name = "test1TransactionManager")
    @Primary
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

数据库2的同理。

Dao
public interface User1Dao {
    @Insert("insert into users values(null,#{name},#{age});")
    public int addUser(@Param("name") String name, @Param("age") Integer age);
}
注意事项

在多数据源的情况下,使用@Transactional注解时,应该指定事务管理者@Transactional(transactionManager = "test1TransactionManager")

事物管理

怎么进行事物管理呢,简单,往下看。

声明式事务

找到service实现类,加上@Transactional 注解就行,此@Transactional注解来自org.springframework.transaction.annotation包 ,不是来自javax.transaction 。而且@Transactional不仅可以注解在方法上,也可以注解在类上。当注解在类上的时候意味着此类的所有public方法都是开启事务的。如果类级别和方法级别同时使用了@Transactional注解,则使用在类级别的注解会重载方法级别的注解。

注意:Springboot提供了一个@EnableTransactionManagement注解在配置类上来开启声明式事务的支持。注解@EnableTransactionManagement是默认打开的,想要关闭事务管理,想要在程序入口将这个注解改为false

分布式事物管理

啥是分布式事务呢,比如我们在执行一个业务逻辑的时候有两步分别操作A数据源和B数据源,当我们在A数据源执行数据更改后,在B数据源执行时出现运行时异常,那么我们必须要让B数据源的操作回滚,并回滚对A数据源的操作。这种情况在支付业务时常常出现,比如买票业务在最后支付失败,那之前的操作必须全部回滚,如果之前的操作分布在多个数据源中,那么这就是典型的分布式事务回滚

了解了什么是分布式事务,那分布式事务在java的解决方案就是JTA(即Java Transaction API)。

springboot官方提供了 Atomikos , Bitronix ,Narayana 的类事务管理器

类事务管理器Atomikos
POM
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jta-atomikosartifactId>
dependency>
配置文件
# Mysql 1
mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test1.username = root
mysql.datasource.test1.password = 123456

mysql.datasource.test1.minPoolSize = 3
mysql.datasource.test1.maxPoolSize = 25
mysql.datasource.test1.maxLifetime = 20000
mysql.datasource.test1.borrowConnectionTimeout = 30
mysql.datasource.test1.loginTimeout = 30
mysql.datasource.test1.maintenanceInterval = 60
mysql.datasource.test1.maxIdleTime = 60

# Mysql 2
mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test2.username =root
mysql.datasource.test2.password =123456

mysql.datasource.test2.minPoolSize = 3
mysql.datasource.test2.maxPoolSize = 25
mysql.datasource.test2.maxLifetime = 20000
mysql.datasource.test2.borrowConnectionTimeout = 30
mysql.datasource.test2.loginTimeout = 30
mysql.datasource.test2.maintenanceInterval = 60
mysql.datasource.test2.maxIdleTime = 60
读取配置文件信息

以下是读取数据库1的配置文件

@Data
@ConfigurationProperties(prefix = "mysql.datasource.test1")
public class DBConfig1 {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String testQuery;
}

读取数据库2的配置文件略

创建数据源

数据源1:

@Configuration
// basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "tech.tengshe789.test01", sqlSessionTemplateRef = "testSqlSessionTemplate")
public class MyBatisConfig1 {

    // 配置数据源
    @Primary
    @Bean(name = "testDataSource")
    public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(testConfig.getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(testConfig.getPassword());
        mysqlXaDataSource.setUser(testConfig.getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("testDataSource");

        xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
        xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
        xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
        xaDataSource.setTestQuery(testConfig.getTestQuery());
        return xaDataSource;
    }

    @Primary
    @Bean(name = "testSqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    @Primary
    @Bean(name = "testSqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

数据库2略

如何启动
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
 }

定时任务

在做项目时有时候会有定时器任务的功能,比如某某时间应该做什么,多少秒应该怎么样之类的。

spring支持多种定时任务的实现。我们来介绍下使用Quartz 和Scheduler

Spring Schedule

Spring Schedule 实现定时任务有两种方式 1. 使用XML配置定时任务, 2. 使用 @Scheduled 注解。

代码

固定等待时间 @Scheduled(fixedDelay = 时间间隔 )

固定间隔时间 @Scheduled(fixedRate = 时间间隔 )

@Component
public class ScheduleJobs {
    public final static long SECOND = 1 * 1000;
    FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");


    @Scheduled(fixedDelay = SECOND * 2)
    public void fixedDelayJob() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("[FixedDelayJob Execute]"+fdf.format(new Date()));
    }
}

Corn表达式 @Scheduled(cron = Corn表达式)

@Component
public class ScheduleJobs {
    public final static long SECOND = 1 * 1000;
    FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");


    @Scheduled(cron = "0/4 * * * * ?")
    public void cronJob() {
        System.out.println("[CronJob Execute]"+fdf.format(new Date()));
    }
}
启动

要在主方法上加上@EnableScheduling

Quartz

POM
  <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-quartz-starterartifactId>
    dependency>
配置文件
# spring boot 2.x 已集成Quartz,无需自己配置
spring.quartz.job-store-type=jdbc
spring.quartz.properties.org.quartz.scheduler.instanceName=clusteredScheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000
spring.quartz.properties.org.quartz.jobStore.useProperties=false
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=10
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
配置类
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail uploadTaskDetail() {
        return JobBuilder.newJob(UploadTask.class).withIdentity("uploadTask").storeDurably().build();
    }

    @Bean
    public Trigger uploadTaskTrigger() {
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");
        return TriggerBuilder.newTrigger().forJob(uploadTaskDetail())
                .withIdentity("uploadTask")
                .withSchedule(scheduleBuilder)
                .build();
    }
}
实现类

创建一个配置类,分别制定具体任务类和触发的规则

@Configuration
@DisallowConcurrentExecution
public class UploadTask extends QuartzJobBean {
    @Resource
    private TencentYunService tencentYunService;
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("任务开始");
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("任务结束");
    }
}

@DisallowConcurrentExecution禁止并发执行

并发执行方面,系统默认为true,即第一个任务还未执行完整,第二个任务如果到了执行时间,则会立马开启新线程执行任务,这样如果我们是从数据库读取信息,两次重复读取可能出现重复执行任务的情况,所以我们需要将这个值设置为false,这样第二个任务会往后推迟,只有在第一个任务执行完成后才会执行第二个任务

日志管理

log4j

POM

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
            <exclusions>
                
                <exclusion>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-loggingartifactId>
                exclusion>
            exclusions>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-log4jartifactId>
            <version>1.3.8.RELEASEversion>
        dependency>
配置文件

文件名称log4j.properties

#log4j.rootLogger=CONSOLE,info,error,DEBUG
log4j.rootLogger=info,error,CONSOLE,DEBUG
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender     
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout     
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n     
log4j.logger.info=info
log4j.appender.info=org.apache.log4j.DailyRollingFileAppender
log4j.appender.info.layout=org.apache.log4j.PatternLayout     
log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n  
log4j.appender.info.datePattern='.'yyyy-MM-dd
log4j.appender.info.Threshold = info   
log4j.appender.info.append=true   
#log4j.appender.info.File=/home/admin/pms-api-services/logs/info/api_services_info
log4j.appender.info.File=/Users/dddd/Documents/testspace/pms-api-services/logs/info/api_services_info
log4j.logger.error=error  
log4j.appender.error=org.apache.log4j.DailyRollingFileAppender
log4j.appender.error.layout=org.apache.log4j.PatternLayout     
log4j.appender.error.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n  
log4j.appender.error.datePattern='.'yyyy-MM-dd
log4j.appender.error.Threshold = error   
log4j.appender.error.append=true   
#log4j.appender.error.File=/home/admin/pms-api-services/logs/error/api_services_error
log4j.appender.error.File=/Users/dddd/Documents/testspace/pms-api-services/logs/error/api_services_error
log4j.logger.DEBUG=DEBUG
log4j.appender.DEBUG=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DEBUG.layout=org.apache.log4j.PatternLayout     
log4j.appender.DEBUG.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n  
log4j.appender.DEBUG.datePattern='.'yyyy-MM-dd
log4j.appender.DEBUG.Threshold = DEBUG   
log4j.appender.DEBUG.append=true   
#log4j.appender.DEBUG.File=/home/admin/pms-api-services/logs/debug/api_services_debug
log4j.appender.DEBUG.File=/Users/dddd/Documents/testspace/pms-api-services/logs/debug/api_services_debug
使用
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

使用AOP统一处理Web请求日志

POM
<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
    dependency>
代码
@Aspect
@Component
public class WebLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution(public * tech.tengshe789.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP_METHOD : " + request.getMethod());
        logger.info("IP : " + request.getRemoteAddr());
        Enumeration enu = request.getParameterNames();
        while (enu.hasMoreElements()) {
            String name = (String) enu.nextElement();
            logger.info("name:{},value:{}", name, request.getParameter(name));
        }
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        logger.info("RESPONSE : " + ret);
    }
}

lombok 插件

非常简单的办法

POM
<dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <version>1.18.0version>
dependency>
代码

类中添加@Slf4j 注解即可。使用是直接输入log全局变量

Lombok的其他用法
@Data 标签,生成getter/setter toString()等方法 
@NonNull : 让你不在担忧并且爱上NullPointerException 
@CleanUp : 自动资源管理:不用再在finally中添加资源的close方法 
@Setter/@Getter : 自动生成set和get方法 
@ToString : 自动生成toString方法 
@EqualsAndHashcode : 从对象的字段中生成hashCode和equals的实现 
@NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor 
自动生成构造方法 
@Data : 自动生成set/get方法,toString方法,equals方法,hashCode方法,不带参数的构造方法 
@Value : 用于注解final@Builder : 产生复杂的构建器api类
@SneakyThrows : 异常处理(谨慎使用) 
@Synchronized : 同步方法安全的转化 
@Getter(lazy=true) : 
@Log : 支持各种logger对象,使用时用对应的注解,如:@Log4

拦截器

拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。

(1)拦截器是基于java的反射机制的,而过滤器是基于函数回调。

(2)拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。

(3)拦截器只能对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用。

(4)在Controller的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。

过滤器(filter)和拦截器(interceptor)是有区别的,详情 ,他们的执行顺序: 先filter 后 interceptor

->过滤器应用场景:设置编码字符、过滤铭感字符

->拦截器应用场景:拦截未登陆用户、审计日志

自定义拦截器

代码

注册拦截器

@Configuration
public class WebAppConfig {
    @Autowired
    private LoginIntercept loginIntercept;

    @Bean
    public WebMvcConfigurer WebMvcConfigurer() {
        return new WebMvcConfigurer() {
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(loginIntercept).addPathPatterns("/*");
            };
        };
    }

}

创建模拟登录拦截器,验证请求是否有token参数

@Slf4j
@Component
public class LoginIntercept implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        log.info("开始拦截登录请求....");
        String token = request.getParameter("token");
        if (StringUtils.isEmpty(token)) {
            response.getWriter().println("not found token");
            return false;
        }
        return true;
    }
}

缓存

在 Spring Boot中,通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:  Generic  , JCache (JSR-107), EhCache 2.x  ,Hazelcast  , Infinispan  ,Redis  ,Guava , Simple

EhCache

POM
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-cacheartifactId>
dependency>
新建ehcache.xml 文件

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    updateCheck="false">
    <diskStore path="java.io.tmpdir/Tmp_EhCache" />

    
    <defaultCache maxElementsInMemory="5000" eternal="false"
        timeToIdleSeconds="120" timeToLiveSeconds="120"
        memoryStoreEvictionPolicy="LRU" overflowToDisk="false" />

    <cache name="baseCache" maxElementsInMemory="10000"
        maxElementsOnDisk="100000" />

ehcache>

配置信息介绍

name:缓存名称。

maxElementsInMemory:缓存最大个数。

eternal:对象是否永久有效,一但设置了,timeout将不起作用。

timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。

timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。

overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。

diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。

maxElementsOnDisk:硬盘最大缓存个数。

diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.

diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。

memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。

clearOnFlush:内存数量最大时是否清除。

关于注解和代码使用
@CacheConfig(cacheNames = "baseCache")
public interface UserDao {
    @Select("select * from users where name=#{name}")
    @Cacheable
    UserEntity findName(@Param("name") String name);
}
清除缓存
@Autowired
private CacheManager cacheManager;
@RequestMapping("/remoKey")
public void remoKey() {
    cacheManager.getCache("baseCache").clear();
}
启动

主方法启动时加上@EnableCaching即可

Redis

使用自带驱动器连接

使用RedisTemplate 连接

POM
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
配置文件

单机

#redis
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

集群或哨兵模式

#Matser的ip地址  
redis.hostName=192.168.177.128
#端口号  
redis.port=6382
#如果有密码  
redis.password=
#客户端超时时间单位是毫秒 默认是2000 
redis.timeout=10000  

#最大空闲数  
redis.maxIdle=300  
#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal  
#redis.maxActive=600  
#控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性  
redis.maxTotal=1000  
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。  
redis.maxWaitMillis=1000  
#连接的最小空闲时间 默认1800000毫秒(30分钟)  
redis.minEvictableIdleTimeMillis=300000  
#每次释放连接的最大数目,默认3  
redis.numTestsPerEvictionRun=1024  
#逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1  
redis.timeBetweenEvictionRunsMillis=30000  
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个  
redis.testOnBorrow=true  
#在空闲时检查有效性, 默认false  
redis.testWhileIdle=true  

#redis集群配置      
spring.redis.cluster.nodes=192.168.177.128:7001,192.168.177.128:7002,192.168.177.128:7003,192.168.177.128:7004,192.168.177.128:7005,192.168.177.128:7006
spring.redis.cluster.max-redirects=3

#哨兵模式
#redis.sentinel.host1=192.168.177.128
#redis.sentinel.port1=26379

#redis.sentinel.host2=172.20.1.231  
#redis.sentinel.port2=26379
配置类
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;

    //自定义缓存key生成策略
//    @Bean
//    public KeyGenerator keyGenerator() {
//        return new KeyGenerator(){
//            @Override
//            public Object generate(Object target, java.lang.reflect.Method method, Object... params) {
//                StringBuffer sb = new StringBuffer();
//                sb.append(target.getClass().getName());
//                sb.append(method.getName());
//                for(Object obj:params){
//                    sb.append(obj.toString());
//                }
//                return sb.toString();
//            }
//        };
//    }
    //缓存管理器
    @Bean 
    public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        //设置缓存过期时间 
        cacheManager.setDefaultExpiration(10000);
        return cacheManager;
    }
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        StringRedisTemplate template = new StringRedisTemplate(factory);
        setSerializer(template);//设置序列化工具
        template.afterPropertiesSet();
        return template;
    }
     private void setSerializer(StringRedisTemplate template){
            @SuppressWarnings({ "rawtypes", "unchecked" })
            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);
            template.setValueSerializer(jackson2JsonRedisSerializer);
     }
}
Dao
@Mapper
@CacheConfig(cacheNames = "users")
public interface UserMapper {

    @Insert("insert into user(name,age) values(#{name},#{age})")
    int addUser(@Param("name")String name,@Param("age")String age);

    @Select("select * from user where id =#{id}")
    @Cacheable(key ="#p0") 
    User findById(@Param("id") String id);

    @CachePut(key = "#p0")
    @Update("update user set name=#{name} where id=#{id}")
    void updataById(@Param("id")String id,@Param("name")String name);

    //如果指定为 true,则方法调用后将立即清空所有缓存
    @CacheEvict(key ="#p0",allEntries=true)
    @Delete("delete from user where id=#{id}")
    void deleteById(@Param("id")String id);

}

@Cacheable将查询结果缓存到redis中,(key=”#p0”)指定传入的第一个参数作为redis的key。

@CachePut,指定key,将更新的结果同步到redis中

@CacheEvict,指定key,删除缓存数据,allEntries=true,方法调用后将立即清除缓存

使用Jedis连接

要注意,redis在5.0版本以后不支持Jedis

POM
    <dependency>
      <groupId>redis.clientsgroupId>
      <artifactId>jedisartifactId>
    dependency>
配置类
@Data
@Component
@ConfigurationProperties(prefix="redis")
public class RedisConfig {
    private String host;
    private int port;
    private int timeout;//秒
    private String password;
    private int poolMaxTotal;
    private int poolMaxIdle;
    private int poolMaxWait;//秒
}
@Service
public class RedisPoolFactory {

    @Autowired
    RedisConfig redisConfig;

    @Bean
    public JedisPool edisPoolFactory() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
        poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
        poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
        JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
                redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);
        return jp;
    }

}

监控中心

Springboot监控中心是干什么的呢?他是针对微服务的 服务状态、Http请求资源进行监控,可以看到服务器内存变化(堆内存、线程、日志管理),可以检测服务配置连接地址是否可用(模拟访问,懒加载),可以统计有多少Bean有什么单例多例,可以统计SpringMVC有多少@RequestMapping

Actuator

Actuator是spring boot的一个附加功能,可帮助你在应用程序生产环境时监视和管理应用程序。

可以使用HTTP的各种请求来监管,审计,收集应用的运行情况.返回的是json

缺点:没有可视化界面。

在springboot2.0中,Actuator的端点(endpoint)现在默认映射到/application,比如,/info 端点现在就是在/application/info。但你可以使用management.context-path来覆盖此默认值。

POM
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
配置信息
# Actuator 通过下面的配置启用所有的监控端点,默认情况下,这些端点是禁用的;
management:
  endpoints:
    web:
      exposure:
        include: "*"
spring:
  profiles:
    active: prod
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test
    username: root
    password: 123456
Actuator访问路径

通过actuator/+端点名就可以获取相应的信息。

路径 作用
/actuator/beans 显示应用程序中所有Spring bean的完整列表。
/actuator/configprops 显示所有配置信息。
/actuator/env 陈列所有的环境变量。
/actuator/mappings 显示所有@RequestMapping的url整理列表。
/actuator/health 显示应用程序运行状况信息 up表示成功 down失败
/actuator/info 查看自定义应用信息

Admin-UI分布式微服务监控中心

Admin-UI底层使用actuator,实现监控信息 的界面

POM
        
        <dependency>
            <groupId>de.codecentricgroupId>
            <artifactId>spring-boot-admin-starter-serverartifactId>
            <version>2.0.0version>
        dependency>
        
        <dependency>
            <groupId>de.codecentricgroupId>
            <artifactId>spring-boot-admin-starter-clientartifactId>
            <version>2.0.0version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.jolokiagroupId>
            <artifactId>jolokia-coreartifactId>
        dependency>
        <dependency>
            <groupId>com.googlecode.json-simplegroupId>
            <artifactId>json-simpleartifactId>
            <version>1.1version>
application.yml配置文件
//服务端
spring:
  application:
    name: spring-boot-admin-server
//客户端
spring:
  boot:
    admin:
      client:
        url: http://localhost:8080
server:
  port: 8081

management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

性能优化

扫包优化

默认情况下,我们会使用 @SpringBootApplication 注解来自动获取应用的配置信息,但这样也会给应用带来一些副作用。使用这个注解后,会触发自动配置( auto-configuration )和 组件扫描 ( component scanning ),这跟使用 @Configuration@EnableAutoConfiguration@ComponentScan 三个注解的作用是一样的。这样做给开发带来方便的同时,也会有三方面的影响:

1、会导致项目启动时间变长。当启动一个大的应用程序,或将做大量的集成测试启动应用程序时,影响会特别明显。

2、会加载一些不需要的多余的实例(beans)。

3、会增加 CPU 消耗。

针对以上三个情况,我们可以移除 @SpringBootApplication 和 @ComponentScan 两个注解来禁用组件自动扫描,然后在我们需要的 bean 上进行显式配置。

SpringBoot JVM参数调优

各种参数
参数名称 含义 默认值
-Xms 初始堆大小 物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn 年轻代大小(1.4or lator) 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-XX:NewSize 设置年轻代大小(for 1.3/1.4)
-XX:MaxNewSize 年轻代最大值(for 1.3/1.4)
-XX:PermSize 设置持久代(perm gen)初始值 物理内存的1/64
-XX:MaxPermSize 设置持久代最大值 物理内存的1/4
-Xss 每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长) 和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:”” -Xss is translated in a VM flag named ThreadStackSize” 一般设置这个值就可以了。
-XX:ThreadStackSize Thread Stack Size (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]
-XX:NewRatio 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
-XX:SurvivorRatio Eden区与Survivor区的大小比值 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:LargePageSizeInBytes 内存页的大小不可设置过大, 会影响Perm的大小 =128m
-XX:+UseFastAccessorMethods 原始类型的快速优化
-XX:+DisableExplicitGC 关闭System.gc() 这个参数需要严格的测试
-XX:MaxTenuringThreshold 垃圾最大年龄 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效.
-XX:+AggressiveOpts 加快编译
-XX:+UseBiasedLocking 锁机制的性能改善
-Xnoclassgc 禁用垃圾回收
-XX:SoftRefLRUPolicyMSPerMB 每兆堆空闲空间中SoftReference的存活时间 1s softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap
-XX:PretenureSizeThreshold 对象超过多大是直接在旧生代分配 0 单位字节 新生代采用Parallel Scavenge GC时无效 另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象.
-XX:TLABWasteTargetPercent TLAB占eden区的百分比 1%
-XX:+CollectGen0First FullGC时是否先YGC false
调优策略
  1. 初始化堆内存和最大堆相同
  2. 减少垃圾回收次数
内部调优

输入 -XX:+PrintGCDetails 是为了在控制台显示回收的信息

外部调优

进入对应jar的目录,在CMD输入java -server -Xms32m -Xmx32m  -jar springboot.jar

使用工具java visual vm

使用工具java console

将Servlet容器从Tomcat变成Undertow

Undertow 是一个采用 Java 开发的灵活的高性能 Web 服务器,提供包括阻塞和基于 NIO 的非堵塞机制。Undertow 是红帽公司的开源产品,是 JBoss默认的 Web 服务器。��

Undertow

POM

首先,从依赖信息里移除 Tomcat 配置

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-tomcatartifactId>
                exclusion>
            exclusions>
        dependency>

然后添加 Undertow:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-undertowartifactId>
dependency>

Tomcat 优化

见��Spring Boot Memory Performance

热部署

热部署,就是在应用程序在不停止的情况下,自动实现新的部署

原理

使用类加载器classroad来检测字节码文件,然后重新加载到jvm内存中

第一步:检测本地.class文件变动(版本号,修改时间不一样)

第二步:自动监听,实现部署

应用场景

本地开发时,可以提高运行环境

Dev-tools

spring-boot-devtools 是一个为开发者服务的一个模块,其中最重要的功能就是自动应用代码更改到最新的App上面去

POM
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <optional>trueoptional>
            <scope>truescope>
        dependency>
原理
  1. devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),因为其采用的虚拟机机制,该项重启是很快的。
  2. devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现(注意:不同的模板配置不一样)

发布打包

Jar类型打包方式

1.使用mvn clean package 打包

2.使用java –jar 包名

war类型打包方式

1.使用mvn celan package 打包

2.使用java –jar 包名

外部Tomcat运行

1.使用mvn celan package 打包

2.将war包 放入到tomcat webapps下运行即可。

注意:springboot2.0内置tomcat8.5.25,建议使用外部Tomcat9.0版本运行即可,否则报错版本不兼容。

POM

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <maimClass>com.itmayiedu.app.AppmaimClass>
                configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackagegoal>
                        goals>
                    execution>
                executions>

            plugin>
        plugins>
    build>

参考文献

JdbcTemplate

SpringBoot分页插件PageHelper

jpa

Spring For All 社区 Spring 官方教程翻译

SpringBoot使用Redis缓存

Spring Boot Admin简单使用

Spring Boot 性能优化

WebFlux

感谢以上大大们~!

广告时间:想要了解更多精彩新姿势?请访问我的博客

你可能感兴趣的:(java,springboot)