项目整合了redis非关系型数据库,swagger2生成API,lombok插件,druid数据源,mybatis,mybatis-plus, 多数据源的配置,log4j2和logback日志,定时器,springboot的多环境配置,rabbitmq的整合及其延时消息的设置
相关源码已上传至下载资源,链接为:
https://download.csdn.net/download/qq_39615245/10617010
目录
10.2 创建RunTask类,继承QuartzJobBean,如下:
1. 快速搭建一个 Spring Boot 项目
IDEA版本 | 2018.1.6 |
Springboot版本 | 2.0.3.RELEASE |
jdk | 1.8 |
Maven | Apache Maven 3.0.5 |
1.1 搭建项目
创建Spring Boot操作步骤如下:
1.在File菜单里面选择 New > Project,然后选择Spring Initializr。
2.写入项目的name,group等信息
3.勾选web中的web
4.finish
1.2 项目结构
根据上面的操作已经初始化了一个Spring Boot的框架了,项目结构如下:
如你所见,项目里面基本没有代码,除了几个空目录外,还包含如下几样东西。
1. pom.xml:Maven构建说明文件。
2. SpeedApplication.java:一个带有main()方法的类,用于启动应用程序(关键)。
3. SpeedApplicationTests.java:一个空的Junit测试类,它加载了一个使用Spring Boot字典配置功能的Spring应用程序上下文。
4. application.properties:一个空的properties文件,你可以根据需要添加配置属性。个人比较喜欢yml配置文件,后面的application配置文件会使用yml文件
1.3 Spring Boot
(1) 轻量化
(2) 提供 Spring 框架各种默认配置来简化项目配置
(3) 内嵌 Web 容器
(4) 没有冗余代码生成和XML配置要求
1.4 Maven导包
spring-boot-starter:核心模块,包括了自动配置支持、日志和YAML
spring-boot-starter-test:测试模块,包括JUnit、Hamcrest、Mockito
spring-boot-starter-web:Web模块
如果项目中main方法不能识别,点开maven工具栏clean install后即可
1.5 测试下
一个 Spring Boot 案例应该包括四个部分(主加载类、逻辑实现类、单元测试类、以及资源配置文件)。
1. 资源配置文件:这个文件主要记录了框架下各种设置;前面,我们提到过 Spring Boot 提供 Spring 的默认设置,所以一开始并不需要对这个文件做任何修改,让框架内嵌的Web容器加载该文件即可。
* 注意:命名为application.properties ,并且默认端口为8080。
2. 主加载类:Spring Boot 框架下,最重要的一个类,也是启动整个框架的入口。一般有两种代码模板,好像也没有什么区别。这里先写一种:
@SpringBootApplication
public class SpeedApplication {
public static void main(String[] args) {
SpringApplication.run(SpeedApplication.class, args);
}
}
3. 逻辑实现类:就是我们提供的服务接口,一般就是我们的Controller层。这里实现一个简单的”hello world!”的Controller,便于测试。 启动项目后,访问 http://localhost:8080/hello 来访问这个控制器:
@RestController
public class HellCoroller {
@RequestMapping("/hello")
public String sayHello(){
return "hello world!";
}
}
4. 单元测试类:顾名思义,就是一个用来测试我们的逻辑实现类的类。下面的项目是新创建的promote项目
2. SpringBoot整合MyBatis并添加分页插件
2.1 依赖Jar包
<!--开发web项目相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL Driver驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
<scope>runtime</scope>
</dependency>
<!-- mybatis配置 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- springboot分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<version>1.16.20</version>
</dependency>
2.1.1 lombok插件
Lombok介绍: 在项目中使用Lombok可以减少很多重复代码的书写。比如说getter/setter/toString等方法的编写。
参考网址:https://blog.csdn.net/motui/article/details/79012846
在IDEA上安装lombok插件,流程如下:
Fileà-->setting-->Build,Execution,Deployment-->Annotationà选中Enable annotation processingàApply;
选择pluginsà搜素框搜索lombokà安装重启IDEA
2.2 配置数据源信息
spring:
#数据源配置
datasource:
url: jdbc:mysql:///link_dev
password: root
username: root
driver-class-name: com.mysql.jdbc.Driver
创建entity目录,创建User实体类
@Data
public class User {
private Integer id;
private String username;
private Date ctm;
private Integer age;
}
sql语句
CREATE TABLE `user` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`userName` varchar(40) DEFAULT NULL,
`ctm` date DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '钱', '2018-07-15', '25');
INSERT INTO `user` VALUES ('2', '王', '2018-07-11', '14');
INSERT INTO `user` VALUES ('3', '李', '2018-07-09', '23');
INSERT INTO `user` VALUES ('4', '吕', '2018-07-19', '1');
INSERT INTO `user` VALUES ('5', '张', '2018-07-15', '2');
INSERT INTO `user` VALUES ('6', '谢', '2018-07-16', '3');
INSERT INTO `user` VALUES ('7', '周', '2018-07-16', '4');
INSERT INTO `user` VALUES ('8', '云', '2018-07-16', '5');
INSERT INTO `user` VALUES ('9', '马', '2018-07-16', '6');
2.3 代码写入
2.3.1 创建UserMapper接口
创建mapper目录,增加UserMapper接口
package com.jhw.promote.mapper;
import com.jhw.promote.entity.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Author:
* CreateTime:2018/7/16 22:13
* Desc:user表的操作数据的接口
*/
@Mapper//这里可以使用@Mapper注解,但是每个mapper都加注解比较麻烦,所以统一配置@MapperScan在扫描路径在application类中
@Repository
public interface UserMapper {
@Select("SELECT * FROM user")
List<User> selectUsers ();
}
2.3.2 创建service层
1. 创建service目录,创建UserService接口
package com.jhw.promote.service;
import com.jhw.promote.entity.User;
import java.util.List;
/**
* Author:
* CreateTime:2018/7/16 22:19
* Desc:
*/
public interface UserService {
List<User> getAllUser(int pageNum,int pageSize) ;
}
2. 在service中创建impl目录,创建UserServiceImpl实现类
package com.jhw.promote.service.impl;
import com.github.pagehelper.PageHelper;
import com.jhw.promote.dao.RedisDao;
import com.jhw.promote.entity.User;
import com.jhw.promote.mapper.UserMapper;
import com.jhw.promote.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Author:
* CreateTime:2018/7/16 22:21
* Desc:
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/*
* 这个方法中用到了我们开头配置依赖的分页插件pagehelper
* 很简单,只需要在service层传入参数,然后将参数传递给一个插件的一个静态方法即可;
* pageNum 开始页数
* pageSize 每页显示的数据条数
* */
@Override
public List<User> getAllUser(int pageNum, int pageSize) {
//将参数传给这个方法就可以实现物理分页了,非常简单。
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectUsers();
return users;
}
}
2.3.3 controller层中调用方法
依赖注入
@Autowired
private UserService userService;
调用方法
userService. getAllUser(pageNum,pageSize);
2.3.4 启动类中加载mapper
在mapper文件中可以不加@Mapper,在启动类上加注解引号中为mapper所在包的映射,即可:
@MapperScan("com.jhw.promote.mapper")
2.3.5 xml配置文件开发版本
1. 除了上述的注解sql查询整合方式外,springboot整合mybatis还可以使用xml配置文件的方式进行整合,在以上基础上,将UserMapper.java改为如下: 注意 : @Mapper注解一定要增加
package com.jhw.promote.mapper;
import com.jhw.promote.entity.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Author:
* CreateTime:2018/7/16 22:13
* Desc:user表的操作数据的接口
*/
//@Mapper 这里可以使用@Mapper注解,但是每个mapper都加注解比较麻烦,所以统一配置@MapperScan在扫描路径在application类中
@Mapper
public interface UserMapper {
List<User> selectUsers();
}
2. 在yml文件中添加配置
在resources目录下,创建mapper目录,并将之路径添加application.yml中如下:
mybatis:
mapper-locations: classpath:mapper/*.xml #xml文件所在目录
type-aliases-package: com.jhw.promote.entity #一般对应我们的实体类所在的包
3. 在mapper目录下配置xml文件
这种方式和普通的ssm框架格式差不多,UserMapper.xml如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.jhw.promote.mapper.UserMapper" >
<select id="selectUsers" resultType="com.jhw.promote.entity.User">
SELECT
id,
userName,
age,
ctm
FROM
user
order by
ctm desc
</select>
</mapper>
3. 整合日志(log4j2,logback)
特别提醒:Spring Boot 只有1.3.x和1.3.x以下版本才支持log4j的日志配置,1.3.x以上版本只支持log4j2,logback的日志配置
3.1 整合log4j2
3.1.1 依赖jar包
<!-- Spring Boot web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Spring Boot log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- 加上这个才能辨认到log4j2.yml文件 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
3.1.2 配置文件
Resources目录下增加log4j2.yml文件,内容如下:
Configuration:
status: warn
Properties: # 定义全局变量
Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下:
#测试:-Dlog.level.console=warn -Dlog.level.jhw=trace
#生产:-Dlog.level.console=warn -Dlog.level.jhw=info
- name: log.level.console
value: trace
- name: log.level.jhw
value: trace
# 日志存储路径
- name: log.path
value: /opt/logs
# 日志名称
- name: project.name
value: promote
Appenders:
Console: #输出到控制台
name: CONSOLE
target: SYSTEM_OUT
ThresholdFilter:
level: ${sys:log.level.console} # “sys:”表示:如果VM参数中没指定这个变量值,则使用本文件中定义的缺省全局变量值
onMatch: ACCEPT
onMismatch: DENY
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
RollingFile: # 输出到文件,超过128MB归档
- name: ROLLING_FILE
ignoreExceptions: false
fileName: ${log.path}/${project.name}.log
filePattern: "${log.path}/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
Policies:
SizeBasedTriggeringPolicy:
size: "128 MB"
DefaultRolloverStrategy:
max: 1000
Loggers:
Root:
level: info
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
Logger: # 为com.jhw.promote包配置特殊的Log级别,方便调试
- name: com.jhw.promote
additivity: false
level: ${sys:log.level.jhw}
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
3.1.3 调用log输出日志
private static final Logger logger=LoggerFactory.getLogger(UserController.class);
在需要的地方调用logger.info(),debug(),error()等,
3.2 整合logback
3.2.1 依赖jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 去除默认配置 日志为log4j2时需要配置 -->
<!--<exclusions>-->
<!--<exclusion>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-logging</artifactId>-->
<!--</exclusion>-->
<!--</exclusions>-->
</dependency>
<!--logback依赖包-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
3.2.2 配置文件
Resources目录下增加logback-spring.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="log.path" value="D:/opt/logs" />
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_debug.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<!--<logger name="org.springframework.web" level="info"/>-->
<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-->
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<!--开发环境:打印控制台-->
<springProfile name="dev">
<logger name="com.nmys.view" level="debug"/>
</springProfile>
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<!--生产环境:输出到文件-->
<!--<springProfile name="pro">-->
<!--<root level="info">-->
<!--<appender-ref ref="CONSOLE" />-->
<!--<appender-ref ref="DEBUG_FILE" />-->
<!--<appender-ref ref="INFO_FILE" />-->
<!--<appender-ref ref="ERROR_FILE" />-->
<!--<appender-ref ref="WARN_FILE" />-->
<!--</root>-->
<!--</springProfile>-->
</configuration>
里面的输出路径根据自己的情况进行更改
3.2.3 添加yml或properties配置信息
#properties
logging.config=classpath:logback-spring.xml
-------------------------------------------------------
#yml
logging:
config: classpath:logback-spring.xml
3.2.4 调用logback输出日志
private static final Logger logger=LoggerFactory.getLogger(UserController.class);
4. 整合Druid
4.1 什么是Druid?
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池(据说是目前最好的连接池)
4.2 依赖jar包
在pom.xml中增加依赖
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
4.3 增加配置文件内容(yml为例)
在application.yml中增加以下内容:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
username: root
password: root
# 初始化时建立的连接数
initial-size: 5
# 最大连接数
max-active: 20
# 最小连接数
min-idle: 5
# 获取连接最大等待时间,单位:毫秒
max-wait: 2000
# 是否缓存preparedStatement
pool-prepared-statements: false
# 最大preparedStatement缓存数,当pool-prepared-statements=true时需要大于0
max-pool-prepared-statement-per-connection-size: -1
# 检测连接是否失效的sql
validation-query: SELECT 'x'
# 检测连接是否失效的超时时间,单位:秒
validation-query-timeout: 2
filters: stat,wall
# Spring aop监控的包路径
aop-patterns: com.jhw.promote.service.*
# 数据库连接池监控统计插件
web-stat-filter:
enabled: true
url-pattern: /*
# 过滤掉如下请求
exclusions: '*.gif,*.png,*.jpg,*.html,*.js,*.css,*.ico,/druid/*'
# 数据库连接池监控页面插件
stat-view-servlet:
enabled: true
#druid的访问路径,相当于配置web.xml
url-pattern: '/druid/*'
#允许清空统计数据
reset-enable: true
#登录账号密码
login-username: root
login-password: 12345678
#ip白名单
allow: 127.0.0.1
#ip黑名单(优先级高于allow)
deny:
filter:
# 监控统计
stat:
enabled: true
db-type: mysql
# 打印慢sql
log-slow-sql: true
# 超过200毫秒即为慢sql
slow-sql-millis: 200
# sql防火墙
wall:
enabled: true
db-type: mysql
#对认定的攻击sql进行日志输出 由于没有配置log4j2,会报错
log-violation: false
# 对认定的攻击sql抛出异常
throw-exception: true
config:
# 是否允许下述操作
alter-table-allow: false
truncate-allow: false
drop-table-allow: false
update-where-none-check: true
# metadata会暴露数据的表结构
metadata-allow: false
# 日志
log4j2:
enabled: true
# log4j2仅记录druid的sql执行日志
statement-log-enabled: false
connection-log-enabled: false
result-set-log-enabled: false
statement-executable-sql-log-enable: true
# log4j2配置文件
logging:
config: classpath:log4j2.xml
4.4 添加log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<!--只接受程序中INFO级别的日志进行处理-->
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<!--处理DEBUG级别的日志,并把该日志放到logs/debug.log文件中-->
<!--打印出DEBUG级别日志,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileDebug" fileName="./logs/debug.log"
filePattern="logs/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="DEBUG"/>
<ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
<!--处理INFO级别的日志,并把该日志放到logs/info.log文件中-->
<RollingFile name="RollingFileInfo" fileName="./logs/info.log"
filePattern="logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--只接受INFO级别的日志,其余的全部拒绝处理-->
<ThresholdFilter level="INFO"/>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
<!--处理WARN级别的日志,并把该日志放到logs/warn.log文件中-->
<RollingFile name="RollingFileWarn" fileName="./logs/warn.log"
filePattern="logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="WARN"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
<!--处理error级别的日志,并把该日志放到logs/error.log文件中-->
<RollingFile name="RollingFileError" fileName="./logs/error.log"
filePattern="logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="ERROR"/>
<PatternLayout
pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
<!--druid的日志记录追加器-->
<RollingFile name="druidSqlRollingFile" fileName="./logs/druid-sql.log"
filePattern="logs/$${date:yyyy-MM}/api-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</appenders>
<loggers>
<root level="INFO">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
<appender-ref ref="RollingFileDebug"/>
</root>
<!--记录druid-sql的记录-->
<logger name="druid.sql.Statement" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<logger name="druid.sql.Statement" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<!--log4j2 自带过滤日志-->
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<Logger name="org.crsh.plugin" level="warn" />
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
<logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
</loggers>
</configuration>
4.5 测试
打开浏览器输入http://localhost:8080/druid 进入登录页面进行登录
5. 整合Mybatis-Plus
5.1 什么是Mybatis-Plus
Mybatis-Plus(简称MP)是一个Mybatis的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生.有以下特性:
- 无侵入:Mybatis-Plus 在 Mybatis 的基础上进行扩展,只做增强不做改变,引入 Mybatis-Plus 不会对您现有的 Mybatis 构架产生任何影响,而且 MP 支持所有 Mybatis 原生的特性
- 依赖少:仅仅依赖 Mybatis 以及 Mybatis-Spring
- 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作
- 预防Sql注入:内置Sql注入剥离器,有效预防Sql注入攻击
- 通用CRUD操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 多种主键策略:支持多达4种主键策略(内含分布式唯一ID生成器),可自由配置,完美解决主键问题
- 支持ActiveRecord:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作
- 支持代码生成:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用(P.S. 比 Mybatis 官方的 Generator 更加强大!)
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 支持关键词自动转义:支持数据库关键词(order、key……)自动转义,还可自定义关键词
- 内置分页插件:基于Mybatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
- 内置性能分析插件:可输出Sql语句以及其执行时间,建议开发测试时启用该功能,能有效解决慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,预防误操作
5.2 依赖jar包
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.0.7</version>
</dependency>
5.3 application.yml文件配置
#mybatis
mybatis-plus:
mapper-locations: classpath:/mapper/**Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.jhw.promote.entity
global-config:
#主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
id-type: 2
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
field-strategy: 2
#驼峰下划线转换
db-column-underline: true
#刷新mapper 调试神器
refresh-mapper: true
#数据库大写下划线转换
#capital-mode: true
#序列接口实现类配置
#key-generator:
#逻辑删除配置(下面3个配置)
logic-delete-value: 0
logic-not-delete-value: 1
#自定义SQL注入器
#sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector
#自定义填充策略接口实现
#meta-object-handler: com.baomidou.springboot.xxx
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
5.4 mybatis的配置,注入分页插件的Bean
在config目录下创建MybatisPlusConfig
@Configuration
@MapperScan("com.jhw.promote.mapper*")//扫描dao或者是Mapper接口
public class MybatisPlusConfig {
/**
* mybatis-plus 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor page = new PaginationInterceptor();
page.setDialectType("mysql");
return page;
}
}
5.5 逻辑实现
5.5.1 实体类的创建
其中的注解中的字段和数据库中的表一一对应;
@Data //lombok的注解
@TableName("students")
public class Student {
@TableId(value="id",type=IdType.AUTO)
private Integer id;
@TableField("stu_name")
private String stuName;
@TableField("stu_number")
private String stuNumber;
private Integer gender;
private Integer age;
private String password;
@TableField("stu_mobile")
private String stuMobile;
@TableField("par_name")//家长姓名
private String parName;
@TableField("par_mobile")
private String parMobile;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@TableField("is_delete")
private Integer isDelete;
}
sql语句
CREATE TABLE `students` (
`id` int(40) NOT NULL AUTO_INCREMENT,
`stu_name` varchar(45) DEFAULT NULL,
`stu_number` varchar(45) DEFAULT NULL,
`gender` int(40) DEFAULT NULL,
`age` int(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`stu_mobile` varchar(10) DEFAULT NULL,
`par_name` varchar(40) DEFAULT NULL,
`par_mobile` varchar(40) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`is_delete` int(2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `students` VALUES ('1', '张', '1456', '1', '15', '123456', '1396563478', '张父', '139646578945', '2018-07-01 14:57:40', '0');
INSERT INTO `students` VALUES ('2', '李', '1457', '1', '15', '123456', '1396563478', '李父', '139646578945', '2018-07-01 14:57:40', '0');
INSERT INTO `students` VALUES ('3', '王', '1458', '1', '15', '123456', '1396563478', '王父', '139646578945', '2018-07-01 14:57:40', '1');
INSERT INTO `students` VALUES ('4', '云', '1459', '1', '15', '123456', '1396563478', '云父', '139646578945', '2018-07-01 14:57:40', '0');
INSERT INTO `students` VALUES ('5', '田', '1455', '1', '15', '123456', '1396563478', '田父', '139646578945', '2018-07-01 14:57:40', '1');
INSERT INTO `students` VALUES ('6', '钱', '1454', '1', '15', '123456', '1396563478', '钱父', '139646578945', '2018-07-01 14:57:40', '1');
5.5.2 创建表数据层控制接口StudentMapper
Mapper目录下创建StudentMapper.java,内容如下:
@Repository
public interface StudentMapper {
List<Student> findAllStudent();
List<Student> findSomeColumn();
void deleteById(Integer id);
void updateByPrimarKeySelective(Student student);
void saveStudent(Student student);
List<Student> findAllStudentPage(Pagination page);
@Select("select * from tb_student where gender = #{gender}")
@Results({
@Result(column="stu_name",property="stuName"),
@Result(column="stu_mobile",property="stuMobile"),
@Result(column="stu_number",property="stuNumber"),
@Result(column="par_name",property="parName"),
@Result(column="par_mobile",property="parMobile"),
@Result(column="create_time",property="createTime")
})
List<Student> findStuByGender(Integer gender);
}
类中以两种方式进行了表的操作,注解和xml共同使用
5.5.3 创建StudentMapper的配置文件
在resource目录下创建StudentMaper.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jhw.promote.mapper.StudentMapper">
<resultMap id="BaseResultMap" type="com.jhw.promote.entity.Student">
<id column="id" property="id"></id>
<result column="stu_name" property="stuName"></result>
<result column="stu_mobile" property="stuMobile"></result>
<result column="stu_number" property="stuNumber"></result>
<result column="create_time" property="createTime"></result>
<result column="par_mobile" property="parMobile"></result>
<result column="par_name" property="parName"></result>
<result column="is_delete" property="isDelete"></result>
</resultMap>
<sql id="base_column_list">
stu_name,stu_mobile,stu_number,create_time,par_mobile,par_name
</sql>
<!-- 拼接 -->
<insert id="saveStudent" parameterType="com.jhw.promote.entity.Student">
insert into students
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="stuName != null">
stu_name,
</if>
<if test="stuMobile">
stu_mobile,
</if>
<if test="stuNumber">
stu_number,
</if>
<if test="roleId">
role_id,
</if>
<if test="parMobile">
par_mobile,
</if>
<if test="parName">
par_name,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="stuName != null">#{stuName},</if>
<if test="stuMobile != null">#{stuMobile},</if>
<if test="stuNumber!= null">#{stuNumber},</if>
<if test="roleId !=null">#{roleId},</if>
<if test="parMobile != null">#{parMobile},</if>
<if test="parName !=null">#{parName},</if>
</trim>
</insert>
<delete id="deleteById" parameterType="java.lang.Integer">
delete from students
where id = #{id}
</delete>
<update id="updateByPrimarKeySelective" parameterType="com.jhw.promote.entity.Student">
update students
<set>
<if test="stuName ! = null">
stu_name = #{stuName}
</if>
<if test="password ! =null">
password = #{password}
</if>
<if test="stuMobile ! = null">
stu_mobile = #{stuMobile}
</if>
</set>
</update>
<select id="findSomeColumn" resultMap="BaseResultMap">
select
<include refid="base_column_list" />
from students
</select>
<select id="findAllStudent" resultMap="BaseResultMap">
select * from students
</select>
<select id="findAllStudentPage" resultMap="BaseResultMap" resultType="com.jhw.promote.entity.Student">
select * from students
</select>
</mapper>
5.5.4 分页方法的使用
在controller中,使用如下内容:
Page page = new Page(pageNum,pageSize);
Page allStudentPage = studentService.findAllStudentPage(page);
return allStudentPage.getRecords();//类型为List
Service层就和平常的逻辑代码一样了
6. 整合Redis
6.1 依赖jar包
在pom.xml文件中添加以下jar包
<!--redis start-->
<!--nosql数据库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.0集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.8</version>
</dependency>
<!--redis end-->
6.2 配置Redis信息
spring:
#Redis配置
redis:
#数据库索引
database: 0
host: 127.0.0.1
port: 6379
password:
#连接超时时间
timeout: 5000
jedis:
pool:
#最大连接数
max-active: 8
#最大阻塞等待时间(负数表示没限制)
max-wait: -1
#最大空闲
max-idle: 8
#最小空闲
min-idle: 0
6.3 编写RedisConfig类
在目录中建立config包,创建RedisConfig类:
package com.jhw.promote.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.nio.charset.Charset;
/**
* Author:
* CreateTime:2018/7/16 16:28
* Desc:
*/
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteEnumUsingToString).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
}
类中的redisTemplate为加载redis是的设置,
stringRedisTemplate为返回的redisTemplate,
FastJsonRedisSerializer是重写了FastJsonRedisSerializer的方法
6.4 编写RedisDao类
创建dao目录,新建RedisDao类:
package com.jhw.promote.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Author:
* CreateTime:2018/7/16 16:32
* Desc:
*/
@SuppressWarnings("unchecked")
@Component
public class RedisDao {
@SuppressWarnings("rawtypes")
@Autowired
private RedisTemplate redisTemplate;
/**
* 批量删除对应的value
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量删除key
* @param pattern
*/
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0)
redisTemplate.delete(keys);
}
/**
* 删除对应的value
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
* @param key
* @return
*/
public String get(final String key) {
Object result = null;
redisTemplate.setValueSerializer(new StringRedisSerializer());
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
if(result==null){
return null;
}
return result.toString();
}
/**
* 写入缓存
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量写入map
* @param key
* @param value
* @return
*/
public boolean hmset(String key, Map<String, String> value) {
boolean result = false;
try {
redisTemplate.opsForHash().putAll(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 获取map
* @param key
* @return
*/
public Map<String,String> hmget(String key) {
Map<String,String> result =null;
try {
result= redisTemplate.opsForHash().entries(key);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
方法中为基本的redis操作方法
6.5 调用redis方法
在需要用到redis的类中注入RedisDao,调用相应方法即可
@Autowired
private RedisDao redisDao;
7. 整合swagger2
7.1 swagger是什么?
Swagger是接口管理工具(Api文档工具),通过注解的方式将所有接口集中到一起,并通过页面显示出来,相当于接口文档,方便开发者开发和前后端对接
7.2 依赖jar包
在pom.xml中加入以下依赖:
<!--swagger2 依赖jar包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
7.3 编写swagger2配置类
在config目录中创建Swagger2类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* Author:
* CreateTime:2018/7/16 19:34
* Desc:
*/
@Configuration
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.jhw.promote.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("springboot利用swagger构建api文档")
.description("简单优雅的restfun风格,https://blog.csdn.net/qq_39615245/article/details/81139180")
.termsOfServiceUrl("https://blog.csdn.net/qq_39615245")
.version("1.0")
.build();
}
}
注意:RequestHandlerSelectors.basePackage("com.jhw.promote.controller")的引号内为controller的路径
7.4 启动类配置
在启动类上添加注解:
@EnableSwagger2
7.5 Swagger2的注解意义
@Api:用在请求的类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的注解"
value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
description=”描述”
@Api(value =" " ,tags = "UserController", description = "用户访问类")
@ApiOperation:用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"
@ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息")
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
@ApiImplicitParams({
@ApiImplicitParam(name="mobile",value="手机号",required=true,paramType="form"),
@ApiImplicitParam(name="password",value="密码",required=true,paramType="form"),
@ApiImplicitParam(name="age",value="年龄",required=true,paramType="form",dataType="Integer")
})
@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiOperation(value = "select1请求",notes = "多个参数,多种的查询参数类型")
@ApiResponses({
@ApiResponse(code=400,message="请求参数没填好"),
@ApiResponse(code=404,message="请求路径没有或页面跳转路径不对")
})
@ApiModel:用于响应类上,表示一个返回响应数据的信息
(这种一般用在post创建的时候,使用@RequestBody这样的场景,
请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在属性上,描述响应类的属性
package com.jhw.promote.entity;
/**
* Author:
* CreateTime:2018/7/16 21:28
* Desc:
*/
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@ApiModel(description= "返回响应数据")
@Data
public class RestMessage implements Serializable{
@ApiModelProperty(value = "是否成功")
private boolean success=true;
@ApiModelProperty(value = "返回对象")
private Object data;
@ApiModelProperty(value = "错误编号")
private Integer errCode;
@ApiModelProperty(value = "错误信息")
private String message;
}
浏览器中输入http://localhost:8080/swagger-ui.html# 在页面上的提现如下:
8 springBoot热部署
8.1 依赖jar包
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<build>
<plugins>
<!--maven构建-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
8.2 setting设置
Setting-->compile-->勾选 Build project automatically-->ok
8.3 Registry…设置
Ctrl+Shift+Alt+/ => Register… => 找到并勾选compiler.automake.allow.when.app.running => IDEA重启
9 springboot多数据源配置
在项目开发中,会遇到要用不同数据库的数据这种情况,这是就需要多数据源的使用了,配置多数据源有多种方法:jpa,spring,aop等方法,以下这种方法属于spring,提供数据支持
9.1 yml文件配置
在此配置了两个数据源,如果想要更多,据需添加即可
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# 数据源1
primary:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
username: root
password: root
## 数据源2
secondary:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/link?useUnicode=true&characterEncoding=utf8
username: root
password: root
druid:
# 初始化时建立的连接数
initial-size: 5
9.2 数据源配置文件
在config目录下创建DataSource1Config.java文件
@MapperScan(basePackages="com.jhw.promote.mapper.secondaryDb",sqlSessionTemplateRef = "secondSqlSessionTemplate")很关键, 这块的注解就是指明了扫描dao层,并且给dao层注入指定的SqlSessionTemplate。所有@Bean都需要按照命名指定正确
package com.jhw.promote.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* Author:
* CreateTime:2018/7/20 9:08
* Desc:数据源1的配置—加载sessionfactory
*/
@Configuration
@MapperScan(basePackages = "com.jhw.promote.mapper.primaryDb", sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class DataSource1Config {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
@Primary
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//这里是在resources目录下建立的mapper/ primary,里面必须有xml文件,如果是注解式开发,这不需要这行代码
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "primarySqlSessionTemplate")
@Primary
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
创建DataSource2Config.java文件
package com.jhw.promote.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* Author:
* CreateTime:2018/7/20 9:08
* Desc:数据源2的配置—加载sessionfactory
*/
@Configuration
@MapperScan(basePackages = "com.jhw.promote.mapper.secondaryDb", sqlSessionTemplateRef = "secondSqlSessionTemplate")
public class DataSource2Config {
@Bean(name = "secondDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "secondSqlSessionFactory")
public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//这里是在resources目录下建立的mapper/second,里面必须有xml文件,如果是注解式开发,这不需要这行代码
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/second/*.xml"));
return bean.getObject();
}
@Bean(name = "secondTransactionManager")
public DataSourceTransactionManager secondTransactionManager(@Qualifier("secondDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondSqlSessionTemplate")
public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
9.3 dao层和xml文件
dao层和xml需要按照库来分在不同的目录
primary库dao层在com.jhw.promote.mapper.primary.包下,secondary库在com.jhw.promote.mapper. second包下,
primary的xml在resources/mapper/primary目录下,secondary的xml在resources/mapper/ second目录下
调用不同的数据源时,调用相应的mapper就可以了(将启动类和mybatis-plus配置类上的mapperScan注解注释掉)
10. 整合定时器
定时任务实现的几种方式:
1. Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
2. ScheduledExecutorService:也jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
3. Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
4. Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
10.1 依赖jar包
Springboot2.0.0 以后, 则在spring-boot-starter中已经包含了quart的依赖,则可以直接使用spring-boot-starter-quartz依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
如果是1.5.9则要使用以下添加依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
10.2 创建RunTask类,继承QuartzJobBean,如下:
package com.jhw.promote.task;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.Date;
/**
* Author:
* CreateTime:2018/7/24 10:05
* Desc:
*/
public class RunTask extends QuartzJobBean {
/**
* 执行定时任务
* @param jobExecutionContext
* @throws JobExecutionException
*/
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("quartz task " + new Date());
}
}
10.3 创建配置类QuartzConfig
主要有两种方式,一种是SimpleScheduleBuilder,另一种是CronScheduleBuilder
- SimpleScheduleBuilder:只能设置时间单位周期,即:每隔多长时间进行一次调度
@Configuration public class QuartzConfig { @Bean public JobDetail teatQuartzDetail(){ return JobBuilder.newJob(TestQuartz.class).withIdentity("RunTask").storeDurably().build(); } @Bean public Trigger testQuartzTrigger(){ SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(10) //设置时间周期单位秒 .repeatForever(); return TriggerBuilder.newTrigger().forJob(teatQuartzDetail()) .withIdentity("RunTask ") .withSchedule(scheduleBuilder) .build(); } }
-
CronScheduleBuilder:通过cron表达式能够精确表达调度时间,用的较多
@Configuration public class QuartzConfig { @Bean public JobDetail QuartzDetail(){ return JobBuilder.newJob(RunTask.class).withIdentity("RunTask").storeDurably().build(); } @Bean public Trigger QuartzTrigger(){ return TriggerBuilder.newTrigger().forJob(QuartzDetail()) .withIdentity("RunTask") .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? ")) .build(); } }
10.4 Cron表达式详解
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year或 Seconds Minutes Hours DayofMonth Month DayofWeek
每一个域可出现的字符如下:
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year:可出现", - * /"四个字符,有效范围为1970-2099年
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
举几个例子:
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
按顺序依次为
秒(0~59)
分钟(0~59)
小时(0~23)
天(月)(0~31,但是你需要考虑你月的天数)
月(0~11)
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
年份(1970-2099)
其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”
“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天
“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样
“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”
“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可选) 留空, 1970-2099 , - * /
2)Cron表达式范例:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
11. springboot的多环境配置
创建如下文件:
不同环境的配置设置一个配置文件,例如:dev环境下的配置配置在application-dev.properties中;prod环境下的配置配置在application-prod.properties中。
在application.properties中指定使用哪一个文件
spring:
profiles:
active: prod
application-dev.yml(dev环境下的配置)
spring:
profiles: dev
application-prod.yml(prod环境下的配置)
spring:
profiles: prod
测试:
@Autowired
private Environment env;
@RequestMapping("/testProfile")
public String testProfile(){
System.out.println(env.getProperty("spring.profiles"));
System.out.println(env.getProperty("spring.profiles.active"));
return env.getProperty("spring.profiles");
}
访问接口,返回值正确即可,启动是,控制台日志中有如下记录:
2018-07-25 08:31:44.549 INFO 7664 --- [ restartedMain] com.jhw.promote.PromoteApplication : The following profiles are active: dev
12 整合消息对列(RabbitMq)
1.从社区活跃度
按照目前网络上的资料,RabbitMQ 、activeM 、ZeroMQ 三者中,综合来看,RabbitMQ 是首选。
2.持久化消息比较
ZeroMq 不支持,ActiveMq 和RabbitMq 都支持。持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了,消息不会丢失的机制。
3.综合技术实现
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。
RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。当然ZeroMq 也可以做到,不过自己必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。
4.高并发
毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的erlang 语言。
5.比较关注的比较, RabbitMQ 和 Kafka
RabbitMq 比Kafka 成熟,在可用性上,稳定性上,可靠性上, RabbitMq 胜于 Kafka (理论上)。
另外,Kafka 的定位主要在日志等方面, 因为Kafka 设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以 如果业务方面还是建议选择 RabbitMq 。
还有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出来很多。
12.1 安装RabbitMq(Windows版本)
(1):下载erlang,原因在于RabbitMQ服务端代码是使用并发式语言erlang编写的,下载地址:http://www.erlang.org/downloads,双击.exe文件进行安装就好,安装完成之后创建一个名为ERLANG_HOME的环境变量,其值指向erlang的安装目录,同时将%ERLANG_HOME%\bin加入到Path中,最后打开命令行,输入erl,如果出现erlang的版本信息就表示erlang语言环境安装成功;
(2):下载RabbitMQ,下载地址:http://www.rabbitmq.com/install-windows.html,同样双击.exe进行安装就好,环境变量配置如下:
(3) 激活rabbitmq_management
1.cmd命令窗中键入命令:"D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.3\sbin\rabbitmq-plugins.bat" enable rabbitmq_management
2.启动mq
net start RabbitMQ
3.停止mq
net stop RabbitMQ
4.测试
测试地址: http://localhost:15672/
默认的用户名:guest
默认的密码为:guest
成功即可
12.2 RabbitMq工作模式
1.简单队列
依赖jar包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件添加内容:
spring:
#rabbitmq配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# listener:
# simple:
# prefetch: 1
在config目录创建配置文件RabbitMQConfig,内容如下:
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Author:
* CreateTime:2018/8/9 9:41
* Desc:RabbitMQ配置
*/
@Configuration
public class RabbitMQConfig {
private static final String QUEUE_MODEL1_NAME = "test_model1_queue";
@Bean
public Queue queue1(){
/**
* amqp_queue_declare
* durable
* 是否持久化
* exclusive
* 是否为私有队列,如果设为true则为私有,只有自己的应用程序能够消费当前队列消息。
* autoDelete
* 当没有消费者的时候队列是否自动删除(但是如果自始至终都没有消费者订阅的话,那么即不存在消费者,
* 队列不会自动删除。之后当存在消费者之后当消费者为0的时候自动删除)
* arguments
* arguments 填充amqp_empty_table
* name
* 队列名称
* @returns amqp_queue_declare_ok_t
*/
return new Queue(QUEUE_MODEL1_NAME, false, false, false, null);
}
}
创建消息生产者,为方便测试,将生产者写为接口直接调取
@Autowired
private AmqpTemplate amqpTemplate;
@RequestMapping(value="/rabbitMq1",method = RequestMethod.GET)
public void testSimpleQueue() {
String message = "Hello RabbitMQ !";
amqpTemplate.convertAndSend("test_model1_queue", message);
System.out.println("[model1] send " + message + " ok");
}
创建消费者,在目录中创建listener目录,创建SimpleRecv类,监听消息
@RabbitListener(queues = "test_model1_queue")
@Component
public class SimpleRecv {
@RabbitHandler
public void process(String message) {
System.out.println("[model1] rev : " + message);
}
}
运行项目,访问接口,控制台输出如下:
[model1] send Hello RabbitMQ ! ok
[model1] rev : Hello RabbitMQ !
2 工作对列
工作队列分两种情况:
轮询分发:不管消费者处理速度性能快慢,每个消费者都是按顺序分别每个拿一个的原则,比如3个消费者, 消费者1拿1个,然后消费者2拿一个,然后消费者3拿一个,然后消费者1开始拿,即使中间有消费者已经处理完了,也必须等待其他消费者都拿完一个才能消费到。
公平分发:根据消费者处理性能,性能好的消费的数据量多,性能差的消费的数据量少,这种情况也是日常工作中使用最为正常的,轮询模式用的较少,区别在于prefetch默认是1,如果设置为0就是轮询模式。
公平分发模式:
RabbitMQConfig中增加如下代码:
private static final String QUEUE_MODEL2_NAME = "test_model2_queue";
@Bean
public Queue queue2(){
return new Queue(QUEUE_MODEL2_NAME, false, false, false, null);
}
增加生产者(接口形式):
@RequestMapping(value="/rabbitMq2",method = RequestMethod.GET)
public void testWorkFairQueue() {
for (int i = 0; i < 20; i++) {
String message = "Hello RabbitMQ " + i;
// 发送消息
amqpTemplate.convertAndSend("test_model2_queue", message);
System.out.println("[model2] Sent '" + message + "'");
}
}
Listener目录中创建多个消费者WorkRecv1
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Author:
* CreateTime:2018/8/9 15:29
* Desc:
*/
@RabbitListener(queues = "test_model2_queue")
@Component
public class WorkRecv1 {
@RabbitHandler
public void process(String message){
System.out.println("[1] rev : " + message);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
WorkRecv2
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Author:
* CreateTime:2018/8/9 15:29
* Desc:
*/
@RabbitListener(queues = "test_model2_queue")
@Component
public class WorkRecv2 {
@RabbitHandler
public void process(String message){
System.out.println("[2] rev : " + message);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行项目,访问接口,控制台输出:
[model2] Sent 'Hello RabbitMQ 0' ... [model2] Sent 'Hello RabbitMQ 19' [1] rev : Hello RabbitMQ 0 [2] rev : Hello RabbitMQ 1 [1] rev : Hello RabbitMQ 2 [1] rev : Hello RabbitMQ 3 [1] rev : Hello RabbitMQ 4 [2] rev : Hello RabbitMQ 5 [1] rev : Hello RabbitMQ 6 [1] rev : Hello RabbitMQ 7 [1] rev : Hello RabbitMQ 8 [2] rev : Hello RabbitMQ 9 [1] rev : Hello RabbitMQ 10 [1] rev : Hello RabbitMQ 11 [1] rev : Hello RabbitMQ 12 [2] rev : Hello RabbitMQ 13 [1] rev : Hello RabbitMQ 14 [1] rev : Hello RabbitMQ 15 [1] rev : Hello RabbitMQ 16 [2] rev : Hello RabbitMQ 17 [1] rev : Hello RabbitMQ 18 [1] rev : Hello RabbitMQ 19
可以看出,消费者1和2获得的消息不等同
轮询分发模式: 在配置文件中, prefetch值设置为0,运行项目,访问接口
[model2] Sent 'Hello RabbitMQ 0' ... [model2] Sent 'Hello RabbitMQ 19' [1] rev : Hello RabbitMQ 0 [2] rev : Hello RabbitMQ 1 [1] rev : Hello RabbitMQ 2 [2] rev : Hello RabbitMQ 3 [1] rev : Hello RabbitMQ 4 [1] rev : Hello RabbitMQ 6 [2] rev : Hello RabbitMQ 5 [1] rev : Hello RabbitMQ 8 [1] rev : Hello RabbitMQ 10 [2] rev : Hello RabbitMQ 7 [1] rev : Hello RabbitMQ 12 [1] rev : Hello RabbitMQ 14 [2] rev : Hello RabbitMQ 9 [1] rev : Hello RabbitMQ 16 [1] rev : Hello RabbitMQ 18 [2] rev : Hello RabbitMQ 11 [2] rev : Hello RabbitMQ 13 [2] rev : Hello RabbitMQ 15 [2] rev : Hello RabbitMQ 17 [2] rev : Hello RabbitMQ 19
3 订阅模式
订阅模式即一个生产者发送消息给多个消费者,且每个消费者都收到一次,也即是一个消息能够被多个消费者消费。类似于我们订阅同一微信公众号,微信公众号推送图文,我们每个人都能收到一份。
交换机有几种类型,分别是:direct, topic, headers, fanout这里我们只讨论一种,就是 fanout,生产者发送消息给交换机,交换机分发绑定在交换机上的队列,每个队列对应一个消费者。
在RabbitMQConfig中加入以下内容,定义交换机、队列、以及队列与交换机的绑定关系:
/**
* 订阅模式
*/
private static final String EXCHANGE_FANNOUT_NAME = "test_exchange_fanout";
private static final String QUEUE_PS_SMS_NAME = "test_queue_fanout_sms";
private static final String QUEUE_PS_EMAIL_NAME = "test_queue_fanout_email";
//注入交换机
@Bean("fanoutExchange")
public FanoutExchange fanoutExchange(){
return new FanoutExchange(EXCHANGE_FANNOUT_NAME);
}
//定义两个队列
@Bean
public Queue fanoutSmsQueue(){
return new Queue(QUEUE_PS_SMS_NAME, false, false, false, null);
}
@Bean
public Queue fanoutEmailQueue(){
return new Queue(QUEUE_PS_EMAIL_NAME, false, false, false, null);
}
//将两个队列和交换机绑定
@Bean
public Binding smsQueueExchangeBinding(FanoutExchange fanoutExchange, Queue fanoutSmsQueue){
return BindingBuilder.bind(fanoutSmsQueue).to(fanoutExchange);
}
@Bean
public Binding emailQueueExchangeBinding(FanoutExchange fanoutExchange, Queue fanoutEmailQueue){
return BindingBuilder.bind(fanoutEmailQueue).to(fanoutExchange);
}
创建生产者(接口形式):
/**
* 订阅模式
*/
@RequestMapping(value="/rabbitMqFanout",method = RequestMethod.GET)
public void testFanoutQueue() {
String message = "Hello, fanout message ";
// 发送消息
amqpTemplate.convertAndSend("test_exchange_fanout", "", message);
System.out.println("[Fanout] Sent '" + message + "'");
}
创建多个消费者:
EmailRecv
/**
* Author:
* CreateTime:2018/8/11 12:21
* Desc:email消费者
*/
@RabbitListener(queues = "test_queue_fanout_email")
@Component
public class EmailRecv {
@RabbitHandler
public void process(String message){
System.out.println("[email] rev : " + message);
}
}
SmsRecv
/**
* Author:
* CreateTime:2018/8/11 12:20
* Desc:sms消费者
*/
@RabbitListener(queues = "test_queue_fanout_sms")
@Component
public class SmsRecv {
@RabbitHandler
public void process(String message){
System.out.println("[sms] rev : " + message);
}
}
运行项目,访问接口,结果如下:
[Fanout] Sent 'Hello, fanout message ' [sms] rev : Hello, fanout message [email] rev : Hello, fanout message
4 路由模式
跟订阅模式类似,只不过在订阅模式的基础上加上了类型,订阅模式是分发到所有绑定到交换机的队列,路由模式只分发到绑定在交换机上面指定路由键的队列。
在RabbitMQConfig中加入以下内容:
/**
* 路由模式
*/
private static final String EXCHANGE_DIRECT_NAME = "test_exchange_direct";
private static final String QUEUE_PS_INFO_NAME = "test_queue_direct_info";
private static final String QUEUE_PS_ERROR_NAME = "test_queue_direct_error";
//注入交换机
@Bean("directExchange")
public DirectExchange directExchange(){
return new DirectExchange(EXCHANGE_DIRECT_NAME);
}
//定义两个队列
@Bean
public Queue directInfoQueue(){
return new Queue(QUEUE_PS_INFO_NAME, false, false, false, null);
}
@Bean
public Queue directErrorQueue(){
return new Queue(QUEUE_PS_ERROR_NAME, false, false, false, null);
}
//将两个队列和交换机绑定 BindingBuilder.bind(队列).to(交换机).with(routing_key)---路由键;
@Bean
public Binding infoQueueExchangeBinding(DirectExchange directExchange, Queue directInfoQueue){
return BindingBuilder.bind(directInfoQueue).to(directExchange).with("info");
}
@Bean
public Binding errorQueueExchangeBinding(DirectExchange directExchange, Queue directErrorQueue){
return BindingBuilder.bind(directErrorQueue).to(directExchange).with("error");
}
创建生产者:
/**
* 订阅模式
*/
@RequestMapping(value="/rabbitMqDirect",method = RequestMethod.GET)
public void testDirectQueue() {
String message1 = "Direct error ";
String message2 = "Direct info ";
String message3 = "Direct message ";
// 发送消息
amqpTemplate.convertAndSend("test_exchange_direct", "error", message1);
amqpTemplate.convertAndSend("test_exchange_direct", "info", message2);
amqpTemplate.convertAndSend("test_exchange_direct", "", message3);
System.out.println("[Direct] Sent '" + message1 + "'|'"+ message2 +"'|'"+ message3+"'");
}
创建多个消费者:
DirectError
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Author:
* CreateTime:2018/8/11 13:15
* Desc:
*/
@RabbitListener(queues = "test_queue_direct_error")
@Component
public class DirectError {
@RabbitHandler
public void process(String message){
System.out.println("[error] rev : " + message);
}
}
DirectInfo
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Author:
* CreateTime:2018/8/11 13:15
* Desc:
*/
@RabbitListener(queues = "test_queue_direct_info")
@Component
public class DirectInfo {
@RabbitHandler
public void process(String message){
System.out.println("[info] rev : " + message);
}
}
运行项目,访问接口,结果如下:
[Direct] Sent 'Direct error '|'Direct info '|'Direct message ' [info] rev : Direct info [error] rev : Direct error
有结果看出:消息3由于没有写入路由键,致使没有被消费者接收到
5 主题模式(通配符模式)
跟路由模式类似,只不过路由模式是指定固定的路由键,而主题模式是可以模糊匹配路由键,类似于SQL中=和like的关系。
在RabbitMQConfig中加入以下内容:
/**
* 通配符模式(主题模式)
*/
private static final String EXCHANGE_TOPIC_NAME = "test_exchange_topic";
private static final String QUEUE_TOPIC_INFO_NAME = "test_queue_topic_info";
private static final String QUEUE_TOPIC_ERROR_NAME = "test_queue_topic_error";
//注入交换机
@Bean("topictExchange")
public TopicExchange topicExchange(){
return new TopicExchange(EXCHANGE_TOPIC_NAME);
}
//定义两个队列
@Bean
public Queue topicInfoQueue(){
return new Queue(QUEUE_TOPIC_INFO_NAME, false, false, false, null);
}
@Bean
public Queue topicErrorQueue(){
return new Queue(QUEUE_TOPIC_ERROR_NAME, false, false, false, null);
}
//将两个队列和交换机绑定 BindingBuilder.bind(队列).to(交换机).with(routing_key)---路由键;
//*表示精确到一个单词,key.*只能匹配如key.word这样的词,而key.#可以是0个或多个
@Bean
public Binding topic_InfoQueueExchangeBinding(TopicExchange topicExchange, Queue topicInfoQueue){
return BindingBuilder.bind(topicInfoQueue).to(topicExchange).with("info.*.*");
}
@Bean
public Binding topic_ErrorQueueExchangeBinding(TopicExchange topicExchange, Queue topicErrorQueue){
return BindingBuilder.bind(topicErrorQueue).to(topicExchange).with("#.error.#");
}
创建生产者:
/**
* 通配符模式(主题模式)
*/
@RequestMapping(value="/rabbitMqTopic",method = RequestMethod.GET)
public void testTopicQueue() {
// 发送消息
amqpTemplate.convertAndSend("test_exchange_topic", "info.1", "info.1");
amqpTemplate.convertAndSend("test_exchange_topic", "info.1.2", "info.1.2");
amqpTemplate.convertAndSend("test_exchange_topic", "info.error.2", "info.error.2");
amqpTemplate.convertAndSend("test_exchange_topic", "error", "error");
amqpTemplate.convertAndSend("test_exchange_topic", "error.info", "error.info");
amqpTemplate.convertAndSend("test_exchange_topic", "error.info.1", "error.info.1");
}
创建多个消费者:
TopicError
@RabbitListener(queues = "test_queue_topic_error")
@Component
public class TopicError {
@RabbitHandler
public void process(String message){
System.out.println("[error] rev : " + message);
}
}
TopicInfo
@RabbitListener(queues = "test_queue_topic_info")
@Component
public class TopicInfo {
@RabbitHandler
public void process(String message){
System.out.println("[info] rev : " + message);
}
}
运行项目,调取接口,结果如下:
[error] rev : info.error.2 [info] rev : info.1.2 [error] rev : error [info] rev : info.error.2 [error] rev : error.info [error] rev : error.info.1
可以看出 info.error.2 消息被两个消费者都消费了,info.*.*只能匹配3个单词,#则可以匹配0个或多个
12.3 整合延时消息
12.3.1 安装延时插件
打开http://www.rabbitmq.com/community-plugins.html页面, 下载对应RabbitMQ版本的插件
将其放置到RabbitMQ安装目录下的plugins目录下,并使用如下命令启动这个插件:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
关闭命令为:
rabbitmq-plugins disable rabbitmq_delayed_message_exchange
12.3.2 整合代码
RabbitMQConfig类中增加如下代码:
/**
* 延时消息 注意交换机为CustomExchange类型
*/
private static final String DELAY_EXCHANGE_NAME = "delay_exchange_name";
private static final String DELAY_QUEUE_NAME1 = "delay_queue_name1";
private static final String DELAY_QUEUE_NAME2 = "delay_queue_name2";
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAY_EXCHANGE_NAME, "x-delayed-message",true, false,args);
}
@Bean
public Queue delayQueue1() {
return new Queue(DELAY_QUEUE_NAME1, true, false, false, null);
}
@Bean
public Binding bindingQueue1() {
return BindingBuilder.bind(delayQueue1()).to(delayExchange()).with(DELAY_QUEUE_NAME1).noargs();
}
@Bean
public Queue delayQueue2() {
return new Queue(DELAY_QUEUE_NAME2, true, false, false, null);
}
@Bean
public Binding bindingQueue2() {
return BindingBuilder.bind(delayQueue2()).to(delayExchange()).with(DELAY_QUEUE_NAME2).noargs();
}
创建生产者:
/**
* 延时通信
*/
@RequestMapping(value="/delayQueue",method = RequestMethod.GET)
public void daleyQueue() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("消息发送时间:"+sdf.format(new Date()));
String message="这是信息";
for (int i = 0; i < 10; i++) {
long time = i*10000;
if (i%3==0){
amqpTemplate.convertAndSend("delay_exchange_name", "delay_queue_name1", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("x-delay",time);
return message;
}
});
}else{
amqpTemplate.convertAndSend("delay_exchange_name", "delay_queue_name2", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("x-delay",time);
return message;
}
});
}
}
}
创建消费者:
DurableKey1
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* Author:
* CreateTime:2018/8/20 9:12
* Desc:
*/
@RabbitListener(queues = "delay_queue_name1")
@Component
public class DurableKey1 {
@RabbitHandler
public void process(String message){
System.out.println("start one work and now data:"+new Date().toString());
System.out.println("This delay_queue_name1 msg is:"+message);
}
}
DurableKey2
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* Author:
* CreateTime:2018/8/20 9:12
* Desc:
*/
@RabbitListener(queues = "delay_queue_name1")
@Component
public class DurableKey2 {
@RabbitHandler
public void process(String message){
System.out.println("start one work and now data:"+new Date().toString());
System.out.println("This delay_queue_name2 msg is:"+message);
}
}
启动项目,访问接口,结果如下:
消息发送时间:2018-08-21 08:32:11 start one work and now data:Tue Aug 21 08:32:12 CST 2018 This delay_queue_name1 msg is:这是信息 start one work and now data:Tue Aug 21 08:32:22 CST 2018 This delay_queue_name2 msg is:这是信息 start one work and now data:Tue Aug 21 08:32:32 CST 2018 This delay_queue_name2 msg is:这是信息 start one work and now data:Tue Aug 21 08:32:42 CST 2018 This delay_queue_name1 msg is:这是信息 start one work and now data:Tue Aug 21 08:32:52 CST 2018 This delay_queue_name2 msg is:这是信息 start one work and now data:Tue Aug 21 08:33:02 CST 2018 This delay_queue_name2 msg is:这是信息 start one work and now data:Tue Aug 21 08:33:12 CST 2018 This delay_queue_name1 msg is:这是信息 start one work and now data:Tue Aug 21 08:33:22 CST 2018 This delay_queue_name2 msg is:这是信息 start one work and now data:Tue Aug 21 08:33:32 CST 2018 This delay_queue_name2 msg is:这是信息 start one work and now data:Tue Aug 21 08:33:42 CST 2018 This delay_queue_name1 msg is:这是信息