秦音

个人博客


  • 首页

  • 标签

  • 分类

  • 归档

Hadoop简单环境搭建

发表于 2020-07-23 | 分类于 Hadoop

1.前言

   Hadoop是运行在Linux,虽然借助工具也可以运行在Windows上,但是建议还是运行在Linux系统上。本文章描述的是在linux上的安装过程.
参考链接:
   Hadoop的官网地址
   hadoop安装包下载地址
   史上最详细的Hadoop环境搭建

2.环境准备

必须确保已经安装好java的JDK以及ssh。

Hadoop Java版本选择
安装好JDK后,必须设置JDK的环境变量。

ssh必须安装并且保证sshd一直运行,主要便用Hadoop脚本管理远端Hadoop守护进程。

3.Hadoop本地模式安装

4.Hadoop伪分布式模式安装

5.完全分布式安装

图片开始图片结束

s

Linux安装链接:Hadoop Java版本

  

内容折叠标题

折叠内容

PostgresSQL命令行操作

发表于 2020-07-20 | 分类于 Pgsql

1.通过ssh连接上数据库所在服务器,比如235服务器。
2.然后切换到pgsql用户。可以先切换到root用户,然后切换到pgsql用户,也可以直接切换到pgsql用户。

1
2
3
4
# 切换到pgsql用户
su -postgres
# 进入到bash
psql

之后出现“postgres=# ” 即连接成功,即可通过命令行方式操作数据库。

pgsql命令行常见命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#修改密码:  (注意最后的分号)
alter user postgres with password '密码';
#退出pgsql
\q
#退出bash
exit
#登录数据库
psql -U user_name -d database_name -h serverhost -p port -W password
#切换数据库
\c jx_test
#查看当前数据库所有表
\d
#执行sql (pgsql命令行是严重区分大小写的,即关键字必须写成大写。且尾部必须加;)
SELECT * FROM document ;

删除数据库:
方法1–系统命令: 如果数据库的名称为数字,则需要方法1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 切换到postgres用户(系统用户)
sudo su -postgres
# 创建数据库 create db_name
create 1234;
#直接访问数据库(默认进入本地postgres数据库)
psql
# 查看数据库列表
\l
# 退出列表页面
:q
# 退出客户端
\q
# 删除数据库 dropdb db_name
dropdb 1234

方法二 —psql命令行

1
2
3
4
5
6
# 登录客户端
sudo -u postgres psql
# 创建数据库 (命令行 CREATE TABLE,跨行时直接按 ENTER 键或者 \ 表示换行)
create database admin;
# 删除数据库
drop database admin;

一些操作截图:

Jenkins自动化项目部署

发表于 2020-07-03 | 分类于 Jenkins

1. 自动化部署

   此文本的自动部署是默认安装好JenKins、JDK以及打包所需要的maven等配置。如果没有安装好Jenkins或其他配置,可参考:Jenkins之——构建Java Maven项目(WAR)并发布到远程Tomcat
① 创建项目工程
   在创建项目工程时,比如parse_test_youqiang工程的某些基础配置与parse_sign_test_api有公共部分,则可以在创建parse_test_youqiang项目工程时,将parse_sign_test_api的参数配置全部复制到parse_test_youqiang中,然后再进去修改单独配置参数。当然,两个项目一点共同点都没有也可以这样操作,只是要进去对所有的配置进行全部修改,比较麻烦。
   因为是从已有工程复制过去的,所以parse_test_youqiang工程创建好后,默认带有初始值。可根据业务自行修改。

② 设置构建保存策略:

打开一个Jenkins job的Configure页面,勾选上”Discard old builds”,填入Day of keep builds (保留几天的build记录)和Max# of builds to keep (保留最多几个build记录)。点击Advanced按钮,还可以填入Days of keep artifacts (保留几天的artifact)和 Max# of builds to keep with artifacts(保留最多几个artifacts)。 通常来讲,如果磁盘空间足够,这4个选项设置为15就可以了(也就是两周 + 1天)。如果磁盘空间不足时,可以将这4个选项都设置为3。 Jenkins job在每次build结束后(无论成功还是失败)都会自动执行discard old builds`,这样当下次执行build后,这个job占用的磁盘空间就会被释放。

1
2
$JENKINS_HOME/jobs/[JOB_NAME]/builds 目录存储了该Jenkins job的全部构建记录(目录名为Build Number)。
$JENKINS_HOME/jobs/[JOB_NAME]/builds/BUILD_NUMBER/artifacts 目录存储了该次构建的artifact。

③ 选取源代码地址、分支、gitlab版本
   项目打包:
   输入打包命令,JenKins会自动执行该命令。

需要注意的是,在后端打包时,需要进入到项目工程对应的目录下。
而JenKins会自动在JenKins的工作空间创建以项目工程名称的文件夹,然后将代码拉取到该目录下。故需要先进入到该目录下在执行maven打包命令。前端也是,也要进入到该目录下,只是执行的打包命令不同而已。
(注意,vue前端打包时,需要在将项目初始化,即:/data1/node-v14.4.0-linux-x64/bin/npm install。因为该命令只需要执行一次即可,故不写入自动部署命令脚本中。如果不初始化项目,会导致打包失败)

④ 打好的jar传输与jar启动:

⑤ 前端文件传输:前端与后端配置基本相同,只是文件的路径不同的差异。

⑥ JenKins的公共配置:


参考链接:Post-build Actions的ssh配置

2.JenKins设置中文

   由于本Jenkins已经设置过了,故显示为中文

   在插件管理,搜索 Localization: Chinese (Simplified),然后点击Install without restart

   等待插件安装完成:

   安装完成后,需要重启JenKins:在地址栏输入 restart 重启Jenkins:

   即可看到效果:

   如果重启后没有中文化,可能是插件没安装完全,上面步骤再来一次。

3.根据不同参数打不同环境的jar


   在构建自动部署过程中引用该参数:

   然后根据该参数选中的不同选项,发布到不同的服务器上去启动:

   当env_options字段选中的值为parse_test时,则使用parse_test用户进行jar传输与启动:

   当env_options字段选中的值为parse_rel时,则使用parse_rel用户进行jar传输与启动:

   设置打包过程中使用env_options的值来进行不同的jar传输与启动:


   实现后打包效果为:


   即根据env_options选中的不同的值,来实现不同服务器的打包部署。但这种方式的前提是代码仓库Git地址要是同一个地址,且打包的分支也是同一个分支才行,不满足这两个条件则不适合这种自动部署方式。打包时选择不同分支进行打包的操作后面介绍。

4. 打包时选择不同git分支自动部署

   在插件管理中,安装Git Parameter Plug-In插件,安装后重启JenKins,安装步骤与JenKins设置中文步骤类似。
   插件安装好后,进入待设置的工程配置里面:

   在拉取源代码时,设置拉取源代码分支参数值:

参考链接:jenkins发布代码选择不同分支

Java技术栈版本选择

发表于 2020-07-03 | 分类于 Java

Java相关技术选择

组件名称 版本 说明
Java 1.8.0_212 elasticsearch依赖最低版本1.8.0_131
springboot2至少依赖1.8
Spring Boot 2.1.5.RELEASE feign多文件需要简单配置
Servlet 容器 tomcat 忠宇反馈评审人员对于不熟悉的undertow持否定态度,采用springboot自带的tomcat
Elasticsearch 6.3.2 采用restclient代替transportclient,根据需要再开发2.4相关版本
数据库 PostgreSQL(11.4)
持久层框架 MyBatis(3.5.1) mybatis-spring-boot-starter(2.0.1)内部mybatis就是3.5.1
swagger 2.7.0 从2.8.0之后版本ui不好用
参数校验 javax.validation 统一异常处理
统一异常处理 分层处理 需要自己封装
统一返回数据对象 需要自己封装
工具类 commons-lang3
commons-collections4
commons-io
http(okhttp)
json(jackson)
日期操作
需要自己封装

版本详情

Java

官方当前1.8最新稳定版 Java SE Development Kit 8u212

最低要求版本1.8.0_131(elasticsearch6.0官方要求的最低版本)

Spring Boot

最新稳定版 2.1.5.RELEASE

之前项目中使用springboot 如果需要使用feign的文件上传需要配置的地方比较多
2.0以后feign采用openfeign,不需要配置,只有在多文件上传时需要简单配置

class MultipartSupportConfig {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    public Encoder feignEncoder() {
        return new SpringMultipartEncoder(new SpringEncoder(messageConverters));
    }
}

class SpringMultipartEncoder extends SpringFormEncoder {

    public SpringMultipartEncoder() {
        this(new Default());
    }

    public SpringMultipartEncoder(Encoder delegate) {
        super(delegate);
        MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
        processor.addWriter(new SpringSingleMultipartFileWriter());
        processor.addWriter(new SpringManyMultipartFilesWriter());
    }

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (bodyType.equals(MultipartFile.class)) {
            // 单MultipartFile判断
            MultipartFile file = (MultipartFile) object;
            Map data = singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
            return;
        } else if (bodyType.equals(MultipartFile[].class)) {
            // MultipartFile数组处理
            MultipartFile[] file = (MultipartFile[]) object;
            if (file != null) {
                Map data = singletonMap(file.length == 0 ? "" : file[0].getName(), object);
                super.encode(data, MAP_STRING_WILDCARD, template);
                return;
            }
        }
        // 其他类型调用父类默认处理方法
        super.encode(object, bodyType, template);
    }
}

文件上传的feign配置

@FeignClient(name = "file", url = "http://127.0.0.1:8080", configuration = FileFeign.MultipartSupportConfig.class)

Servlet容器

使用springboot自带的tomcat

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

使用统一异常处理捕获文件大小异常

配置文件上传大小为10M示例

application.yml

