学习 准备 尝试 谨慎小心

0%

原理

DynamicDataSource

DynamicDataSource

image-20191128204335091

接口 DataSource 向外提供一个 getConnection() 方法,用来获取数据库连接,AbstractRoutingDataSource 实现了 getConnection() 方法

1
// line 166
2
@Override
3
public Connection getConnection() throws SQLException {
4
	return determineTargetDataSource().getConnection();
5
}
6
7
... 省略若干代码 
8
       
9
   // line 190
10
   /**
11
 * Retrieve the current target DataSource. Determines the
12
 * {@link #determineCurrentLookupKey() current lookup key}, performs
13
 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
14
 * falls back to the specified
15
 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
16
 * @see #determineCurrentLookupKey()
17
 */
18
protected DataSource determineTargetDataSource() {
19
	Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
20
	Object lookupKey = determineCurrentLookupKey();
21
	DataSource dataSource = this.resolvedDataSources.get(lookupKey);
22
	if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
23
		dataSource = this.resolvedDefaultDataSource;
24
	}
25
	if (dataSource == null) {
26
		throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
27
	}
28
	return dataSource;
29
}
30
31
/**
32
 * Determine the current lookup key. This will typically be
33
 * implemented to check a thread-bound transaction context.
34
 * <p>Allows for arbitrary keys. The returned key needs
35
 * to match the stored lookup key type, as resolved by the
36
 * {@link #resolveSpecifiedLookupKey} method.
37
 */
38
@Nullable
39
protected abstract Object determineCurrentLookupKey();

然而 ….

AbstractRoutingDataSource 的getConnection() 方法只是调用了 determinTargetDataSource().getConnection() 来获取真正DataSource的getConnection()。

image-20191128203413052

这是典型的装饰模式!!自己没有的功能通过引入其他类来增强。

我们先来看看 AbstractRoutingDataSource 的类结构

image-20191128204258331

被框框套住的都是重要的。

方法determineCurrentLookupKey() 是留给我们开发者的(就像你家的网线口),我们通过实现该方法在不同数据源之间切换。

实践

1. 配置多数据源

在 application.yml 如下配置

1
spring:
2
  datasource:
3
    # 数据源类型
4
    type: com.alibaba.druid.pool.DruidDataSource
5
    # 默认数据源
6
    default-datasource:
7
      driver-class-name: com.mysql.cj.jdbc.Driver
8
      url: jdbc:mysql://localhost:3306/db0?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8
9
      username: root
10
      password: 123456
11
12
    # 多数据源
13
    target-datasources:
14
      datasource1:
15
        driver-class-name: com.mysql.cj.jdbc.Driver
16
        url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8
17
        username: root
18
        password: 123456
19
20
      datasource2:
21
        driver-class-name: com.mysql.cj.jdbc.Driver
22
        url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&allowMultiQueries=true&serverTimezone=GMT%2B8
23
        username: root
24
        password: 123456
25
26
    # druid 默认配置
27
    druid:
28
      # 初始连接数
29
      initial-size: 10
30
      # 最大连接池数量
31
      max-active: 100
32
      # 最小连接池数量
33
      min-idle: 10
34
      # 配置获取连接等待超时的时间
35
      max-wait: 60000
36
      # 打开PSCache,并且指定每个连接上PSCache的大小
37
      pool-prepared-statements: true
38
      max-pool-prepared-statement-per-connection-size: 20
39
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
40
      timeBetweenEvictionRunsMillis: 60000
41
      # 配置一个连接在池中最小生存的时间,单位是毫秒
42
      min-evictable-idle-time-millis: 300000
43
      validation-query: SELECT 1 FROM DUAL
44
      test-while-idle: true
45
      test-on-borrow: false
46
      test-on-return: false
47
      stat-view-servlet:
48
        enabled: true
49
        url-pattern: /monitor/druid/*
50
      filter:
51
        stat:
52
          log-slow-sql: true
53
          slow-sql-millis: 1000
54
          merge-sql: false
55
        wall:
56
          config:
57
            multi-statement-allow: true
58
59
# MyBatis
60
mybatis:
61
  # 搜索指定包别名
62
  typeAliasesPackage: com.liuchuanv
63
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
64
  mapperLocations: classpath*:mapper/**/*Mapper.xml
65
  # 加载全局的配置文件
66
  configLocation: classpath:mybatis-config.xml

此处配置的名称(如 defaultDataSource、targetDataSources)的命名并无特殊要求,只要和下面第n步的 DataSourceConfig 中对应起来就可以

