共计 14234 个字符,预计需要花费 36 分钟才能阅读完成。
Druid
数据库连接池负责调配、治理和开释数据库连贯,它容许应用程序重复使用一个现有的数据库连贯,而不是再从新建设一个;开释闲暇工夫超过最大闲暇工夫的数据库连贯来防止因为没有开释数据库连贯而引起的数据库连贯脱漏。通过数据库连接池能明显提高对数据库操作的性能。在 Java 利用程序开发中,罕用的连接池有 DBCP、C3P0、Proxool 等。
Spring Boot 默认提供了若干种可用的连接池,默认的数据源是:org.apache.tomcat.jdbc.pool.DataSource。而 Druid 是阿里系提供的一个开源连接池,除在连接池之外,Druid 还提供了十分优良的数据库监控和扩大性能。接下来,咱们就来解说如何实现 Spring Boot 与 Druid 连接池的集成。
Druid 是阿里开源的一个 JDBC 利用组件,其包含三局部:
- DruidDriver: 代理 Driver,可能提供基于 Filter-Chain 模式的插件体系。
- DruidDataSource: 高效可治理的数据库连接池。
-
SQLParser: 实用的 SQL 语法分析
通过 Druid 连接池中间件,咱们能够实现:
- 能够监控数据库拜访性能,Druid 内置提供了一个功能强大的 StatFilter 插件,可能具体统计 SQL 的执行性能,这对于线上剖析数据库拜访性能有帮忙。
- 替换传统的 DBCP 和 C3P0 连接池中间件。Druid 提供了一个高效、功能强大、可扩展性好的数据库连接池。
- 数据库明码加密。间接把数据库明码写在配置文件中,容易导致平安问题。DruidDruiver 和 DruidDataSource 都反对 PasswordCallback。
- SQL 执行日志,Druid 提供了不同的 LogFilter,可能反对 Common-Logging、Log4j 和 JdkLog,你能够按须要抉择相应的 LogFilter,监控你利用的数据库拜访状况。
- 扩大 JDBC,如果你要对 JDBC 层有编程的需要,能够通过 Druid 提供的 Filter-Chain 机制,很不便编写 JDBC 层的扩大插件。
更多详细信息参考官网文档:https://github.com/alibaba/dr…
集成实际
Spring Cloud 与 Spring Boot 的版本如下
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 版本对应关系:https://start.spring.io/actuator/info-->
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR10</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
<java.version>1.8</java.version>
<spring-mybatis-plus.version>3.4.0</spring-mybatis-plus.version>
<druid.version>1.1.9</druid.version>
</properties>
增加 Druid 的依赖
<!-- Druid 引入 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
Nacos 上增加对应我的项目的配置
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://***.***.***.***:3306/nbgls?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: ***
password: ***
filters: stat,wall,log4j,config
max-active: 100
initial-size: 1
max-wait: 60000
min-idle: 1
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 50
max-pool-prepared-statement-per-connection-size: 20
参数阐明:- spring.datasource.druid.max-active 最大连接数
- spring.datasource.druid.initial-size 初始化大小
- spring.datasource.druid.min-idle 最小连接数
- spring.datasource.druid.max-wait 获取连贯期待超时工夫
- spring.datasource.druid.time-between-eviction-runs-millis 距离多久才进行一次检测,检测须要敞开的闲暇连贯,单位是毫秒
- spring.datasource.druid.min-evictable-idle-time-millis 一个连贯在池中最小生存的工夫,单位是毫秒
- spring.datasource.druid.filters=config,stat,wall,log4j 配置监控统计拦挡的 filters,去掉后监控界面 SQL 无奈进行统计,’wall’用于防火墙
配置 DataSourceConfig 注册 druidDataSource
DruidDataSourceProperties.java
@Data
@RefreshScope
@Component
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DruidDataSourceProperties {
/**
* 读取配置文件中数据库的连贯信息
*/
/**
* 驱动名称
*/
private String driverClassName;
/**
* 地址
*/
private String url;
/**
* 用户名
*/
private String username;
/**
* 明码
*/
private String password;
// jdbc connection pool
private int initialSize;
private int minIdle;
private int maxActive = 100;
private long maxWait;
private long timeBetweenEvictionRunsMillis;
private long minEvictableIdleTimeMillis;
private String validationQuery;
private boolean testWhileIdle;
private boolean testOnBorrow;
private boolean testOnReturn;
private boolean poolPreparedStatements;
private int maxPoolPreparedStatementPerConnectionSize;
// filter
private String filters;
}
DataSourceConfig.java
@Configuration
@EnableConfigurationProperties({DruidDataSourceProperties.class})
public class DataSourceConfig {private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceConfig.class);
@Autowired
private DruidDataSourceProperties druidDataSourceProperties;
@Bean
public DataSource druidDataSource() {LOGGER.info("==================================== InitDatabaseConfig -> dataSource -> 开始创立数据库 ====================================");
// 数据库连贯对象
Connection connection = null;
Statement statement = null;
String url = druidDataSourceProperties.getUrl();
String driverClassName = druidDataSourceProperties.getDriverClassName();
String username = druidDataSourceProperties.getUsername();
String password = druidDataSourceProperties.getPassword();
try {
// 如果尝试去连贯不存在的数据库会报错,所以这里连贯的时候不带数据库名称
String connectionUrl = url.replace(("/" + (url.substring(0, url.indexOf("?"))).substring(((url.substring(0, url.indexOf("?"))).lastIndexOf("/")) + 1)), "");
// 从连贯地址中截取出数据库名称
String databaseName = (url.substring(0, url.indexOf("?"))).substring(((url.substring(0, url.indexOf("?"))).lastIndexOf("/")) + 1);
// 设置驱动
Class.forName(driverClassName);
// 连贯数据库
connection = DriverManager.getConnection(connectionUrl, username, password);
statement = connection.createStatement();
// 创立数据库
statement.executeUpdate("create database if not exists `" + databaseName + "` default character set utf8mb4 COLLATE utf8mb4_general_ci");
}catch (Exception e) {e.printStackTrace();
LOGGER.info("==================================== InitDatabaseConfig -> dataSource -> 创立数据库出错:" + e.getMessage() + "====================================");
}finally {
try {
// 敞开连贯
statement.close();
connection.close();}catch (SQLException e) {LOGGER.info("==================================== InitDatabaseConfig -> dataSource -> 敞开数据库出错:" + e.getMessage() + "====================================");
}
LOGGER.info("==================================== InitDatabaseConfig -> dataSource -> 创立数据库完结 ====================================");
}
// 创立数据源
DruidDataSource druidDataSource = new DruidDataSource();
// 设置数据源
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
druidDataSource.setInitialSize(druidDataSourceProperties.getInitialSize());
druidDataSource.setMinIdle(druidDataSourceProperties.getMinIdle());
druidDataSource.setMaxActive(druidDataSourceProperties.getMaxActive());
druidDataSource.setMaxWait(druidDataSourceProperties.getMaxWait());
druidDataSource.setTimeBetweenEvictionRunsMillis(druidDataSourceProperties.getTimeBetweenEvictionRunsMillis());
druidDataSource.setMinEvictableIdleTimeMillis(druidDataSourceProperties.getMinEvictableIdleTimeMillis());
druidDataSource.setValidationQuery(druidDataSourceProperties.getValidationQuery());
druidDataSource.setTestWhileIdle(druidDataSourceProperties.isTestWhileIdle());
druidDataSource.setTestOnBorrow(druidDataSourceProperties.isTestOnBorrow());
druidDataSource.setTestOnReturn(druidDataSourceProperties.isTestOnReturn());
druidDataSource.setPoolPreparedStatements(druidDataSourceProperties.isPoolPreparedStatements());
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidDataSourceProperties.getMaxPoolPreparedStatementPerConnectionSize());
try {druidDataSource.setFilters(druidDataSourceProperties.getFilters());
druidDataSource.init();} catch (SQLException throwables) {throwables.printStackTrace();
}
// 返回数据源
return druidDataSource;
}
/**
* 注册 Servlet 信息,配置监控视图
*
* @return
*/
@Bean
@ConditionalOnMissingBean
public ServletRegistrationBean<Servlet> druidServlet() {ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<Servlet>(new StatViewServlet(), "/druid/*");
// 白名单:servletRegistrationBean.addInitParameter("allow","127.0.0.1");
//IP 黑名单 (存在独特时,deny 优先于 allow) : 如果满足 deny 的话提醒:Sorry, you are not permitted to view this page.
// servletRegistrationBean.addInitParameter("deny","192.168.1.119");
// 登录查看信息的账号密码, 用于登录 Druid 监控后盾
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "admin");
// 是否可能重置数据.
servletRegistrationBean.addInitParameter("resetEnable", "true");
return servletRegistrationBean;
}
/**
* 注册 Filter 信息, 监控拦截器
*
* @return
*/
@Bean
@ConditionalOnMissingBean
public FilterRegistrationBean<Filter> filterRegistrationBean() {FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
增加 log4j 依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在 resources 目录下,新建一个 log4j.properties 参数配置文件
#############
# 输入到控制台
#############
# log4j.rootLogger 日志输入类别和级别:只输入不低于该级别的日志信息 DEBUG < INFO < WARN < ERROR < FATAL
# WARN:日志级别 CONSOLE:输入地位本人定义的一个名字 logfile:输入地位本人定义的一个名字
log4j.rootLogger=WARN,CONSOLE,logfile
# 配置 CONSOLE 输入到控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 配置 CONSOLE 设置为自定义布局模式
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
# 配置 CONSOLE 日志的输入格局 [frame] 2019-08-22 22:52:12,000 %r 消耗毫秒数 %p 日志的优先级 %t 线程名 %C 所属类名通常为全类名 %L 代码中的行号 %x 线程相关联的 NDC %m 日志 %n 换行
log4j.appender.CONSOLE.layout.ConversionPattern=[frame] %d{yyyy-MM-dd HH:mm:ss,SSS} - %-4r %-5p [%t] %C:%L %x - %m%n
################
# 输入到日志文件中
################
# 配置 logfile 输入到文件中 文件大小达到指定尺寸的时候产生新的日志文件
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
# 保留编码格局
log4j.appender.logfile.Encoding=UTF-8
# 输入文件地位此为我的项目根目录下的 logs 文件夹中
log4j.appender.logfile.File=logs/root.log
# 后缀能够是 KB,MB,GB 达到该大小后创立新的日志文件
log4j.appender.logfile.MaxFileSize=10MB
# 设置滚定文件的最大值 3 指能够产生 root.log.1、root.log.2、root.log.3 和 root.log 四个日志文件
log4j.appender.logfile.MaxBackupIndex=3
# 配置 logfile 为自定义布局模式
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
##########################
# 对不同的类输入不同的日志文件
##########################
# club.bagedate 包下的日志独自输入
log4j.logger.club.bagedate=DEBUG,bagedate
# 设置为 false 该日志信息就不会退出到 rootLogger 中了
log4j.additivity.club.bagedate=false
# 上面就和下面配置一样了
log4j.appender.bagedate=org.apache.log4j.RollingFileAppender
log4j.appender.bagedate.Encoding=UTF-8
log4j.appender.bagedate.File=logs/bagedate.log
log4j.appender.bagedate.MaxFileSize=10MB
log4j.appender.bagedate.MaxBackupIndex=3
log4j.appender.bagedate.layout=org.apache.log4j.PatternLayout
log4j.appender.bagedate.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %F %p %m%n
启动后拜访 http://localhost:9530/druid/l…
MyBatis Plus
- Mybatis-plus 是对 Mybatis 框架的二次封装和扩大纯正血统
- 齐全继承原生 Mybatis 的所有个性起码依赖, 仅仅依赖 Mybatis 以及 Mybatis-Spring 性能损耗小
- 启动即会主动注入根本 CURD,性能无损耗,间接面向对象操作主动热加载,Mapper 对应的 xml 能够热加载,大大减少重启 Web 服务器工夫,晋升开发效率
- 自带 Sql 性能剖析插件,开发测试时,能无效解决慢查问全局拦挡
- 提供全表 delete、update 操作智能剖析阻断防止 Sql 注入, 内置 Sql 注入内容剥离器,预防 Sql 注入攻打
集成实际
配置
(不蕴含代码生成)
增加 MyBatis Plus 的依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${spring-mybatis-plus.version}</version>
</dependency>
Nacos 上增加 MyBatis Plus 相干的配置
mybatis-plus:
mapperLocations: classpath*:com/example/admin/mapper/xml/*Mapper.xml
type-aliases-package: com.example.admin.mapper
global-config:
# 逻辑删除配置
db-config:
# 删除前
logic-not-delete-value: 1
# 删除后
logic-delete-value: 0
配置 MyBatisPlusConfig
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {"com.example.auth.mapper"})
@RefreshScope
public class MyBatisPlusConfig {@Value("${mybatis-plus.mapper-locations}")
private String configLocation;
@Value("${mybatis-plus.type-aliases-package}")
private String typeAliasesPackage;
@Autowired
private ResourceLoader resourceLoader = new DefaultResourceLoader();
/**
* 乐观锁插件 MP3.3.0 之后更改
* 分页插件
*/
@Bean
public MybatisPlusInterceptor optimisticLockerInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
测试
新建实体类 UserEntity.java
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@TableName("sys_user")
@ApiModel(value = "用户信息")
public class UserEntity extends BaseEntity {
/**
* 主键 ID
* default value: null
*/
@TableId(value = "user_id", type = IdType.AUTO)
@ApiModelProperty(value = "主键 ID")
private Long userId;
/**
* 姓名
* default value: null
*/
private String name;
/**
* 用户角色,分为普通用户 / 管理员
* default value: null
*/
@ApiModelProperty(value = "用户类别,分为普通用户(ROLE_USER)/ 管理员(ROLE_ADMIN)")
private String role;
/**
* 用户账号
* default value: null
*/
@ApiModelProperty(value = "用户名")
private String userName;
/**
* 邮箱
* default value: null
*/
@ApiModelProperty(value = "邮箱")
private String email;
/**
* 电话号码
* default value: null
*/
@ApiModelProperty(value = "电话号码")
private String telephone;
/**
* 用户性别,F/M
* default value: null
*/
@ApiModelProperty(value = "用户性别,F/M")
private String gender;
/**
* 头像存储门路
* default value: null
*/
@ApiModelProperty(value = "头像存储门路", hidden = true)
private String avatar;
/**
* 明码
* default value: null
*/
@ApiModelProperty(value = "明码", hidden = true)
private String password;
/**
* 是否启用,默认为 Y
* default value: null
*/
@ApiModelProperty(value = "是否启用,默认为 Y")
private String enabled;
/**
* 用户标签信息,以; 宰割
* default value: null
*/
@ApiModelProperty(value = "用户标签信息,以; 宰割")
private String tags;
}
BaseEntity.java
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class BaseEntity extends Model {
/**
* 创建人
* default value: null
*/
private Long createBy;
/**
* 创立日期
* insert 时主动填充
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 批改人
* default value: null
*/
private Long updateBy;
/**
* 批改日期
* update 时主动填充
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE, update="NOW()")
private Date updateTime;
@Version
@TableField(fill = FieldFill.INSERT, update="%s+1")
private Integer version;
}
对于 MyBatis Plus 注解的应用,能够参考官网 https://baomidou.com/guide/an…
这里要阐明的是 TableField 的 fill 主动填充性能
public enum FieldFill {
/**
* 默认不解决
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
- 填充原理是间接给
entity
的属性设置值!!! - 注解则是指定该属性在对应状况下必有值, 如果无值则入库会是
null
MetaObjectHandler
提供的默认办法的策略均为: 如果属性有值则不笼罩, 如果填充值为null
则不填充- 字段必须申明
TableField
注解, 属性fill
抉择对应策略, 该申明告知Mybatis-Plus
须要预留注入SQL
字段 - 填充处理器
MyMetaObjectHandler
在 Spring Boot 中须要申明@Component
或@Bean
注入 - 要想依据注解
FieldFill.xxx
和字段名
以及字段类型
来辨别必须应用父类的strictInsertFill
或者strictUpdateFill
办法 - 不须要依据任何来辨别能够应用父类的
fillStrategy
办法
这里自定义 MyMetaObjectHandler 对 updateTime,version 等字段做解决
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {log.info("start insert fill ....");
this.strictInsertFill(metaObject, "create_time", LocalDateTime.class, LocalDateTime.now());
// 或者
this.strictInsertFill(metaObject, "version", Integer.class, 1);
this.strictInsertFill(metaObject, "update_time", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {log.info("start update fill ....");
this.strictUpdateFill(metaObject, "update_time", LocalDateTime.class, LocalDateTime.now());
}
}
新建 mapper
@Mapper
public interface UserMapper extends BaseMapper<UserEntity> {}
新建 service
public interface IUserService extends IService<UserEntity> {}
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements IUserService {}
即可应用