server:
  # 该配置项可以在统一异常处理中捕获上传文件大小异常
  tomcat:
    max-swallow-size: -1
spring:
  servlet:
    multipart:
      max-file-size: 10MB

统一异常处理

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    @ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
    public String handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
        return e.getMessage();
    }

}

上面的方法只能处理单文件的异常,对于多文件上传,一个表单项上传多文件的情况,如果是每个文件不过超过10MB,那么选择两个6MB的同样会报错,可以采用如下配置

@SpringBootApplication(exclude = MultipartAutoConfiguration.class)

在启动类上排除MultipartAutoConfiguration,使用自定义的MultipartResolver

@Value("${spring.servlet.multipart.max-file-size:1MB}")
private String maxFileSize;

@Bean(name = "multipartResolver")
@Primary
public MultipartResolver multipartResolver() {
    log.info("maxUploadSize {}", maxFileSize);
    long maxSize = DataSize.parse(maxFileSize).toBytes();
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setDefaultEncoding("utf-8");
    multipartResolver.setMaxUploadSizePerFile(maxSize);
    return multipartResolver;
}

Elasticsearch

使用6.3.2作为开发版本

采用RestHighLevelClient操作

依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.3.2</version>
</dependency>

示例

RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(
            new HttpHost("localhost", 9200, "http"),
            new HttpHost("localhost", 9201, "http")));

创建索引
client.indices().create(request);
删除索引
client.indices().delete(request);
查询数据
client.search(request);

数据库

PostgreSQL 11.4

持久层框架

MyBatis 3.5.1

mybatis-spring-boot-starter(2.0.1)内部mybatis就是3.5.1

swagger

从2.8.0开始界面变化太大,操作不方便,使用2.7.0

依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>${swagger.version}</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${swagger.version}</version>
</dependency>

参数校验

比如参数不能为空,一些正则的校验

