一、SpringBoot相关知识理解
- SpringBoot简介
在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配置有一些厌烦?那么您就不妨来试试使用Spring Boot来让你更易上手,更简单快捷地构建Spring应用!
Spring Boot让我们的Spring应用变的更轻量化。比如:你可以仅仅依靠一个Java类来运行一个Spring应用。你也可以打包你的应用为jar并通过使用java -jar来运行你的Spring Web应用。
SpringBoot并不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式。
注:SpringBoot是一个独立的应用程序,单独war工程,不需要集成tomcat插件运行,因为SpringBoot自身已经内嵌了tomcat。创建SpringBoot项目时可以jar也可以是war,但是不建议建成war工程(如果要使用jsp可以建成war,但是SpringBoot不建议使用jsp),因为SpringBoot是一个独立的应用程序,所以建议选择jar创建SpringBoot项目。
Spring Boot的主要优点:
- 为所有Spring开发者更快的入门(简化Maven配置、自动配置Spring)。
- 开箱即用,提供各种默认配置来简化项目配置。同时也可以修改默认值来满足特定的需求。
- 内嵌式容器简化Web项目(嵌入的Tomcat,无需部署war文件)。
- 没有冗余代码生成和XML配置的要求。
- 提供了一些大型项目中常见的非功能特性:生产就绪功能,如:指标,健康检查和外部配置等。
- 使用SpringBoot环境要求:
Java 7及以上,Spring Framework 4.1.5及以上。
二、SpringBoot快速入门及环境搭建(jdk1.8+springboot1.3.2)
- 创建一个maven管理的jar工程,在此不做示例。
- 在pom文件中引入依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>chuancy</groupId>
<artifactId>SpringBoot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 必须要引入继承springboot-parent,因为该父工程帮助实现了很多jar包的依赖管理,不要写jar包版本,这样可以控制jar包版本冲突的问题 。-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
<dependencies>
<!-- 不需要引入SpringMVC,SpringBoot默认集成,只需要引入SpringBoot的web组件:springboot-web即可。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- spring-boot-starter-parent作用:
在pom.xml中引入spring-boot-start-parent,可以提供dependency management,也就是说依赖管理,引入以后再声明其它dependency的时候就不需要version了。 - spring-boot-starter-web作用:
springweb 核心组件,包含springmvc等。
- 创建测试类测试框架是否搭建成功:
package chauncy.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @classDesc: 功能描述(第一个springboot)
* 注解:@RestController是SpringBoot提供的http restful风格,等同于 :类上@Controller+每个方法@ResponseBody,表示该类所有请求方法返回json格式。
* @author: ChauncyWang
* @version: 1.0
*/
@RestController
@RequestMapping("/TestController")
@EnableAutoConfiguration
public class TestController {
@RequestMapping("/hello")
public String hello(){
return "success";
}
public static void main(String[] args) {
//运行SpringBoot应用,不要写多个main函数,因为SpringBoot是一个独立的应用程序。
SpringApplication.run(TestController.class, args);
}
}
- 相关知识汇总:
- @RestController解释:
在类上加上RestController 表示修饰该Controller类所有的方法返回JSON格式,直接可以编写Restful接口。 - @EnableAutoConfiguration解释:
该注解作用在于:让Spring Boot根据应用所声明的依赖来对Spring框架进行自动配置。
这个注解告诉Spring Boot根据添加的jar依赖猜测开发设计者想如何配置Spring。由于spring-boot-starter-web添加了Tomcat和Spring MVC,所以auto-configuration将假定开发设计者正在开发一个web应用并相应地对Spring进行设置。 - SpringApplication.run(TestController.class, args);解释
标识TestController为启动类。 - Spring启动方式:
- 启动方式1:
以上环境搭建第3步。
Springboot默认端口号为8080,启动成功后,打开浏览器访问http://127.0.0.1:8080/TestController/hello,可以看到页面输出:success。
但是因为SpringBoot默认端口为8080,再写一个Controller同样也需要启动的话,如果写两个main方法同时启动会报端口号被占用的异常,所以不建议写在某一个业务Controller中进行启动,建议写通用类进行启动配置的操作,例以下启动方式2. - 启动方式2:
@ComponentScan(basePackages = “chauncy.controller”)–控制器扫包范围
@ComponentScan(basePackages = "chauncy.controller")
@EnableAutoConfiguration
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
三、使用SpringBoot进行Web开发
- 静态资源访问:
在进行Web应用开发的时候,需要引用大量的js、css、图片等静态资源。
默认配置:
Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则:
/static
/public
/resources
/META-INF/resources
举例:我们可以在src/main/resources/目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/test.jpg(访问不需要加static)。如能显示图片,配置成功。 - 全局捕获异常:
@ExceptionHandler 表示拦截异常。
@ControllerAdvice 是controller的一个辅助类,最常用的就是作为全局异常处理的切面类。
@ControllerAdvice 可以指定扫描范围。
@ControllerAdvice 约定了几种可行的返回值:
- 如果是直接返回 model 类的话,需要使用 @ResponseBody 进行 json 转换。
- 返回 String,表示跳到某个 view。
- 返回 modelAndView。
代码示例,当应用有运行时异常进行捕获处理:
package chauncy.controller.catchexception;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @classDesc: 功能描述:(全局捕获异常)
* @author: ChauncyWang
* @verssion: v1.0
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody//拦截异常返回json格式的结果
public Map<String,Object> execptionHandler(){
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", "500");
result.put("msg", "系统错误,请稍后重试。");
return result;
}
}
- 渲染Web页面
在以上的代码中,都是通过@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?
在动态HTML实现上,Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,可以很快的上手开发动态网站。
Spring Boot提供了默认配置的模板引擎主要有以下几种:
- Thymeleaf
- FreeMarker
- Velocity
- Groovy
- Mustache
Spring Boot建议使用以上这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置。
当使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。
PS:模板引擎主要作用是动态页面静态化,为什么要动态页面静态化,因为好让搜索引擎搜索到,就是SEO(Search Engine Optimization搜索引擎优化),最终转化成html(伪静态),可以提高互联网站在搜索引擎中的排名,获取流量收益等。
- 使用Freemarker模板引擎渲染web视图
- 在pom.xml文件中引入freemarker依赖(注意一定要使用springboot相关依赖,不要从maven库中找):
<!-- 引入freeMarker的依赖包. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
- 在默认路径src/main/resources/templates下,创建一个后缀名为ftl的文件,内容如下:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title></title>
</head>
<body>
${name}<br/>
<#if sex==1>
男
<#elseif sex==2>
女
<#else>
其他
</#if>
<br/>
<#list userList as user>
${user}
</#list>
</body>
</html>
- 后台编写请求访问创建的ftl文件:
package chauncy.controller.web;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/IndexController")
public class IndexController {
@RequestMapping("/index")
public String index(ModelMap map){
//ModelMap类似于HttpServletRequest的request.setAttribute,只不过ModelMap是SpringMVC封装好的。
map.put("name", "ChauncyWang");
map.put("sex", 1);
List<String> userlist=new ArrayList<String>();
userlist.add("chauncy1");
userlist.add("chauncy2");
userlist.add("chauncy3");
map.put("userList", userlist);
return "index";
}
}
- Freemarker的一些配置:
在src/main/resources下新建application.properties文件,添加如下配置(具体属性含义及修改参考互联网资料):
########################################################
###FREEMARKER (FreeMarkerAutoConfiguration)
########################################################
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
- 使用JSP渲染Web视图
SpringBoot要求使用jsp的话项目必须构建为war工程。
- 创建一个maven管理的war工程,在此不做示例。
- 在pom.xml文件中引入相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
<dependencies>
<!-- SpringBoot 核心组件 -->
<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>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
</dependencies>
- 在src/main/resources下新建application.properties文件,添加如下配置:
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
- 在src/main/webapp下创建文件夹WEB-INF/jsp,创建文件index.jsp:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
</head>
<body>
this is springboot!
</body>
</html>
- 后台编写请求访问index.jsp文件(简化启动,运行main即可):
package chauncy.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/IndexController")
@EnableAutoConfiguration
public class IndexController {
@RequestMapping("/index")
public String index(){
return "index";
}
public static void main(String[] args) {
SpringApplication.run(IndexController.class, args);
}
}
四、数据访问
- springboot整合使用JdbcTemplate:
- 创建一个maven管理的jar工程,在此不做示例。
- 在pom.xml文件中引入相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 在src/main/resources下新建application.properties文件,添加如下配置:
spring.datasource.url=jdbc:mysql://localhost:3306/architect?serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- 编写后台请求实现使用JdbcTemplate对数据库表增加记录的操作:
package chauncy.service;
public interface UserService {
public void createJdbcUser();
}
package chauncy.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import chauncy.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void createJdbcUser() {
jdbcTemplate.update("insert into users values(null,?,?);", "chauncy", 18);
}
}
package chauncy.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import chauncy.service.UserService;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
@Autowired
UserService userService;
@RequestMapping("/index")
public String index(){
userService.createJdbcUser();
return "add success";
}
}
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = "chauncy.*")
@EnableAutoConfiguration
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 使用浏览器访问请求,若正常造成请求,数据库users表应该增加一条记录。
注意:使用JdbcTemplate需要spring-boot-starter-parent在1.5以上。
- springboot整合使用mybatis:
- 创建一个maven管理的jar工程,在此不做示例。
- 在pom.xml文件中引入相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 在src/main/resources下新建application.properties文件,添加如下配置:
spring.datasource.url=jdbc:mysql://localhost:3306/architect?serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- 编写后台请求实现使用Mybatis对数据库表条件查询的操作:
package chauncy.entity;
public class UserEntity {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package chauncy.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import chauncy.entity.UserEntity;
public interface UserMapper {
@Select("select * from users where name=#{name}")
UserEntity findUser(@Param("name")String name);
}
package chauncy.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import chauncy.entity.UserEntity;
import chauncy.mapper.UserMapper;
@Controller
@RequestMapping("/IndexController")
public class IndexController {
@Autowired
private UserMapper userMapper;
@ResponseBody
@RequestMapping("/getUserName")
public UserEntity getUserName(String name) {
return userMapper.findUser(name);
}
}
package chauncy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = "chauncy.*")
@MapperScan(basePackages="chauncy.*")
@EnableAutoConfiguration
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 使用浏览器造成get请求进行访问,若条件匹配应有json数据返回。
- springboot整合使用springjpa:
什么是SpringJPA?SpringJPA其实就是对Hibernate的封装。
- 创建一个maven管理的jar工程,在此不做示例。
- 在pom.xml文件中引入相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 在src/main/resources下新建application.properties文件,添加如下配置:
spring.datasource.url=jdbc:mysql://localhost:3306/architect?serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- 编写后台请求实现使用SpringJPA对数据库表条件查询的操作:
package chauncy.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
//注解都是使用javax反射包下的
@Entity(name = "users")
public class UserEntity {
@Id
@GeneratedValue
private Integer id;
@Column
private String name;
@Column
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserEntity [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
package chauncy.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import chauncy.entity.UserEntity;
//规范:使用jpa的dao层必须要继承JpaRepository<实体类, 序列化主键>接口
public interface UserDao extends JpaRepository<UserEntity, Integer> {
}
package chauncy.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import chauncy.dao.UserDao;
import chauncy.entity.UserEntity;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
@Autowired
private UserDao userDao;
@RequestMapping("/index")
public String index(Integer id) {
UserEntity userEntity = userDao.findOne(id);
return userEntity.toString();
}
}
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableAutoConfiguration
@ComponentScan(basePackages = "chauncy.*")
@EnableJpaRepositories(basePackages = "chauncy.*")
@EntityScan("chauncy.*")
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 使用浏览器造成get请求进行访问,若条件匹配应有toString数据返回。
- springboot整合多数据源:
有的时候一个项目需要操作多个JDBC,此时就需要整合多数据源。
例:大型互联网公司分为base数据库(字典、相同配置信息)、主数据库,需要操作多数据源。
操作多数据源的方式:1.使用注解,但是较繁琐,需要写很多,不推荐。2.分包进行配置,一个包对一个数据源进行操作。
PS:Spring默认支持多数据源事务回滚。
- 创建一个maven管理的jar工程,在此不做示例。
- 在pom.xml文件中引入相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 在src/main/resources下新建application.properties文件,添加如下配置:
spring.datasource.test1.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.test1.url = jdbc:mysql://localhost:3306/test01?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
spring.datasource.test1.username = root
spring.datasource.test1.password = root
spring.datasource.test2.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.test2.url = jdbc:mysql://localhost:3306/test02?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
spring.datasource.test2.username = root
spring.datasource.test2.password = root
- 使用java代码增加两个数据源的配置:
package chauncy.datasource;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
/**
* @classDesc: 功能描述(数据源1的配置)
* @author: ChauncyWang
* @version: 1.0
*/
@Configuration // 注册到spring容器中
@MapperScan(basePackages="chauncy.test01",sqlSessionFactoryRef="test1SqlSessionFactory")
public class Datasource1 {
/**
* @methodDesc: 功能描述(配置test1数据库)
* @author: ChauncyWang
* @param: @return
* @returnType: DataSource
*/
@Bean(name = "test1DataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.test1")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
/**
* @methodDesc: 功能描述(test1 sql会话工厂)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @param: @throws Exception
* @returnType: SqlSessionFactory
*/
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(
// new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
return bean.getObject();
}
/**
* @methodDesc: 功能描述(test1 事物管理)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @returnType: DataSourceTransactionManager
*/
@Bean(name = "test1TransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package chauncy.datasource;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
/**
* @classDesc: 功能描述(数据源2的配置)
* @author: ChauncyWang
* @version: 1.0
*/
@Configuration // 注册到spring容器中
@MapperScan(basePackages="chauncy.test02",sqlSessionFactoryRef="test2SqlSessionFactory")
public class Datasource2 {
/**
* @methodDesc: 功能描述(配置test2数据库)
* @author: ChauncyWang
* @param: @return
* @returnType: DataSource
*/
@Bean(name = "test2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
/**
* @methodDesc: 功能描述(test2 sql会话工厂)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @param: @throws Exception
* @returnType: SqlSessionFactory
*/
@Bean(name = "test2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(
// new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test2/*.xml"));
return bean.getObject();
}
/**
* @methodDesc: 功能描述(test2 事物管理)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @returnType: DataSourceTransactionManager
*/
@Bean(name = "test2TransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- 编写分包请求,对两个数据源进行操作:
package chauncy.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import chauncy.entity.UserEntity;
import chauncy.mapper.UserMapper;
import chauncy.service.UserService;
import chauncy.test01.service.UserService1;
import chauncy.test02.service.UserService2;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
@Autowired
private UserService1 userService1;
@Autowired
private UserService2 userService2;
@RequestMapping("/add")
public String add(String name, Integer age) {
userService1.addUser(name, age);
userService2.addUser(name, age);
return "success";
}
}
package chauncy.test01.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import chauncy.test01.mapper.UserMapper1;
@Service
public class UserService1 {
@Autowired
private UserMapper1 userMapper1;
public int addUser(String name,Integer age){
return userMapper1.addUser(name, age);
}
}
package chauncy.test01.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
public interface UserMapper1 {
@Insert("insert into users values(null,#{name},#{age})")
public int addUser(@Param("name")String name,@Param("age")Integer age);
}
package chauncy.test02.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import chauncy.test02.mapper.UserMapper2;
@Service
public class UserService2 {
@Autowired
private UserMapper2 userMapper2;
public int addUser(String name,Integer age){
return userMapper2.addUser(name, age);
}
}
package chauncy.test02.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
public interface UserMapper2 {
@Insert("insert into users values(null,#{name},#{age})")
public int addUser(@Param("name")String name,@Param("age")Integer age);
}
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = "chauncy.*")
@EnableAutoConfiguration
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 使用浏览器造成get请求进行访问,返回success后比对test01、test02数据库是否都有数据新增,若是则证明springboot整合多数据源成功。
五、事务管理
- springboot整合事物管理:
springboot默认集成事物,只主要在方法上加上@Transactional即可。 - 分布式事务(操作多个数据源)解决方案:
jta+atomikos(不仅限于springboot)、两段提交协议、MQ推送。 - SpringBoot分布式事物管理:
使用springboot+jta+atomikos分布式事物管理。把所有的数据源都交给atomikos进行事务管理。 - 操作多个数据源进行事务管理时,问题重现:
package chauncy.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import chauncy.entity.UserEntity;
import chauncy.mapper.UserMapper;
import chauncy.service.UserService;
import chauncy.test01.service.UserService1;
import chauncy.test02.service.UserService2;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
@Autowired
private UserService1 userService1;
@RequestMapping("/addUser1AndUser2")
public String addUser1AndUser2(){
userService1.addUser1AndUser2();
return "success";
}
}
package chauncy.test01.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import chauncy.test01.mapper.UserMapper1;
import chauncy.test02.mapper.UserMapper2;
@Service
public class UserService1 {
@Autowired
private UserMapper1 userMapper1;
@Autowired
private UserMapper2 userMapper2;
@Transactional
public int addUser1AndUser2(){
userMapper1.addUser("test01", 18);
System.out.println("数据库01插入完毕。。");
userMapper2.addUser("test02", 19);
int i=1/0;
return 0;
}
}
package chauncy.test01.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
public interface UserMapper1 {
@Insert("insert into users values(null,#{name},#{age})")
public int addUser(@Param("name")String name,@Param("age")Integer age);
}
package chauncy.test02.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
public interface UserMapper2 {
@Insert("insert into users values(null,#{name},#{age})")
public int addUser(@Param("name")String name,@Param("age")Integer age);
}
package chauncy.datasource;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
/**
* @classDesc: 功能描述(数据源1的配置)
* @author: ChauncyWang
* @version: 1.0
*/
@Configuration // 注册到spring容器中
@MapperScan(basePackages="chauncy.test01",sqlSessionFactoryRef="test1SqlSessionFactory")
public class Datasource1 {
/**
* @methodDesc: 功能描述(配置test1数据库)
* @author: ChauncyWang
* @param: @return
* @returnType: DataSource
*/
@Bean(name = "test1DataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.test1")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
/**
* @methodDesc: 功能描述(test1 sql会话工厂)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @param: @throws Exception
* @returnType: SqlSessionFactory
*/
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(
// new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));
return bean.getObject();
}
/**
* @methodDesc: 功能描述(test1 事物管理)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @returnType: DataSourceTransactionManager
*/
@Bean(name = "test1TransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package chauncy.datasource;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
/**
* @classDesc: 功能描述(数据源2的配置)
* @author: ChauncyWang
* @version: 1.0
*/
@Configuration // 注册到spring容器中
@MapperScan(basePackages="chauncy.test02",sqlSessionFactoryRef="test2SqlSessionFactory")
public class Datasource2 {
/**
* @methodDesc: 功能描述(配置test2数据库)
* @author: ChauncyWang
* @param: @return
* @returnType: DataSource
*/
@Bean(name = "test2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource testDataSource() {
return DataSourceBuilder.create().build();
}
/**
* @methodDesc: 功能描述(test2 sql会话工厂)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @param: @throws Exception
* @returnType: SqlSessionFactory
*/
@Bean(name = "test2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// bean.setMapperLocations(
// new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test2/*.xml"));
return bean.getObject();
}
/**
* @methodDesc: 功能描述(test2 事物管理)
* @author: ChauncyWang
* @param: @param dataSource
* @param: @return
* @returnType: DataSourceTransactionManager
*/
@Bean(name = "test2TransactionManager")
public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- 问题:上述代码会出现,造成访问请求报错后,一个方法中两个不同数据源的事务无法统一进行回滚的问题,即test01数据库的users表没有新增记录,test02数据库的users表新增了记录,正确结果是都不应该增加记录。
- 使用springboot+jta+atomikos分布式事物管理,解决以上问题:
- 在pom文件中新增引入jta依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
- 在application.properties中创建两个数据源:
# Mysql 1
mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test01?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
mysql.datasource.test1.username = root
mysql.datasource.test1.password = root
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.datasource.test1.testQuery = select 1
# Mysql 2
mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8
mysql.datasource.test2.username =root
mysql.datasource.test2.password =root
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
mysql.datasource.test2.testQuery = select 1
server.port = 80
server.context.path=/
- 读取application.properties配置文件的信息:
package chauncy.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@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;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMinPoolSize() {
return minPoolSize;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(int maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getBorrowConnectionTimeout() {
return borrowConnectionTimeout;
}
public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {
this.borrowConnectionTimeout = borrowConnectionTimeout;
}
public int getLoginTimeout() {
return loginTimeout;
}
public void setLoginTimeout(int loginTimeout) {
this.loginTimeout = loginTimeout;
}
public int getMaintenanceInterval() {
return maintenanceInterval;
}
public void setMaintenanceInterval(int maintenanceInterval) {
this.maintenanceInterval = maintenanceInterval;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public String getTestQuery() {
return testQuery;
}
public void setTestQuery(String testQuery) {
this.testQuery = testQuery;
}
}
package chauncy.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "mysql.datasource.test2")
public class DBConfig2 {
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;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMinPoolSize() {
return minPoolSize;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(int maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getBorrowConnectionTimeout() {
return borrowConnectionTimeout;
}
public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {
this.borrowConnectionTimeout = borrowConnectionTimeout;
}
public int getLoginTimeout() {
return loginTimeout;
}
public void setLoginTimeout(int loginTimeout) {
this.loginTimeout = loginTimeout;
}
public int getMaintenanceInterval() {
return maintenanceInterval;
}
public void setMaintenanceInterval(int maintenanceInterval) {
this.maintenanceInterval = maintenanceInterval;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public String getTestQuery() {
return testQuery;
}
public void setTestQuery(String testQuery) {
this.testQuery = testQuery;
}
}
- 创建多数据源,放入Atomikos数据源中进行集中管理:
package chauncy.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.mysql.cj.jdbc.MysqlXADataSource;
import chauncy.config.DBConfig1;
@Configuration
//basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "chauncy.test01", sqlSessionTemplateRef = "testSqlSessionTemplate1")
public class TestMybatisConfig1 {
// 配置数据源
@Primary
@Bean(name = "testDataSource1")
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("testDataSource1");
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;
}
@Bean(name = "testSqlSessionFactory1")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource1") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean(name = "testSqlSessionTemplate1")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("testSqlSessionFactory1") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package chauncy.datasource;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.mysql.cj.jdbc.MysqlXADataSource;
import chauncy.config.DBConfig1;
@Configuration
//basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "chauncy.test02", sqlSessionTemplateRef = "testSqlSessionTemplate2")
public class TestMybatisConfig2 {
// 配置数据源
@Bean(name = "testDataSource2")
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("testDataSource2");
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;
}
@Bean(name = "testSqlSessionFactory2")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource2") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean(name = "testSqlSessionTemplate2")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("testSqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- 启动加载配置:
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import chauncy.config.DBConfig1;
import chauncy.config.DBConfig2;
@ComponentScan(basePackages = "chauncy.*")
@EnableAutoConfiguration
@EnableConfigurationProperties(value={DBConfig1.class,DBConfig2.class})
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 变更成以上配置后,再次验证“4. 操作多个数据源进行事务管理时,问题重现”中所提问题,不会重现,则证明使用springboot+jta+atomikos可解决操作多个数据源事务管理的问题。
六、日志管理
- 使用log4j记录日志:
- 新建log4j配置文件:
#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=D://EclipseWorkspace/EclipseMars2/ArchitectFirstPhase/AllProjectLogs/SpringBoot-Mybatis.log
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=D://EclipseWorkspace/EclipseMars2/ArchitectFirstPhase/AllProjectLogs/SpringBoot-Mybatis.log
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=D://EclipseWorkspace/EclipseMars2/ArchitectFirstPhase/AllProjectLogs/SpringBoot-Mybatis.log
- 使用log4j进行日志打印:
package chauncy.controller;
import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
private static Logger logger = Logger.getLogger(IndexController.class);
@RequestMapping("/log")
public String log(){
logger.info("test log");
return "success";
}
}
- 使用浏览器造成请求,监视控制台打印信息,若正确打印,证明springboot整合log4j成功。
- 使用AOP统一处理Web请求日志:
- 在pom文件中新增引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 编写切面类对日志进行处理:
package chauncy.log;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
public class WebLogAspect {
private static Logger logger=LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * chauncy.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);
}
}
- 对chauncy.controller包下任意类任意方法造成请求,都会打印相关详细信息,如请求URL、当前服务器IP、请求类型、参数名称、参数值、请求状态(是否成功)等。
七、缓存支持
- 缓存分为:1.jvm缓存(只能在java中使用,并且只在当前jvm有效) 2. 内存缓存(redis、mongodb等非关系型数据库)
- 为什么要用到缓存?
因为使用缓存可以减轻数据库压力。 - 注解配置与EhCache使用:
- pom文件引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 在src/main/resources下新建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">
<!--
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:内存数量最大时是否清除。
-->
<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>
- 编写后台请求,持久化层查询使用缓存:
package chauncy.controller;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import chauncy.entity.UserEntity;
import chauncy.mapper.UserMapper;
import chauncy.service.UserService;
import chauncy.test01.mapper.UserMapper1;
import chauncy.test01.service.UserService1;
import chauncy.test02.service.UserService2;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
private static Logger logger = Logger.getLogger(IndexController.class);
@Autowired
private UserMapper1 userMapper1;
@RequestMapping("/getUserName")
public UserEntity getUserName(String name){
return userMapper1.findByUserName(name);
}
}
package chauncy.test01.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import chauncy.entity.UserEntity;
@CacheConfig(cacheNames = "baseCache")
public interface UserMapper1 {
@Select("select * from users t where t.name=#{name}")
@Cacheable
public UserEntity findByUserName(@Param("name")String name);
}
- 编写启动类,启动容器:
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import chauncy.config.DBConfig1;
import chauncy.config.DBConfig2;
@ComponentScan(basePackages = "chauncy.*")
@EnableCaching // 开启缓存注解
@EnableAutoConfiguration
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 清除缓存的请求代码:
package chauncy.controller;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
private static Logger logger = Logger.getLogger(IndexController.class);
@Autowired
private CacheManager cacheManager;
@RequestMapping("/removeCache")
public String removeCache() {
cacheManager.getCache("baseCache").clear();
return "success";
}
}
八、其他内容
- 使用@Scheduled创建定时任务:
在Spring Boot的主类中加入@EnableScheduling注解,启用定时任务的配置(以下示例为简单demo,没考虑分布式、异常等情况):
package chauncy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTask {
@Scheduled(fixedRate=1000)
public void add(){
System.out.println("正在执行。。。"+System.currentTimeMillis());
}
}
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import chauncy.config.DBConfig1;
import chauncy.config.DBConfig2;
@ComponentScan(basePackages = {"chauncy.*","chauncy"})
@EnableScheduling//开启定时任务注解
@EnableAutoConfiguration
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 使用@Async实现异步调用(多线程的使用):
启动加上@EnableAsync,需要执行异步方法上加入@Async。
PS:多线程的情况,执行路径会打乱。使用多线程的好处:提高程序的效率。
package chauncy.controller;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
private static Logger logger = Logger.getLogger(IndexController.class);
@Autowired
private UserService1 userService1;
@RequestMapping("/sendSms")
public String sendSms(){
System.out.println("userService1->sendSms()开始调用");
userService1.sendSms();
System.out.println("userService1->sendSms()结束调用");
return "success";
}
}
package chauncy.test01.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class UserService1 {
//当一个方法执行时间比较长,就要使用多线程
@Async
public void sendSms(){
System.out.println("sendSms()方法开始执行。。。");
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("i:"+i);
}
System.out.println("sendSms()方法结束执行。。。");
}
}
package chauncy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import chauncy.config.DBConfig1;
import chauncy.config.DBConfig2;
@ComponentScan(basePackages = {"chauncy.*","chauncy"})
@EnableAsync//开启异步注解
@EnableAutoConfiguration
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
- 获取自定义参数:
- 配置文件中创建自定义参数:
name=ChauncyWang
- 代码中获取自定义参数:
package chauncy.controller;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/IndexController")
public class IndexController {
private static Logger logger = Logger.getLogger(IndexController.class);
@Value("${name}")
private String name;
@RequestMapping("/getName")
public String getName(){
return name;
}
}
- 多环境区分:
- 实际项目开发中分为:Dev(开发环境)、Sit(集成测试环境)、Pre(预生产环境、灰度环境)、Prd(生产环境),针对于不同环境需要进行不同的配置处理,此时就要进行多环境区分。
- 具体实现:
- 新建两个不同环境配置文件:
- application-pre.properties
name=pre.ChauncyWang
- application-prd.properties
name=prd.ChauncyWang
- 在主环境配置文件中声明使用哪个环境:
spring.profiles.active=prd
- 获取自定义参数步骤如上述3。
- 修改端口号:
在配置文件中写入:
server.port = 80
server.context.path=/
- SpringBootYml使用:
创建application.yml(注意ymL规范,冒号:后面要有空格):
server:
port: 80
context-path: /
- 发布打包
- 使用命令mvn package进行打包。
- 打包完成会在项目target目录下生成相应的包。
- 使用java -jar 包名 运行包。
如果报错“没有主清单”,在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>chauncy.App</maimClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>