使用 Druid 数据源的话,要在 pom.xml 中引入依赖

1
<!--阿里数据库连接池 -->
2
<dependency>
3
    <groupId>com.alibaba</groupId>
4
    <artifactId>druid-spring-boot-starter</artifactId>
5
    <version>1.1.10</version>
6
</dependency>

2. 实现动态数据源

DynamicDataSource 动态数据源,在多个数据源之间切换

1
public class DynamicDataSource extends AbstractRoutingDataSource {
2
3
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
4
        super.setDefaultTargetDataSource(defaultTargetDataSource);
5
        super.setTargetDataSources(targetDataSources);
6
        super.afterPropertiesSet();
7
    }
8
9
    @Override
10
    protected Object determineCurrentLookupKey() {
11
        return DataSourceContextHolder.getDataSourceType();
12
    }
13
}

DataSourceContextHolder 数据源上下文,使用线程变量来存储代表当前使用的数据源的key值(每个key值都对应一个数据源,用以区分多数据源)

1
public class DataSourceContextHolder {
2
3
    public static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();
4
5
    public static void setDataSourceType(String dsType) {
6
        CONTEXT_HOLDER.set(dsType);
7
    }
8
9
    public static String getDataSourceType() {
10
        return CONTEXT_HOLDER.get();
11
    }
12
13
    public static void removeDataSourceType() {
14
        CONTEXT_HOLDER.remove();
15
    }
16
17
}

DataSourceType 数据源对应的key(其实单纯的用字符串来表示数据源,替换枚举类DataSourceType也是可以的,但是写代码时要注意字符串统一)

1
public enum  DataSourceType {
2
    /** 默认数据源key */
3
    DEFAULT_DATASOURCE,
4
5
    /** 数据源1key*/
6
    DATASOURCE1,
7
8
    /** 数据源2key*/
9
    DATASOURCE2;
10
}

3. 将数据源添加到 Spring 容器中

1
@Configuration
2
public class DataSourceConfig {
3
4
    @Bean
5
    @ConfigurationProperties(prefix = "spring.datasource.default-datasource")
6
    public DataSource defaultDataSource() {
7
        return DruidDataSourceBuilder.create().build();
8
    }
9
10
    @Bean
11
    @ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource1")
12
    public DataSource dataSource1() {
13
        return DruidDataSourceBuilder.create().build();
14
    }
15
16
    @Bean
17
    @ConfigurationProperties(prefix = "spring.datasource.target-datasources.datasource2")
18
    public DataSource dataSource2() {
19
        return DruidDataSourceBuilder.create().build();
20
    }
21
22
    @Bean
23
    @Primary
24
    public DataSource dynamicDataSource(DataSource defaultDataSource, DataSource dataSource1, DataSource dataSource2) {
25
        // 注意:该方法的参数名称要和前面前面三个datasource对象在Spring容器中的bean名称一样
26
        // 或者使用 @Qualifier 指定具体的bean
27
        Map<Object, Object> targetDataSources = new HashMap<>();
28
        targetDataSources.put(DataSourceType.DEFAULT_DATASOURCE.name(), defaultDataSource);
29
        targetDataSources.put(DataSourceType.DATASOURCE1.name(), dataSource1);
30
        targetDataSources.put(DataSourceType.DATASOURCE2.name(), dataSource2);
31
        return new DynamicDataSource(defaultDataSource, targetDataSources);
32
    }
33
}

测试

为了方便,省略了 Service 层

TestController

1
@RestController
2
@RequestMapping("/test")
3
public class TestController {
4
5
    @Autowired
6
    private TestMapper testMapper;
7
8
    @GetMapping
9
    public List<Map<String, Object>> test(String dataSourceIndex) {
10
        // 根据参数值的不同,切换数据源
11
        if ("1".equals(dataSourceIndex)) {
12
            DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE1.name());
13
        } else if ("2".equals(dataSourceIndex)) {
14
            DataSourceContextHolder.setDataSourceType(DataSourceType.DATASOURCE2.name());
15
        }
16
        List<Map<String, Object>> mapList = testMapper.selectList();
17
        // 清除线程内部变量数据源key
18
        DataSourceContextHolder.removeDataSourceType();
19
        return mapList;
20
    }
21
}

TestMapper

1
@Repository
2
public interface TestMapper {
3
    /**
4
     * 查询列表
5
     * @return
6
     */
7
    List<Map<String, Object>> selectList();
8
}

TestMapper.xml