@NotEmpty(message = "手机号不能为空")
@Pattern(regexp = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$", message = "手机号格式不对")
private String telephone;

示例

请求对象
@Data
public class ParamVo {
    @NotEmpty(message = "用户名不能为空")
    private String username;
    @NotEmpty(message = "手机号不能为空")
    @Pattern(regexp = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$", message = "手机号格式不对")
    private String telephone;
}

请求controller
@ApiOperation(value = "参数校验")
@PostMapping("/paramValidate")
public ParamVo paramValidate(@RequestBody @Valid ParamVo paramVo) {
    return paramVo;
}

统一异常处理
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleBindException(MethodArgumentNotValidException e) {
    // log.error("参数校验异常:{}", e);
    StringBuilder errorStr = new StringBuilder();
    e.getBindingResult().getAllErrors().
            forEach(x -> errorStr.append(x.getDefaultMessage() + ","));
    String message = errorStr.substring(0, errorStr.length() - 1);
    // message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
    return message;
}

日常代码记录

发表于 2020-06-30 | 分类于 代码分类

1. mybatis的n+1(1次主查询会伴随着n次副查询,即collection查询)

场景:要查询Meeting对象,同时需要关联查询出每一个meeting对象对应的List和List。如果按照普通的查询 查询,会导致n+1查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("xn_meeting")
public class Meeting implements Serializable {

private static final long serialVersionUID = 1L;

@TableId(type = IdType.AUTO)
private Long id;

/**
* 会议名称
*/
private String name;

/**
* 会议创建人
*/
private Long createUserId;

/**
* 会议资料集合
*/
@TableField(exist = false)
private List<FileEntity> fileList;

/**
* 参会人员详细信息
*/
@TableField(exist = false)
private List<MeetingUsersEntity> userList;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("xn_file")
public class FileEntity implements Serializable {

private static final long serialVersionUID = 1L;

private String uuid;

/**
* 资料名称
*/
private String fileName;

/**
* 文件大小
*/
private Long fileSize;

/**
* 文件类型
*/
private String fileType;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class MeetingUsersEntity extends User implements Serializable {

private static final long serialVersionUID = 1L;

/**
* 参会人员是否参会标识 0:参会 1:不参会
*/
private Integer meetingStatus;

/**
* 会议室名称
*/
private String roomName;

}

查询优化:使用left join关联查询,而不是多次查询。前提是查询中需要使用as注明字段的名称,方可通过下面的方式进行一一匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<select id="questMeeting" resultMap="meetingFileMap">
select xm.id, xm.name as meetingName,xu2.name as createUserName,xmr.name as meetingRoomName,
xu.id as userId,xu.name as uName,xu.username as username,xu.phone_number as phoneNumber,xmu.status as meetingStatus,xf.uuid as xmfUuid,xf.file_name as fileName,xf.source_type as sourceType,xf.file_size as fileSize
from xn_meeting xm
left join xn_meeting_users xmu on xmu.meeting_id = xm.id
left join xn_user xu on xu.id = xmu.user_id and xu.deleted = 0
left join xn_user xu2 on xu2.id = xm.create_user_id
left join xn_meeting_room xmr on xmr.id = xm.meeting_room_id
left join (select * from xn_file where source_type = #{sourceType} and source_id = #{id} and deleted = 0) xf on
xf.source_id = xm.id
where xm.deleted = 0 and xm.id = #{id}
</select>

<resultMap id="meetingFileMap" type="com.aiidc.xn.meeting.entity.Meeting">
<id property="id" column="id"/>
<result property="name" column="meetingName"/>
<collection property="userList" column="id" ofType="com.aiidc.xn.user.entity.User">
<id property="id" column="userId"/>
<result property="name" column="uName"/>
<result property="meetingStatus" column="meetingStatus"/>
</collection>
<collection property="fileList" column="id" ofType="com.aiidc.xn.filestore.entity.FileEntity" resultMap="fileMap"/>
</resultMap>

<resultMap id="fileMap" type="com.aiidc.xn.filestore.entity.FileEntity">
<id property="uuid" column="xmfUuid"/>
<result property="fileName" column="fileName"/>
<result property="fileSize" column="fileSize"/>
</resultMap>

其中:

property:子查询返回的结果集
column:子查询时所需要的查询条件
select:子查询的sql对应的id

但这种方式不适合加上分页条件,即如果查询的结果需要使用分页,则不能使用该方式。

另:将查询出的结果作为另一个查询的条件:

1
2
3
4
5
6
7
<collection property="systemId" column="{pid=id}" select="getAllSystemByUserId"/>

<select id="getAllSystemByUserId" resultType="com.aiidc.xn.user.entity.XnSystem">
select xs.id as id,xs.name as name from xn_system_user xsu
left join xn_system xs on xsu.system_id = xs.id
where xsu.user_id = #{id}
</select>

2.Mybatis Plus and与or并用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dimension one = this.getOne(Wrappers.<Dimension>lambdaQuery()
.and(e -> e.eq(Dimension::getCreateTime, "官方还让他让他人员同意"))
.and(e -> {e.eq(Dimension::getDimName, "名称").or().eq(Dimension::getId, "111");})
);

// if (StringUtils.isNotBlank(keyword)) {
// query.and(e->{
// e.like(Case::getName, keyword)
// .or().like(Case::getSerialNumber, keyword)
// .or().like(Case::getBmsah, keyword)
// .or().like(Case::getApplicant, keyword)
// .or().like(Case::getCaseFrom, keyword)
// .or().like(Case::getRespondent, keyword)
// .or().like(Case::getUndertaker, keyword);
// });
// }
// if(StringUtils.isNotBlank(currentUser)){
// query.and(e->e.eq(Case::getCreateUser,currentUser));
// }

3.minio文件服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public InputStream download(String bucket, String objectName) throws FileDownloadException, FileNotExistException {
InputStream stream;
try {
exist(bucket, objectName);
stream = minioClient.getObject(bucket, objectName);
return stream;
} catch (MinioException | NoSuchAlgorithmException | IOException | InvalidKeyException | XmlPullParserException | FileServerAccessException e) {
throw new FileDownloadException(bucket, objectName, e);
}
}

@Override
public boolean exist(String bucket, String objectName) throws FileServerAccessException, FileNotExistException {
try {
minioClient.statObject(bucket, objectName);
return true;
} catch (ErrorResponseException e) { //不存在是会抛出异常的
ErrorCode errorCode = e.errorResponse().errorCode();
if (errorCode == ErrorCode.NO_SUCH_KEY || errorCode == ErrorCode.NO_SUCH_OBJECT) {
log.error("文件{}在存储桶{}中不存在!", objectName, bucket);
return false;
}
log.error("查询文件是否存在报错:", e);
throw new FileNotExistException(bucket, objectName, e);
} catch (MinioException | IOException | InvalidKeyException | NoSuchAlgorithmException | XmlPullParserException e) {
log.error("访问文件服务器出错,文件:{},文件桶:{},错误:{}", objectName, bucket, e);
throw new FileServerAccessException("", e);
}
}

   下载:从minio下载文件时,注意文件的路径,即objectName参数不是文件的全路径,而只是文件的相对路径。
eg:http://172.16.16.222:9000/mxzf/public/image/20200429/1588144931194000854/test.jpg的test.jpg图片,通过minio下载的话。objectName参数应该为:public/image/20200429/1588144931194000854/test.jpg。
   而类似http://172.16.16.222:9000/mxzf/public/image/20200429/1588144931194000854/test.jpg这种资源路径,可以直接通过:

1
2
URL image = new URL("http://172.16.16.222:9000/mxzf/public/image/20200429/1588144931194000854/test.jpg");
BufferedReader in = new BufferedReader(new InputStreamReader(image.openStream()));

这种直接获取对应的文件信息。

4.多字段排序问题

   实际场景:获取会议信息时,有根据创建时间排序,会议开始时间排序的需求,如果在sql语句层面去解决,需要进行多次判断,且不易维护。
   解决措施:在Vo实体类中建立容器:LinkedHashSet sorts。通过判断容器里面的元素,来达到拼接sql的作用。其中的Sort类为MeetPageVo 的内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class MeetPageVo extends PageRequest {
@ApiModelProperty("排序规则,默认为创建时间降序排序")
private LinkedHashSet<Sort> sorts;

@ApiModel("排序")
@Setter
@Getter
public static class Sort {
@ApiModelProperty("排序字段:创建时间:CREATE_TIME,开始时间:START_TIME")
private SortField field = SortField.CREATE_TIME;
@ApiModelProperty("排序方向:升序ASC,降序DESC")
private Direction direction = Direction.DESC;

public Sort() {
}

public Sort(SortField field, Direction direction) {
this.field = field;
this.direction = direction;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Sort sort = (Sort) o;
return field == sort.field && direction == sort.direction;
}

@Override
public int hashCode() {
return Objects.hash(field, direction);
}
}

public enum SortField {
/** 创建时间 */
CREATE_TIME,
/** 开始时间 */
START_TIME;
}

public enum Direction {
DESC, ASC;
}

}

然后在业务层进行设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LinkedHashSet<MeetPageVo.Sort> sorts = meetPageVo.getSorts();
LinkedHashSet<MeetPageVo.Sort> querySorts = new LinkedHashSet<>();

if (CollectionUtils.isEmpty(sorts)) {//如果排序容器中没有元素,就设置默认排序方式:创建时间倒序排序
querySorts.add(new MeetPageVo.Sort(MeetPageVo.SortField.CREATE_TIME, MeetPageVo.Direction.DESC));
} else {
MeetPageVo.Sort querySort;
// 遍历容器中的排序字段 以及对应的排序方式
for (MeetPageVo.Sort sort : sorts) {
querySort = new MeetPageVo.Sort();
MeetPageVo.SortField field = sort.getField();
querySort.setField(Objects.isNull(field) ? MeetPageVo.SortField.CREATE_TIME : field);

MeetPageVo.Direction direction = sort.getDirection();
querySort.setDirection(Objects.isNull(direction) ? MeetPageVo.Direction.DESC : direction);

querySorts.add(querySort);
}
}
meetQueryVo.setSorts(querySorts);

在sql层进行拼接:

1
2
3
4
order by
<foreach collection="sorts" item="sort" separator=",">
xm.${sort.field} ${sort.direction}
</foreach>

Spring实现类注入注意点

发表于 2020-06-19 | 分类于 Spring

1.Spring常用标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
@Scope注解 作用域
@Lazy(true) 表示延迟初始化
@Service用于标注业务层组件、
@Controller用于标注控制层组件(如struts中的action)
@Repository用于标注数据访问组件,即DAO组件。
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
@Scope用于指定scope作用域的(用在类上)
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)
@Resource 默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
@DependsOn:定义Bean初始化及销毁时的顺序
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用
@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用

   当一个接口有两个不同的实现方法时,Spring在注入的时候会由于不知道该注入那个一个实现而报错。可以通过@Primary告诉spring在犹豫的时候优先选择哪一个具体的实现。
场景:
   定义接口:

1
2
3
public interface Singer {
String sing(String lyrics);
}

   有下面的两个实现类:

1
2
3
4
5
6
7
8
@Component // 加注解,让spring识别
public class MetalSinger implements Singer{

@Override
public String sing(String lyrics) {
return "I am singing with DIO voice: "+lyrics;
}
}

   注意,这里没有注解

1
2
3
4
5
6
public class OperaSinger implements Singer {
@Override
public String sing(String lyrics) {
return "I am singing in Bocelli voice: "+lyrics;
}
}
1
2
@Autowired
private Singer singer;

   在注入Singer对象时,会报错误:I am singing with DIO voice: song lyrics. 原因很简单,就是OperaSinger这个类上面根本没有加上注解@Copmonent或者@Service,所以spring注入的时候,只能找到MetalSinger这个实现类.所以才有这个结果。

   但是如果一旦 OperaSinger 这个类加上了@Copmonent 或者 @Service 注解,有趣的事情就会发生,你会发现一个错误的结果或异常:

1
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [main.service.Singer] is defined: expected single matching bean but found 2: metalSinger,operaSinger

   第一种解决办法:提示很明确了,spring 根据类型无法选择到底注入哪一个。这个时候@Primay 可以闪亮登场了。

1
2
3
4
5
6
7
8
9
@Primary
@Component
public class OperaSinger implements Singer{

@Override
public String sing(String lyrics) {
return "I am singing in Bocelli voice: "+lyrics;
}
}

   如果代码改成这样,再次运行,结果如下:I am singing in Bocelli voice: song lyrics, 用@Primary 告诉spring在犹豫的时候优先选择哪一个具体的实现。
   第二种解决办法:用@Qualifier这个注解来解决问题,将上面的两个类改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component // 加注解,让spring识别
@Qualifier("metalSinger")
public class MetalSinger implements Singer{

@Override
public String sing(String lyrics) {
return "I am singing with DIO voice: "+lyrics;
}
}

@Component
@Qualifier("opreaSinger")
public class OperaSinger implements Singer {
@Override
public String sing(String lyrics) {
return "I am singing in Bocelli voice: "+lyrics;
}
}

   然后在调用的方法上面使用@Qualifier标签,@Qualifier限定哪个bean应该被自动注入。当Spring无法判断出哪个bean应该被注入时,@Qualifier注解有助于消除歧义bean的自动注入。

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class SingerService {
private static final Logger logger = LoggerFactory.getLogger(SingerService.class);

@Autowired
private Singer singer;

@Qualifier("opreaSinger")
public String sing(){
return singer.sing("song lyrics");
}
}

2.Spring自定义Bean

   springboot的标签生成bean方式,bean的名称是与方法名称相匹配的:

1
2
3
4
5
@Bean
@ConditionalOnBean(WxConfig.class)
public WxAuthService wxBindService(WxService wxService) {
return new WxAuthServiceAdapter(wxService);
}

   上面的bean的名称为wxBindService.也可在@Bean(””)指定生成bean的名称,比如:@Bean("wxAuthService")

注意点:
   ① @Bean用在方法上,@Component用在类上,用了这2个注解就可以将自定的bean就放入ioc了。注意:有@Bean的方法的类,必须加上@Configuration,表明这个是个配置类,相当于xml文件的作用。不然,你加上@Bean没有用啊!!
   ② @ConfigurationProperties作用是绑定配置文件中的值和类的成员变量,使得new出的对象是有初始值的。但是,用了这个注解后,类并没有到容器中。必须要加@Component,放入容器。

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "casic.filestore.minio")
public class MinioProperties {
private String url;
private String accessKey;
private String secretKey;

public MinioProperties() {

}
//设置set、get方法

   通过@ConfigurationProperties标签,在创建MinioProperties对象的时候,会自动设置MinioProperties对象中的字段的初始值为配置文件中设置的值。
   ③ 如果不用Component,那么需要注入上面这个对象的类,必须加上@EnableConfigurationProperties。这样,使用了@EnableConfigurationProperties这个注解后,IOC容器中也会有了。那么你就可以在这个类中使用@AutoWired 等注解来注入上述组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import io.minio.MinioClient;
import io.minio.errors.InvalidEndpointException;
import io.minio.errors.InvalidPortException;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import javax.crypto.KeyGenerator;
import java.security.NoSuchAlgorithmException;

@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class FileStoreConfig {

@Bean
@Lazy
public MinioClient minioClient(MinioProperties minioProperties) throws InvalidPortException, InvalidEndpointException {
MinioClient minioClient = new MinioClient(minioProperties.getUrl(),minioProperties.getAccessKey(),minioProperties.getSecretKey());
return minioClient;
}
}

3.开发常见标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值

@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类

@Validated:用于校验请求中的数据是否正常。即修饰实体类时,通过在实体类中的字段上添加类似
@NotNull、 @Size(max=32,message="code is null")
等标签,如果在请求时数据异常则会统一抛出异常,方便异常中心统一处理。
@PathVariable:带占位符的 URL 是 Spring3.0 新增的功能;
通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:
URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中;
@TableLogic:在字段上加上这个注解再执行BaseMapper的删除方法时,删除方法会变成修改
@TableField:@TableField(exist = false)注解加载bean属性上,表示当前属性不是数据库的字段,但在项目中必须使用,
这样在新增等使用bean的时候,mybatis-plus就会忽略这个,不会报错
@NotBlank(message = "密码不能为空!")
设置字段的验证规则,不需要再业务层再进行单独判断

@TableLogic:注解表示逻辑删除,在字段上加上这个注解再执行BaseMapper的删除方法时,删除方法会变成修改
没有@TableLogic注解调用deleteById/removeById,直接删除数据。
SQL:delete from table where id = 1
有注解走Update方法
SQL:Update table set isDelete = 1 where id = 1

Swagger:
@JsonIgnore:不让前端或文档显示某字段注解

Lombok:
@EqualsAndHashCode(callSuper = true):
a.这个注解会生成equals(Object other) 和 hashCode()方法。 
b.它默认使用非静态,非瞬态的属性 
c.可通过参数exclude排除一些属性 
d.可通过参数of指定仅使用哪些属性 
e.它默认仅使用该类中定义的属性且不调用父类的方法 
f.可通过callSuper=true解决e问题。让其生成的方法中可以调用父类的方法。

Maven导入jar报错排查

发表于 2020-06-18 | 分类于 Maven

maven可能的错误:
   ① mvn打包时,某个jar明明存在但还是报找不到异常;
   ② 导入新jar时,总是下载失败,或者是手动下载后将jar放入指定目录单maven还是报找不到该jar
   ③ idea 的maven警告

解决方法:
   ① 修改默认的settings.xml文件中的国外镜像为国内镜像
   在配置文件settings.xml文件中:

1
2
3
4
5
6
7
8
9
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<!-- 将镜像下载地址替换为阿里的下载地址 -->
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>

   ② 在C盘也放置一个setting.xml文件
   虽然在idea中可能指定的自定义的settings.xml文件,但maven有时候还是会去读取默认的在C盘的C:\Users\youqiang.m2\settings.xml文件,导致仓库地址也是默认仓库。故需要将自定义的settings.xml文件拷贝到C盘中,去覆盖默认的settings.xml文件。
   ③ 设置idea的mvaen安装目录

Druid实现简单数据库连接池监控

发表于 2020-06-17 | 分类于 Druid

1. Druid简单介绍

   Druid是阿里巴巴开源的数据库连接池,号称是Java语言中最好的数据库连接池,能够提供强大的监控和扩展功能。
   GitHub地址:https://github.com/alibaba/druid

优点:
   ① 可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
   ② 替换DBCP和C3P0,Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
   ③ 数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDriver和DruidDataSource都支持PasswordCallback。
   ④ SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。
   ⑤ 扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter-Chain机制,很方便编写JDBC层的扩展插件。

2. druid基础配置

2.1 创建基本的SpringBoot项目

此次使用的是基于Mysql与Postgresql的双数据源搭建的demo。(双数据源搭建查看上一篇博客内容)

2.2 导入jar

   之后导入druid.jar。Druid 0.1.18之后版本都发布到maven中央仓库中,所以你只需要在项目的pom.xml中加上dependency就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--druid-demo所需jar,本demo使用的是log4j来做日志记录-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>

<!--需要将Spring-boot中去掉logback的依赖-->
<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>

<!--日志-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

2.3 设置druid相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
spring:
# MySQL connection config
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/yq_mysql?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: admin
# Druid 数据源专用配置
# 初始化大小,最小,最大
initialSize: 3
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 30000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,使用的日志组件为log4j2
filters: stat,wall,log4j2
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录.合并多个DruidDataSource的监控数据
#useGlobalDataSourceStat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=10000
# Thymeleaf configurations
thymeleaf:
mode: HTML
cache: false
servlet.content-type: text/html
encoding: UTF-8
# jpa configurations
jpa:
#配置指明在程序启动的时候要删除并且创建实体类对应的表。这个参数很危险,因为他会把对应的表删除掉然后重建。所以千万不要在生成环境中使用。只有在测试环境中,一开始初始化数据库结构的时候才能使用一次。过后使用update
#hibernate.ddl-auto: create
hibernate.ddl-auto: update
#默认的存储引擎切换为 InnoDB
database-platform: org.hibernate.dialect.MySQL57InnoDBDialect
# 配置在日志中打印出执行的 SQL 语句信息。
show-sql: true
pgdatasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/yq_pgsql?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: postgres
password: admin
servlet:
multipart:
max-file-size: 100MB
max-request-size: 200MB

2.4 配置Druid监控统计功能

   基于Druid的Filter-Chain扩展机制,Druid提供了3个非常有用的具有监控统计功能的Filter:

StatFilter用于统计监控信息;
WallFilter基于SQL语义分析来实现防御SQL注入攻击;
LogFilter 用于输出JDBC执行的日志。

如果在项目中需要使用Druid提供的这些监控统计功能,可以通过以下两种途径进行配置。
①方式一:基于Servlet 注解的配置
   对于使用Servlet 3.0的项目,在启动类上加上注解@ServletComponentScan 启用Servlet自动扫描,并在自定义的DruidStatViewServlet/DruidStatFilter 上分别加上注解@WebServlet/@WebFilter 使其能够被自动发现。

DruidStatViewServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* druid数据源状态监控.
*/
import com.alibaba.druid.support.http.StatViewServlet;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

@WebServlet(urlPatterns = "/druid/*",initParams = {
//IP白名单 没有配置或者为空则允许所有访问
@WebInitParam(name="allow",value = "127.0.0.1"),
// IP黑名单 存在共同时,deny优先于allow
@WebInitParam(name = "deny", value = "192.168.1.10"),
// 用户名
@WebInitParam(name = "loginUsername", value = "root"),
// 密码
@WebInitParam(name = "loginPassword", value = "123"),
// 禁用HTML页面上的“Reset All”功能
@WebInitParam(name = "resetEnable", value = "false")
})
public class DruidStatViewServlet extends StatViewServlet {
}

DruidStatFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* druid过滤器
* /**
* WebStatFilter用于采集web-jdbc关联监控的数据。
* 属性filterName声明过滤器的名称,可选
* 属性urlPatterns指定要过滤 的URL模式,也可使用属性value来声明.(指定要过滤的URL模式是必选属性)
*/
import com.alibaba.druid.support.http.WebStatFilter;

import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;

@WebFilter(filterName = "druidWebStatFilter",urlPatterns = "/*",initParams = {
// 忽略资源
@WebInitParam(name = "exclusions", value = "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*")
})
public class DruidStatFilter extends WebStatFilter {
}

之后在启动类加上@ServletComponentScan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;

//暂时关闭Spring 自带的Security用户认证
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
@ServletComponentScan
public class DruidApplication {

public static void main(String[] args) {
SpringApplication.run(DruidApplication.class, args);
}

}

②方式二
   使用Spring的注解@Bean对自定义的Servlet或Filter进行注册,Servlet使用ServletRegistrationBean进行注册,Filter使用FilterRegistrationBean进行注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootConfiguration
public class DruidMonitorConfig {
@Bean
public ServletRegistrationBean servletRegistrationBean() {
System.out.println("init Druid Monitor Servlet ...");
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),
"/druid/*");
// IP白名单
servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
// IP黑名单(共同存在时,deny优先于allow)
servletRegistrationBean.addInitParameter("deny", "192.168.1.10");
// 控制台管理用户
servletRegistrationBean.addInitParameter("loginUsername", "root");
servletRegistrationBean.addInitParameter("loginPassword", "123");
// 是否能够重置数据 禁用HTML页面上的“Reset All”功能
servletRegistrationBean.addInitParameter("resetEnable", "false");
return servletRegistrationBean;
}

@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}

此demo采用的第二种方式(注释启动类的//@ServletComponentScan)。

3. Druid使用log4j2进行日志输出

3.1 pom.xml中springboot版本依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--Spring-boot中去掉logback的依赖-->
<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>

<!--日志-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.6</version>
</dependency>

3.2 log4j2.xml文件中的日志配置(完整,可直接拷贝使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>

<Console name="Console" target="SYSTEM_OUT">
<!--只接受程序中DEBUG级别的日志进行处理-->
<ThresholdFilter level="DEBUG" 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="DEBUG">
<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.3 配置application.yml

1
2
3
4
5
6
7
8
9
10
11
# 配置日志输出
spring:
datasource:
druid:
filter:
slf4j:
enabled=true
statement-create-after-log-enabled=false
statement-close-after-log-enabled=false
result-set-open-after-log-enabled=false
result-set-close-after-log-enabled=false

4. 测试与运行

4.1 相关实体类创建

   相关数据库表、数据创建,以及相关dao,service,controller创建。(本demo采用上一个博客的相关类)
   系统结构目录如下:

4.2 测试

swagger上查询结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"id": 1,
"name": "游强",
"account": "yq",
"password": "admin"
},
{
"id": 2,
"name": "秦音",
"account": "qy",
"password": "admin"
}
]

然后访问:http://localhost:8081/druid/

大概就是下面这样的图:

控制日志记录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2020-06-18 00:11:15.948  INFO 34916 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-06-18 00:11:15.948 INFO 34916 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-06-18 00:11:15.957 INFO 34916 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@68197e8b] was not registered for synchronization because synchronization is not active
2020-06-18 00:11:16.030 INFO 34916 --- [nio-8081-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [org.postgresql.jdbc.PgConnection@5d7d2a16] will not be managed by Spring
==> Preparing: select * from yq_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, name, account, password
<== Row: 1, 游强, yq, admin
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@68197e8b]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f98edcf] was not registered for synchronization because synchronization is not active
2020-06-18 00:11:16.691 INFO 34916 --- [nio-8081-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@12d550e0] will not be managed by Spring
==> Preparing: select * from yq_user where id = ?
==> Parameters: 2(Long)
<== Columns: id, name, account, password
<== Row: 2, 秦音, qy, admin
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f98edcf]

