這是一篇優雅的Springboot2.0使用手冊

最近再研究springboot的原理😋很有收穫,如今讓我分享一下springboot如何使用吧~javascript

想要解鎖更多新姿式?請訪問個人博客html

啥是Springboot

和書上理解的不一樣,我認爲Springboot是一個優秀的快速搭建框架,他經過maven繼承方式添加依賴來整合不少第三方工具,能夠避免各類麻煩的配置,有各類內嵌容器簡化Web項目,還能避免依賴的干擾,它內置tomcat,jetty容器,使用的是java app運行程序,而不是傳統的用把war放在tomcat等容器中運行java

和JFinal的區別

JFinal是國人出品的一個web + orm 框架 ,JFinal,優勢是開發迅速、代碼量少、學習簡單、功能強大、輕量級、易擴展。核心就是極致簡潔。他沒有商業機構的支持,因此宣傳不到位,少有人知。node

Springboot相比與JFinal最大的優勢就是支持的功能很是多,能夠很是方便的將spring的各類框架如springframework , spring-mvc, spring-security, spring-data-jpa, spring-cache等等集成起來進行自動化配置 ,並且生態 比較好,不少產品都對Springboot作出必定支持。mysql

與Springcloud的區別

能夠這麼理解,Springboot裏面包含了Springcloud,Springcloud只是Springboot裏面的一個組件而已。react

Springcloud提供了至關完整的微服務架構。而微服務架構,本質來講就是分佈式架構,意味着你要將原來是一個總體的項目拆分紅一個個的小型項目,而後利用某種機制將其聯合起來,例如服務治理、通訊框架等基礎設施。jquery

SpringBoot和SpringMVC區別

SpringBoot的Web組件,默認集成的是SpringMVC框架。git

快速使用

要往下看的話,注意了👇github

  • 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依賴建立的。web

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

1533536250534

很簡單,在這個界面裏面就能夠建立Springboot了。接下來在添加一些組件。

1533536423232

大功告成!

寫一個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

規則:

一、名用大寫比較規範

二、=兩邊別打空格

三、名值對寫完後別打分號

自定義參數

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

1533537772267

啓動程序後,嘗試訪問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.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </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<GoodsVo> goodsList = goodsService.listGoodsVo();
        model.addAttribute("goodsList", goodsList);
        return "goods_list";
    }

前臺

這裏注意Thymeleaf語法,Thymeleaf很像HTML,不一樣之處在標籤加了一個th前綴

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>商品列表</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!-- jquery -->
    <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.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </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<String, Object> result) {
        result.put("nickname", "tEngSHe789");
        result.put("old", "18");
        result.put("my Blog", "HTTPS://blog.tengshe789.tech/");
        List<String> listResult = new ArrayList<String>();
        listResult.add("guanyu");
        listResult.add("zhugeliang");
        result.put("listResult", listResult);
        return "index";
    }

前臺

<!DOCTYPE html>
<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.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
    </parent>
    <dependencies>
        <!-- SpringBoot web 核心組件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
    <!-- SpringBoot 外部tomcat支持 -->    
    <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </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 註解方式,另外一種是使用其功能性端點方式。

img

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<Void> create(@RequestBody Publisher<Person> personStream) {
            return this.repository.save(personStream).then();
    }
 
    @GetMapping("/person")
    Flux<Person> list() {
            return this.repository.findAll();
    }
 
    @GetMapping("/person/{id}")
    Mono<Person> 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<City> 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.boot</groupId>
     <artifactId>spring-boot-starter-jdbc</artifactId>
</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.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
    </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<GoodsVo> listGoodsVo();
}

建立service

@Service
public class GoodsService {

    @Autowired
    GoodsDao goodsDao;

    /*
     * 展現商品列表
     */
    public List<GoodsVo> listGoodsVo() {
        return goodsDao.listGoodsVo();
    }
}

Mybatis整合分頁插件PageHelper

PageHelper 是一款好用的開源免費的 Mybatis 第三方物理分頁插件

POM

<dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </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<User> findUserList();
}
Service層
@Service
public class UserService {
    @Autowired
    private UserMapper userDao;

    /**
     * page 當前頁數<br>
     * size 當前展現的數據<br>    
     */
    public PageInfo<User> findUserList(int page, int size) {
        // 開啓分頁插件,放在查詢語句上面
        PageHelper.startPage(page, size);
        List<User> listUser = userDao.findUserList();
        // 封裝分頁以後的數據
        PageInfo<User> pageInfoUser = new PageInfo<User>(listUser);
        return pageInfoUser;
    }
}

整合SpringJPA

spring-data-jpa三個步驟:

  1. 聲明持久層的接口,該接口繼承 Repository(或Repository的子接口,其中定義了一些經常使用的增刪改查,以及分頁相關的方法)。
  2. 在接口中聲明須要的業務方法。Spring Data 將根據給定的策略生成實現代碼。
  3. 在 Spring 配置文件中增長一行聲明,讓 Spring 爲聲明的接口建立代理對象。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,爲繼承 Repository 或其子接口的接口建立代理對象,並將代理對象註冊爲 Spring Bean,業務層即可以經過 Spring 自動封裝的特性來直接使用該對象。

詳情:JPA官方網站

POM

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </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<User> 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 , BitronixNarayana類事務管理器

類事務管理器Atomikos

POM
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</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.boot</groupId>
      <artifactId>spring-boot-starter-quartz-starter</artifactId>
    </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

<!-- spring boot start -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <!-- 排除自帶的logback依賴 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- springboot-log4j -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </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.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
    </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<String> 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.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.0</version>
</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.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

新建ehcache.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<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.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </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<String, String> 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.clients</groupId>
      <artifactId>jedis</artifactId>
    </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.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </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.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-server</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--客戶端-->
        <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jolokia</groupId>
            <artifactId>jolokia-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1</version>

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 三個註解的做用是同樣的。這樣作給開發帶來方便的同時,也會有三方面的影響:

一、會致使項目啓動時間變長。當啓動一個大的應用程序,或將作大量的集成測試啓動應用程序時,影響會特別明顯。

二、會加載一些不須要的多餘的實例(beans)。

三、會增長 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. 減小垃圾回收次數
內部調優

1533633207629

輸入 -XX:+PrintGCDetails 是爲了在控制檯顯示回收的信息

1533633302622

外部調優

進入對應jar的目錄,在CMD輸入java -server -Xms32m -Xmx32m  -jar springboot.jar

使用工具java visual vm

1533633302622

使用工具java console

1533633885354

將Servlet容器從Tomcat變成Undertow

Undertow 是一個採用 Java 開發的靈活的高性能 Web 服務器,提供包括阻塞和基於 NIO 的非堵塞機制。Undertow 是紅帽公司的開源產品,是 JBoss默認的 Web 服務器。👇

Undertow

POM

首先,從依賴信息裏移除 Tomcat 配置

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

而後添加 Undertow:

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

Tomcat 優化

見👉Spring Boot Memory Performance

熱部署

熱部署,就是在應用程序在不中止的狀況下,自動實現新的部署

原理

使用類加載器classroad來檢測字節碼文件,而後從新加載到jvm內存中

第一步:檢測本地.class文件變更(版本號,修改時間不同)

第二步:自動監聽,實現部署

應用場景

本地開發時,能夠提升運行環境

Dev-tools

spring-boot-devtools 是一個爲開發者服務的一個模塊,其中最重要的功能就是自動應用代碼更改到最新的App上面去

POM

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </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.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <maimClass>com.itmayiedu.app.App</maimClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>

            </plugin>
        </plugins>
    </build>

參考文獻

JdbcTemplate

SpringBoot分頁插件PageHelper

jpa

Spring For All 社區 Spring 官方教程翻譯

SpringBoot使用Redis緩存

Spring Boot Admin簡單使用

Spring Boot 性能優化

WebFlux

感謝以上大大們~!

廣告時間:想要了解更多精彩新姿式?請訪問個人博客

相關文章
相關標籤/搜索