1
<?xml version="1.0" encoding="UTF-8" ?>
2
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
3
<mapper namespace="com.liuchuanv.dynamicdatasource.mapper.TestMapper">
4
	<select id="selectList" resultType="java.util.Map">
5
		SELECT * FROM test
6
	</select>
7
</mapper>

别忘了要准备数据哦!

下面SQL语句,创建3个数据库,然后在3个数据库中都创建一张test表,并各自插入不同的数据。

1
2
-- 创建数据库
3
create database db0 character set utf8 collate utf8_general_ci;
4
create database db1 character set utf8 collate utf8_general_ci;
5
create database db2 character set utf8 collate utf8_general_ci;
6
7
-- 在数据库db1下执行以下SQL
8
use db0;
9
create table test(
10
	id int(11) primary key auto_increment,
11
	name varchar(20)
12
) ;
13
insert into test(name) values('张三');
14
15
16
-- 在数据库db1下执行以下SQL
17
use db1;
18
create table test(
19
	id int(11) primary key auto_increment,
20
	name varchar(20)
21
) ;
22
insert into test(name) values('李四');
23
24
-- 在数据库db2下执行以下SQL
25
use db2;
26
create table test(
27
	id int(11) primary key auto_increment,
28
	name varchar(20)
29
) ;
30
insert into test(name) values('王五');

OK,一切准备就绪,启动应用吧!!!

一启动就出现了各种各样的,似乎无穷无尽的报错!一头黑线。

1. 找不到TestMapper

1
Field testMapper in com.liuchuanv.dynamicdatasource.controller.TestController required a bean of type 'com.liuchuanv.dynamicdatasource.mapper.TestMapper' that could not be found.

解决方法:在 DynamicdatasourceApplication 头上添加注解 @MapperScan("com.liuchuanv.*.mapper")

2. dynamicDataSource 依赖循环

1
┌─────┐
2
|  dynamicDataSource defined in class path resource [com/liuchuanv/dynamicdatasource/common/DataSourceConfig.class]
3
↑     ↓
4
|  defaultDataSource defined in class path resource [com/liuchuanv/dynamicdatasource/common/DataSourceConfig.class]
5
↑     ↓
6
|  org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
7
└─────┘

解决方法:在 DynamicdatasourceApplication 头上修改注解 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

终于处理好所有的问题,终于能痛痛快快的访问 http://localhost:8080/test

image-20191128232747322

使用的是默认数据源 defaultDataSource

image-20191128232835697

使用的是数据源 dataSource1

image-20191128232913327

使用的是数据源 dataSource2

建议大家在心里总结一下整个的过程,其实很简单

1
@Configuration
2
public class DruidConfig
3
{
4
    @Bean
5
    @ConfigurationProperties("spring.datasource.druid.master")
6
    public DataSource masterDataSource()
7
    {
8
        return DruidDataSourceBuilder.create().build();
9
    }
10
11
    @Bean
12
    @ConfigurationProperties("spring.datasource.druid.slave1")
13
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave1", name = "enabled", havingValue = "true")
14
    public DataSource slave1DataSource()
15
    {
16
        return DruidDataSourceBuilder.create().build();
17
    }
18
19
    @Bean
20
    @ConfigurationProperties("spring.datasource.druid.slave2")
21
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave2", name = "enabled", havingValue = "true")
22
    public DataSource slave2DataSource()
23
    {
24
        return DruidDataSourceBuilder.create().build();
25
    }
26
27
    @Bean
28
    @ConfigurationProperties("spring.datasource.druid.slave3")
29
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave3", name = "enabled", havingValue = "true")
30
    public DataSource slave3DataSource()
31
    {
32
        return DruidDataSourceBuilder.create().build();
33
    }
34
35
    @Bean(name = "dynamicDataSource")
36
    @Primary
37
    public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slave1DataSource, DataSource slave2DataSource, DataSource slave3DataSource)
38
    {
39
        Map<Object, Object> targetDataSources = new HashMap<>();
40
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
41
        targetDataSources.put(DataSourceType.SLAVE1.name(), slave1DataSource);
42
        targetDataSources.put(DataSourceType.SLAVE2.name(), slave2DataSource);
43
        targetDataSources.put(DataSourceType.SLAVE3.name(), slave3DataSource);
44
        return new DynamicDataSource(masterDataSource, targetDataSources);
45
    }
46
}

在这样一个类中,我迷茫了!

这么多的DataSource,系统运行的时候到底加载了哪一个啊?