5. druid数据库密码加密

   本demo只对主数据源MySQL进行了密码加密,对从数据源密码步骤类似。

5.1 密码加密

5.1.1 通过druid-1.0.18.jar提供的ConfigTools工具对密码进行加密:

   在druid所在的目录下打开cmd窗口:

1
2
3
4
5
6
7
#格式:java -cp druid.jar com.alibaba.druid.filter.config.ConfigTools you_password
F:\Download>java -cp druid-1.1.9.jar com.alibaba.druid.filter.config.ConfigTools admin
privateKey:MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAwB/rXIuWe5IHbIZOC9+VB4iRuBow0ca3pztVSIYtJjgkCSbWVN4fWzjfeO54DY9GKhkIufCTE/T+Fgci+UjtRQIDAQABAkBh2uAiDubioYoueGmgGozpfWHbB1v+PNylzM6vVcgBQouOqd0T1/tqs1IxCATSt+vvJ5BKsbgRPWO+nJAHqJyBAiEA+M3jVoGlsHA65j1JlqqPuHk7MXNIhwlajNEYq9F2l00CIQDFrmI9XZDZp0wpm6Vy9YGSuqAvLdqcwMh7LY2g3+jh2QIgDIBj5OvcxHHPM9RuhyiI0i8dP03YnhhlOWAkSjXbLJ0CIQCtsscbyMVomrovrVY5p0PNnDL4gcAgEL2YjrRt8ZF+MQIgOjRYMqQxMLKIzS1JBwjxK3X54xVbyLwgm0DNKlrXJ48=
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMAf61yLlnuSB2yGTgvflQeIkbgaMNHGt6c7VUiGLSY4JAkm1lTeH1s433jueA2PRioZCLnwkxP0/hYHIvlI7UUCAwEAAQ==
password:UDwkTJKXPXEIPjlYFnLKNJ5Tq5mWmEV5yLuENS8LqdAsb5QavQoM6jF3gQKgxmTX5GcXz5Ncxw5XknVQyab3kA==

