├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── xjt
│ │ └── proxy
│ │ ├── MysqlProxyApplication.java
│ │ ├── aop
│ │ └── DataSourceConfig.java
│ │ ├── common
│ │ └── MyMapper.java
│ │ ├── controller
│ │ └── UserController.java
│ │ ├── domain
│ │ └── User.java
│ │ ├── dynamicdatasource
│ │ ├── DataSourceContextAop.java
│ │ ├── DataSourceContextHolder.java
│ │ ├── DataSourceSelector.java
│ │ ├── DynamicDataSource.java
│ │ └── DynamicDataSourceEnum.java
│ │ ├── mapper
│ │ └── UserMapper.java
│ │ └── service
│ │ └── UserService.java
└── resources
│ ├── application.yml
│ ├── logback-spring.xml
│ ├── mapper
│ └── UserMapper.xml
│ └── sql
│ └── user.sql
└── test
└── java
└── com
└── xjt
└── proxy
├── MysqlProxyApplicationTests.java
└── service
└── UserServiceTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**
5 | !**/src/test/**
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 |
30 | ### VS Code ###
31 | .vscode/
32 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Copyright 2012-2019 the original author or authors.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
6 | * the License. You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
11 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | * specific language governing permissions and limitations under the License.
13 | */
14 | import java.net.*;
15 | import java.io.*;
16 | import java.nio.channels.*;
17 | import java.util.Properties;
18 |
19 | public class MavenWrapperDownloader {
20 |
21 | private static final String WRAPPER_VERSION = "0.5.5";
22 | /**
23 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
24 | */
25 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
26 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
27 |
28 | /**
29 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the
30 | * default one.
31 | */
32 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties";
33 |
34 | /**
35 | * Path where the maven-wrapper.jar will be saved to.
36 | */
37 | private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar";
38 |
39 | /**
40 | * Name of the property which should be used to override the default download url for the wrapper.
41 | */
42 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
43 |
44 | public static void main(String args[]) {
45 | System.out.println("- Downloader started");
46 | File baseDirectory = new File(args[0]);
47 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
48 |
49 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
50 | // wrapperUrl parameter.
51 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
52 | String url = DEFAULT_DOWNLOAD_URL;
53 | if (mavenWrapperPropertyFile.exists()) {
54 | FileInputStream mavenWrapperPropertyFileInputStream = null;
55 | try {
56 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
57 | Properties mavenWrapperProperties = new Properties();
58 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
59 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
60 | } catch (IOException e) {
61 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
62 | } finally {
63 | try {
64 | if (mavenWrapperPropertyFileInputStream != null) {
65 | mavenWrapperPropertyFileInputStream.close();
66 | }
67 | } catch (IOException e) {
68 | // Ignore ...
69 | }
70 | }
71 | }
72 | System.out.println("- Downloading from: " + url);
73 |
74 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
75 | if (!outputFile.getParentFile().exists()) {
76 | if (!outputFile.getParentFile().mkdirs()) {
77 | System.out.println(
78 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
79 | }
80 | }
81 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
82 | try {
83 | downloadFileFromURL(url, outputFile);
84 | System.out.println("Done");
85 | System.exit(0);
86 | } catch (Throwable e) {
87 | System.out.println("- Error downloading");
88 | e.printStackTrace();
89 | System.exit(1);
90 | }
91 | }
92 |
93 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
94 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
95 | String username = System.getenv("MVNW_USERNAME");
96 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
97 | Authenticator.setDefault(new Authenticator() {
98 | @Override
99 | protected PasswordAuthentication getPasswordAuthentication() {
100 | return new PasswordAuthentication(username, password);
101 | }
102 | });
103 | }
104 | URL website = new URL(urlString);
105 | ReadableByteChannel rbc;
106 | rbc = Channels.newChannel(website.openStream());
107 | FileOutputStream fos = new FileOutputStream(destination);
108 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
109 | fos.close();
110 | rbc.close();
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Taoxj/mysql-proxy/64f48834c71ebf555ddc538d3c447340f6bce118/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip
2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 本篇博客为该项目的详细介绍,看完后觉得不错的同学记得给个star哦!
3 |
4 | ### 前言
5 |
6 | 相信有经验的同学都清楚,当db的读写量过高时,我们会备份一份或多份的从库用于做数据的读取,然后主库就主要承担写入的功能(也有读取需要,但压力不大),当db分好主从库后,我们还需要在项目实现自动连接主从库,达到读写分离的效果。实现读写分离并不困难,只要在数据库连接池手动控制好对应的db服务地址即可,但那样就会侵入业务代码,而且一个项目操作数据库的地方可能很多,如果都手动控制的话无疑会是很大的工作量,对此,我们有必要改造出一套方便的工具。
7 |
8 | 以Java语言来说,如今大部分的项目都是基于Spring Boot框架来搭建项目架构的,结合Spring本身自带的AOP工具,我们可以很容易就构建能实现读写分离效果的注解类,用注解的话可以达到对业务代码无入侵的效果,而且使用上也比较方便。
9 |
10 | 下面就简单带大家写个demo。
11 |
12 | ### 环境部署
13 |
14 | 数据库:MySql
15 |
16 | 库数量:2个,一主一从
17 |
18 | 关于mysql的主从环境部署之前已经写过文章介绍过了,这里就不再赘述,参考[《windows版的mysql主从复制环境搭建》](https://www.cnblogs.com/yeya/p/11878009.html)
19 |
20 | ### 开始项目
21 |
22 | 首先,毫无疑问,先开始搭建一个SpringBoot工程,然后在pom文件中引入如下依赖:
23 |
24 | ```
25 |
26 |
27 | com.alibaba
28 | druid-spring-boot-starter
29 | 1.1.10
30 |
31 |
32 | org.mybatis.spring.boot
33 | mybatis-spring-boot-starter
34 | 1.3.2
35 |
36 |
37 | tk.mybatis
38 | mapper-spring-boot-starter
39 | 2.1.5
40 |
41 |
42 | mysql
43 | mysql-connector-java
44 | 8.0.16
45 |
46 |
47 |
48 | org.springframework.boot
49 | spring-boot-starter-jdbc
50 | provided
51 |
52 |
53 | org.springframework.boot
54 | spring-boot-starter-aop
55 | provided
56 |
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-starter-web
61 |
62 |
63 | org.projectlombok
64 | lombok
65 | true
66 |
67 |
68 | com.alibaba
69 | fastjson
70 | 1.2.4
71 |
72 |
73 | org.springframework.boot
74 | spring-boot-starter-test
75 | test
76 |
77 |
78 | org.springframework.boot
79 | spring-boot-starter-data-jpa
80 |
81 |
82 | ```
83 |
84 | #### 目录结构
85 |
86 | 引入基本的依赖后,整理一下目录结构,完成后的项目骨架大致如下:
87 |
88 | 
89 |
90 | #### 建表
91 |
92 | 创建一张表user,在主库执行sql语句同时在从库生成对应的表数据
93 |
94 | ```
95 | DROP TABLE IF EXISTS `user`;
96 | CREATE TABLE `user` (
97 | `user_id` bigint(20) NOT NULL COMMENT '用户id',
98 | `user_name` varchar(255) DEFAULT '' COMMENT '用户名称',
99 | `user_phone` varchar(50) DEFAULT '' COMMENT '用户手机',
100 | `address` varchar(255) DEFAULT '' COMMENT '住址',
101 | `weight` int(3) NOT NULL DEFAULT '1' COMMENT '权重,大者优先',
102 | `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
103 | `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
104 | PRIMARY KEY (`user_id`)
105 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
106 |
107 | INSERT INTO `user` VALUES ('1196978513958141952', '测试1', '18826334748', '广州市海珠区', '1', '2019-11-20 10:28:51', '2019-11-22 14:28:26');
108 | INSERT INTO `user` VALUES ('1196978513958141953', '测试2', '18826274230', '广州市天河区', '2', '2019-11-20 10:29:37', '2019-11-22 14:28:14');
109 | INSERT INTO `user` VALUES ('1196978513958141954', '测试3', '18826273900', '广州市天河区', '1', '2019-11-20 10:30:19', '2019-11-22 14:28:30');
110 | ```
111 |
112 |
113 | #### 主从数据源配置
114 |
115 |
116 | application.yml,主要信息是主从库的数据源配置
117 |
118 | ```
119 | server:
120 | port: 8001
121 | spring:
122 | jackson:
123 | date-format: yyyy-MM-dd HH:mm:ss
124 | time-zone: GMT+8
125 | datasource:
126 | type: com.alibaba.druid.pool.DruidDataSource
127 | driver-class-name: com.mysql.cj.jdbc.Driver
128 | master:
129 | url: jdbc:mysql://127.0.0.1:3307/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
130 | username: root
131 | password:
132 | slave:
133 | url: jdbc:mysql://127.0.0.1:3308/user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
134 | username: root
135 | password:
136 | ```
137 |
138 | 因为有一主一从两个数据源,我们用枚举类来代替,方便我们使用时能对应
139 |
140 | ```
141 | @Getter
142 | public enum DynamicDataSourceEnum {
143 | MASTER("master"),
144 | SLAVE("slave");
145 | private String dataSourceName;
146 | DynamicDataSourceEnum(String dataSourceName) {
147 | this.dataSourceName = dataSourceName;
148 | }
149 | }
150 | ```
151 |
152 | 数据源配置信息类 **DataSourceConfig**,这里配置了两个数据源,masterDb和slaveDb
153 |
154 | ```
155 | @Configuration
156 | @MapperScan(basePackages = "com.xjt.proxy.mapper", sqlSessionTemplateRef = "sqlTemplate")
157 | public class DataSourceConfig {
158 |
159 | // 主库
160 | @Bean
161 | @ConfigurationProperties(prefix = "spring.datasource.master")
162 | public DataSource masterDb() {
163 | return DruidDataSourceBuilder.create().build();
164 | }
165 |
166 | /**
167 | * 从库
168 | */
169 | @Bean
170 | @ConditionalOnProperty(prefix = "spring.datasource", name = "slave", matchIfMissing = true)
171 | @ConfigurationProperties(prefix = "spring.datasource.slave")
172 | public DataSource slaveDb() {
173 | return DruidDataSourceBuilder.create().build();
174 | }
175 |
176 | /**
177 | * 主从动态配置
178 | */
179 | @Bean
180 | public DynamicDataSource dynamicDb(@Qualifier("masterDb") DataSource masterDataSource,
181 | @Autowired(required = false) @Qualifier("slaveDb") DataSource slaveDataSource) {
182 | DynamicDataSource dynamicDataSource = new DynamicDataSource();
183 | Map