干净利落的总结

  • 默认按照类型byType来查找applicationContext.getBean(bean.class)

  • 如果使用了注解 @Qualifier,则用*value值匹配bean名称 *applicationContext.getBean("Qualifier的value值")

  • 如果使用了注解 @Primary,则会优先加载该组件

  • 如果同时使用了@Qualifier@Primary,则按照注解 @Qualifier 的策略查找

  • 如果Spring容器存在多个同类型组件,并且没有使用@Qualifier@Primary,此时会按属性名和bean名称匹配查找 applicationContext.getBean("属性名")

综上所述,当Spring容器中存在多个同类型的bean时,加载优先级:*@Qulifier > @Primary > 属性名 *

python 共有八种数据类型:

  • number(数字)

  • string(字符串)

  • Boolean(布尔值)

  • None(空值)

  • list(列表)

  • tuple(元组)

  • dict(字典)

  • set(集合)

    number 数字

    img

string 字符串

img

Boolean 布尔值

img

None 空值

img

list 列表

img

tuple 元组

img

dict 字典

img

set 集合

img

数据类型转换

img

[TOC]

Free Mybatis plugin

A idea plugin for mybatis . free-idea-mybatis是一款增强idea对mybatis支持的插件,主要功能如下:

  • 生成mapper xml文件
  • 快速从代码跳转到mapper及从mapper返回代码
  • mybatis自动补全及语法错误提示

MyBatis Log Plugin

MyBatis Log Plugin

把 mybatis 输出的sql日志还原成完整的sql语句。
将日志输出的sql语句中的问号 ? 替换成真正的参数值。
通过 “Tools -> MyBatis Log Plugin” 菜单或快捷键 “Ctrl+Shift+Alt+O” 启用。
点击窗口左边的 “Filter” 按钮,可以过滤不想要输出的sql语句。
点击窗口左边的 “Format Sql” 按钮,可以格式化输出的sql语句。
选中console的sql日志,右击 “Restore Sql from Selection” 菜单可以还原sql语句。
前提条件:输出的sql日志必须包含”Preparing:”和”Parameters:”才能正常解析。

Alibaba Java Coding Guidelines

阿里巴巴代码规范检查插件,当然规范可以参考《阿里巴巴Java开发手册》。

Lombok

GitHub

避免写一大堆无用的getter和setter

Restfultookit

Spring MVC网页开发的时候,我们都是通过requestmapping的方式来定义页面的URL地址的,为了找到这个地址我们一般都是cmd+shift+F的方式进行查找,大家都知道,我们URL的命名一个是类requestmapping+方法requestmapping,查找的时候还是有那么一点不方便的,restfultookit就能很方便的帮忙进行查找。

比如要找 /liquid/pot/view2 这个URL,按 Ctrl+\ 或者 Ctrl+Alt+N

1568697342855

Background Image Plus

idea背景修改插件,让你的idea与众不同,可以设置自己喜欢的图片作为code背景

.ignore

GitHub

各类版本控制忽略文件生成工具

括号着色 Rainbow Brackets

对各个对称括号进行着色,方便查看

IDEA 常用快捷键

  • 根据函数返回值自动补全变量 ctrl + alt + v
  • 新建一行并将光标移动下一行 shift + enter

代码块快捷键

  • ifn

    1
    if (xxx == null) {
    2
        
    3
    }
  • inn

    1
    if (xxx != null) {
    2
        
    3
    }

全局命名变量、方法、文件夹名

选中变量名,按快捷键 Shift + F6

对于类名也是同样的操作

[TOC]

IDEA 设置/取消设置默认打开工程

  1. 打开 Project -> Setting -> System Setting
  2. 在 Strart/Shutdown 栏勾选/取消勾选 “Reopen last project on startup”

1568601752966

  1. 重启IDEA

打开新项目后,所有的maven依赖都没有导入

打开新项目后,所有的maven依赖都没有导入

解决方法:

点击右侧“Maven Projects”,在 “Lifecycle” 中点击 “clean”,执行结束之后,再点击 “install”。最后点击最左上角的按钮“Reimport All Maven Projects”。

Java文件显示带J文件

Java文件图标变成下面这样,而且包也没有分层。

1564968569160

解决方法:

打开 “Project” 中的 “Project Structure”

img

点击删除后,重新 “Add Content Root” 。

清除recent project

第一种方法

打开 “Open Recent” 中的 “Manage Projects”

1564968095507

在 “Recent Projects“ 中一个一个的删除。

1564968172891

第二种方法

找到IDEA的配置文件:.IntelliJIdea2018.2\config\options\recentProjects.xml,在其中进行清除。