F:\Download>
5.1.2 配置文件配置加密解密参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
datasource:
name: MySQL
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/yq_mysql?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
# 修改密码为密文
password: UDwkTJKXPXEIPjlYFnLKNJ5Tq5mWmEV5yLuENS8LqdAsb5QavQoM6jF3gQKgxmTX5GcXz5Ncxw5XknVQyab3kA==
# 公钥
publickey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMAf61yLlnuSB2yGTgvflQeIkbgaMNHGt6c7VUiGLSY4JAkm1lTeH1s433jueA2PRioZCLnwkxP0/hYHIvlI7UUCAwEAAQ==
# 配置 connection-properties,启用ConfigFilter解密密码,以及配置公钥${publickey}
connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publickey};password=${spring.datasource.password}
druid:
filter:
config:
# 启动ConfigFilter
enabled: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,在原来的filters后面添加config,使用逗号分隔
filters: stat,wall,log4j2,config

   需要用到生成的 publicKey 和 password。而privateKey私钥,用于生成密文密码用,不用管。

5.2 配置ConfigFilter

5.2.1 配置文件从本地文件系统中读取

   SpringBoot默认是从本项目中的soureces目录中读取,故如果是使用本地application.yml文件中的配置参数,下面的代码可以忽略不添加。而且某些配置SpringBoot会自动注入,不需要手动设置。

1
2
3
4
5
6
7
8
9
10
11
try {
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/application.yml"));
datasource.setName(properties.getProperty("name"));
datasource.setUsername(properties.getProperty("username"));
datasource.setPassword(properties.getProperty("password"));
datasource.setDriverClassName(properties.getProperty("driverClassName"));
datasource.setUrl(properties.getProperty("url"));
} catch (IOException e) {
e.printStackTrace();
}
5.2.2 配置文件从远程http服务器中读取
1
2
3
4
5
6
7
8
9
10
11
try {
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("http://127.0.0.1/application.yml"));
datasource.setName(properties.getProperty("name"));
datasource.setUsername(properties.getProperty("username"));
datasource.setPassword(properties.getProperty("password"));
datasource.setDriverClassName(properties.getProperty("driverClassName"));
datasource.setUrl(properties.getProperty("url"));
} catch (IOException e) {
e.printStackTrace();
}

   这种配置方式,使得一个应用集群中,多个实例可以从同一个地方读取配置,集中配置,集中修改,部署更简单。

5.2.3 通过jvm启动参数来使用ConfigFilter

   DruidDataSource支持jvm启动参数配置filters,所以可以:

1
java -Ddruid.filters=./config/application.yml

5.3 手动密码加密解密

   5.1中的加密是通过外部cmd命令生成的密码,5.2是使用druid封装好的解密工具对密文进行解密。但其实也可以通过自定义的代码来实现加密解密。
   其实druid的加密解密都是通过ConfigToolsTest.java文件中的encrypt和decrypt方法来实现的。故只要单独调用这两个方法,即可实现自定义加密解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws Exception {
String password = "admin";
//使用ConfigTools生成一组公私钥
String[] keyPair = ConfigTools.genKeyPair(512);
//获取公钥
System.out.println("privateKey:" + keyPair[0]);
//获取私钥
System.out.println("publicKey:" + keyPair[1]);
//通过公钥+密码明文,对密码进行加密
System.out.println("password:" + ConfigTools.encrypt(keyPair[0], password));

//然后通过上面得到的密码密文+公钥,解密密码获得明文
String pwd = "n2giOipoUromAsrEUFApiG15LOKwvyp+5tumwlnQz2NiMs/vtqmkzkAb5cQmACqIKTEFhBBu4GuR/ggtdbYxPw==";
String pub = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANNhfjPiJkaOd07kp0F9ut92rwMWNO6zRkHHCBNiGpIPr6OKG9OvDcnsTyQ4d9hVINIIRwd+NQ4NF76AgijZ7r0CAwEAAQ==";
String decrypt = ConfigTools.decrypt(pub, pwd);
System.out.println(decrypt);
}

   可以将手动生成的密码密文与公钥写入配置文件,然后在创建dataSource这个bean的时候,手动解密写入dataSource中。

5.4 相关问题记录

5.4.1 配置 connection-properties 失误

druid官网上的配置是:

1
connection-properties: config.decrypt=true;config.decrypt.key=${publickey}

但这样配置会报下面的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2020-06-18 14:41:39.925 ERROR 6388 --- [nio-8081-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException: Failed to decrypt.
### The error may exist in file [D:\work\code\qinyin\druid\target\classes\mapper\MysqlUserMapper.xml]
### The error may involve com.example.druid.mapper.mysqlMapper.MysqlUserMapper.getOne
### The error occurred while executing a query
### Cause: java.lang.IllegalArgumentException: Failed to decrypt.] with root cause

java.lang.IllegalArgumentException: Illegal character $
at com.alibaba.druid.util.Base64.base64toInt(Base64.java:174) ~[druid-1.1.9.jar:1.1.9]
at com.alibaba.druid.util.Base64.base64ToByteArray(Base64.java:140) ~[druid-1.1.9.jar:1.1.9]
at com.alibaba.druid.util.Base64.base64ToByteArray(Base64.java:107) ~[druid-1.1.9.jar:1.1.9]
at com.alibaba.druid.filter.config.ConfigTools.getPublicKey(ConfigTools.java:94) ~[druid-1.1.9.jar:1.1.9]
at com.alibaba.druid.filter.config.ConfigFilter.getPublicKey(ConfigFilter.java:223) ~[druid-1.1.9.jar:1.1.9]
at com.alibaba.druid.filter.config.ConfigFilter.decrypt(ConfigFilter.java:195) ~[druid-1.1.9.jar:1.1.9]

   目前不清楚是自己跟官网上的其他配置有出入还是官网错误,暂时没找到原因。但我使用下面的配置,就可以正常访问或查询数据:

1
connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publickey};password=${spring.datasource.password}
5.4.2 配置不写入配置文件,而是写入代码中

   如果connection-properties: config.decrypt=true;config.decrypt.key=${spring.datasource.publickey};password=${spring.datasource.password}这个配置不写入配置文件,而是在代码中体现:

1
2
3
4
5
6
7
8
9
try {
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/application.yml"));
//config.decrypt=true;config.decrypt.key=${spring.datasource.publickey};password=${spring.datasource.password}
String connectionProperties = "config.decrypt=true;" + "config.decrypt.key=" + publickey + ";" + "password=" + password;
datasource.setConnectionProperties(connectionProperties);
} catch (IOException e) {
e.printStackTrace();
}

会报下面的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2020-06-18 14:38:14.990 ERROR 24840 --- [reate-248705782] com.alibaba.druid.pool.DruidDataSource   : create connection SQLException, url: jdbc:mysql://localhost:3306/yq_mysql?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false, errorCode 1045, state 28000

java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3978) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:871) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.MysqlIO.proceedHandshakeWithPluggableAuthentication(MysqlIO.java:1714) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1224) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.ConnectionImpl.coreConnect(ConnectionImpl.java:2199) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:2230) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2025) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:778) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:47) ~[mysql-connector-java-5.1.47.jar:5.1.47]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_191]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_191]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_191]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_191]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.47.jar:5.1.47]

6. 问题记录

如果在项目中使用的是log4j而不是log4j2,那么可能会出现如下警告:

6.1 log4j警告

在项目运行过程中,有log4j配置缺失警告:

1
2
log4j:WARN No appenders could be found for logger (druid.sql.Connection).
log4j:WARN Please initialize the log4j system properly.

   该警告不影响项目正常运行,但如果想要消除警告,有多种方式:
   ①手动在resources目录下创建log4j.properties配置文件来指定相关log4j参数(网上找的答案,但手动操作过,发现没有效果。也有说是因为SpringBoot中含有logback这个依赖,需要将该依赖解除,自定义的log4j.properties配置才会生效。但也亲自操作过,也没有效果,可能是操作过程中有失误)。
   ②强制在启动类中设置日志缺失环境

1
2
3
4
public static void main(String[] args) {
BasicConfigurator.configure(); //自动快速地使用缺省Log4j环境
SpringApplication.run(DruidApplication.class, args);
}

   虽然这种方式解决log4j的警告,但个人感觉日志显示的级别过于详细。且只对主数库MySQL进行详细记录,但对PostgreSQL只是简单的记录。对该日志级别设置暂时没找到方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f91ab56] was not registered for synchronization because synchronization is not active
2020-06-17 22:08:06.988 INFO 32188 --- [nio-8081-exec-6] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [org.postgresql.jdbc.PgConnection@136e7f5c] will not be managed by Spring
==> Preparing: select * from yq_user where id = ?
==> Parameters: 1(Long)
<== Columns: id, name, account, password
<== Row: 1, 游强, yq, admin
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f91ab56]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1428c0e1] was not registered for synchronization because synchronization is not active
0 [http-nio-8081-exec-6] DEBUG druid.sql.Connection - {conn-110001} connected
7 [http-nio-8081-exec-6] DEBUG druid.sql.Connection - {conn-110002} connected
13 [http-nio-8081-exec-6] DEBUG druid.sql.Connection - {conn-110003} connected
2020-06-17 22:08:07.569 INFO 32188 --- [nio-8081-exec-6] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
15 [http-nio-8081-exec-6] DEBUG druid.sql.Connection - {conn-110003} pool-connect
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2713ea39] will not be managed by Spring
==> Preparing: select * from yq_user where id = ?
134 [http-nio-8081-exec-6] DEBUG druid.sql.Statement - {conn-110003, pstmt-120000} created. select * from yq_user where id = ?
==> Parameters: 2(Long)
143 [http-nio-8081-exec-6] DEBUG druid.sql.Statement - {conn-110003, pstmt-120000} Parameters : [2]
143 [http-nio-8081-exec-6] DEBUG druid.sql.Statement - {conn-110003, pstmt-120000} Types : [BIGINT]
143 [http-nio-8081-exec-6] DEBUG druid.sql.Statement - {conn-110003, pstmt-120000} executed. 5.6849 millis. select * from yq_user where id = ?
148 [http-nio-8081-exec-6] DEBUG druid.sql.ResultSet - {conn-110003, pstmt-120000, rs-150000} open
149 [http-nio-8081-exec-6] DEBUG druid.sql.ResultSet - {conn-110003, pstmt-120000, rs-150000} Header: [id, name, account, password]
149 [http-nio-8081-exec-6] DEBUG druid.sql.ResultSet - {conn-110003, pstmt-120000, rs-150000} Result: [2, 秦音, qy, admin]
<== Columns: id, name, account, password
<== Row: 2, 秦音, qy, admin
<== Total: 1
150 [http-nio-8081-exec-6] DEBUG druid.sql.ResultSet - {conn-110003, pstmt-120000, rs-150000} closed
150 [http-nio-8081-exec-6] DEBUG druid.sql.Statement - {conn-110003, pstmt-120000} clearParameters.
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1428c0e1]
151 [http-nio-8081-exec-6] DEBUG druid.sql.Connection - {conn-110003} pool-recycle
900023 [Druid-ConnectionPool-Destroy-486994287] DEBUG druid.sql.Connection - {conn-110001} closed
900024 [Druid-ConnectionPool-Destroy-486994287] DEBUG druid.sql.Connection - {conn-110002} closed
960025 [Druid-ConnectionPool-Destroy-486994287] DEBUG druid.sql.Connection - {conn-110003} closed

6.2 log4j日志配置文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#此句为定义名为stdout的输出端是哪种类型,可以是
#org.apache.log4j.ConsoleAppender(控制台),
#org.apache.log4j.FileAppender(文件),
#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),
#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
#此句为定义名为stdout的输出端的layout是哪种类型,可以是
#org.apache.log4j.HTMLLayout(以HTML表格形式布局),
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#如果使用pattern布局就要指定的打印信息的具体格式ConversionPattern,打印参数如下:
#%m 输出代码中指定的消息
#%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
#%r 输出自应用启动到输出该log信息耗费的毫秒数
#%c 输出所属的类目,通常就是所在类的全名
#%t 输出产生该日志事件的线程名
#%n 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n”
#%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
#%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。
#[QC]是log信息的开头,可以为任意字符,一般为项目简称。
#比如:输出的信息:#[TS] DEBUG [main] AbstractBeanFactory.getBean(189) | Returning cached instance of singleton bean 'MyAutoProxy'
log4j.appender.stdout.layout.ConversionPattern= [QC] %p [%t] %C.%M(%L) | %m%n

#定义名为R的输出端的类型为每天产生一个日志文件。
log4j.appender.R=org.apache.log4j.RollingFileAppender
#此句为定义名为R的输出端的文件名为D:\\Tomcat 5.5\\logs\\qc.log可以自行修改。
log4j.appender.R.File=D:/work/code/qinyin/druid/src/main/resources/druid.log
#log4j.appender.R.File=${catalina.home}/logs/ddoMsg.log
log4j.appender.R.MaxFileSize=1024KB
log4j.appender.R.MaxBackupIndex=100
#与log4j.appender.stdout.layout属性相同
log4j.appender.R.layout=org.apache.log4j.PatternLayout
#与log4j.appender.stdout.layout.ConversionPattern属性相同
log4j.appender.R.layout.ConversionPattern= %d{yyyy-MM-dd HH:mm:ss} %5p %c %t: - %m%n

#INFO WARN ERROR DEBUG
#此句为将等级为INFO的日志信息输出到stdout和R这两个目的地,stdout和R的定义在下面的代码,可以任意起名。(此文本设置的是:stdout为控制台输出,R为文本输出)
# 等级可分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL,如果配置OFF则不打出任何信息,
# #如果配置为INFO这样只显示INFO, WARN, ERROR的log信息,而DEBUG信息不会被显示,
log4j.rootLogger=OFF, stdout, R
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog

#指定com.example.druid包下的所有类的等级为DEBUG。
log4j.logger.com.example.druid =OFF
#这两句是把这两个包下出现的错误的等级设为ERROR,如果项目中没有配置EHCache,则不需要这两句。
log4j.logger.com.opensymphony.oscache=OFF
log4j.logger.net.sf.navigator=OFF
#这句是displaytag的包。(QC问题列表页面所用)
log4j.logger.org.displaytag=OFF
#此句为Spring的包。
log4j.logger.org.springframework=OFF
#此两句是hibernate的包。
log4j.logger.org.hibernate.ps.PreparedStatementCache=OFF
log4j.logger.org.hibernate=OFF

7. 知识扩展

7.1 线程池介绍

   Java中已经提供了创建线程池的一个类:Executor,而我们创建时,一般使用它的子类:ThreadPoolExecutor

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,  
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

   这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:

   从图中,可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收;
   maximumPoolSize就是线程池中可以容纳的最大线程的数量;
   keepAliveTime就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间;
   util就是计算这个时间的一个单位;
   workQueue就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出);
   threadFactory就是创建线程的线程工厂;
   handler是一种拒绝策略,我们可以在任务满了知乎,拒绝执行某些任务。

线程池的执行流程又是怎样的呢?

   从图中可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

handler的拒绝策略:
   第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
   第二种DisCardPolicy:不执行新任务,也不抛出异常
   第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
   第四种CallerRunsPolicy:直接调用execute来执行当前任务

四种常见的线程池:
   CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
   SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
   SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
   FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

参考链接:
Springboot+Druid搭建简单demo
如何在Spring Boot中配置数据库密码加密?

SpringBoot整合MyBatis多数据源

发表于 2020-06-14 | 分类于 SpringBoot

1.项目创建

  创建一个Spring Boot 项目工程,并且添加Mybatis、web以及相关数据源依赖(此处只选择了Mysql依赖):

  或者在现有项目中,添加相关依赖,比如:mysql依赖以及postgresql依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.6</version>
</dependency>

<!--连接mysql所需jar-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

  然后添加数据库连接池Druid依赖,此处整合JdbcTemplate时也是要添加Druid依赖,但是要添加Spring Boot打造的Druid,不能使用传统的Druid。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2.双数据源配置

  设置双数据源配置,在项目中application.yml中配置数据库基本信息,然后提供两个DataSource即可。application.yml中的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
spring:
#主数据源,即默认的数据源,SpringBoot在启动时会加载默认数据源
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/yq_pgsql?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: postgres
password: admin
#从数据源
mysqldatasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/yq_mysql?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: admin
jpa:
properties.hibernate.hbm2ddl.auto: update
properties.hibernate.dialect: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
#设置mybatis扫描包路径
mybatis:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*
mybatis-plus:
mapper-locations: classpath*:mapper/*
#设置日志显示配置,即显示sql查询语句与相关参数在控制台上
logging:
level:
com.example.qinyin.datasource.mysqlMapper : debug
com.example.qinyin.datasource.pgsqlMapper : debug

  之后创建两个DataSource的提供来源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

/**
* 设置数据源1
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}

/**
* 设置数据源2
*/
@Bean
@ConfigurationProperties(prefix = "spring.mysqldatasource")
DataSource mysqlDataSource() {
return DruidDataSourceBuilder.create().build();
}
}

3.MyBatis配置

  对于Mybatis的配置,相对于JdbcTemplate的配置是有那么麻烦一点,因为要根据不同的数据源设置不同的SqlSessionFactory和SqlSessionTemplate这两个bean,故每个数据源都要单独配置。

3.1 配置第一个数据MyBatis配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import javax.annotation.Resource;