(我的是在 C:\Users\xu目录下)

1564968327387

不自动导入java.util.Date

之前有一段时间在使用IDEA的时候,发现通过快捷键Alt + Enter导入并没有提示有java.util.Date的包,仅仅只有java.sql.Date的包。于是每次使用都需要通过手写import java.util.Date;来进行导包。博主在好生不爽了一段时间后,终于在网上找到了解决办法,本文就是用来记录一下解决过程的。

  找到设置(Alt + Shift + S),搜索“Auto Import”。如下图所示,只需要把java.util.Date导入提示的排除设置删除即可。

1560417646626

  同理,因为一般项目中很少使用得到java.sql.Date,所以我们可以添加这么一条设置,用于排除java.sql.Date的导入提示。

1560417679601

去掉Mybatis 中 No DataSource 和 SQL Dialect 的检查

在Mybatis的Mapper.xml 编写SQL时,经常出现下面这种弹框干扰,有时甚至没法正常编辑

image-20191122095348737

解决方法:

image-20191122095626396

ModuleNotFoundError: No module named ‘requests’

原因:没有导入requests库
解决方法:

  1. 打开cmd,进入PYTHON_HOME\Scripts 目录
  2. 执行命令 pip install requests

Python安装pip出错:no matching distribution found for xxx

解决方法:

  1. 打开cmd,进入PYTHON_HOME\Scripts 目录
  2. 执行命令 easy_install pip 来安装pip
  3. 更新pip的版本 easy_install --upgrade pip

  1. 修改注册表

    • 按下 Win+R,输入 regedit

    • 依次打开 HKEY_LOCAL_MACHINE -> SOFTWARE -> Microsoft - Windows -> CurrentVersion -> Authentication -> LogonUI -> Background

    • 右键OEMBackground键值修改(如果没有这个键值就新建一个)

      1569385149336

      • 将值设置为1

      1569385173001

  2. 将背景图片复制到指定目录

    • 将图片文件重命名为 backgroundDefault.jpg
    • 将图片文件复制到 C:\Windows\System32\oobe\info\Backgrounds 目录(没有该目录的话,则创建)

win7 系统本身是没有虚拟桌面功能的,需要使用第三方软件。

Virgo

Virgo 是一款轻量到极致的虚拟桌面软件,这款软件采用 C 语言编写,只有 323 行代码。整个软件大小仅为 8KB,却能实现完善的虚拟桌面功能。下载地址 Github Virgo 或者 百度云盘 提取码: w2wt

初次打开 Virgo,没有任何提示,也没有菜单和选项,只有一个任务栏的常驻图标显示当前的桌面序号。

在 Virgo 中最多可以分为 4 个虚拟桌面,而且所有操作只能靠快捷键

1
ALT + 1 ~ 4 切换到 1 ~ 4 号桌面
2
CTRL + 1 ~ 4 将当前活动的窗口移动到 1 ~ 4 号桌面
3
ALT + CTRL + SHIFT + Q 退出程序
4
ALT + CTRL + SHIFT + S 启用或停用其他快捷键

Virgo 的快捷键设计很讲究,它只让你记两个操作:切换和移动,分别对应 ALT 和 CTRL 键,每次操作只需要左手的两个手指就可以操作。
再加上 Windows 中自带的 Win + 1 ~ 4 快捷键可以选择任务栏中的 1 ~ 4 个应用,只需左手就可以快速切换多个桌面和软件。

Virgo 不仅可以像自带虚拟桌面一样隔离两个桌面正在运行的窗口。
而且当你点击 QQ、微信等图标时,这些窗口会直接显示在你的当前窗口,跟正常情况没有区别,也不会出现「打断操作」的情况.Virgo 用它那 8KB 的大小,就能为那些尚在使用 Windows XP、7、8 的用户增添虚拟桌面功能。也能为 Win 10 用户带来不一样的虚拟桌面体验。

自从chrome浏览器升级75版本之后,在 chrome://flags 中就无法找到 save as mhtml选项了,无法将网页保存为 mhtml 格式了。

原来chrome搞了个”Chrome Flag Ownership”的项目,目的是清理未使用的和过时的flags,现在save-page-as-mhtml仅作为开发者测试使用

解决操作步骤:

  1. 右键chrome浏览器快捷方式,点击 “属性”,在 “快捷方式” 栏中追加参数 -- save as mhtml

    image-20191112135931970

    image-20191112140122749

  2. 然后关闭所有的chrome浏览器网页,重新打开chrome。

  3. 直接按 Ctrl+S 就可保存为mhtml