/**
* 指明该类是一个配置类,配置类中要扫描的包是com.example.qinyin.datasource.pgsqlMapper,即该包下的Mapper接口将操作dataSource中的数据;
* 对应的SqlSessionFactory和SqlSessionTemplate分别是sqlSessionFactory()和sqlSessionTemplate()返回的bean,
* 在MyBatisConfigPgSql内部,分别对外提供SqlSessionFactory和SqlSessionTemplate两个bean即可,SqlSessionFactory根据dataSource创建,
* 然后再根据创建好的SqlSessionFactory创建一个SqlSessionTemplate。
*/
@Configuration
@MapperScan(basePackages = "com.example.qinyin.datasource.pgsqlMapper",
sqlSessionFactoryRef = "sqlSessionFactory",
sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfigPgSql {

//注入DataSourceConfig中的数据源1指定的dataSource配置
@Resource(name = "dataSource")
DataSource dataSource;

@Value("${mybatis.mapper-locations}")
private String mapperLocations;

@Value("${mybatis.type-aliases-package}")
private String typeAliasesPackage;

@Bean
SqlSessionFactory sqlSessionFactory() {
SqlSessionFactory sessionFactory = null;
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
try {
//这里需要设置扫描mapper的路径,否则容易导致mapper中的方法无法映射到xml中去,导致报:Invalid bound statement (not found) 错误
//当Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//通过读取配置文件中的值来指定扫描路径
bean.setMapperLocations(resolver.getResources(mapperLocations));
//直接写死值来指定扫描路径
//bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
// 设置typeAlias(别名) 包扫描路径。可配置可不配置(猜想:可能不同的数据源查询返回的实体类字段有差异,故指定不同的别名包来映射不同的查询结果)
// bean.setTypeAliasesPackage(typeAliasesPackage);

//开启驼峰式映射功能
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);
bean.setConfiguration(configuration);

sessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sessionFactory;
}

@Bean
SqlSessionTemplate sqlSessionTemplate() {
return new SqlSessionTemplate(sqlSessionFactory());
}
}

在定义SqlSessionFactoryBean的时候,dataSource属性是必须指定的,它表示用于连接数据库的数据源。当然,也可以指定一些其他的属性,下面简单列举几个:
(1)mapperLocations:它表示我们的Mapper文件存放的位置,当我们的Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。
(2)configLocation:用于指定Mybatis的配置文件位置。如果指定了该属性,那么会以该配置文件的内容作为配置信息构建对应的SqlSessionFactoryBuilder,但是后续属性指定的内容会覆盖该配置文件里面指定的对应内容。
(3)typeAliasesPackage:它一般对应我们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名作为包括包名的别名。多个package之间可以用逗号或者分号等来进行分隔。(value的值一定要是包的全名)
(4)typeAliases:数组类型,用来指定别名的。指定了这个属性后,Mybatis会把这个类型的短名称作为这个类型的别名,前提是该类上没有标注@Alias注解,否则将使用该注解对应的值作为此种类型的别名。(value的值一定要是类的完全限定名)

1
2
3
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);

设置驼峰式命名映射规则,在单数据源的时候,可以不用设置。SqlSessionFactoryBean会自动读取appliaction.yml配置文件中的参数(如下代码)来设置。但如果有多个数据源时,SqlSessionFactoryBean默认都会主数据源设置驼峰映射规则,而从数据源无法映射。故需要手动给从数据源设置(但建议每个数据源都设置上)。否则会导致某个数据源结果映射正常,但另一个数据源结果映射失败。

1
2
3
4
5
6
7
8
9
mybatis:
#扫描mapper包路径
mapper-locations: classpath:mapper/*
#别名包路径
type-aliases-package: com.example.druid.entity
configuration:
#是否开启驼峰式命名映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2 配置第二个数据MyBatis配置:

  与第一个数据源的配置方式类似,配置第二个数据源配置,只需要修改启动的相关mapper路径与DataSource来源即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.example.qinyin.datasource.mysqlMapper",
sqlSessionFactoryRef = "sqlSessionFactoryMySql",
sqlSessionTemplateRef = "sqlSessionTemplateMySql")
public class MybatisConfigMySql {

//注入DataSourceConfig中的数据源2指定的dataSource配置
@Resource(name = "mysqlDataSource")
DataSource mysqlDataSource;

@Value("${mybatis.mapper-locations}")
private String mapperLocations;

@Value("${mybatis.type-aliases-package}")
private String typeAliasesPackage;

@Bean
SqlSessionFactory sqlSessionFactoryMySql() {
SqlSessionFactory sessionFactory = null;
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(mysqlDataSource);
try {
//这里需要设置扫描mapper的路径,否则容易导致mapper中的方法无法映射到xml中去,导致报:Invalid bound statement (not found) 错误
//当Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//通过读取配置文件中的值来指定扫描路径
bean.setMapperLocations(resolver.getResources(mapperLocations));
//直接写死值来指定扫描路径
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
// 设置typeAlias(别名) 包扫描路径。可配置可不配置(猜想:可能不同的数据源查询返回的实体类字段有差异,故指定不同的别名包来映射不同的查询结果)
// bean.setTypeAliasesPackage(typeAliasesPackage);

//开启驼峰式映射功能
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);
bean.setConfiguration(configuration);
sessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sessionFactory;
}

@Bean
SqlSessionTemplate sqlSessionTemplateMySql() {
return new SqlSessionTemplate(sqlSessionFactoryMySql());
}
}

4.实体类创建

4.1 创建数据源1 PostgreSql的表结构与相关实体类

数据库表创建并添加值:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE "public"."yq_user" (
"id" int8 NOT NULL DEFAULT nextval('yq_user_id_seq'::regclass),
"name" varchar(255) COLLATE "pg_catalog"."default",
"account" varchar(255) COLLATE "pg_catalog"."default",
"password" varchar(255) COLLATE "pg_catalog"."default",
CONSTRAINT "yq_user_pkey" PRIMARY KEY ("id")
)
;

ALTER TABLE "public"."yq_user"
OWNER TO "postgres";

INSERT INTO "public"."yq_user"("id", "name", "account", "password") VALUES (1, '游强', 'yq', 'admin');

数据库内容:

id name account password
1 游强 yq admin

相关实体类User,service,mapper等自行创建。

4.2 创建数据源2 MySql的表结构与相关实体类

数据库表创建并添加值:

1
2
3
4
5
6
7
8
9
CREATE TABLE `yq_user` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`account` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO `yq_mysql`.`yq_user`(`id`, `name`, `account`, `password`) VALUES (2, '秦音', 'qy', 'admin');

数据库内容:

id name account password
2 秦音 qy admin

相关实体类User,service,mapper等自行创建。
两个数据相关配置创建成功后,结构如下图:

  本项目只是一个demo,故没有service层,且没有将两个数据源的实体类User没有区分开,都共用一个User(因为两个数据源的查询结构返回值都是一样的字段属性),用户可根据业务情况自行扩展。
当个数据源的mapper文件需要分开放置在不同的包下,因为在配置数据源时,会指定当前数据源去扫描那一个mapper,故需要将不同数据源的mapper文件放在不同的包下,但mapper对应的xml文件不受该限制。

5.测试效果

在同一个controller中引用两个不同数据源的mapper,来查询数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Slf4j
@RestController()
@RequestMapping("/user")
@Api(description = "用户")
public class UserController {

//数据源1
@Autowired
private PgsqlUserMapper pgsqlUserMapper;

//数据源2
@Autowired
private MysqlUserMapper mysqlUserMapper;

@ApiOperation("获取用户")
@GetMapping("/getUser")
public List<User> receiveTask() {
User userpg = pgsqlUserMapper.getOne(1L);
User usermy = mysqlUserMapper.getOne(2L);
return Arrays.asList(userpg, usermy);
}

}

查询结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"id": 1,
"name": "游强",
"account": "yq",
"password": "admin"
},
{
"id": 2,
"name": "秦音",
"account": "qy",
"password": "admin"
}
]

6.遇到的问题

6.1 启动报错

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.qinyin.datasource.pgsqlMapper.getOne

即mapper中的接口方法没有找到其实现方法,即没有映射到xml文件中去。
原因:在启动的时候,Spring Boot默认把PgsqlUserMapper和MysqlUserMapper中的xml文件过滤掉了。
解决办法:
第一种:硬办法,将xml文件中的查询语句写在mapper文件中,采用@Sercelt(“”) 、@Insert(“”)等方法来操作;
第二种:让SpringBoot不过滤相关xml文件
在pom文件中build节点中添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

同时,在数据源配置类中(比如:MybatisConfigPgSql)创建sqlSessionFactory时,添加配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 这里需要设置扫描mapper的路径,否则容易导致mapper中的方法无法映射到xml中去,导致报:Invalid bound statement (not found) 错误
* 注意sqlSessionFactoryBean.setTypeAliasesPackage参数不支持通配符*,如果有多个包可以通过","等分割
* 如果需要加载依赖传递过来的jar包中的mapper目录下的xml,classpath:mapper/*.xml 修改为classpath*:mapper/*.xml
*/
//这里需要设置扫描mapper的路径,否则容易导致mapper中的方法无法映射到xml中去,导致报:Invalid bound statement (not found) 错误
//当Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//通过读取配置文件中的值来指定扫描路径
bean.setMapperLocations(resolver.getResources(mapperLocations));
//直接写死值来指定扫描路径
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
// 设置typeAlias(别名) 包扫描路径。可配置可不配置(猜想:可能不同的数据源查询返回的实体类字段有差异,故指定不同的别名包来映射不同的查询结果)
// bean.setTypeAliasesPackage(typeAliasesPackage);

然后把项目中的target删掉,重新启动。

6.2 项目启动时,数据库连接警告

Sun Aug 05 21:18:18 CST 2018 WARN: Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

警告显示:不建议在没有服务器身份验证的情况下建立SSL连接,需要通过设置useSSL=false显式禁用SSL
解决办法:在application.yml文件的数据源url中添加useSSL=false

1
2
3
4
5
mysqldatasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/yq_mysql?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: admin

出现下面的错误,在后面加上“?serverTimezone=GMT%2B8”(在数据库的连接url上设置),设置下时区即可。

参考链接:
Druid 多数据源支持.md

MySQL与PostgreSQL安装

发表于 2020-06-13 | 分类于 SQL

mysql安装

此博客只是记录在win10系统上安装MySql流程。

1.mysql下载

第一种方式,去数据库的官网http://www.mysql.com下载MySQL。
点击进入后的首页如下:

然后点击downloads,community,选择MySQL Community Server。如下图:

滑到下面,找到Recommended Download,然后点击go to download page。如下图:

点击download进入下载页面选择No thanks, just start my download就可以开始下载了,当然你要是想注册也可以注册一下。

第二种方式,也可以直接点击https://dev.mysql.com/downloads/mysql/ 进去下载对应系统的MySql版本信息
选择需要下载的对应系统版本的mysql:

同样可以选择只下载不注册:

2.MySQL安装

打开刚刚下载好的安装包,开始安装MySQL。安装大部分情况都选择默认设置,但也有一些位置根据自己实际情况来定:
选择 I accept 然后点击next进入下一步。

这里选择Developer Default,然后点击next进入下一步。

此处可以修改MySQL的安装目录,也可以使用默认路径。

这一步是检查安装条件,直接点击next进入下一步就可以了。

会提示电脑是否含有MySQL的依赖插件,会提示是否进行安装:

这里直接点击execute执行就可以了,执行完后点击next进入下一步。

一直等待全部依赖安装完成

继续点击next进入下一步

选择第一个然后点击next进入下一步。

这里直接点击next进入下一步

这里直接点击next进入下一步

设置root密码然后点击next进入下一步

设置MySQL的服务名称:

然后等待安装

全部安装完成后点击点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

继续点击next进入下一步

点击Finish完成MySQL的安装

安装完成后进入MySQL的安装目录,进入MySQL Sever,其目录下的文件如下:

bin目录下保存了MySQL常用的命令工具以及管理工具
data目录是MySQL默认用来保存数据文件以及日志文件的地方(我的因刚安装还没有data文件夹)
docs目录下是MySQL的帮助文档
include目录和lib目录是MySQL所依赖的头文件以及库文件
share目录下保存目录文件以及日志文件。

mysql的shell控制端:C:\Program Files\MySQL\MySQL Shell 8.0\bin\mysqlsh.exe或者C:\ProgramData\Microsoft\Windows\Start Menu\Programs\MySQL\MySQL Shell
MySQL Shell操作参考链接:https://www.cnblogs.com/zengkefu/p/5667037.html
mysql的Workbench端位置:MySQL Workbench 8.0 CE
mysql服务链接客户端:MySQL 8.0 Command Line Client
Mysql服务启动快捷键:MySQL Notifier 1.1.8

参考链接:
MySQL-Installer-community-8.0.13.0 社区版安装解决方案

3.MySQL安装成功与否测试

进入bin目录,按住shift键然后点击鼠标右键可以选择在该目录下打开命令窗口,或者在地址栏中输入cmd进入命令窗口。输入1mysql -u root -p1后回车,然后会提示输入密码,输入密码后就会进入MySQL的操作管理界面。
输入show databases;(注意末尾有分号)可以查看当前MySQL中的数据库列表,
输入use test;可以进入test数据库(前提是要有此数据库),
输入show tables可以查看test数据库中的所有表,
输入quit可以退出MySQL的操作管理界面。

或者使用mysql的Workbench端MySQL Workbench 8.0 CE来连接MySQL服务,看是否能连接上:

又或者使用navicat Prenmium工具测试创建MySQL连接是否成功

4.MySQL在使用过程中遇到的问题

在使用Navicat Premium 12连接MySQL数据库时会出现Authentication plugin ‘caching_sha2_password’ cannot be loaded的错误。
出现这个原因是mysql8 之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是caching_sha2_password, 解决问题方法有两种,一种是升级navicat驱动,一种是把mysql用户登录密码加密规则还原成mysql_native_password.
这里用第二种方式 ,解决方法如下:
①在mysql的bin目录打开cmd窗口,运行:mysql -u root -p 命令,会提示输入密码。

②修改账户密码加密规则并更新用户密码

1
2
3
4
#更新一下用户的密码 
ALTER USER 'root'@'localhost' IDENTIFIED BY 'admin' PASSWORD EXPIRE NEVER; 
#修改加密规则 
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'admin';

③刷新权限并重置密码
FLUSH PRIVILEGES; #刷新权限
现在再次打开Navicat Premium 12连接MySQL问题数据库就会发现可以连接成功了
(上面三个步骤经操作有效,下面的未经测试,效果未知。参考链接https://www.cnblogs.com/zhurong/p/9898675.html)

1
2
3
4
5
6
#单独重置密码命令:
alter user 'root'@'localhost' identified by '111111'; 
#给root用户设置新密码:
mysql> update user set password=password("123456") where user="root";
#刷新数据库
mysql> flush privileges;

但update user set password=password("123456") where user="root";这个对不是5.7版本有效,5.7版本得要update mysql.user set authentication_string=password('*******') where user='*******'

5.MySQLworkbench使用教程

5.1 创建数据库

可以使用工具栏上的创建数据库直接创建数据库,如下图:

或者使用命令:create database 数据库名称;(注意最后有分号)来创建。注意,此方法可能不会立马刷新,可以在左下角点击右键选择Refresh all 来刷新。

5.2 创建表、删除表

可以在你想要创建表的Tables上面点击右键,选择Create Table,或者使用命令create table table_name(column_name column_type);

注意,创建数据表的命令前需要使用use指明你要使用的数据库。(可以在你将要使用的数据库上面点击右键选择Set as Default Schema来使你将要用的数据库变为默认的,这样可以不用写use语句)
通过命令drop table table_name;来删除一张表。

5.3 修改表的名字

通过使用命令:

1
2
3
alter table old_name rename to new_name
#或
rename table old_name to new_name

来更改数据表名。

5.4 为数据表增加、修改、删除字段
1
2
3
4
5
6
#语句来为数据表增加字段。
alter table table_name add column_name column_type;
#语句来修改数据表字段名称。
alter table table_name change column_name new_column_name new_column_name_type;
#删除数据表字段。
alter table table_name drop column_name;

5.5 关于修改数据库名字

在低版本的MySQL中提供了一个命令rename来修改数据库的名称(注意不是数据库的表),不过这个命令会造成数据的丢失,所以这个命令已经被删掉,现在如果想要重命名一个数据库名称的话,需要先导出原有数据库中的所有数据,然后新建一个数据库,然后把导出的数据导入到新的数据库中,最后删掉原来的数据库。整个过程比较繁琐,数据量大的时候会非常浪费时间,所以不推荐修改数据库的名字,因此在给数据库命名的时候要考虑好应该给数据库起一个合理的名字。

最后,附上MySQL Workbench汉化解决方案链接:https://blog.csdn.net/weixin_40845165/article/details/84105822,感兴趣的可以自行配置。

PostgreSql安装

pgsql 9.4.26版本的可以在本机win10 正常安装
12.3-1版本会报error running icacls异常。(网上说postgreSQL9需要vc2013;postgreSQL10需要vc2017,但真实性不确定)

1.PostgreSql的获取与安装

获取安装包:https://www.enterprisedb.com/downloads/postgres-postgresql-downloads

点击下载的安装包开始安装

选择程序安装目录

选择数据存放目录

输入数据库超级用户和创建的OS用户的密码

注:数据库超级用户是一个非管理员账户,这是为了减少黑客利用在 PostgreSQL 发现的缺陷对系统造成损害,因此需要对数据库超级用户设置密码。

设置服务监听端口,默认为5432

选择运行时语言环境

注:选择数据库存储区域的运行时语言环境(字符编码格式)。
在选择语言环境时,若选择”default locale”会导致安装不正确;同时,PostgreSQL 不支持 GBK 和 GB18030 作为字符集,如果选择其它四个中文字符集:中文繁体 香港(Chinese[Traditional], Hong Kong S.A.R.)、中文简体 新加坡(Chinese[Simplified], Singapore)、中文繁体 台湾(Chinese[Traditional], Taiwan)和中文繁体 澳门(Chinese[Traditional], Marco S.A.R.),会导致查询结果和排序效果不正确。建议选择”C”,即不使用区域。

安装过程


安装完成

安装完成后,从开始文件夹可以看到

安装目录可以看到

其中:data存放数据文件、日志文件、控制文件、配置文件等。uninstall-postgresql.exe用于卸载已安装的数据库管理系统。
安装参考链接:https://blog.csdn.net/u012325865/article/details/81951916

2.PostgreSql在使用过程中遇到的问题

2.1 无法连接服务器

进行安装,安装完成后,在运行bin目录下的psql.exe -U postgres 命令后显示:

其实产生这样的原因很简单,那是因为没有连接服务器—–psqlgres并没有启动,导致没有服务可连。接下来是启动的步骤:
①.在电脑左下角处搜索—-控制面板
②.右键桌面上的我的电脑—-选择管理
③.进入管理工具找到其中的—服务,并双击进入
④.在服务中找到postgresql提供的服务,查看postgresql的服务是否启动,没有启动就启动该服务。如果没有postgresql服务选项,说明postgresql没有注册到服务目录中,将postgresql注册到服务列表中的步骤在文章后面介绍。

⑤. 最后测试一下,是否已经启动(解决问题)。

2.2 安装postgresql后找不到服务 postgresql service

使用postgresql-9.4.26-1-windows-x64.exe安装后,让电脑重新启动,但是重启后,依然没有在服务中找到pstgresql的服务,也就没有办法启动服务。并且查看pgsql的安装目录,data也是空的(或者不是空的)。因此需要自己注册服务,cd 到pgsql的安装目录的bin目录下,我的是:D:\work\study\postgresql\9.4.26\bin
之后运行以下命令:

1
pg_ctl.exe register -N PostgreSQL -D D:\work\study\postgresql\9.4.26\data\

每个版本的这个命令的格式可能有差别,可以使用pg_ctl.exe --help来查看语法格式。注意-D 指定data的目录。之后就注册承重。

启动

1
.\pg_ctl.exe -D ..\data\ -l D:\work\study\postgresql\9.4.26\log\logfile.LOG start

这样日志就在D:\work\study\postgresql\9.4.26\log\logfile.LOG(该日志目录并不是在安装过程自动创建的,需要手动创建,或者指定其他目录文件)中生成,并且也启动了服务。
参考链接:https://www.cnblogs.com/heenhui2016/p/11613423.html

123>
YouQiang

YouQiang

技术总结

25 日志
18 分类
35 标签
GitHub
© 2020 YouQiang