├── LICENSE
├── README.md
├── pom.xml
├── sqlman-core
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── sqlman
│ │ └── core
│ │ ├── SqlDialectSupport.java
│ │ ├── SqlLogger.java
│ │ ├── SqlLoggerSupplier.java
│ │ ├── SqlNaming.java
│ │ ├── SqlNamingStrategy.java
│ │ ├── SqlScript.java
│ │ ├── SqlScriptResolver.java
│ │ ├── SqlSentence.java
│ │ ├── SqlSource.java
│ │ ├── SqlSourceProvider.java
│ │ ├── SqlUtils.java
│ │ ├── SqlVersion.java
│ │ ├── SqlVersionManager.java
│ │ ├── dialect
│ │ ├── AbstractDialectSupport.java
│ │ ├── MySQLDialectSupport.java
│ │ ├── OracleDialectSupport.java
│ │ ├── SQLServerDialectSupport.java
│ │ └── SQLiteDialectSupport.java
│ │ ├── exception
│ │ ├── DuplicatedVersionException.java
│ │ ├── IncorrectSyntaxException.java
│ │ └── MalformedNameException.java
│ │ ├── logger
│ │ ├── AbstractLoggerSupplier.java
│ │ ├── Slf4jLogger.java
│ │ └── Slf4jLoggerSupplier.java
│ │ ├── naming
│ │ └── StandardNamingStrategy.java
│ │ ├── script
│ │ ├── DruidScript.java
│ │ ├── DruidScriptResolver.java
│ │ └── DruidSentence.java
│ │ ├── source
│ │ ├── AbstractSourceProvider.java
│ │ ├── ClasspathSource.java
│ │ └── ClasspathSourceProvider.java
│ │ ├── spring
│ │ ├── AbstractManagerProperties.java
│ │ ├── JdbcManagerConfiguration.java
│ │ ├── JdbcManagerProperties.java
│ │ ├── dialect
│ │ │ ├── AbstractDialectProperties.java
│ │ │ ├── MySQLDialectConfiguration.java
│ │ │ ├── MySQLDialectProperties.java
│ │ │ ├── OracleDialectConfiguration.java
│ │ │ ├── OracleDialectProperties.java
│ │ │ ├── SQLServerDialectConfiguration.java
│ │ │ ├── SQLServerDialectProperties.java
│ │ │ ├── SQLiteDialectConfiguration.java
│ │ │ └── SQLiteDialectProperties.java
│ │ ├── logger
│ │ │ ├── AbstractLoggerProperties.java
│ │ │ ├── Slf4jLoggerConfiguration.java
│ │ │ └── Slf4jLoggerProperties.java
│ │ └── script
│ │ │ ├── AbstractNamingProperties.java
│ │ │ ├── AbstractProviderProperties.java
│ │ │ ├── AbstractResolverProperties.java
│ │ │ ├── ClasspathProviderConfiguration.java
│ │ │ ├── ClasspathProviderProperties.java
│ │ │ ├── DruidResolverConfiguration.java
│ │ │ ├── DruidResolverProperties.java
│ │ │ ├── StandardNamingConfiguration.java
│ │ │ └── StandardNamingProperties.java
│ │ └── version
│ │ ├── AbstractVersionManager.java
│ │ ├── JdbcAction.java
│ │ ├── JdbcInstruction.java
│ │ ├── JdbcIsolation.java
│ │ ├── JdbcMode.java
│ │ ├── JdbcTransaction.java
│ │ └── JdbcVersionManager.java
│ └── resources
│ └── META-INF
│ └── spring.factories
├── sqlman-git
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── io
│ │ └── sqlman
│ │ └── git
│ │ ├── GitCheckoutConfig.java
│ │ ├── GitCleanConfig.java
│ │ ├── GitClient.java
│ │ ├── GitClientFactory.java
│ │ ├── GitCloneConfig.java
│ │ ├── GitConfig.java
│ │ ├── GitPullConfig.java
│ │ ├── source
│ │ └── GitSourceProvider.java
│ │ └── spring
│ │ ├── GitProviderConfiguration.java
│ │ └── GitProviderProperties.java
│ └── resources
│ └── META-INF
│ └── spring.factories
├── sqlman-svn
└── pom.xml
├── sqlman-test
├── pom.xml
└── src
│ └── test
│ ├── java
│ └── io
│ │ └── sqlman
│ │ └── core
│ │ ├── spring
│ │ └── SqlmanTestApplication.java
│ │ └── test
│ │ ├── MySQLSupportTest.java
│ │ ├── OracleSupportTest.java
│ │ ├── SQLServerSupportTest.java
│ │ └── SQLiteSupportTest.java
│ ├── lib
│ └── ojdbc6-11.2.0.4.0.jar
│ └── resources
│ ├── application.yml
│ └── sqlman
│ ├── v1.0.0!创建表.sql
│ ├── v1.0.1-ATOMIC-SERIALIZABLE!插入数据.sql
│ └── v1.0.2!删除表.sql
└── sqlman-vcs
├── pom.xml
└── src
└── main
└── java
└── io
└── sqlman
└── vcs
├── VcsClient.java
├── VcsClientFactory.java
├── source
├── VcsSource.java
├── VcsSourceProvider.java
└── VcsUpdateStrategy.java
└── spring
└── VcsProviderProperties.java
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SQLMan [](https://jitpack.io/#core-lib/sqlman)
2 | #### 基于Java语言的关系型数据库迭代升级版本化管理与自动化执行插件,兼容主流关系型数据库,支持其方言的所有 DDL / DML / DCL 语法,旨在让SQL脚本成为项目代码的一部分,让数据库升级也纳入版本化管理之中。
3 |
4 | ## 适用场景
5 | 项目开发中难免会伴随着数据库表结构的迭代升级和表数据更新维护,同时根据不同的开发时期又分为 LOCAL、ALPHA、BETA、PRE-PRODUCTION 以及 PRODUCTION 或更多阶段。
6 | 当一位开发者对数据库进行升级后,需要把数据库升级SQL脚本同步给所有开发者进行同样的升级,否则其他开发者将代码运行在未升级的数据库上将会得到意料之外的结果。
7 | 当开发者人数越多,其各自的LOCAL数据库版本同步以及数据状态的维护与管理也越发混乱。并且在严格情况下开发者不应该手动升级和维护数据库,应当让程序自动执行以避免人为操作的失误带来不必要的损失。
8 |
9 | ## 功能特性
10 | * 兼容主流数据库
11 | * 支持全部 DDL / DML / DCL 语法
12 | * 支持多 SQL 语句脚本 one-by-one 或 atomic 执行方式
13 | * 可选脚本执行事务隔离级别
14 | * 支持自动备份变动表
15 | * 支持 Spring 自动配置
16 | * 可集成全 Java 平台框架
17 |
18 | ## 执行流程
19 | 1. 获取数据库升级排它锁,这是一个逻辑锁,用于避免多个 SQLMan 实例同时升级同一个库。
20 | 2. 创建数据库版本记录表,如果不存在则创建,存在则不做任何处理。
21 | 3. 检测数据库当前最新版本号,即上次已执行的脚本版本号。
22 | 4. 加载比当前最新版本号更高的SQL脚本资源,并且按照脚本版本号从低到高排序。
23 | 5. 遍历SQL脚本资源,解析SQL脚本语句以执行,并插入当前最新版本记录。
24 | 6. 释放数据库升级排它锁。
25 |
26 | ## 安装步骤
27 | 1. 设置 jitpack.io 仓库
28 | ```xml
29 |
30 | jitpack.io
31 | https://jitpack.io
32 |
33 | ```
34 |
35 | 2. 添加 SQLMan 依赖
36 | ```xml
37 |
38 | com.github.core-lib
39 | sqlman
40 | v1.2.1
41 |
42 | ```
43 | ## Spring-Boot 集成
44 | SQLMan 与 Spring-Boot 集成时,只需要通过配置即可,下面展示的是 SQLMan 所有配置项及其缺省值。
45 | 为了方便说明则将其都展示出来,如果缺省配置合适的话,就无需在项目配置文件中加入这些配置。
46 | 只需要在项目中加入SQLMan依赖以及在 resource/sqlman/ 目录或子目录下放置SQL脚本即可,SQL脚本的命名规则在文档后面会详细说明。
47 | ```yaml
48 | # SQLMan 配置
49 | sqlman:
50 | # 是否开启
51 | enabled: true
52 | # 管理器实现方式,目前支持 jdbc
53 | manager: jdbc
54 | # 当项目有多个数据源时,指定对应的数据源 bean 名称
55 | data-source: dataSource
56 | # 缺省事务隔离级别
57 | default-isolation: REPEATABLE_READ
58 | # 缺省执行模式
59 | default-mode: DANGER
60 | # 方言配置
61 | dialect:
62 | # 版本记录表表名
63 | table: schema_version
64 | # 方言类型
65 | type: MySQL
66 | # 脚本配置
67 | script:
68 | # 脚本资源提供器
69 | provider: classpath
70 | # 脚本位置的 ANT 路径表达式
71 | location: sqlman/**/*.sql
72 | # 脚本解析器
73 | resolver: druid
74 | # SQL脚本方言
75 | dialect: MySQL
76 | # SQL脚本字符集
77 | charset: UTF-8
78 | # 命名配置
79 | naming:
80 | # SQL脚本命名策略
81 | strategy: standard
82 | # 日志配置
83 | logger:
84 | # 日志提供器
85 | supplier: slf4j
86 | # 日志级别
87 | level: INFO
88 | ```
89 |
90 | ## Spring-MVC 集成
91 | SQLMan 与 Spring-MVC 集成核心的 bean 为最后面的 SqlVersionManager 并且需要注入对应的数据源 bean 和指定其初始化方法为 upgrade ,其余的注入均有其缺省值,
92 | 且缺省值如文中所示。
93 |
94 | ```xml
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | ```
129 |
130 | ## 代码调用
131 | 当项目采用的框架不是基于 Spring 家族的,可以参考与 Spring-MVC 集成的思路或采用纯代码调用方式来集成。同样的,只有数据源参数是必选的,其余都有其缺省值,且缺省值如下面代码所示。
132 |
133 | ```java
134 | // dataSource 为项目的数据源对象
135 | JdbcVersionManager sqlman = new JdbcVersionManager(dataSource);
136 |
137 | // MySQL 方言,表名为 schema_version
138 | sqlman.setDialectSupport(new MySQLDialectSupport("schema_version"));
139 |
140 | // 加载 sqlman/**/*.sql 路径的脚本,使用标准SQL脚本命名策略
141 | sqlman.setSourceProvider(new ClasspathSourceProvider("sqlman/**/*.sql", new StandardNamingStrategy()));
142 |
143 | // 使用 Druid SQL 解析器,方言为 MySQL,字符集为 UTF-8
144 | sqlman.setScriptResolver(new DruidScriptResolver(JdbcUtils.MYSQL, "UTF-8"));
145 |
146 | // 采用 SLF4J 日志实现,日志级别为 INFO
147 | sqlman.setLoggerSupplier(new Slf4jLoggerSupplier(SqlLogger.Level.INFO));
148 |
149 | // 执行升级流程
150 | sqlman.upgrade();
151 | ```
152 |
153 | ## 命名规则
154 | SQL脚本需要遵循一定的命名规则以配合SQLMan进行版本高低的区分以及执行脚本时采用的指令。
155 |
156 | 插件内部提供了一个标准的SQL脚本资源命名策略解析器(StandardNamingStrategy),其规则如下:
157 | 1. 以 v 开头,不区分大小写。(必选)
158 | 2. 紧跟着任意级版本数字,以 . 分隔,例如 1.0.0、2.4.13.8 或 2019.06.13 等。(必选)
159 | 3. 指定脚本执行指令列表,以 - 为前缀,例如 -ATOMIC、-READ_COMMITTED 或 -REPEATABLE_READ、-SAFETY、-DANGER 等。(可选)
160 | 4. 添加脚本备注,以 ! 为前缀,例如 !add-some-column、!drop-useless-tables 等。(可选)
161 | 5. 以 .sql 为后缀。(必选)
162 |
163 | 命名例子:
164 |
165 | | SQL脚本名称 | 规则解释 |
166 | | :------- | :------- |
167 | | v1.0.0.sql | 只有版本号 |
168 | | v2.4.13.8-ATOMIC.sql | 版本号 + 一个指令 |
169 | | v2.4.13.8-ATOMIC-REPEATABLE_READ.sql | 版本号 + 多个指令 |
170 | | v2.4.13.8-SAFETY.sql | 版本号 + 安全模式 |
171 | | v2.4.13.8-DANGER!delete-data.sql | 版本号 + 危险模式 + 备注 |
172 | | v2019.06.13!drop-useless-tables.sql | 版本号 + 备注 |
173 | | v2019.06.13-REPEATABLE_READ!init-admin-data.sql | 版本号 + 指令 + 备注 |
174 |
175 | 标准命名策略中版本号的高低对比基于版本数字的分段对比,并不是字符串对比。例如:
176 | * 1.0.0 比 0.0.1 高
177 | * 1.2.4 比 1.2.3 高
178 | * 2.3.1 比 2.2.4 高
179 | * 3.23.5.1 比 3.23.5 高
180 | * 2019.06.13 比 2019.06.08 高
181 |
182 | ## 指令说明
183 |
184 | 指令在脚本命名中不区分大小写,目前支持的指令及其解释如下:
185 |
186 | | 指令名称 | 指令含义 | 指令说明 | 缺省值 |
187 | | :------- | :------- | :------- | :----- |
188 | | ATOMIC | 原子性执行 | 当SQL脚本包含多条SQL语句时,将其置于同一个事务中执行。| 非原子性执行,即一条SQL语句一个事务。|
189 | | READ_UNCOMMITTED | 读未提交隔离级别 | 设置SQL语句执行事务的隔离界别为读未提交 | 依赖数据源的事务隔离级别 |
190 | | READ_COMMITTED | 读已提交隔离级别 | 设置SQL语句执行事务的隔离界别为读已提交 | 依赖数据源的事务隔离级别 |
191 | | REPEATABLE_READ | 可重复读隔离级别 | 设置SQL语句执行事务的隔离界别为可重复读 | 依赖数据源的事务隔离级别 |
192 | | SERIALIZABLE | 串行化隔离级别 | 设置SQL语句执行事务的隔离界别为串行化 | 依赖数据源的事务隔离级别 |
193 | | SAFETY | 安全模式 | 当SQL脚本设置为安全模式,即每条SQL语句执行前会自动备份被操作的表 | 危险模式 |
194 | | DANGER | 危险模式 | 当SQL脚本设置为安全模式,即每条SQL语句执行前不自动备份被操作的表 | 危险模式 |
195 |
196 | 其中每个SQL脚本的隔离级别只能选取一种,通常情况下依赖隔离级别的脚本需要原子性执行即通过-ATOMIC指令来指定,缺省为one-by-one模式。
197 |
198 | 同理执行模式也只能在危险模式中选取一种,备份表的名称为:原表名_bak_脚本_版_本_号$语句下标
199 |
200 | ## 原子模式
201 | * 缺省模式的多SQL语句脚本在执行过程中,当其中某条SQL执行失败后,程序下次启动时将会从该脚本的**失败SQL**开始。
202 | * 原子模式的多SQL语句脚本在执行过程中,当其中某条SQL执行失败后,程序下次启动时将会从该脚本的**首条SQL**开始。
203 |
204 | ## 执行模式
205 | * 安全模式:即每条SQL语句执行前**会自动备份**被操作的表
206 | * 危险模式:即每条SQL语句执行前**不自动备份**被操作的表
207 |
208 | ## 注意事项
209 | 由于部分数据库不支持 DDL 语句的失败回滚,例如 MySQL ,所以当一个原子性多SQL语句脚本中包含有 DDL 语句时,
210 | 其后面的SQL语句执行失败且进行整体回滚后,其实已成功的 DDL 并没有真正回滚,又由于是原子性脚本,所以程序下次启动时会从该脚本的首条SQL开始执行,
211 | 当执行到上次已成功但无法回滚的 DDL 时会失败,或重复执行,为了避免这种情况请尽量将 DDL 语句单独作为一个脚本,或采用缺省的 one-by-one 模式。
212 |
213 | ## 支持数据库
214 | * MySQL
215 | * Oracle
216 | * SQLServer
217 | * SQLite
218 |
219 | 后续将会增加更多数据库的支持。
220 |
221 | ## 版本记录
222 | * v1.2.1
223 | 1. 日志输出bug修复
224 | * v1.2.0
225 | 1. 自动备份bug修复
226 | * v1.1.0
227 | 1. 支持表自动备份
228 | * v1.0.6
229 | 1. Spring Bean 命名规范
230 | * v1.0.5
231 | 1. 第一个正式版本
232 | 2. 增加 README
233 |
234 | ## 协议声明
235 | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0)
236 |
237 | ## 联系作者
238 | QQ 646742615 不会钓鱼的兔子
239 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | io.sqlman
8 | sqlman
9 | v1.2.1
10 | pom
11 | sqlman
12 |
13 |
14 | sqlman-core
15 | sqlman-vcs
16 | sqlman-git
17 | sqlman-svn
18 | sqlman-test
19 |
20 |
21 |
22 | UTF-8
23 | 1.7
24 | 1.7
25 |
26 |
27 |
28 |
29 | jitpack.io
30 | https://jitpack.io
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-compiler-plugin
39 | 3.8.1
40 |
41 | ${maven.compiler.source}
42 | ${maven.compiler.target}
43 |
44 |
45 |
46 | org.apache.maven.plugins
47 | maven-source-plugin
48 | 3.0.1
49 |
50 | true
51 |
52 |
53 |
54 | compile
55 |
56 | jar
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/sqlman-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | sqlman
7 | io.sqlman
8 | v1.2.1
9 |
10 | 4.0.0
11 | sqlman-core
12 | sqlman-core
13 |
14 |
15 |
16 | com.github.core-lib
17 | loadkit
18 | v1.0.1
19 |
20 |
21 | com.alibaba
22 | druid
23 | 1.1.16
24 |
25 |
26 | org.slf4j
27 | slf4j-api
28 | 1.7.26
29 |
30 |
31 | org.springframework.boot
32 | spring-boot
33 | 2.0.1.RELEASE
34 | provided
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-autoconfigure
39 | 2.0.1.RELEASE
40 | true
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-configuration-processor
45 | 2.0.1.RELEASE
46 | true
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlDialectSupport.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 |
6 | /**
7 | * SQL方言
8 | * 根据系统的数据库类型而选,由于不同数据库的 DQL, DML, DDL, DCL 差异比较大,所以通过不同的实现类来支持。
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/5/18 13:13
12 | */
13 | public interface SqlDialectSupport {
14 |
15 | /**
16 | * 创建版本升级记录表,如果已经创建则不做任何变化。
17 | *
18 | * @param connection 连接
19 | * @throws SQLException SQL异常
20 | */
21 | void create(Connection connection) throws SQLException;
22 |
23 | /**
24 | * 检测当前版本,如果没有任何升级记录则返回{@code null}
25 | *
26 | * @param connection 连接
27 | * @return 当前版本
28 | * @throws SQLException SQL异常
29 | */
30 | SqlVersion detect(Connection connection) throws SQLException;
31 |
32 | /**
33 | * 更新当前版本
34 | *
35 | * @param connection 连接
36 | * @param version 当前版本
37 | * @throws SQLException SQL异常
38 | */
39 | void update(Connection connection, SqlVersion version) throws SQLException;
40 |
41 | /**
42 | * 删除版本升级记录表,如果已经删除则不做任何变化。
43 | *
44 | * @param connection 连接
45 | * @throws SQLException SQL异常
46 | */
47 | void remove(Connection connection) throws SQLException;
48 |
49 | /**
50 | * 获取版本升级的排他锁,当获取失败时抛出{@link SQLException}
51 | *
52 | * @param connection 连接
53 | * @throws SQLException SQL异常
54 | */
55 | void lockup(Connection connection) throws SQLException;
56 |
57 | /**
58 | * 释放版本升级的排他锁
59 | *
60 | * @param connection 连接
61 | * @throws SQLException SQL异常
62 | */
63 | void unlock(Connection connection) throws SQLException;
64 |
65 | /**
66 | * 备份表
67 | *
68 | * @param connection 连接
69 | * @param script 脚本
70 | * @param ordinal 语句下标
71 | * @throws SQLException SQL异常
72 | */
73 | void backup(Connection connection, SqlScript script, int ordinal) throws SQLException;
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlLogger.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | /**
4 | * SQL日志记录器
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/17 17:59
8 | */
9 | public interface SqlLogger {
10 |
11 | /**
12 | * 获取日志记录器名称
13 | *
14 | * @return 日志记录器名称
15 | */
16 | String getName();
17 |
18 | /**
19 | * 获取日志级别
20 | *
21 | * @return 日志级别
22 | */
23 | SqlLogger.Level getLevel();
24 |
25 | /**
26 | * 判断 Trace 日志级别是否开启
27 | *
28 | * @return 开启则返回{@code true} 否则返回{@code false}
29 | */
30 | boolean isTraceEnabled();
31 |
32 | /**
33 | * trace 级别日志输出
34 | *
35 | * @param msg 消息
36 | */
37 | void trace(String msg);
38 |
39 | /**
40 | * trace 级别日志输出
41 | *
42 | * @param format 消息格式
43 | * @param arguments 消息参数
44 | */
45 | void trace(String format, Object... arguments);
46 |
47 | /**
48 | * trace 级别日志输出
49 | *
50 | * @param msg 消息
51 | * @param t 异常
52 | */
53 | void trace(String msg, Throwable t);
54 |
55 | /**
56 | * 判断 Debug 日志级别是否开启
57 | *
58 | * @return 开启则返回{@code true} 否则返回{@code false}
59 | */
60 | boolean isDebugEnabled();
61 |
62 | /**
63 | * debug 级别日志输出
64 | *
65 | * @param msg 消息
66 | */
67 | void debug(String msg);
68 |
69 | /**
70 | * debug 级别日志输出
71 | *
72 | * @param format 消息格式
73 | * @param arguments 消息参数
74 | */
75 | void debug(String format, Object... arguments);
76 |
77 | /**
78 | * debug 级别日志输出
79 | *
80 | * @param msg 消息
81 | * @param t 异常
82 | */
83 | void debug(String msg, Throwable t);
84 |
85 | /**
86 | * 判断 Info 日志级别是否开启
87 | *
88 | * @return 开启则返回{@code true} 否则返回{@code false}
89 | */
90 | boolean isInfoEnabled();
91 |
92 | /**
93 | * info 级别日志输出
94 | *
95 | * @param msg 消息
96 | */
97 | void info(String msg);
98 |
99 | /**
100 | * info 级别日志输出
101 | *
102 | * @param format 消息格式
103 | * @param arguments 消息参数
104 | */
105 | void info(String format, Object... arguments);
106 |
107 | /**
108 | * info 级别日志输出
109 | *
110 | * @param msg 消息
111 | * @param t 异常
112 | */
113 | void info(String msg, Throwable t);
114 |
115 | /**
116 | * 判断 Warn 日志级别是否开启
117 | *
118 | * @return 开启则返回{@code true} 否则返回{@code false}
119 | */
120 | boolean isWarnEnabled();
121 |
122 | /**
123 | * warn 级别日志输出
124 | *
125 | * @param msg 消息
126 | */
127 | void warn(String msg);
128 |
129 | /**
130 | * warn 级别日志输出
131 | *
132 | * @param format 消息格式
133 | * @param arguments 消息参数
134 | */
135 | void warn(String format, Object... arguments);
136 |
137 | /**
138 | * warn 级别日志输出
139 | *
140 | * @param msg 消息
141 | * @param t 异常
142 | */
143 | void warn(String msg, Throwable t);
144 |
145 | /**
146 | * 判断 Error 日志级别是否开启
147 | *
148 | * @return 开启则返回{@code true} 否则返回{@code false}
149 | */
150 | boolean isErrorEnabled();
151 |
152 | /**
153 | * error 级别日志输出
154 | *
155 | * @param msg 消息
156 | */
157 | void error(String msg);
158 |
159 | /**
160 | * error 级别日志输出
161 | *
162 | * @param format 消息格式
163 | * @param arguments 消息参数
164 | */
165 | void error(String format, Object... arguments);
166 |
167 | /**
168 | * error 级别日志输出
169 | *
170 | * @param msg 消息
171 | * @param t 异常
172 | */
173 | void error(String msg, Throwable t);
174 |
175 | /**
176 | * 日志级别
177 | */
178 | enum Level {
179 | /**
180 | * TRACE
181 | */
182 | TRACE,
183 | /**
184 | * DEBUG
185 | */
186 | DEBUG,
187 | /**
188 | * INFO
189 | */
190 | INFO,
191 | /**
192 | * WARN
193 | */
194 | WARN,
195 | /**
196 | * ERROR
197 | */
198 | ERROR
199 | }
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlLoggerSupplier.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | /**
4 | * SQL日志记录器供应商
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/27 11:03
8 | */
9 | public interface SqlLoggerSupplier {
10 |
11 | /**
12 | * 日志记录器供应
13 | *
14 | * @param clazz 日志记录器所属类
15 | * @return 日志记录器
16 | */
17 | SqlLogger supply(Class> clazz);
18 |
19 | /**
20 | * 日志记录器供应
21 | *
22 | * @param name 日志记录器名称
23 | * @return 日志记录器
24 | */
25 | SqlLogger supply(String name);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlNaming.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import java.util.Objects;
4 | import java.util.Set;
5 |
6 | /**
7 | * SQL脚本信息
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/24 20:48
11 | */
12 | public class SqlNaming {
13 | private final String name;
14 | private final String version;
15 | private final Set parameters;
16 | private final String description;
17 |
18 | public SqlNaming(String name, String version, Set parameters, String description) {
19 | if (name == null) {
20 | throw new IllegalArgumentException("name must not be null");
21 | }
22 | if (version == null) {
23 | throw new IllegalArgumentException("version must not be null");
24 | }
25 | if (parameters == null) {
26 | throw new IllegalArgumentException("parameters must not be null");
27 | }
28 | if (description == null) {
29 | throw new IllegalArgumentException("description must not be null");
30 | }
31 | this.name = name;
32 | this.version = version;
33 | this.parameters = parameters;
34 | this.description = description;
35 | }
36 |
37 | public String getName() {
38 | return name;
39 | }
40 |
41 | public String getVersion() {
42 | return version;
43 | }
44 |
45 | public Set getParameters() {
46 | return parameters;
47 | }
48 |
49 | public String getDescription() {
50 | return description;
51 | }
52 |
53 | @Override
54 | public boolean equals(Object o) {
55 | if (this == o) return true;
56 | if (o == null || getClass() != o.getClass()) return false;
57 | SqlNaming sqlNaming = (SqlNaming) o;
58 | return Objects.equals(version, sqlNaming.version);
59 | }
60 |
61 | @Override
62 | public int hashCode() {
63 | return Objects.hash(version);
64 | }
65 |
66 | @Override
67 | public String toString() {
68 | return name;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlNamingStrategy.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import io.sqlman.core.exception.MalformedNameException;
4 |
5 | import java.util.Comparator;
6 |
7 | /**
8 | * SQL脚本命名策略
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/5/24 21:57
12 | */
13 | public interface SqlNamingStrategy extends Comparator {
14 |
15 | /**
16 | * 验证命名是否合法
17 | *
18 | * @param name SQL脚本名称
19 | * @return 如果名称合法则返回{@code true}否则返回{@code false}
20 | */
21 | boolean check(String name);
22 |
23 | /**
24 | * SQL脚本名称解析
25 | *
26 | * @param name SQL脚本名称
27 | * @return SQL脚本信息
28 | * @throws MalformedNameException SQL脚本资源命名不合法,即{@link this#check(String)}返回{@code false}。
29 | */
30 | SqlNaming parse(String name) throws MalformedNameException;
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlScript.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import java.util.Enumeration;
4 | import java.util.Set;
5 |
6 | /**
7 | * SQL脚本
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/17 11:00
11 | */
12 | public interface SqlScript {
13 |
14 | /**
15 | * SQL语句数量
16 | *
17 | * @return 语句数量
18 | */
19 | int sqls();
20 |
21 | /**
22 | * 获取该脚本指定序号的SQL语句
23 | * 序号从{@code 1}开始
24 | *
25 | * @param ordinal SQL语句序号
26 | * @return 指定序号的SQL语句
27 | * @throws IndexOutOfBoundsException 序号超出边界时抛出
28 | */
29 | SqlSentence sentence(int ordinal) throws IndexOutOfBoundsException;
30 |
31 | /**
32 | * SQL语句列表
33 | *
34 | * @return 语句列表
35 | */
36 | Enumeration sentences();
37 |
38 | /**
39 | * 脚本名称
40 | *
41 | * @return 脚本名称
42 | */
43 | String name();
44 |
45 | /**
46 | * 脚本版本号
47 | *
48 | * @return 脚本版本号
49 | */
50 | String version();
51 |
52 | /**
53 | * 指令列表
54 | *
55 | * @return 指令列表
56 | */
57 | Set instructions();
58 |
59 | /**
60 | * 脚本描述
61 | *
62 | * @return 脚本描述
63 | */
64 | String description();
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlScriptResolver.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import io.sqlman.core.exception.IncorrectSyntaxException;
4 |
5 | import java.io.IOException;
6 |
7 | /**
8 | * SQL脚本解析器
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/5/22 10:38
12 | */
13 | public interface SqlScriptResolver {
14 |
15 | /**
16 | * 解析SQL脚本
17 | *
18 | * @param source 脚本资源
19 | * @return SQL脚本
20 | * @throws IncorrectSyntaxException 语法错误异常
21 | * @throws IOException I/O异常
22 | */
23 | SqlScript resolve(SqlSource source) throws IncorrectSyntaxException, IOException;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlSentence.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | /**
4 | * SQL语句
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/17 11:19
8 | */
9 | public interface SqlSentence {
10 |
11 | /**
12 | * 语句序号,从{@code 1}开始
13 | *
14 | * @return 语句序号
15 | */
16 | int ordinal();
17 |
18 | /**
19 | * 语句内容
20 | *
21 | * @return 语句内容
22 | */
23 | String value();
24 |
25 | /**
26 | * 数据库名
27 | *
28 | * @return 数据库名
29 | */
30 | String schema();
31 |
32 | /**
33 | * 操作表名
34 | *
35 | * @return 操作表名
36 | */
37 | String table();
38 | }
39 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlSource.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.Set;
6 |
7 | /**
8 | * SQL脚本资源
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/5/24 17:28
12 | */
13 | public interface SqlSource {
14 |
15 | /**
16 | * 脚本名称
17 | *
18 | * @return 脚本名称
19 | */
20 | String name();
21 |
22 | /**
23 | * 脚本版本号
24 | *
25 | * @return 脚本版本号
26 | */
27 | String version();
28 |
29 | /**
30 | * 参数列表
31 | *
32 | * @return 参数列表
33 | */
34 | Set parameters();
35 |
36 | /**
37 | * 脚本描述
38 | *
39 | * @return 脚本描述
40 | */
41 | String description();
42 |
43 | /**
44 | * 脚本输入流
45 | *
46 | * @return 脚本输入流
47 | * @throws IOException I/O异常
48 | */
49 | InputStream open() throws IOException;
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlSourceProvider.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import io.sqlman.core.exception.DuplicatedVersionException;
4 | import io.sqlman.core.exception.MalformedNameException;
5 |
6 | import java.io.IOException;
7 | import java.util.Comparator;
8 | import java.util.Enumeration;
9 |
10 | /**
11 | * SQL脚本提供器
12 | *
13 | * @author Payne 646742615@qq.com
14 | * 2019/5/17 17:59
15 | */
16 | public interface SqlSourceProvider extends Comparator {
17 |
18 | /**
19 | * 获取所有SQL脚本
20 | * 实现类返回的结果必须遵循脚本版本的先后顺序。
21 | * 实现类当发现脚本资源中包含重复的SQL脚本版本时应当抛出
22 | *
23 | * @return 所有SQL脚本
24 | * @throws MalformedNameException SQL脚本资源命名不合法
25 | * @throws DuplicatedVersionException SQL脚本资源版本重复
26 | * @throws IOException I/O异常
27 | */
28 | Enumeration acquire() throws MalformedNameException, DuplicatedVersionException, IOException;
29 |
30 | /**
31 | * 获取从指定版本起始的所有SQL脚本,当included参数为{@code true}时包括起始版本,否则不包含起始版本。
32 | * 实现类返回的结果必须遵循脚本版本的先后顺序。
33 | *
34 | * @param version 起始版本
35 | * @param included 是否包含起始版本
36 | * @return 所有SQL脚本
37 | * @throws MalformedNameException SQL脚本资源命名不合法
38 | * @throws DuplicatedVersionException SQL脚本资源版本重复
39 | * @throws IOException I/O异常
40 | */
41 | Enumeration acquire(String version, boolean included) throws MalformedNameException, DuplicatedVersionException, IOException;
42 |
43 | /**
44 | * 验证命名是否合法
45 | *
46 | * @param name SQL脚本名称
47 | * @return 如果名称合法则返回{@code true}否则返回{@code false}
48 | */
49 | boolean check(String name);
50 |
51 | /**
52 | * SQL脚本名称解析
53 | *
54 | * @param name SQL脚本名称
55 | * @return SQL脚本信息
56 | * @throws MalformedNameException SQL脚本资源命名不合法,即{@link this#check(String)}返回{@code false}。
57 | */
58 | SqlNaming parse(String name) throws MalformedNameException;
59 | }
60 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlUtils.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 |
8 | /**
9 | * SQL脚本工具类
10 | *
11 | * @author Payne 646742615@qq.com
12 | * 2019/5/22 10:56
13 | */
14 | public class SqlUtils {
15 |
16 | private SqlUtils() {
17 | }
18 |
19 | /**
20 | * 字符串截断,如果字符串为{@code null} 则返回{@code ""}
21 | *
22 | * @param value 字符串
23 | * @param length 截断长度
24 | * @return 截断后的字符串
25 | */
26 | public static String truncate(String value, int length) {
27 | if (value == null) {
28 | return "";
29 | }
30 | if (value.length() < length) {
31 | return value;
32 | }
33 | return value.substring(0, length);
34 | }
35 |
36 | /**
37 | * 将输入流内容转换成字符串
38 | *
39 | * @param in 输入流
40 | * @param charset 字符集
41 | * @return 输入流内容
42 | * @throws IOException I/O异常
43 | */
44 | public static String stringify(InputStream in, String charset) throws IOException {
45 | ByteArrayOutputStream out = new ByteArrayOutputStream();
46 | byte[] buf = new byte[1024];
47 | int len;
48 | while ((len = in.read(buf)) != -1) {
49 | out.write(buf, 0, len);
50 | }
51 | return out.toString(charset);
52 | }
53 |
54 | /**
55 | * 如果字符串是{@code null}或者是空字符串则返回缺省值,否则返回本身
56 | *
57 | * @param value 字符串本身
58 | * @param defaultValue 缺省值
59 | * @return 如果字符串是{@code null}或者是空字符串则返回缺省值,否则返回本身
60 | */
61 | public static String ifEmpty(String value, String defaultValue) {
62 | return value == null || value.isEmpty() ? defaultValue : value;
63 | }
64 |
65 | /**
66 | * 判断字符串是否为{@code null} 或空字符串
67 | *
68 | * @param value 字符串
69 | * @return 是否为{@code null} 或空字符串
70 | */
71 | public static boolean isEmpty(String value) {
72 | return value == null || value.isEmpty();
73 | }
74 |
75 | /**
76 | * 去除"`"符号
77 | *
78 | * @param name 名称
79 | * @return 去除"`"符号的名称
80 | */
81 | public static String unwrap(String name) {
82 | return name.replace("`", "");
83 | }
84 |
85 | /**
86 | * 加上"`"符号
87 | *
88 | * @param name 名称
89 | * @return 加上"`"符号的名称
90 | */
91 | public static String wrap(String name) {
92 | return '`' + name + "`";
93 | }
94 |
95 | /**
96 | * 删除文件/目录
97 | *
98 | * @param file 文件/目录
99 | * @param recursively 是否递归删除
100 | * @return {@code true} 删除成功 {@code false} 删除失败
101 | */
102 | public static boolean delete(File file, boolean recursively) {
103 | if (!file.exists()) {
104 | return true;
105 | }
106 | if (file.isDirectory() && recursively) {
107 | boolean deleted = true;
108 | File[] files = file.listFiles();
109 | for (int i = 0; files != null && i < files.length; i++) {
110 | deleted = deleted && delete(files[i], true);
111 | }
112 | return deleted;
113 | } else {
114 | return file.delete();
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlVersion.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 |
4 | import java.sql.Timestamp;
5 |
6 | /**
7 | * 数据库版本
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/18 13:48
11 | */
12 | public class SqlVersion {
13 | private Integer id;
14 | private String name;
15 | private String version;
16 | private Integer ordinal;
17 | private String description;
18 | private Integer sqlQuantity;
19 | private Boolean success;
20 | private Integer rowEffected;
21 | private Integer errorCode;
22 | private String errorState;
23 | private String errorMessage;
24 | private Timestamp timeExecuted;
25 |
26 | public Integer getId() {
27 | return id;
28 | }
29 |
30 | public void setId(Integer id) {
31 | this.id = id;
32 | }
33 |
34 | public String getName() {
35 | return name;
36 | }
37 |
38 | public void setName(String name) {
39 | this.name = name;
40 | }
41 |
42 | public String getVersion() {
43 | return version;
44 | }
45 |
46 | public void setVersion(String version) {
47 | this.version = version;
48 | }
49 |
50 | public Integer getOrdinal() {
51 | return ordinal;
52 | }
53 |
54 | public void setOrdinal(Integer ordinal) {
55 | this.ordinal = ordinal;
56 | }
57 |
58 | public String getDescription() {
59 | return description;
60 | }
61 |
62 | public void setDescription(String description) {
63 | this.description = description;
64 | }
65 |
66 | public Integer getSqlQuantity() {
67 | return sqlQuantity;
68 | }
69 |
70 | public void setSqlQuantity(Integer sqlQuantity) {
71 | this.sqlQuantity = sqlQuantity;
72 | }
73 |
74 | public Boolean getSuccess() {
75 | return success;
76 | }
77 |
78 | public void setSuccess(Boolean success) {
79 | this.success = success;
80 | }
81 |
82 | public Integer getRowEffected() {
83 | return rowEffected;
84 | }
85 |
86 | public void setRowEffected(Integer rowEffected) {
87 | this.rowEffected = rowEffected;
88 | }
89 |
90 | public Integer getErrorCode() {
91 | return errorCode;
92 | }
93 |
94 | public void setErrorCode(Integer errorCode) {
95 | this.errorCode = errorCode;
96 | }
97 |
98 | public String getErrorState() {
99 | return errorState;
100 | }
101 |
102 | public void setErrorState(String errorState) {
103 | this.errorState = errorState;
104 | }
105 |
106 | public String getErrorMessage() {
107 | return errorMessage;
108 | }
109 |
110 | public void setErrorMessage(String errorMessage) {
111 | this.errorMessage = errorMessage;
112 | }
113 |
114 | public Timestamp getTimeExecuted() {
115 | return timeExecuted;
116 | }
117 |
118 | public void setTimeExecuted(Timestamp timeExecuted) {
119 | this.timeExecuted = timeExecuted;
120 | }
121 |
122 | @Override
123 | public String toString() {
124 | return "" + version + "#" + ordinal;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/SqlVersionManager.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core;
2 |
3 | import io.sqlman.core.exception.DuplicatedVersionException;
4 | import io.sqlman.core.exception.IncorrectSyntaxException;
5 | import io.sqlman.core.exception.MalformedNameException;
6 |
7 | import java.io.IOException;
8 | import java.sql.SQLException;
9 | import java.util.Enumeration;
10 |
11 | /**
12 | * 数据库版本管理器
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/5/18 12:27
16 | */
17 | public interface SqlVersionManager {
18 |
19 | /**
20 | * 执行SQL脚本的升级
21 | *
22 | * @throws SQLException SQL异常
23 | */
24 | void upgrade() throws SQLException;
25 |
26 | /**
27 | * 执行指定SQL脚本的升级
28 | *
29 | * @param script 指定脚本
30 | * @throws SQLException SQL异常
31 | * @see NullPointerException 当指定脚本为{@code null} 时
32 | */
33 | void upgrade(SqlScript script) throws SQLException;
34 |
35 | /**
36 | * 执行指定SQL脚本指定语句序号的升级,语句序号从{@code 1}开始
37 | *
38 | * @param script 指定脚本
39 | * @param ordinal 指定语句序号
40 | * @throws SQLException SQL异常
41 | * @see NullPointerException 当指定脚本为{@code null} 时
42 | * @see IndexOutOfBoundsException 当指定语句序号超出边界时
43 | */
44 | void upgrade(SqlScript script, int ordinal) throws SQLException;
45 |
46 | /**
47 | * 获取所有SQL脚本
48 | * 实现类返回的结果必须遵循脚本版本的先后顺序
49 | * 实现类当发现脚本资源中包含重复的SQL脚本版本时应当抛出
50 | *
51 | * @return 所有SQL脚本
52 | * @throws MalformedNameException SQL脚本资源命名不合法
53 | * @throws DuplicatedVersionException SQL脚本资源版本重复
54 | * @throws IOException I/O异常
55 | */
56 | Enumeration acquire() throws MalformedNameException, DuplicatedVersionException, IOException;
57 |
58 | /**
59 | * 获取从指定版本起始的所有SQL脚本,当included参数为{@code true}时包括起始版本,否则不包含起始版本。
60 | * 实现类返回的结果必须遵循脚本版本的先后顺序。
61 | *
62 | * @param version 起始版本
63 | * @param included 是否包含起始版本
64 | * @return 所有SQL脚本
65 | * @throws MalformedNameException SQL脚本资源命名不合法
66 | * @throws DuplicatedVersionException SQL脚本资源版本重复
67 | * @throws IOException I/O异常
68 | */
69 | Enumeration acquire(String version, boolean included) throws MalformedNameException, DuplicatedVersionException, IOException;
70 |
71 | /**
72 | * 解析SQL脚本
73 | *
74 | * @param source 脚本资源
75 | * @return SQL脚本
76 | * @throws IncorrectSyntaxException 语法错误异常
77 | * @throws IOException I/O异常
78 | */
79 | SqlScript resolve(SqlSource source) throws IncorrectSyntaxException, IOException;
80 |
81 | /**
82 | * 创建版本升级记录表,如果已经创建则不做任何变化。
83 | *
84 | * @throws SQLException SQL异常
85 | */
86 | void create() throws SQLException;
87 |
88 | /**
89 | * 检测当前版本,如果没有任何升级记录则返回{@code null}
90 | *
91 | * @return 当前版本
92 | * @throws SQLException SQL异常
93 | */
94 | SqlVersion detect() throws SQLException;
95 |
96 | /**
97 | * 更新当前版本
98 | *
99 | * @param version 当前版本
100 | * @throws SQLException SQL异常
101 | */
102 | void update(SqlVersion version) throws SQLException;
103 |
104 | /**
105 | * 删除版本升级记录表,如果已经删除则不做任何变化。
106 | *
107 | * @throws SQLException SQL异常
108 | */
109 | void remove() throws SQLException;
110 |
111 | /**
112 | * 获取版本升级的排他锁,当获取失败时抛出{@link SQLException}。
113 | *
114 | * @throws SQLException SQL异常
115 | */
116 | void lockup() throws SQLException;
117 |
118 | /**
119 | * 释放版本升级的排他锁
120 | *
121 | * @throws SQLException SQL异常
122 | */
123 | void unlock() throws SQLException;
124 |
125 | /**
126 | * 获取日志记录器
127 | *
128 | * @param clazz 日志记录器所在类
129 | * @return 日志记录器
130 | */
131 | SqlLogger logger(Class> clazz);
132 |
133 | /**
134 | * 获取日志记录器
135 | *
136 | * @param name 日志记录器名称
137 | * @return 日志记录器
138 | */
139 | SqlLogger logger(String name);
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/dialect/AbstractDialectSupport.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 | import io.sqlman.core.SqlScript;
5 | import io.sqlman.core.SqlSentence;
6 | import io.sqlman.core.SqlVersion;
7 |
8 | import java.sql.Connection;
9 | import java.sql.PreparedStatement;
10 | import java.sql.ResultSet;
11 | import java.sql.SQLException;
12 |
13 | import static io.sqlman.core.SqlUtils.*;
14 |
15 | /**
16 | * 抽象的数据库方言
17 | *
18 | * @author Payne 646742615@qq.com
19 | * 2019/5/24 9:52
20 | */
21 | public abstract class AbstractDialectSupport implements SqlDialectSupport {
22 | protected String table = "SCHEMA_VERSION";
23 |
24 | protected AbstractDialectSupport() {
25 | }
26 |
27 | protected AbstractDialectSupport(String table) {
28 | if (table == null || table.trim().isEmpty()) {
29 | throw new IllegalArgumentException("table must not be null or blank string");
30 | }
31 | this.table = table;
32 | }
33 |
34 | @Override
35 | public SqlVersion detect(Connection connection) throws SQLException {
36 | StringBuilder dql = new StringBuilder();
37 |
38 | dql.append(" SELECT");
39 | dql.append(" ID AS id,");
40 | dql.append(" NAME AS name,");
41 | dql.append(" VERSION AS version,");
42 | dql.append(" ORDINAL AS ordinal,");
43 | dql.append(" DESCRIPTION AS description,");
44 | dql.append(" SQL_QUANTITY AS sqlQuantity,");
45 | dql.append(" SUCCESS AS success,");
46 | dql.append(" ROW_EFFECTED AS rowEffected,");
47 | dql.append(" ERROR_CODE AS errorCode,");
48 | dql.append(" ERROR_STATE AS errorState,");
49 | dql.append(" ERROR_MESSAGE AS errorMessage,");
50 | dql.append(" TIME_EXECUTED AS timeExecuted");
51 | dql.append(" FROM");
52 | dql.append(" ").append(table);
53 | dql.append(" WHERE");
54 | dql.append(" ID = (SELECT MAX(ID) FROM ").append(table).append(")");
55 |
56 | PreparedStatement statement = connection.prepareStatement(dql.toString());
57 | ResultSet result = statement.executeQuery();
58 | // 一条数据也没有
59 | if (!result.next()) {
60 | return null;
61 | }
62 |
63 | SqlVersion version = new SqlVersion();
64 |
65 | version.setId(result.getInt("id"));
66 | version.setName(result.getString("name"));
67 | version.setVersion(result.getString("version"));
68 | version.setOrdinal(result.getInt("ordinal"));
69 | version.setDescription(result.getString("description"));
70 | version.setSqlQuantity(result.getInt("sqlQuantity"));
71 | version.setSuccess(result.getBoolean("success"));
72 | version.setRowEffected(result.getInt("rowEffected"));
73 | version.setErrorCode(result.getInt("errorCode"));
74 | version.setErrorState(result.getString("errorState"));
75 | version.setErrorMessage(result.getString("errorMessage"));
76 | version.setTimeExecuted(result.getTimestamp("timeExecuted"));
77 |
78 | return version;
79 | }
80 |
81 | @Override
82 | public void update(Connection connection, SqlVersion version) throws SQLException {
83 | StringBuilder dml = new StringBuilder();
84 |
85 | dml.append(" INSERT INTO ").append(table).append(" (");
86 | dml.append(" NAME,");
87 | dml.append(" VERSION,");
88 | dml.append(" ORDINAL,");
89 | dml.append(" DESCRIPTION,");
90 | dml.append(" SQL_QUANTITY,");
91 | dml.append(" SUCCESS,");
92 | dml.append(" ROW_EFFECTED,");
93 | dml.append(" ERROR_CODE,");
94 | dml.append(" ERROR_STATE,");
95 | dml.append(" ERROR_MESSAGE,");
96 | dml.append(" TIME_EXECUTED");
97 | dml.append(" )");
98 | dml.append(" VALUES");
99 | dml.append(" (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
100 |
101 | PreparedStatement statement = connection.prepareStatement(dml.toString());
102 | statement.setString(1, truncate(version.getName(), 225));
103 | statement.setString(2, truncate(version.getVersion(), 24));
104 | statement.setInt(3, version.getOrdinal());
105 | statement.setString(4, truncate(version.getDescription(), 128));
106 | statement.setInt(5, version.getSqlQuantity());
107 | statement.setBoolean(6, version.getSuccess());
108 | statement.setInt(7, version.getRowEffected());
109 | statement.setInt(8, version.getErrorCode());
110 | statement.setString(9, truncate(version.getErrorState(), 255));
111 | statement.setString(10, truncate(version.getErrorMessage(), 255));
112 | statement.setTimestamp(11, version.getTimeExecuted());
113 |
114 | statement.executeUpdate();
115 | }
116 |
117 | @Override
118 | public void remove(Connection connection) throws SQLException {
119 | connection.prepareStatement("DROP TABLE " + table + "").executeUpdate();
120 | }
121 |
122 | @Override
123 | public void lockup(Connection connection) throws SQLException {
124 | connection.prepareStatement("CREATE TABLE " + table + "_LOCK (NIL INTEGER)").executeUpdate();
125 | }
126 |
127 | @Override
128 | public void unlock(Connection connection) throws SQLException {
129 | connection.prepareStatement("DROP TABLE " + table + "_LOCK").executeUpdate();
130 | }
131 |
132 | @Override
133 | public void backup(Connection connection, SqlScript script, int ordinal) throws SQLException {
134 | final SqlSentence sentence = script.sentence(ordinal);
135 | final String schema = sentence.schema();
136 | final String table = sentence.table();
137 | final String name = isEmpty(schema) ? table : schema + "." + table;
138 | if (table == null || table.trim().isEmpty()) {
139 | return;
140 | }
141 | try {
142 | connection.prepareStatement("SELECT COUNT(*) FROM " + name).executeQuery();
143 | } catch (SQLException e) {
144 | return;
145 | }
146 | final String backup = unwrap(table) + "_bak_" + script.version().replace('.', '_') + "$" + ordinal;
147 | final String bak = isEmpty(schema) ? backup : schema + "." + backup;
148 | connection.prepareStatement("CREATE TABLE " + bak + " AS SELECT * FROM " + name).executeUpdate();
149 | }
150 |
151 | public String getTable() {
152 | return table;
153 | }
154 |
155 | public void setTable(String table) {
156 | this.table = table;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/dialect/MySQLDialectSupport.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 |
5 | import java.sql.Connection;
6 | import java.sql.PreparedStatement;
7 | import java.sql.SQLException;
8 |
9 | /**
10 | * MySQL方言
11 | *
12 | * @author Payne 646742615@qq.com
13 | * 2019/5/21 17:19
14 | */
15 | public class MySQLDialectSupport extends AbstractDialectSupport implements SqlDialectSupport {
16 |
17 | public MySQLDialectSupport() {
18 | }
19 |
20 | public MySQLDialectSupport(String table) {
21 | super(table);
22 | }
23 |
24 | @Override
25 | public void create(Connection connection) throws SQLException {
26 | StringBuilder ddl = new StringBuilder();
27 |
28 | ddl.append(" CREATE TABLE IF NOT EXISTS ").append(table).append(" (");
29 | ddl.append(" ID INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,");
30 | ddl.append(" NAME VARCHAR(255) NOT NULL,");
31 | ddl.append(" VERSION VARCHAR(24) NOT NULL,");
32 | ddl.append(" ORDINAL INT(11) NOT NULL,");
33 | ddl.append(" DESCRIPTION VARCHAR(128) NOT NULL,");
34 | ddl.append(" SQL_QUANTITY INT(11) NOT NULL,");
35 | ddl.append(" SUCCESS BIT(1) NOT NULL,");
36 | ddl.append(" ROW_EFFECTED INT(11) NOT NULL,");
37 | ddl.append(" ERROR_CODE INT(11) NOT NULL,");
38 | ddl.append(" ERROR_STATE VARCHAR(255) NOT NULL,");
39 | ddl.append(" ERROR_MESSAGE VARCHAR(255) NOT NULL,");
40 | ddl.append(" TIME_EXECUTED TIMESTAMP NOT NULL");
41 | ddl.append(" )");
42 |
43 | PreparedStatement statement = connection.prepareStatement(ddl.toString());
44 | statement.executeUpdate();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/dialect/OracleDialectSupport.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 |
5 | import java.sql.Connection;
6 | import java.sql.SQLException;
7 |
8 | /**
9 | * Oracle方言
10 | *
11 | * @author Payne 646742615@qq.com
12 | * 2019/5/26 10:19
13 | */
14 | public class OracleDialectSupport extends AbstractDialectSupport implements SqlDialectSupport {
15 |
16 | public OracleDialectSupport() {
17 | }
18 |
19 | public OracleDialectSupport(String table) {
20 | super(table);
21 | }
22 |
23 | @Override
24 | public void create(Connection connection) throws SQLException {
25 | StringBuilder ddl = new StringBuilder();
26 |
27 | ddl.append(" DECLARE EXISTED NUMBER;");
28 | ddl.append(" BEGIN");
29 | ddl.append(" SELECT COUNT(1) INTO EXISTED FROM USER_TABLES WHERE TABLE_NAME = UPPER('").append(table).append("');");
30 | ddl.append(" IF EXISTED = 0");
31 | ddl.append(" THEN");
32 | ddl.append(" EXECUTE IMMEDIATE");
33 | ddl.append(" 'CREATE TABLE ").append(table).append(" (");
34 | ddl.append(" ID INT NOT NULL PRIMARY KEY,");
35 | ddl.append(" NAME VARCHAR(255) NOT NULL,");
36 | ddl.append(" VERSION VARCHAR(24) NOT NULL,");
37 | ddl.append(" ORDINAL INT NOT NULL,");
38 | ddl.append(" DESCRIPTION VARCHAR(128) NOT NULL,");
39 | ddl.append(" SQL_QUANTITY INT NOT NULL,");
40 | ddl.append(" SUCCESS SMALLINT NOT NULL,");
41 | ddl.append(" ROW_EFFECTED INT NOT NULL,");
42 | ddl.append(" ERROR_CODE INT NOT NULL,");
43 | ddl.append(" ERROR_STATE VARCHAR(255) NOT NULL,");
44 | ddl.append(" ERROR_MESSAGE VARCHAR(255) NOT NULL,");
45 | ddl.append(" TIME_EXECUTED DATE NOT NULL");
46 | ddl.append(" )';");
47 | ddl.append(" EXECUTE IMMEDIATE");
48 | ddl.append(" 'CREATE SEQUENCE ").append(table).append("_SEQUENCE");
49 | ddl.append(" INCREMENT BY 1");
50 | ddl.append(" START WITH 1");
51 | ddl.append(" NOMAXVALUE");
52 | ddl.append(" NOMINVALUE");
53 | ddl.append(" NOCACHE';");
54 | ddl.append(" EXECUTE IMMEDIATE");
55 | ddl.append(" 'CREATE OR REPLACE TRIGGER ").append(table).append("_TRIGGER");
56 | ddl.append(" BEFORE INSERT");
57 | ddl.append(" ON ").append(table);
58 | ddl.append(" FOR EACH ROW");
59 | ddl.append(" BEGIN");
60 | ddl.append(" SELECT ").append(table).append("_SEQUENCE.NEXTVAL INTO :NEW.ID FROM DUAL;");
61 | ddl.append(" END;';");
62 | ddl.append(" END IF;");
63 | ddl.append(" END;");
64 |
65 | connection.prepareStatement(ddl.toString()).executeUpdate();
66 | }
67 |
68 | @Override
69 | public void remove(Connection connection) throws SQLException {
70 | connection.prepareStatement("DROP TRIGGER " + table + "_TRIGGER").executeUpdate();
71 | connection.prepareStatement("DROP SEQUENCE " + table + "_SEQUENCE").executeUpdate();
72 | super.remove(connection);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/dialect/SQLServerDialectSupport.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 | import io.sqlman.core.SqlScript;
5 | import io.sqlman.core.SqlSentence;
6 |
7 | import java.sql.Connection;
8 | import java.sql.SQLException;
9 |
10 | /**
11 | * SQLServer方言
12 | *
13 | * @author Payne 646742615@qq.com
14 | * 2019/5/26 9:08
15 | */
16 | public class SQLServerDialectSupport extends AbstractDialectSupport implements SqlDialectSupport {
17 |
18 | public SQLServerDialectSupport() {
19 | }
20 |
21 | public SQLServerDialectSupport(String table) {
22 | super(table);
23 | }
24 |
25 | @Override
26 | public void create(Connection connection) throws SQLException {
27 | StringBuilder ddl = new StringBuilder();
28 |
29 | ddl.append(" IF NOT EXISTS(");
30 | ddl.append(" SELECT *");
31 | ddl.append(" FROM SYSOBJECTS");
32 | ddl.append(" WHERE ID = OBJECT_ID('").append(table).append("')");
33 | ddl.append(" )");
34 | ddl.append(" CREATE TABLE ").append(table).append(" (");
35 | ddl.append(" ID INT NOT NULL PRIMARY KEY IDENTITY(1, 1),");
36 | ddl.append(" NAME VARCHAR(255) NOT NULL,");
37 | ddl.append(" VERSION VARCHAR(24) NOT NULL,");
38 | ddl.append(" ORDINAL INT NOT NULL,");
39 | ddl.append(" DESCRIPTION VARCHAR(128) NOT NULL,");
40 | ddl.append(" SQL_QUANTITY INT NOT NULL,");
41 | ddl.append(" SUCCESS BIT NOT NULL,");
42 | ddl.append(" ROW_EFFECTED INT NOT NULL,");
43 | ddl.append(" ERROR_CODE INT NOT NULL,");
44 | ddl.append(" ERROR_STATE VARCHAR(255) NOT NULL,");
45 | ddl.append(" ERROR_MESSAGE VARCHAR(255) NOT NULL,");
46 | ddl.append(" TIME_EXECUTED DATETIME NOT NULL");
47 | ddl.append(" )");
48 |
49 | connection.prepareStatement(ddl.toString()).executeUpdate();
50 | }
51 |
52 | @Override
53 | public void backup(Connection connection, SqlScript script, int ordinal) throws SQLException {
54 | SqlSentence sentence = script.sentence(ordinal);
55 | String table = sentence.table();
56 | if (table == null || table.trim().isEmpty()) {
57 | return;
58 | }
59 | try {
60 | connection.prepareStatement("SELECT COUNT(*) FROM " + table).executeQuery();
61 | } catch (SQLException e) {
62 | return;
63 | }
64 | table = table + "_bak_" + script.version().replace('.', '_') + "$" + ordinal;
65 | connection.prepareStatement("SELECT * INTO " + table + " FROM " + sentence.table()).executeUpdate();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/dialect/SQLiteDialectSupport.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 |
5 | import java.sql.Connection;
6 | import java.sql.SQLException;
7 |
8 | /**
9 | * SQLite方言
10 | *
11 | * @author Payne 646742615@qq.com
12 | * 2019/5/23 15:52
13 | */
14 | public class SQLiteDialectSupport extends AbstractDialectSupport implements SqlDialectSupport {
15 |
16 | public SQLiteDialectSupport() {
17 | }
18 |
19 | public SQLiteDialectSupport(String table) {
20 | super(table);
21 | }
22 |
23 | @Override
24 | public void create(Connection connection) throws SQLException {
25 | StringBuilder ddl = new StringBuilder();
26 |
27 | ddl.append(" CREATE TABLE IF NOT EXISTS ").append(table).append(" (");
28 | ddl.append(" ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,");
29 | ddl.append(" NAME VARCHAR(225) NOT NULL,");
30 | ddl.append(" VERSION VARCHAR(24) NOT NULL,");
31 | ddl.append(" ORDINAL INT(11) NOT NULL,");
32 | ddl.append(" DESCRIPTION VARCHAR(128) NOT NULL,");
33 | ddl.append(" SQL_QUANTITY INT(11) NOT NULL,");
34 | ddl.append(" SUCCESS BIT(1) NOT NULL,");
35 | ddl.append(" ROW_EFFECTED INT(11) NOT NULL,");
36 | ddl.append(" ERROR_CODE INT(11) NOT NULL,");
37 | ddl.append(" ERROR_STATE VARCHAR(255) NOT NULL,");
38 | ddl.append(" ERROR_MESSAGE VARCHAR(255) NOT NULL,");
39 | ddl.append(" TIME_EXECUTED TIMESTAMP NOT NULL");
40 | ddl.append(" )");
41 |
42 | connection.prepareStatement(ddl.toString()).executeUpdate();
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/exception/DuplicatedVersionException.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.exception;
2 |
3 | /**
4 | * 版本重复异常
5 | * 当SQL资源中包含重复的版本号时抛出
6 | *
7 | * @author Payne 646742615@qq.com
8 | * 2019/5/25 21:14
9 | */
10 | public class DuplicatedVersionException extends Exception {
11 | private final String version;
12 |
13 | public DuplicatedVersionException(String version) {
14 | this.version = version;
15 | }
16 |
17 | public DuplicatedVersionException(String message, String version) {
18 | super(message);
19 | this.version = version;
20 | }
21 |
22 | public DuplicatedVersionException(String message, Throwable cause, String version) {
23 | super(message, cause);
24 | this.version = version;
25 | }
26 |
27 | public DuplicatedVersionException(Throwable cause, String version) {
28 | super(cause);
29 | this.version = version;
30 | }
31 |
32 | public String getVersion() {
33 | return version;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/exception/IncorrectSyntaxException.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.exception;
2 |
3 | /**
4 | * 错误的语法异常
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 21:34
8 | */
9 | public class IncorrectSyntaxException extends RuntimeException {
10 |
11 | public IncorrectSyntaxException() {
12 | }
13 |
14 | public IncorrectSyntaxException(String message) {
15 | super(message);
16 | }
17 |
18 | public IncorrectSyntaxException(String message, Throwable cause) {
19 | super(message, cause);
20 | }
21 |
22 | public IncorrectSyntaxException(Throwable cause) {
23 | super(cause);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/exception/MalformedNameException.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.exception;
2 |
3 | /**
4 | * 不正确的SQL资源命名异常
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 21:22
8 | */
9 | public class MalformedNameException extends RuntimeException {
10 | private final String name;
11 |
12 | public MalformedNameException(String name) {
13 | this.name = name;
14 | }
15 |
16 | public MalformedNameException(String message, String name) {
17 | super(message);
18 | this.name = name;
19 | }
20 |
21 | public MalformedNameException(String message, Throwable cause, String name) {
22 | super(message, cause);
23 | this.name = name;
24 | }
25 |
26 | public MalformedNameException(Throwable cause, String name) {
27 | super(cause);
28 | this.name = name;
29 | }
30 |
31 | public String getName() {
32 | return name;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/logger/AbstractLoggerSupplier.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.logger;
2 |
3 | import io.sqlman.core.SqlLogger;
4 | import io.sqlman.core.SqlLoggerSupplier;
5 |
6 | /**
7 | * 抽象的日志记录器供应商
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/27 11:28
11 | */
12 | public abstract class AbstractLoggerSupplier implements SqlLoggerSupplier {
13 |
14 | @Override
15 | public SqlLogger supply(Class> clazz) {
16 | return supply(clazz == null ? null : clazz.getName());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/logger/Slf4jLogger.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.logger;
2 |
3 | import io.sqlman.core.SqlLogger;
4 | import org.slf4j.Logger;
5 |
6 | /**
7 | * Slf4j日志记录器
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/27 11:20
11 | */
12 | public class Slf4jLogger implements SqlLogger {
13 | private final Logger logger;
14 | private final SqlLogger.Level level;
15 |
16 | public Slf4jLogger(Logger logger) {
17 | this(logger, Level.INFO);
18 | }
19 |
20 | public Slf4jLogger(Logger logger, SqlLogger.Level level) {
21 | if (logger == null) {
22 | throw new IllegalArgumentException("logger must not be null");
23 | }
24 | if (level == null) {
25 | throw new IllegalArgumentException("level must not be null");
26 | }
27 | this.logger = logger;
28 | this.level = level;
29 | }
30 |
31 | @Override
32 | public String getName() {
33 | return logger.getName();
34 | }
35 |
36 | @Override
37 | public Level getLevel() {
38 | return level;
39 | }
40 |
41 |
42 | @Override
43 | public boolean isTraceEnabled() {
44 | return Level.TRACE.compareTo(level) >= 0 && logger.isTraceEnabled();
45 | }
46 |
47 | @Override
48 | public void trace(String msg) {
49 | if (isTraceEnabled()) {
50 | logger.trace(msg);
51 | }
52 | }
53 |
54 | @Override
55 | public void trace(String format, Object... arguments) {
56 | if (isTraceEnabled()) {
57 | logger.trace(format, arguments);
58 | }
59 | }
60 |
61 | @Override
62 | public void trace(String msg, Throwable t) {
63 | if (isTraceEnabled()) {
64 | logger.trace(msg, t);
65 | }
66 | }
67 |
68 |
69 | @Override
70 | public boolean isDebugEnabled() {
71 | return Level.DEBUG.compareTo(level) >= 0 && logger.isDebugEnabled();
72 | }
73 |
74 | @Override
75 | public void debug(String msg) {
76 | if (isDebugEnabled()) {
77 | logger.debug(msg);
78 | }
79 | }
80 |
81 | @Override
82 | public void debug(String format, Object... arguments) {
83 | if (isDebugEnabled()) {
84 | logger.debug(format, arguments);
85 | }
86 | }
87 |
88 | @Override
89 | public void debug(String msg, Throwable t) {
90 | if (isDebugEnabled()) {
91 | logger.debug(msg, t);
92 | }
93 | }
94 |
95 |
96 | @Override
97 | public boolean isInfoEnabled() {
98 | return Level.INFO.compareTo(level) >= 0 && logger.isInfoEnabled();
99 | }
100 |
101 | @Override
102 | public void info(String msg) {
103 | if (isInfoEnabled()) {
104 | logger.info(msg);
105 | }
106 | }
107 |
108 | @Override
109 | public void info(String format, Object... arguments) {
110 | if (isInfoEnabled()) {
111 | logger.info(format, arguments);
112 | }
113 | }
114 |
115 | @Override
116 | public void info(String msg, Throwable t) {
117 | if (isInfoEnabled()) {
118 | logger.info(msg, t);
119 | }
120 | }
121 |
122 |
123 | @Override
124 | public boolean isWarnEnabled() {
125 | return Level.WARN.compareTo(level) >= 0 && logger.isWarnEnabled();
126 | }
127 |
128 | @Override
129 | public void warn(String msg) {
130 | if (isWarnEnabled()) {
131 | logger.warn(msg);
132 | }
133 | }
134 |
135 | @Override
136 | public void warn(String format, Object... arguments) {
137 | if (isWarnEnabled()) {
138 | logger.warn(format, arguments);
139 | }
140 | }
141 |
142 | @Override
143 | public void warn(String msg, Throwable t) {
144 | if (isWarnEnabled()) {
145 | logger.warn(msg, t);
146 | }
147 | }
148 |
149 |
150 | @Override
151 | public boolean isErrorEnabled() {
152 | return Level.ERROR.compareTo(level) >= 0 && logger.isErrorEnabled();
153 | }
154 |
155 | @Override
156 | public void error(String msg) {
157 | if (isErrorEnabled()) {
158 | logger.error(msg);
159 | }
160 | }
161 |
162 | @Override
163 | public void error(String format, Object... arguments) {
164 | if (isErrorEnabled()) {
165 | logger.error(format, arguments);
166 | }
167 | }
168 |
169 | @Override
170 | public void error(String msg, Throwable t) {
171 | if (isErrorEnabled()) {
172 | logger.error(msg, t);
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/logger/Slf4jLoggerSupplier.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.logger;
2 |
3 | import io.sqlman.core.SqlLogger;
4 | import io.sqlman.core.SqlLoggerSupplier;
5 | import org.slf4j.ILoggerFactory;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | /**
10 | * Slf4j日志记录器供应商
11 | *
12 | * @author Payne 646742615@qq.com
13 | * 2019/5/27 11:19
14 | */
15 | public class Slf4jLoggerSupplier extends AbstractLoggerSupplier implements SqlLoggerSupplier {
16 | private ILoggerFactory loggerFactory;
17 | private SqlLogger.Level level;
18 |
19 | public Slf4jLoggerSupplier() {
20 | this(LoggerFactory.getILoggerFactory(), SqlLogger.Level.INFO);
21 | }
22 |
23 | public Slf4jLoggerSupplier(ILoggerFactory loggerFactory) {
24 | this(loggerFactory, SqlLogger.Level.INFO);
25 | }
26 |
27 | public Slf4jLoggerSupplier(SqlLogger.Level level) {
28 | this(LoggerFactory.getILoggerFactory(), level);
29 | }
30 |
31 | public Slf4jLoggerSupplier(ILoggerFactory loggerFactory, SqlLogger.Level level) {
32 | if (loggerFactory == null) {
33 | throw new IllegalArgumentException("loggerFactory must not be null");
34 | }
35 | if (level == null) {
36 | throw new IllegalArgumentException("level must not be null");
37 | }
38 | this.loggerFactory = loggerFactory;
39 | this.level = level;
40 | }
41 |
42 | @Override
43 | public SqlLogger supply(String name) {
44 | Logger logger = loggerFactory.getLogger(name);
45 | return new Slf4jLogger(logger, level);
46 | }
47 |
48 | public ILoggerFactory getLoggerFactory() {
49 | return loggerFactory;
50 | }
51 |
52 | public void setLoggerFactory(ILoggerFactory loggerFactory) {
53 | this.loggerFactory = loggerFactory;
54 | }
55 |
56 | public SqlLogger.Level getLevel() {
57 | return level;
58 | }
59 |
60 | public void setLevel(SqlLogger.Level level) {
61 | this.level = level;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/naming/StandardNamingStrategy.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.naming;
2 |
3 | import io.sqlman.core.SqlNaming;
4 | import io.sqlman.core.SqlNamingStrategy;
5 | import io.sqlman.core.exception.MalformedNameException;
6 |
7 | import java.util.LinkedHashSet;
8 | import java.util.Set;
9 |
10 | /**
11 | * 标准的命名策略
12 | *
13 | * @author Payne 646742615@qq.com
14 | * 2019/5/24 22:16
15 | */
16 | public class StandardNamingStrategy implements SqlNamingStrategy {
17 | private char separator = '/';
18 | private String splitter = "-";
19 | private String delimiter = "!";
20 | private String extension = ".sql";
21 |
22 | public StandardNamingStrategy() {
23 | }
24 |
25 | public StandardNamingStrategy(char separator, String splitter, String delimiter, String extension) {
26 | if (delimiter == null || delimiter.trim().isEmpty()) {
27 | throw new IllegalArgumentException("delimiter must not be null or blank string");
28 | }
29 | if (splitter == null || splitter.trim().isEmpty()) {
30 | throw new IllegalArgumentException("splitter must not be null or blank string");
31 | }
32 | if (extension == null || extension.trim().isEmpty()) {
33 | throw new IllegalArgumentException("extension must not be null or blank string");
34 | }
35 | this.separator = separator;
36 | this.splitter = splitter.trim();
37 | this.delimiter = delimiter.trim();
38 | this.extension = extension.trim();
39 | }
40 |
41 | @Override
42 | public boolean check(String name) {
43 | if (name == null) {
44 | return false;
45 | }
46 | if (!name.endsWith(extension)) {
47 | return false;
48 | }
49 |
50 | // 去掉后缀
51 | String n = name.substring(0, name.length() - extension.length());
52 |
53 | // 取最后一截
54 | n = n.substring(n.lastIndexOf(separator) + 1);
55 |
56 | // 分隔符前面的是版本号 + 参数后面是描述,描述可以没有
57 | int index = n.lastIndexOf(delimiter);
58 | String value = index < 0 ? n : n.substring(0, index);
59 |
60 | // 拆分符前面是版本号后面是参数
61 | index = value.indexOf(splitter);
62 | String version = index < 0 ? value : value.substring(0, index);
63 |
64 | return version.matches("v?\\d+(\\.\\d+)*");
65 | }
66 |
67 | @Override
68 | public SqlNaming parse(String name) throws MalformedNameException {
69 | if (!check(name)) {
70 | throw new MalformedNameException("invalid name : " + name, name);
71 | }
72 | // 去掉后缀
73 | String n = name.substring(0, name.length() - extension.length());
74 |
75 | // 取最后一截
76 | n = n.substring(n.lastIndexOf(separator) + 1);
77 |
78 | // 分隔符前面的是版本号 + 参数后面是描述,描述可以没有
79 | int index = n.lastIndexOf(delimiter);
80 | String value = index < 0 ? n : n.substring(0, index);
81 | String description = index < 0 ? "" : n.substring(index + delimiter.length());
82 |
83 | // 拆分符前面是版本号后面是参数
84 | index = value.indexOf(splitter);
85 | String version = index < 0 ? value : value.substring(0, index);
86 | String parameter = index < 0 ? "" : value.substring(index + splitter.length());
87 | Set parameters = new LinkedHashSet<>();
88 | while ((index = parameter.indexOf(splitter)) > 0) {
89 | parameters.add(parameter.substring(0, index).trim().toUpperCase());
90 | parameter = parameter.substring(index + splitter.length());
91 | }
92 | if (!parameter.trim().isEmpty()) {
93 | parameters.add(parameter.trim().toUpperCase());
94 | }
95 |
96 | return new SqlNaming(name, version, parameters, description);
97 | }
98 |
99 | /**
100 | * 版本号对比
101 | *
102 | * @param aVer 版本a
103 | * @param bVer 版本b
104 | * @return 如果{@code a == b} 则返回 {@code 0},如果{@code a < b} 则返回 {@code -1} 否则返回 {@code 1}
105 | */
106 | @Override
107 | public int compare(String aVer, String bVer) {
108 | if (aVer.startsWith("v") || aVer.startsWith("V")) {
109 | aVer = aVer.substring(1);
110 | }
111 | if (bVer.startsWith("v") || bVer.startsWith("V")) {
112 | bVer = bVer.substring(1);
113 | }
114 | String[] as = aVer.split("\\.");
115 | String[] bs = bVer.split("\\.");
116 | for (int i = 0; i < as.length && i < bs.length; i++) {
117 | int l = Integer.valueOf(as[i]);
118 | int r = Integer.valueOf(bs[i]);
119 | int comparision = Integer.compare(l, r);
120 | if (comparision != 0) {
121 | return comparision;
122 | }
123 | }
124 | return Integer.compare(as.length, bs.length);
125 | }
126 |
127 | public char getSeparator() {
128 | return separator;
129 | }
130 |
131 | public void setSeparator(char separator) {
132 | this.separator = separator;
133 | }
134 |
135 | public String getSplitter() {
136 | return splitter;
137 | }
138 |
139 | public void setSplitter(String splitter) {
140 | this.splitter = splitter;
141 | }
142 |
143 | public String getDelimiter() {
144 | return delimiter;
145 | }
146 |
147 | public void setDelimiter(String delimiter) {
148 | this.delimiter = delimiter;
149 | }
150 |
151 | public String getExtension() {
152 | return extension;
153 | }
154 |
155 | public void setExtension(String extension) {
156 | this.extension = extension;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/script/DruidScript.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.script;
2 |
3 | import io.sqlman.core.SqlScript;
4 | import io.sqlman.core.SqlSentence;
5 |
6 | import java.util.*;
7 |
8 | /**
9 | * 基于druid的SQL脚本
10 | *
11 | * @author Payne 646742615@qq.com
12 | * 2019/5/22 10:44
13 | */
14 | public class DruidScript implements SqlScript {
15 | private final String name;
16 | private final String version;
17 | private final Set instructions;
18 | private final String description;
19 | private final List sentences;
20 |
21 | public DruidScript(String name, String version, Set instructions, String description, List sentences) {
22 | if (version == null || version.isEmpty()) {
23 | throw new IllegalArgumentException("version must not be null or empty string");
24 | }
25 | if (sentences == null || sentences.isEmpty()) {
26 | throw new IllegalArgumentException("sentences must not be null or empty list");
27 | }
28 | this.name = name;
29 | this.version = version;
30 | this.instructions = instructions;
31 | this.description = description;
32 | this.sentences = sentences;
33 | }
34 |
35 | @Override
36 | public int sqls() {
37 | return sentences.size();
38 | }
39 |
40 | @Override
41 | public SqlSentence sentence(int ordinal) {
42 | return sentences.get(ordinal - 1);
43 | }
44 |
45 | @Override
46 | public Enumeration sentences() {
47 | return Collections.enumeration(sentences);
48 | }
49 |
50 | @Override
51 | public String name() {
52 | return name;
53 | }
54 |
55 | @Override
56 | public String version() {
57 | return version;
58 | }
59 |
60 | @Override
61 | public Set instructions() {
62 | return instructions;
63 | }
64 |
65 | @Override
66 | public String description() {
67 | return description;
68 | }
69 |
70 | @Override
71 | public boolean equals(Object o) {
72 | if (this == o) {
73 | return true;
74 | }
75 | if (o == null || getClass() != o.getClass()) {
76 | return false;
77 | }
78 | DruidScript that = (DruidScript) o;
79 | return Objects.equals(version, that.version);
80 | }
81 |
82 | @Override
83 | public int hashCode() {
84 | return Objects.hash(version);
85 | }
86 |
87 | @Override
88 | public String toString() {
89 | return version;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/script/DruidScriptResolver.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.script;
2 |
3 | import com.alibaba.druid.sql.SQLUtils;
4 | import com.alibaba.druid.sql.ast.SQLObject;
5 | import com.alibaba.druid.sql.ast.SQLStatement;
6 | import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
7 | import com.alibaba.druid.sql.parser.ParserException;
8 | import com.alibaba.druid.util.JdbcUtils;
9 | import io.sqlman.core.*;
10 | import io.sqlman.core.exception.IncorrectSyntaxException;
11 |
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * 基于druid的SQL脚本解析
19 | *
20 | * @author Payne 646742615@qq.com
21 | * 2019/5/22 10:42
22 | */
23 | public class DruidScriptResolver implements SqlScriptResolver {
24 | private String dialect = JdbcUtils.MYSQL;
25 | private String charset = "UTF-8";
26 |
27 | public DruidScriptResolver() {
28 | }
29 |
30 | public DruidScriptResolver(String dialect) {
31 | if (dialect == null || dialect.trim().isEmpty()) {
32 | throw new IllegalArgumentException("dialect must not be null or blank string");
33 | }
34 | this.dialect = dialect;
35 | }
36 |
37 | public DruidScriptResolver(String dialect, String charset) {
38 | if (dialect == null || dialect.trim().isEmpty()) {
39 | throw new IllegalArgumentException("dialect must not be null or blank string");
40 | }
41 | if (charset == null || charset.trim().isEmpty()) {
42 | throw new IllegalArgumentException("charset must not be null or blank string");
43 | }
44 | this.dialect = dialect;
45 | this.charset = charset;
46 | }
47 |
48 | @Override
49 | public SqlScript resolve(SqlSource source) throws IncorrectSyntaxException, IOException {
50 | try (InputStream in = source.open()) {
51 | String text = SqlUtils.stringify(in, charset);
52 | List statements = SQLUtils.parseStatements(text, dialect.toLowerCase());
53 | List sentences = new ArrayList<>(statements.size());
54 | for (int index = 0; index < statements.size(); index++) {
55 | SQLStatement statement = statements.get(index);
56 | String sql = statement.toString();
57 | sql = sql.trim();
58 | String suffix = ";";
59 | while (sql.endsWith(suffix)) {
60 | sql = sql.substring(0, sql.length() - 1);
61 | }
62 | String schema = null;
63 | String table = null;
64 | List children = statement.getChildren();
65 | for (SQLObject child : children) {
66 | if (child instanceof SQLExprTableSource) {
67 | SQLExprTableSource tableSource = (SQLExprTableSource) child;
68 | String name = tableSource.getName().toString();
69 | String[] names = name.split("\\.");
70 | schema = names.length > 1 ? names[names.length - 2] : null;
71 | table = names[names.length - 1];
72 | break;
73 | }
74 | }
75 | SqlSentence sentence = new DruidSentence(index + 1, sql, schema, table);
76 | sentences.add(sentence);
77 | }
78 | return new DruidScript(source.name(), source.version(), source.parameters(), source.description(), sentences);
79 | } catch (ParserException ex) {
80 | throw new IncorrectSyntaxException(ex);
81 | }
82 | }
83 |
84 | public String getDialect() {
85 | return dialect;
86 | }
87 |
88 | public void setDialect(String dialect) {
89 | this.dialect = dialect;
90 | }
91 |
92 | public String getCharset() {
93 | return charset;
94 | }
95 |
96 | public void setCharset(String charset) {
97 | this.charset = charset;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/script/DruidSentence.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.script;
2 |
3 | import io.sqlman.core.SqlSentence;
4 |
5 | /**
6 | * 基于druid的SQL脚本语句
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/22 11:15
10 | */
11 | public class DruidSentence implements SqlSentence {
12 | private final int ordinal;
13 | private final String value;
14 | private final String schema;
15 | private final String table;
16 |
17 | public DruidSentence(int ordinal, String value, String schema, String table) {
18 | if (ordinal < 1) {
19 | throw new IllegalArgumentException("ordinal must not lesser than 1");
20 | }
21 | if (value == null || value.trim().isEmpty()) {
22 | throw new IllegalArgumentException("value must not be null or blank string");
23 | }
24 | this.ordinal = ordinal;
25 | this.value = value;
26 | this.schema = schema;
27 | this.table = table;
28 | }
29 |
30 | @Override
31 | public int ordinal() {
32 | return ordinal;
33 | }
34 |
35 | @Override
36 | public String value() {
37 | return value;
38 | }
39 |
40 | @Override
41 | public String schema() {
42 | return schema;
43 | }
44 |
45 | @Override
46 | public String table() {
47 | return table;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return value;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/source/AbstractSourceProvider.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.source;
2 |
3 | import io.sqlman.core.SqlNaming;
4 | import io.sqlman.core.SqlNamingStrategy;
5 | import io.sqlman.core.SqlSourceProvider;
6 | import io.sqlman.core.exception.MalformedNameException;
7 | import io.sqlman.core.naming.StandardNamingStrategy;
8 |
9 | /**
10 | * 抽象的SQL脚本资源提供器
11 | *
12 | * @author Payne 646742615@qq.com
13 | * 2019/5/25 20:57
14 | */
15 | public abstract class AbstractSourceProvider implements SqlSourceProvider {
16 | protected SqlNamingStrategy namingStrategy = new StandardNamingStrategy();
17 |
18 | protected AbstractSourceProvider() {
19 | }
20 |
21 | protected AbstractSourceProvider(SqlNamingStrategy namingStrategy) {
22 | if (namingStrategy == null) {
23 | throw new IllegalArgumentException("namingStrategy must not be null");
24 | }
25 | this.namingStrategy = namingStrategy;
26 | }
27 |
28 | @Override
29 | public boolean check(String name) {
30 | return namingStrategy.check(name);
31 | }
32 |
33 | @Override
34 | public SqlNaming parse(String name) throws MalformedNameException {
35 | return namingStrategy.parse(name);
36 | }
37 |
38 | @Override
39 | public int compare(String o1, String o2) {
40 | return namingStrategy.compare(o1, o2);
41 | }
42 |
43 | public SqlNamingStrategy getNamingStrategy() {
44 | return namingStrategy;
45 | }
46 |
47 | public void setNamingStrategy(SqlNamingStrategy namingStrategy) {
48 | this.namingStrategy = namingStrategy;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/source/ClasspathSource.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.source;
2 |
3 | import io.sqlman.core.SqlSource;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.net.URL;
8 | import java.util.Set;
9 |
10 |
11 | /**
12 | * 标准脚本资源
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/5/24 17:57
16 | */
17 | public class ClasspathSource implements SqlSource {
18 | private final String name;
19 | private final String version;
20 | private final Set parameters;
21 | private final String description;
22 | private final URL url;
23 |
24 | public ClasspathSource(String name, String version, Set parameters, String description, URL url) {
25 | if (name == null) {
26 | throw new IllegalArgumentException("name must not be null");
27 | }
28 | if (version == null) {
29 | throw new IllegalArgumentException("version must not be null");
30 | }
31 | if (parameters == null) {
32 | throw new IllegalArgumentException("parameters must not be null");
33 | }
34 | if (description == null) {
35 | throw new IllegalArgumentException("description must not be null");
36 | }
37 | if (url == null) {
38 | throw new IllegalArgumentException("url must not be null");
39 | }
40 | this.name = name;
41 | this.version = version;
42 | this.parameters = parameters;
43 | this.description = description;
44 | this.url = url;
45 | }
46 |
47 | @Override
48 | public String name() {
49 | return name;
50 | }
51 |
52 | @Override
53 | public String version() {
54 | return version;
55 | }
56 |
57 | @Override
58 | public Set parameters() {
59 | return parameters;
60 | }
61 |
62 | @Override
63 | public String description() {
64 | return description;
65 | }
66 |
67 | @Override
68 | public InputStream open() throws IOException {
69 | return url.openStream();
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return url.toString();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/source/ClasspathSourceProvider.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.source;
2 |
3 | import io.loadkit.Loaders;
4 | import io.loadkit.Resource;
5 | import io.sqlman.core.SqlNaming;
6 | import io.sqlman.core.SqlNamingStrategy;
7 | import io.sqlman.core.SqlSource;
8 | import io.sqlman.core.SqlSourceProvider;
9 | import io.sqlman.core.exception.DuplicatedVersionException;
10 | import io.sqlman.core.exception.MalformedNameException;
11 |
12 | import java.io.IOException;
13 | import java.util.*;
14 |
15 | /**
16 | * 标准脚本资源提供器
17 | *
18 | * @author Payne 646742615@qq.com
19 | * 2019/5/22 10:02
20 | */
21 | public class ClasspathSourceProvider extends AbstractSourceProvider implements SqlSourceProvider {
22 | private ClassLoader classLoader;
23 | private String scriptLocation = "sqlman/**/*.sql";
24 |
25 | public ClasspathSourceProvider() {
26 | super();
27 | }
28 |
29 | public ClasspathSourceProvider(String scriptLocation) {
30 | this(null, scriptLocation);
31 | }
32 |
33 | public ClasspathSourceProvider(String scriptLocation, SqlNamingStrategy namingStrategy) {
34 | this(null, scriptLocation, namingStrategy);
35 | }
36 |
37 | public ClasspathSourceProvider(ClassLoader classLoader, String scriptLocation) {
38 | if (scriptLocation == null || scriptLocation.trim().isEmpty()) {
39 | throw new IllegalArgumentException("scriptLocation must not be null or blank string");
40 | }
41 | this.classLoader = classLoader;
42 | this.scriptLocation = scriptLocation;
43 | }
44 |
45 | public ClasspathSourceProvider(ClassLoader classLoader, String scriptLocation, SqlNamingStrategy namingStrategy) {
46 | super(namingStrategy);
47 | if (scriptLocation == null || scriptLocation.trim().isEmpty()) {
48 | throw new IllegalArgumentException("scriptLocation must not be null or blank string");
49 | }
50 | this.classLoader = classLoader;
51 | this.scriptLocation = scriptLocation;
52 | }
53 |
54 | @Override
55 | public Enumeration acquire() throws MalformedNameException, DuplicatedVersionException, IOException {
56 | ClassLoader resourceLoader = classLoader;
57 | if (resourceLoader == null) {
58 | resourceLoader = Thread.currentThread().getContextClassLoader();
59 | }
60 | if (resourceLoader == null) {
61 | resourceLoader = this.getClass().getClassLoader();
62 | }
63 | Enumeration resources = Loaders.ant(resourceLoader).load(scriptLocation);
64 | Set sources = new TreeSet<>(new Comparator() {
65 | @Override
66 | public int compare(SqlSource o1, SqlSource o2) {
67 | return namingStrategy.compare(o1.version(), o2.version());
68 | }
69 | });
70 | while (resources.hasMoreElements()) {
71 | Resource resource = resources.nextElement();
72 | String name = resource.getName();
73 | SqlNaming naming = namingStrategy.parse(name);
74 | SqlSource source = new ClasspathSource(naming.getName(), naming.getVersion(), naming.getParameters(), naming.getDescription(), resource.getUrl());
75 | if (!sources.add(source)) {
76 | throw new DuplicatedVersionException("duplicate SQL script version: " + source.version(), source.version());
77 | }
78 | }
79 | return Collections.enumeration(sources);
80 | }
81 |
82 | @Override
83 | public Enumeration acquire(String version, boolean included) throws MalformedNameException, DuplicatedVersionException, IOException {
84 | ClassLoader resourceLoader = classLoader;
85 | if (resourceLoader == null) {
86 | resourceLoader = Thread.currentThread().getContextClassLoader();
87 | }
88 | if (resourceLoader == null) {
89 | resourceLoader = this.getClass().getClassLoader();
90 | }
91 | Enumeration resources = Loaders.ant(resourceLoader).load(scriptLocation);
92 | Set sources = new TreeSet<>(new Comparator() {
93 | @Override
94 | public int compare(SqlSource o1, SqlSource o2) {
95 | return namingStrategy.compare(o1.version(), o2.version());
96 | }
97 | });
98 | while (resources.hasMoreElements()) {
99 | Resource resource = resources.nextElement();
100 | String name = resource.getName();
101 | SqlNaming naming = namingStrategy.parse(name);
102 | int comparision = namingStrategy.compare(naming.getVersion(), version);
103 | SqlSource source = new ClasspathSource(naming.getName(), naming.getVersion(), naming.getParameters(), naming.getDescription(), resource.getUrl());
104 | boolean newer = comparision > 0 || (comparision == 0 && included);
105 | if (newer && !sources.add(source)) {
106 | throw new DuplicatedVersionException("duplicate SQL script version: " + source.version(), source.version());
107 | }
108 | }
109 | return Collections.enumeration(sources);
110 | }
111 |
112 | public ClassLoader getClassLoader() {
113 | return classLoader;
114 | }
115 |
116 | public void setClassLoader(ClassLoader classLoader) {
117 | this.classLoader = classLoader;
118 | }
119 |
120 | public String getScriptLocation() {
121 | return scriptLocation;
122 | }
123 |
124 | public void setScriptLocation(String scriptLocation) {
125 | this.scriptLocation = scriptLocation;
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/AbstractManagerProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring;
2 |
3 | /**
4 | * 抽象SQL脚本版本管理器配置属性
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 9:39
8 | */
9 | public class AbstractManagerProperties {
10 | /**
11 | * whether the sqlman should be enabled
12 | */
13 | private boolean enabled = true;
14 | /**
15 | * sqlman implementation name
16 | */
17 | private String manager = "jdbc";
18 |
19 | public boolean isEnabled() {
20 | return enabled;
21 | }
22 |
23 | public void setEnabled(boolean enabled) {
24 | this.enabled = enabled;
25 | }
26 |
27 | public String getManager() {
28 | return manager;
29 | }
30 |
31 | public void setManager(String manager) {
32 | this.manager = manager;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/JdbcManagerConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring;
2 |
3 | import io.sqlman.core.*;
4 | import io.sqlman.core.version.JdbcIsolation;
5 | import io.sqlman.core.version.JdbcMode;
6 | import io.sqlman.core.version.JdbcVersionManager;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
10 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
11 | import org.springframework.context.ApplicationContext;
12 | import org.springframework.context.annotation.Bean;
13 | import org.springframework.context.annotation.Configuration;
14 |
15 | import javax.annotation.Resource;
16 | import javax.sql.DataSource;
17 | import java.sql.SQLException;
18 | import java.util.Map;
19 |
20 | /**
21 | * 基础SQL脚本版本管理器自动配置
22 | *
23 | * @author Payne 646742615@qq.com
24 | * 2019/5/25 9:43
25 | */
26 | @Configuration
27 | @EnableConfigurationProperties(JdbcManagerProperties.class)
28 | @ConditionalOnClass(JdbcVersionManager.class)
29 | @ConditionalOnProperty(prefix = "sqlman", name = "manager", havingValue = "jdbc", matchIfMissing = true)
30 | public class JdbcManagerConfiguration {
31 |
32 | @Resource
33 | private JdbcManagerProperties properties;
34 |
35 | @Resource
36 | private SqlSourceProvider scriptProvider;
37 |
38 | @Resource
39 | private SqlScriptResolver scriptResolver;
40 |
41 | @Resource
42 | private SqlDialectSupport dialectSupport;
43 |
44 | @Resource
45 | private SqlLoggerSupplier loggerSupplier;
46 |
47 | @Bean
48 | @ConditionalOnMissingBean(SqlVersionManager.class)
49 | @ConditionalOnProperty(prefix = "sqlman", name = "enabled", havingValue = "true", matchIfMissing = true)
50 | public JdbcVersionManager sqlmanJdbcVersionManager(ApplicationContext applicationContext) throws SQLException {
51 | Map map = applicationContext.getBeansOfType(DataSource.class);
52 | if (map.isEmpty()) {
53 | throw new IllegalStateException("no dataSource found in application context");
54 | }
55 | DataSource dataSource = map.size() == 1 ? map.values().iterator().next() : map.get(properties.getDataSource());
56 | if (dataSource == null) {
57 | throw new IllegalStateException("no dataSource found in application context named: " + properties.getDataSource());
58 | }
59 | JdbcIsolation defaultIsolation = properties.getDefaultIsolation();
60 | JdbcMode defaultMode = properties.getDefaultMode();
61 | JdbcVersionManager jdbcVersionManager = new JdbcVersionManager(
62 | dataSource,
63 | scriptProvider,
64 | scriptResolver,
65 | dialectSupport,
66 | loggerSupplier,
67 | defaultIsolation,
68 | defaultMode
69 | );
70 | jdbcVersionManager.upgrade();
71 | return jdbcVersionManager;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/JdbcManagerProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring;
2 |
3 | import io.sqlman.core.version.JdbcIsolation;
4 | import io.sqlman.core.version.JdbcMode;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 |
7 | /**
8 | * 基础SQL脚本版本管理器配置属性
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/5/25 9:40
12 | */
13 | @ConfigurationProperties(prefix = "sqlman")
14 | public class JdbcManagerProperties extends AbstractManagerProperties {
15 | /**
16 | * the dataSource bean name. if your application has more than one dataSources
17 | */
18 | private String dataSource = "dataSource";
19 |
20 | /**
21 | * default transaction isolation level
22 | */
23 | private JdbcIsolation defaultIsolation;
24 |
25 | /**
26 | * default mode: SAFETY or DANGER which means backup or not backup before the sql sentence being execute
27 | */
28 | private JdbcMode defaultMode;
29 |
30 | public String getDataSource() {
31 | return dataSource;
32 | }
33 |
34 | public void setDataSource(String dataSource) {
35 | this.dataSource = dataSource;
36 | }
37 |
38 | public JdbcIsolation getDefaultIsolation() {
39 | return defaultIsolation;
40 | }
41 |
42 | public void setDefaultIsolation(JdbcIsolation defaultIsolation) {
43 | this.defaultIsolation = defaultIsolation;
44 | }
45 |
46 | public JdbcMode getDefaultMode() {
47 | return defaultMode;
48 | }
49 |
50 | public void setDefaultMode(JdbcMode defaultMode) {
51 | this.defaultMode = defaultMode;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/AbstractDialectProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | /**
4 | * 抽象的方言配置属性
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 8:28
8 | */
9 | public abstract class AbstractDialectProperties {
10 | /**
11 | * dataSource dialect. MySQL, SQLite, Oracle, SQLServer, eg.
12 | */
13 | private String type = "MySQL";
14 | /**
15 | * sqlman version table name
16 | */
17 | private String table = "SCHEMA_VERSION";
18 |
19 | public String getType() {
20 | return type;
21 | }
22 |
23 | public void setType(String type) {
24 | this.type = type;
25 | }
26 |
27 | public String getTable() {
28 | return table;
29 | }
30 |
31 | public void setTable(String table) {
32 | this.table = table;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/MySQLDialectConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 | import io.sqlman.core.dialect.MySQLDialectSupport;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * MySQL方言自动配置
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/25 8:31
19 | */
20 | @Configuration
21 | @EnableConfigurationProperties(MySQLDialectProperties.class)
22 | @ConditionalOnClass(MySQLDialectSupport.class)
23 | @ConditionalOnProperty(prefix = "sqlman.dialect", name = "type", havingValue = "MySQL", matchIfMissing = true)
24 | public class MySQLDialectConfiguration {
25 |
26 | @Resource
27 | private MySQLDialectProperties properties;
28 |
29 | @Bean
30 | @ConditionalOnMissingBean(SqlDialectSupport.class)
31 | public MySQLDialectSupport sqlmanMySQLDialectSupport() {
32 | String table = properties.getTable();
33 | return new MySQLDialectSupport(table);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/MySQLDialectProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * MySQL方言配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:19
10 | */
11 | @ConfigurationProperties("sqlman.dialect")
12 | public class MySQLDialectProperties extends AbstractDialectProperties {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/OracleDialectConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 | import io.sqlman.core.dialect.OracleDialectSupport;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * Oracle方言自动配置
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/25 8:38
19 | */
20 | @Configuration
21 | @EnableConfigurationProperties(OracleDialectProperties.class)
22 | @ConditionalOnClass(OracleDialectSupport.class)
23 | @ConditionalOnProperty(prefix = "sqlman.dialect", name = "type", havingValue = "Oracle")
24 | public class OracleDialectConfiguration {
25 |
26 | @Resource
27 | private OracleDialectProperties properties;
28 |
29 | @Bean
30 | @ConditionalOnMissingBean(SqlDialectSupport.class)
31 | public OracleDialectSupport sqlmanOracleDialectSupport() {
32 | String table = properties.getTable();
33 | return new OracleDialectSupport(table);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/OracleDialectProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * Oracle方言配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:29
10 | */
11 | @ConfigurationProperties("sqlman.dialect")
12 | public class OracleDialectProperties extends AbstractDialectProperties {
13 | }
14 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/SQLServerDialectConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 | import io.sqlman.core.dialect.SQLServerDialectSupport;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * SQLServer方言自动配置
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/25 8:38
19 | */
20 | @Configuration
21 | @EnableConfigurationProperties(SQLServerDialectProperties.class)
22 | @ConditionalOnClass(SQLServerDialectSupport.class)
23 | @ConditionalOnProperty(prefix = "sqlman.dialect", name = "type", havingValue = "SQLServer")
24 | public class SQLServerDialectConfiguration {
25 |
26 | @Resource
27 | private SQLServerDialectProperties properties;
28 |
29 | @Bean
30 | @ConditionalOnMissingBean(SqlDialectSupport.class)
31 | public SQLServerDialectSupport sqlmanSQLServerDialectSupport() {
32 | String table = properties.getTable();
33 | return new SQLServerDialectSupport(table);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/SQLServerDialectProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * SQLServer方言配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:29
10 | */
11 | @ConfigurationProperties("sqlman.dialect")
12 | public class SQLServerDialectProperties extends AbstractDialectProperties {
13 | }
14 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/SQLiteDialectConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import io.sqlman.core.SqlDialectSupport;
4 | import io.sqlman.core.dialect.SQLiteDialectSupport;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * SQLite方言自动配置
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/25 8:38
19 | */
20 | @Configuration
21 | @EnableConfigurationProperties(SQLiteDialectProperties.class)
22 | @ConditionalOnClass(SQLiteDialectSupport.class)
23 | @ConditionalOnProperty(prefix = "sqlman.dialect", name = "type", havingValue = "SQLite")
24 | public class SQLiteDialectConfiguration {
25 |
26 | @Resource
27 | private SQLiteDialectProperties properties;
28 |
29 | @Bean
30 | @ConditionalOnMissingBean(SqlDialectSupport.class)
31 | public SQLiteDialectSupport sqlmanSQLiteDialectSupport() {
32 | String table = properties.getTable();
33 | return new SQLiteDialectSupport(table);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/dialect/SQLiteDialectProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.dialect;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * SQLite方言配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:29
10 | */
11 | @ConfigurationProperties("sqlman.dialect")
12 | public class SQLiteDialectProperties extends AbstractDialectProperties {
13 | }
14 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/logger/AbstractLoggerProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.logger;
2 |
3 | import io.sqlman.core.SqlLogger;
4 |
5 | /**
6 | * 抽象的脚本资源命名策略配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 9:03
10 | */
11 | public abstract class AbstractLoggerProperties {
12 | /**
13 | * SQL logger supplier name
14 | */
15 | private String supplier = "slf4j";
16 |
17 | /**
18 | * SQL logger level. TRACE, DEBUG, INFO, WARN, ERROR.
19 | */
20 | private SqlLogger.Level level = SqlLogger.Level.INFO;
21 |
22 | public String getSupplier() {
23 | return supplier;
24 | }
25 |
26 | public void setSupplier(String supplier) {
27 | this.supplier = supplier;
28 | }
29 |
30 | public SqlLogger.Level getLevel() {
31 | return level;
32 | }
33 |
34 | public void setLevel(SqlLogger.Level level) {
35 | this.level = level;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/logger/Slf4jLoggerConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.logger;
2 |
3 | import io.sqlman.core.SqlLoggerSupplier;
4 | import io.sqlman.core.logger.Slf4jLoggerSupplier;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * 基础脚本资源命名策略自动配置
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/25 8:55
19 | */
20 | @Configuration
21 | @EnableConfigurationProperties(Slf4jLoggerProperties.class)
22 | @ConditionalOnClass(Slf4jLoggerSupplier.class)
23 | @ConditionalOnProperty(prefix = "sqlman.logger", name = "supplier", havingValue = "slf4j", matchIfMissing = true)
24 | public class Slf4jLoggerConfiguration {
25 |
26 | @Resource
27 | private Slf4jLoggerProperties properties;
28 |
29 | @Bean
30 | @ConditionalOnMissingBean(SqlLoggerSupplier.class)
31 | public SqlLoggerSupplier sqlmanSlf4jLoggerSupplier() {
32 | return new Slf4jLoggerSupplier(properties.getLevel());
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/logger/Slf4jLoggerProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.logger;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * 基础脚本资源命名策略配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:54
10 | */
11 | @ConfigurationProperties(prefix = "sqlman.logger")
12 | public class Slf4jLoggerProperties extends AbstractLoggerProperties {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/AbstractNamingProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | /**
4 | * 抽象的脚本资源命名策略配置属性
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 9:03
8 | */
9 | public abstract class AbstractNamingProperties {
10 | /**
11 | * SQL script naming strategy implementation
12 | */
13 | private String strategy = "standard";
14 |
15 | public String getStrategy() {
16 | return strategy;
17 | }
18 |
19 | public void setStrategy(String strategy) {
20 | this.strategy = strategy;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/AbstractProviderProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | /**
4 | * 抽象的脚本资源提供器配置属性
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 9:10
8 | */
9 | public abstract class AbstractProviderProperties {
10 | /**
11 | * SQL source provider implementation
12 | */
13 | private String provider = "classpath";
14 |
15 | public String getProvider() {
16 | return provider;
17 | }
18 |
19 | public void setProvider(String provider) {
20 | this.provider = provider;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/AbstractResolverProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | /**
4 | * 抽象的脚本资源解析器配置属性
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/25 9:28
8 | */
9 | public class AbstractResolverProperties {
10 | /**
11 | * SQL script resolver implementation
12 | */
13 | private String resolver = "druid";
14 |
15 | public String getResolver() {
16 | return resolver;
17 | }
18 |
19 | public void setResolver(String resolver) {
20 | this.resolver = resolver;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/ClasspathProviderConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | import io.sqlman.core.SqlNamingStrategy;
4 | import io.sqlman.core.SqlSourceProvider;
5 | import io.sqlman.core.source.ClasspathSourceProvider;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
9 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 |
13 | import javax.annotation.Resource;
14 |
15 | /**
16 | * 基础脚本资源提供器自动配置
17 | *
18 | * @author Payne 646742615@qq.com
19 | * 2019/5/25 8:59
20 | */
21 | @Configuration
22 | @EnableConfigurationProperties(ClasspathProviderProperties.class)
23 | @ConditionalOnClass(ClasspathSourceProvider.class)
24 | @ConditionalOnProperty(prefix = "sqlman.script", name = "provider", havingValue = "classpath", matchIfMissing = true)
25 | public class ClasspathProviderConfiguration {
26 |
27 | @Resource
28 | private ClasspathProviderProperties properties;
29 |
30 | @Resource
31 | private SqlNamingStrategy sqlNamingStrategy;
32 |
33 | @Bean
34 | @ConditionalOnMissingBean(SqlSourceProvider.class)
35 | public ClasspathSourceProvider sqlmanClasspathScriptProvider() {
36 | String location = properties.getLocation();
37 | return new ClasspathSourceProvider(location, sqlNamingStrategy);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/ClasspathProviderProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * 基础脚本资源提供器配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:52
10 | */
11 | @ConfigurationProperties(prefix = "sqlman.script")
12 | public class ClasspathProviderProperties extends AbstractProviderProperties {
13 | /**
14 | * SQL script location ANT path pattern
15 | */
16 | private String location = "sqlman/**/*.sql";
17 |
18 | public String getLocation() {
19 | return location;
20 | }
21 |
22 | public void setLocation(String location) {
23 | this.location = location;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/DruidResolverConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | import io.sqlman.core.SqlScriptResolver;
4 | import io.sqlman.core.script.DruidScriptResolver;
5 | import io.sqlman.core.source.ClasspathSourceProvider;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
9 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 |
13 | import javax.annotation.Resource;
14 |
15 | /**
16 | * druid脚本资源解析器自动配置
17 | *
18 | * @author Payne 646742615@qq.com
19 | * 2019/5/25 9:30
20 | */
21 | @Configuration
22 | @EnableConfigurationProperties(DruidResolverProperties.class)
23 | @ConditionalOnClass(ClasspathSourceProvider.class)
24 | @ConditionalOnProperty(prefix = "sqlman.script", name = "resolver", havingValue = "druid", matchIfMissing = true)
25 | public class DruidResolverConfiguration {
26 |
27 | @Resource
28 | private DruidResolverProperties properties;
29 |
30 | @Bean
31 | @ConditionalOnMissingBean(SqlScriptResolver.class)
32 | public DruidScriptResolver sqlmanDruidScriptResolver() {
33 | String dialect = properties.getDialect();
34 | String charset = properties.getCharset();
35 | return new DruidScriptResolver(dialect, charset);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/DruidResolverProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * druid脚本资源解析器配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 9:28
10 | */
11 | @ConfigurationProperties(prefix = "sqlman.script")
12 | public class DruidResolverProperties extends AbstractResolverProperties {
13 | /**
14 | * SQL script dialect
15 | */
16 | private String dialect = "MySQL";
17 | /**
18 | * SQL script charset
19 | */
20 | private String charset = "UTF-8";
21 |
22 | public String getDialect() {
23 | return dialect;
24 | }
25 |
26 | public void setDialect(String dialect) {
27 | this.dialect = dialect;
28 | }
29 |
30 | public String getCharset() {
31 | return charset;
32 | }
33 |
34 | public void setCharset(String charset) {
35 | this.charset = charset;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/StandardNamingConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | import io.sqlman.core.SqlNamingStrategy;
4 | import io.sqlman.core.naming.StandardNamingStrategy;
5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * 基础脚本资源命名策略自动配置
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/25 8:55
19 | */
20 | @Configuration
21 | @EnableConfigurationProperties(StandardNamingProperties.class)
22 | @ConditionalOnClass(StandardNamingStrategy.class)
23 | @ConditionalOnProperty(prefix = "sqlman.script.naming", name = "strategy", havingValue = "standard", matchIfMissing = true)
24 | public class StandardNamingConfiguration {
25 |
26 | @Resource
27 | private StandardNamingProperties properties;
28 |
29 | @Bean
30 | @ConditionalOnMissingBean(SqlNamingStrategy.class)
31 | public SqlNamingStrategy sqlmanStandardNamingStrategy() {
32 | char separator = properties.getSeparator();
33 | String splitter = properties.getSplitter();
34 | String delimiter = properties.getDelimiter();
35 | String extension = properties.getExtension();
36 | return new StandardNamingStrategy(separator, splitter, delimiter, extension);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/spring/script/StandardNamingProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring.script;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | /**
6 | * 基础脚本资源命名策略配置属性
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/5/25 8:54
10 | */
11 | @ConfigurationProperties(prefix = "sqlman.script.naming")
12 | public class StandardNamingProperties extends AbstractNamingProperties {
13 | /**
14 | * SQL script name separator
15 | */
16 | private char separator = '/';
17 | /**
18 | * SQL script name splitter
19 | */
20 | private String splitter = "-";
21 | /**
22 | * SQL script name delimiter
23 | */
24 | private String delimiter = "!";
25 | /**
26 | * SQL script name extension
27 | */
28 | private String extension = ".sql";
29 |
30 | public char getSeparator() {
31 | return separator;
32 | }
33 |
34 | public void setSeparator(char separator) {
35 | this.separator = separator;
36 | }
37 |
38 | public String getSplitter() {
39 | return splitter;
40 | }
41 |
42 | public void setSplitter(String splitter) {
43 | this.splitter = splitter;
44 | }
45 |
46 | public String getDelimiter() {
47 | return delimiter;
48 | }
49 |
50 | public void setDelimiter(String delimiter) {
51 | this.delimiter = delimiter;
52 | }
53 |
54 | public String getExtension() {
55 | return extension;
56 | }
57 |
58 | public void setExtension(String extension) {
59 | this.extension = extension;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/AbstractVersionManager.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | import io.sqlman.core.*;
4 | import io.sqlman.core.dialect.MySQLDialectSupport;
5 | import io.sqlman.core.exception.DuplicatedVersionException;
6 | import io.sqlman.core.exception.IncorrectSyntaxException;
7 | import io.sqlman.core.exception.MalformedNameException;
8 | import io.sqlman.core.logger.Slf4jLoggerSupplier;
9 | import io.sqlman.core.script.DruidScriptResolver;
10 | import io.sqlman.core.source.ClasspathSourceProvider;
11 |
12 | import javax.sql.DataSource;
13 | import java.io.IOException;
14 | import java.sql.Connection;
15 | import java.sql.SQLException;
16 | import java.util.Enumeration;
17 |
18 | /**
19 | * 抽象的数据库版本管理器
20 | *
21 | * @author Payne 646742615@qq.com
22 | * 2019/5/25 22:24
23 | */
24 | public abstract class AbstractVersionManager implements SqlVersionManager {
25 | protected DataSource dataSource;
26 | protected SqlSourceProvider sourceProvider = new ClasspathSourceProvider();
27 | protected SqlScriptResolver scriptResolver = new DruidScriptResolver();
28 | protected SqlDialectSupport dialectSupport = new MySQLDialectSupport();
29 | protected SqlLoggerSupplier loggerSupplier = new Slf4jLoggerSupplier();
30 | protected ThreadLocal connectionThreadLocal = new ThreadLocal<>();
31 |
32 | protected AbstractVersionManager() {
33 | }
34 |
35 | protected AbstractVersionManager(DataSource dataSource) {
36 | if (dataSource == null) {
37 | throw new IllegalArgumentException("dataSource must not be null");
38 | }
39 | this.dataSource = dataSource;
40 | }
41 |
42 | protected AbstractVersionManager(
43 | DataSource dataSource,
44 | SqlSourceProvider sourceProvider,
45 | SqlScriptResolver scriptResolver,
46 | SqlDialectSupport dialectSupport,
47 | SqlLoggerSupplier loggerSupplier
48 | ) {
49 | if (dataSource == null) {
50 | throw new IllegalArgumentException("dataSource must not be null");
51 | }
52 | if (sourceProvider == null) {
53 | throw new IllegalArgumentException("sourceProvider must not be null");
54 | }
55 | if (scriptResolver == null) {
56 | throw new IllegalArgumentException("scriptResolver must not be null");
57 | }
58 | if (dialectSupport == null) {
59 | throw new IllegalArgumentException("dialectSupport must not be null");
60 | }
61 | if (loggerSupplier == null) {
62 | throw new IllegalArgumentException("loggerSupplier must not be null");
63 | }
64 | this.dataSource = dataSource;
65 | this.sourceProvider = sourceProvider;
66 | this.scriptResolver = scriptResolver;
67 | this.dialectSupport = dialectSupport;
68 | this.loggerSupplier = loggerSupplier;
69 | }
70 |
71 | protected T execute(JdbcTransaction transaction) throws SQLException {
72 | boolean created = false;
73 | Connection connection = null;
74 | try {
75 | connection = connectionThreadLocal.get();
76 | if (connection == null) {
77 | connection = dataSource.getConnection();
78 | connection.setAutoCommit(false);
79 | connectionThreadLocal.set(connection);
80 | created = true;
81 | }
82 | T result = transaction.execute(connection);
83 | if (created) {
84 | connection.commit();
85 | }
86 | return result;
87 | } catch (SQLException ex) {
88 | if (connection != null && created) {
89 | connection.rollback();
90 | }
91 | throw ex;
92 | } catch (Exception ex) {
93 | if (connection != null && created) {
94 | connection.rollback();
95 | }
96 | throw new SQLException(ex.getMessage(), ex);
97 | } finally {
98 | if (connection != null && created) {
99 | connectionThreadLocal.remove();
100 | connection.close();
101 | }
102 | }
103 | }
104 |
105 | protected void perform(final JdbcAction action) throws SQLException {
106 | execute(new JdbcTransaction() {
107 | @Override
108 | public Void execute(Connection connection) throws SQLException {
109 | action.perform(connection);
110 | return null;
111 | }
112 | });
113 | }
114 |
115 | @Override
116 | public Enumeration acquire() throws MalformedNameException, DuplicatedVersionException, IOException {
117 | return sourceProvider.acquire();
118 | }
119 |
120 | @Override
121 | public Enumeration acquire(String version, boolean included) throws MalformedNameException, DuplicatedVersionException, IOException {
122 | return sourceProvider.acquire(version, included);
123 | }
124 |
125 | @Override
126 | public SqlScript resolve(SqlSource source) throws IncorrectSyntaxException, IOException {
127 | return scriptResolver.resolve(source);
128 | }
129 |
130 | @Override
131 | public void create() throws SQLException {
132 | perform(new JdbcAction() {
133 | @Override
134 | public void perform(Connection connection) throws SQLException {
135 | dialectSupport.create(connection);
136 | }
137 | });
138 | }
139 |
140 | @Override
141 | public SqlVersion detect() throws SQLException {
142 | return execute(new JdbcTransaction() {
143 | @Override
144 | public SqlVersion execute(Connection connection) throws SQLException {
145 | return dialectSupport.detect(connection);
146 | }
147 | });
148 | }
149 |
150 | @Override
151 | public void update(final SqlVersion version) throws SQLException {
152 | perform(new JdbcAction() {
153 | @Override
154 | public void perform(Connection connection) throws SQLException {
155 | dialectSupport.update(connection, version);
156 | }
157 | });
158 | }
159 |
160 | @Override
161 | public void remove() throws SQLException {
162 | perform(new JdbcAction() {
163 | @Override
164 | public void perform(Connection connection) throws SQLException {
165 | dialectSupport.remove(connection);
166 | }
167 | });
168 | }
169 |
170 | @Override
171 | public void lockup() throws SQLException {
172 | perform(new JdbcAction() {
173 | @Override
174 | public void perform(Connection connection) throws SQLException {
175 | dialectSupport.lockup(connection);
176 | }
177 | });
178 | }
179 |
180 | @Override
181 | public void unlock() throws SQLException {
182 | perform(new JdbcAction() {
183 | @Override
184 | public void perform(Connection connection) throws SQLException {
185 | dialectSupport.unlock(connection);
186 | }
187 | });
188 | }
189 |
190 | @Override
191 | public SqlLogger logger(Class> clazz) {
192 | return loggerSupplier.supply(clazz);
193 | }
194 |
195 | @Override
196 | public SqlLogger logger(String name) {
197 | return loggerSupplier.supply(name);
198 | }
199 |
200 | public DataSource getDataSource() {
201 | return dataSource;
202 | }
203 |
204 | public void setDataSource(DataSource dataSource) {
205 | this.dataSource = dataSource;
206 | }
207 |
208 | public SqlSourceProvider getSourceProvider() {
209 | return sourceProvider;
210 | }
211 |
212 | public void setSourceProvider(SqlSourceProvider sourceProvider) {
213 | this.sourceProvider = sourceProvider;
214 | }
215 |
216 | public SqlScriptResolver getScriptResolver() {
217 | return scriptResolver;
218 | }
219 |
220 | public void setScriptResolver(SqlScriptResolver scriptResolver) {
221 | this.scriptResolver = scriptResolver;
222 | }
223 |
224 | public SqlDialectSupport getDialectSupport() {
225 | return dialectSupport;
226 | }
227 |
228 | public void setDialectSupport(SqlDialectSupport dialectSupport) {
229 | this.dialectSupport = dialectSupport;
230 | }
231 |
232 | public SqlLoggerSupplier getLoggerSupplier() {
233 | return loggerSupplier;
234 | }
235 |
236 | public void setLoggerSupplier(SqlLoggerSupplier loggerSupplier) {
237 | this.loggerSupplier = loggerSupplier;
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/JdbcAction.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 |
6 | /**
7 | * 数据库操作
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/25 23:54
11 | */
12 | public interface JdbcAction {
13 |
14 | /**
15 | * 执行操作
16 | *
17 | * @param connection 连接
18 | * @throws SQLException SQL异常
19 | */
20 | void perform(Connection connection) throws SQLException;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/JdbcInstruction.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | /**
4 | * JDBC的指令常量
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/5/30 15:04
8 | */
9 | public interface JdbcInstruction {
10 | /**
11 | * 原子性执行:即整个脚本作为一个事务整体,不可分割。
12 | */
13 | String INSTRUCTION_ATOMIC = "ATOMIC";
14 |
15 | /**
16 | * 安全模式:备份被操作的表
17 | */
18 | String INSTRUCTION_SAFETY = "SAFETY";
19 |
20 | /**
21 | * 危险模式:不备份被操作的表
22 | */
23 | String INSTRUCTION_DANGER = "DANGER";
24 |
25 | /**
26 | * 隔离级别:读未提交
27 | */
28 | String INSTRUCTION_READ_UNCOMMITTED = "READ_UNCOMMITTED";
29 | /**
30 | * 隔离级别:读已提交
31 | */
32 | String INSTRUCTION_READ_COMMITTED = "READ_COMMITTED";
33 | /**
34 | * 隔离级别:可重复读
35 | */
36 | String INSTRUCTION_REPEATABLE_READ = "REPEATABLE_READ";
37 | /**
38 | * 隔离级别:串行执行
39 | */
40 | String INSTRUCTION_SERIALIZABLE = "SERIALIZABLE";
41 | }
42 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/JdbcIsolation.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | import java.sql.Connection;
4 | import java.util.Set;
5 |
6 | /**
7 | * JDBC事务隔离级别
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/6/1 10:34
11 | */
12 | public enum JdbcIsolation implements JdbcInstruction {
13 |
14 | /**
15 | * 隔离级别:读未提交
16 | */
17 | READ_UNCOMMITTED(INSTRUCTION_READ_UNCOMMITTED, "read uncommitted", Connection.TRANSACTION_READ_UNCOMMITTED),
18 | /**
19 | * 隔离级别:读已提交
20 | */
21 | READ_COMMITTED(INSTRUCTION_READ_COMMITTED, "read committed", Connection.TRANSACTION_READ_COMMITTED),
22 | /**
23 | * 隔离级别:可重复读
24 | */
25 | REPEATABLE_READ(INSTRUCTION_REPEATABLE_READ, "repeatable read", Connection.TRANSACTION_REPEATABLE_READ),
26 | /**
27 | * 隔离级别:串行执行
28 | */
29 | SERIALIZABLE(INSTRUCTION_SERIALIZABLE, "serializable", Connection.TRANSACTION_SERIALIZABLE);
30 |
31 | /**
32 | * 指令
33 | */
34 | public final String instruction;
35 | /**
36 | * 隔离级别名称
37 | */
38 | public final String name;
39 | /**
40 | * JDBC Connection 隔离级别
41 | */
42 | public final int level;
43 |
44 | JdbcIsolation(String instruction, String name, int level) {
45 | this.instruction = instruction;
46 | this.name = name;
47 | this.level = level;
48 | }
49 |
50 | /**
51 | * 从指令集中获取隔离级别,如果没有隔离级别指令则返回{@code null}
52 | *
53 | * @param instructions 指令集
54 | * @return 对应隔离级别或{@code null}当没有隔离级别指令时
55 | */
56 | public static JdbcIsolation valueOf(Set instructions) {
57 | if (instructions == null || instructions.isEmpty()) {
58 | return null;
59 | }
60 | JdbcIsolation isolation = null;
61 | for (JdbcIsolation value : values()) {
62 | if (instructions.contains(value.instruction)) {
63 | if (isolation != null) {
64 | throw new IllegalArgumentException("multiple transaction isolation level instructions");
65 | } else {
66 | isolation = value;
67 | }
68 | }
69 | }
70 | return isolation;
71 | }
72 |
73 | @Override
74 | public String toString() {
75 | return name;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/JdbcMode.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | import java.util.Set;
4 |
5 | /**
6 | * 模式
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/8/2 15:07
10 | */
11 | public enum JdbcMode {
12 |
13 | /**
14 | * 安全模式
15 | */
16 | SAFETY,
17 | /**
18 | * 危险模式
19 | */
20 | DANGER;
21 |
22 | /**
23 | * 从指令集中获取模式,如果没有模式指令则返回{@code null}
24 | *
25 | * @param instructions 指令集
26 | * @return 对应模式或{@code null}当没有模式指令时
27 | */
28 | public static JdbcMode valueOf(Set instructions) {
29 | if (instructions == null || instructions.isEmpty()) {
30 | return null;
31 | }
32 | JdbcMode mode = null;
33 | for (JdbcMode value : values()) {
34 | if (instructions.contains(value.name())) {
35 | if (mode != null) {
36 | throw new IllegalArgumentException("multiple mode instructions");
37 | } else {
38 | mode = value;
39 | }
40 | }
41 | }
42 | return mode;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/JdbcTransaction.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | import java.sql.Connection;
4 | import java.sql.SQLException;
5 |
6 | /**
7 | * 数据库事务
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/24 10:57
11 | */
12 | public interface JdbcTransaction {
13 |
14 | /**
15 | * 执行事务
16 | *
17 | * @param connection 连接
18 | * @return 事务执行结果
19 | * @throws SQLException SQL异常
20 | */
21 | T execute(Connection connection) throws SQLException;
22 |
23 | }
--------------------------------------------------------------------------------
/sqlman-core/src/main/java/io/sqlman/core/version/JdbcVersionManager.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.version;
2 |
3 | import io.sqlman.core.*;
4 |
5 | import javax.sql.DataSource;
6 | import java.sql.Connection;
7 | import java.sql.PreparedStatement;
8 | import java.sql.SQLException;
9 | import java.sql.Timestamp;
10 | import java.util.Enumeration;
11 | import java.util.Set;
12 |
13 | /**
14 | * 基础SQL版本管理器
15 | *
16 | * @author Payne 646742615@qq.com
17 | * 2019/5/22 16:15
18 | */
19 | public class JdbcVersionManager extends AbstractVersionManager implements SqlVersionManager, JdbcInstruction {
20 |
21 | /**
22 | * 缺省隔离界别
23 | */
24 | protected JdbcIsolation defaultIsolation;
25 |
26 | /**
27 | * 缺省模式
28 | */
29 | protected JdbcMode defaultMode = JdbcMode.DANGER;
30 |
31 | protected JdbcVersionManager() {
32 | super();
33 | }
34 |
35 | public JdbcVersionManager(DataSource dataSource) {
36 | super(dataSource);
37 | }
38 |
39 | public JdbcVersionManager(
40 | DataSource dataSource,
41 | SqlSourceProvider sourceProvider,
42 | SqlScriptResolver scriptResolver,
43 | SqlDialectSupport dialectSupport,
44 | SqlLoggerSupplier loggerSupplier
45 | ) {
46 | super(dataSource, sourceProvider, scriptResolver, dialectSupport, loggerSupplier);
47 | }
48 |
49 | public JdbcVersionManager(
50 | DataSource dataSource,
51 | SqlSourceProvider sourceProvider,
52 | SqlScriptResolver scriptResolver,
53 | SqlDialectSupport dialectSupport,
54 | SqlLoggerSupplier loggerSupplier,
55 | JdbcIsolation defaultIsolation,
56 | JdbcMode defaultMode
57 | ) {
58 | super(dataSource, sourceProvider, scriptResolver, dialectSupport, loggerSupplier);
59 | this.defaultIsolation = defaultIsolation;
60 | this.defaultMode = defaultMode;
61 | }
62 |
63 | @Override
64 | public void upgrade() throws SQLException {
65 | SqlLogger logger = logger(this.getClass());
66 |
67 | logger.info("Schema locking");
68 | lockup();
69 | logger.info("Schema locked");
70 | try {
71 | logger.info("Creating schema version table");
72 | create();
73 |
74 | logger.info("Detecting schema current version");
75 | SqlVersion current = detect();
76 | logger.info("Schema current version is {}", current);
77 |
78 | String version = current != null ? current.getVersion() : null;
79 | int ordinal = current != null ? current.getSuccess() ? current.getOrdinal() + 1 : current.getOrdinal() : 1;
80 | boolean included = current == null || !current.getSuccess() || ordinal < current.getSqlQuantity();
81 | Enumeration sources = current != null ? acquire(version, included) : acquire();
82 | if (!included) {
83 | ordinal = 1;
84 | }
85 |
86 | if (sources.hasMoreElements()) {
87 | logger.info("Schema upgrading");
88 | }
89 |
90 | while (sources.hasMoreElements()) {
91 | SqlSource source = sources.nextElement();
92 | SqlScript script = resolve(source);
93 | if (ordinal == 1) {
94 | upgrade(script);
95 | } else {
96 | int sqls = script.sqls();
97 | for (int odn = ordinal; odn <= sqls; odn++) {
98 | upgrade(script, odn);
99 | }
100 | ordinal = 1;
101 | }
102 | }
103 |
104 | logger.info("Schema is up to date");
105 | } catch (SQLException ex) {
106 | throw ex;
107 | } catch (Exception ex) {
108 | throw new SQLException(ex.getMessage(), ex);
109 | } finally {
110 | logger.info("Schema unlocking");
111 | unlock();
112 | logger.info("Schema unlocked");
113 | }
114 | }
115 |
116 | @Override
117 | public void upgrade(final SqlScript script) throws SQLException {
118 | SqlLogger logger = logger(this.getClass());
119 |
120 | Set instructions = script.instructions();
121 | boolean atomic = instructions != null && instructions.contains(INSTRUCTION_ATOMIC);
122 |
123 | // 原子执行
124 | if (atomic) {
125 | logger.info("Executing script {} atomically", script.name());
126 | perform(new JdbcAction() {
127 | @Override
128 | public void perform(Connection connection) throws SQLException {
129 | int sqls = script.sqls();
130 | for (int ordinal = 1; ordinal <= sqls; ordinal++) {
131 | upgrade(script, ordinal);
132 | }
133 | }
134 | });
135 | }
136 | // 逐条执行
137 | else {
138 | logger.info("Executing script {} non-atomically", script.name());
139 | int sqls = script.sqls();
140 | for (int ordinal = 1; ordinal <= sqls; ordinal++) {
141 | upgrade(script, ordinal);
142 | }
143 | }
144 | }
145 |
146 | @Override
147 | public void upgrade(final SqlScript script, final int ordinal) throws SQLException {
148 | final SqlLogger logger = logger(this.getClass());
149 |
150 | Integer rowEffected = null;
151 | SQLException sqlException = null;
152 | try {
153 | rowEffected = execute(new JdbcTransaction() {
154 | @Override
155 | public Integer execute(Connection connection) throws SQLException {
156 | try {
157 | Set instructions = script.instructions();
158 | JdbcIsolation isolation = JdbcIsolation.valueOf(instructions);
159 | if (isolation == null) {
160 | isolation = defaultIsolation;
161 | }
162 | if (isolation != null && connection.getTransactionIsolation() != isolation.level) {
163 | connection.setTransactionIsolation(isolation.level);
164 | }
165 |
166 | JdbcMode mode = JdbcMode.valueOf(instructions);
167 | if (mode == null) {
168 | mode = defaultMode;
169 | }
170 | if (mode == null) {
171 | mode = JdbcMode.DANGER;
172 | }
173 | if (mode == JdbcMode.SAFETY) {
174 | dialectSupport.backup(connection, script, ordinal);
175 | }
176 |
177 | SqlSentence sentence = script.sentence(ordinal);
178 | String sql = sentence.value();
179 |
180 | logger.info(
181 | "Executing sentence {}/{} of script version {} under {} transaction isolation level : {}",
182 | ordinal,
183 | script.sqls(),
184 | script.version(),
185 | isolation != null ? isolation.name : "default",
186 | sql.replaceAll("\\s+", " ")
187 | );
188 |
189 | PreparedStatement statement = connection.prepareStatement(sql);
190 | int rows = statement.executeUpdate();
191 |
192 | logger.info("Execution completed with {} rows effected", rows);
193 |
194 | return rows;
195 | } catch (SQLException ex) {
196 | throw ex;
197 | } catch (Exception ex) {
198 | String state = ex.getMessage() == null || ex.getMessage().isEmpty() ? "Unknown error" : ex.getMessage();
199 | throw new SQLException(state, state, -1, ex);
200 | }
201 | }
202 | });
203 | } catch (SQLException ex) {
204 | sqlException = ex;
205 | throw sqlException;
206 | } finally {
207 | SqlVersion version = new SqlVersion();
208 | version.setName(SqlUtils.ifEmpty(script.name(), "NO NAME"));
209 | version.setVersion(script.version());
210 | version.setOrdinal(ordinal);
211 | version.setDescription(SqlUtils.ifEmpty(script.description(), "NO DESCRIPTION"));
212 | version.setSqlQuantity(script.sqls());
213 | version.setSuccess(sqlException == null);
214 | version.setRowEffected(rowEffected == null ? 0 : rowEffected);
215 | version.setErrorCode(sqlException == null ? 0 : sqlException.getErrorCode());
216 | version.setErrorState(sqlException == null ? "OK" : SqlUtils.ifEmpty(sqlException.getSQLState(), "NO STATE"));
217 | version.setErrorMessage(sqlException == null ? "OK" : SqlUtils.ifEmpty(sqlException.getMessage(), "NO MESSAGE"));
218 | version.setTimeExecuted(new Timestamp(System.currentTimeMillis()));
219 | update(version);
220 | }
221 | }
222 |
223 | public JdbcIsolation getDefaultIsolation() {
224 | return defaultIsolation;
225 | }
226 |
227 | public void setDefaultIsolation(JdbcIsolation defaultIsolation) {
228 | this.defaultIsolation = defaultIsolation;
229 | }
230 |
231 | public JdbcMode getDefaultMode() {
232 | return defaultMode;
233 | }
234 |
235 | public void setDefaultMode(JdbcMode defaultMode) {
236 | this.defaultMode = defaultMode;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/sqlman-core/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | io.sqlman.core.spring.JdbcManagerConfiguration,\
3 | io.sqlman.core.spring.script.ClasspathProviderConfiguration,\
4 | io.sqlman.core.spring.script.StandardNamingConfiguration,\
5 | io.sqlman.core.spring.script.DruidResolverConfiguration,\
6 | io.sqlman.core.spring.logger.Slf4jLoggerConfiguration,\
7 | io.sqlman.core.spring.dialect.MySQLDialectConfiguration,\
8 | io.sqlman.core.spring.dialect.SQLiteDialectConfiguration,\
9 | io.sqlman.core.spring.dialect.OracleDialectConfiguration,\
10 | io.sqlman.core.spring.dialect.SQLServerDialectConfiguration
--------------------------------------------------------------------------------
/sqlman-git/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | sqlman
7 | io.sqlman
8 | v1.2.1
9 |
10 | 4.0.0
11 | sqlman-git
12 | sqlman-git
13 |
14 |
15 | UTF-8
16 | 1.7
17 | 1.7
18 |
19 |
20 |
21 |
22 | io.sqlman
23 | sqlman-vcs
24 | v1.2.1
25 |
26 |
27 | org.eclipse.jgit
28 | org.eclipse.jgit
29 | 5.4.3.201909031940-r
30 |
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-autoconfigure
35 | 2.0.1.RELEASE
36 | true
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-configuration-processor
41 | 2.0.1.RELEASE
42 | true
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitCheckoutConfig.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import org.eclipse.jgit.api.CheckoutCommand;
4 | import org.eclipse.jgit.api.CreateBranchCommand;
5 |
6 | public class GitCheckoutConfig {
7 | boolean forceRefUpdate = false;
8 | boolean forced = false;
9 | boolean createBranch = false;
10 | boolean orphan = false;
11 | CreateBranchCommand.SetupUpstreamMode upstreamMode;
12 | String startPoint = null;
13 | CheckoutCommand.Stage checkoutStage = null;
14 | boolean checkoutAllPaths;
15 |
16 | public boolean isForceRefUpdate() {
17 | return forceRefUpdate;
18 | }
19 |
20 | public void setForceRefUpdate(boolean forceRefUpdate) {
21 | this.forceRefUpdate = forceRefUpdate;
22 | }
23 |
24 | public boolean isForced() {
25 | return forced;
26 | }
27 |
28 | public void setForced(boolean forced) {
29 | this.forced = forced;
30 | }
31 |
32 | public boolean isCreateBranch() {
33 | return createBranch;
34 | }
35 |
36 | public void setCreateBranch(boolean createBranch) {
37 | this.createBranch = createBranch;
38 | }
39 |
40 | public boolean isOrphan() {
41 | return orphan;
42 | }
43 |
44 | public void setOrphan(boolean orphan) {
45 | this.orphan = orphan;
46 | }
47 |
48 | public CreateBranchCommand.SetupUpstreamMode getUpstreamMode() {
49 | return upstreamMode;
50 | }
51 |
52 | public void setUpstreamMode(CreateBranchCommand.SetupUpstreamMode upstreamMode) {
53 | this.upstreamMode = upstreamMode;
54 | }
55 |
56 | public String getStartPoint() {
57 | return startPoint;
58 | }
59 |
60 | public void setStartPoint(String startPoint) {
61 | this.startPoint = startPoint;
62 | }
63 |
64 | public CheckoutCommand.Stage getCheckoutStage() {
65 | return checkoutStage;
66 | }
67 |
68 | public void setCheckoutStage(CheckoutCommand.Stage checkoutStage) {
69 | this.checkoutStage = checkoutStage;
70 | }
71 |
72 | public boolean isCheckoutAllPaths() {
73 | return checkoutAllPaths;
74 | }
75 |
76 | public void setCheckoutAllPaths(boolean checkoutAllPaths) {
77 | this.checkoutAllPaths = checkoutAllPaths;
78 | }
79 | }
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitCleanConfig.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import java.util.Collections;
4 | import java.util.Set;
5 |
6 | public class GitCleanConfig {
7 | Set paths = Collections.emptySet();
8 | boolean dryRun;
9 | boolean directories;
10 | boolean ignore = true;
11 | boolean force = false;
12 |
13 | public Set getPaths() {
14 | return paths;
15 | }
16 |
17 | public void setPaths(Set paths) {
18 | this.paths = paths;
19 | }
20 |
21 | public boolean isDryRun() {
22 | return dryRun;
23 | }
24 |
25 | public void setDryRun(boolean dryRun) {
26 | this.dryRun = dryRun;
27 | }
28 |
29 | public boolean isDirectories() {
30 | return directories;
31 | }
32 |
33 | public void setDirectories(boolean directories) {
34 | this.directories = directories;
35 | }
36 |
37 | public boolean isIgnore() {
38 | return ignore;
39 | }
40 |
41 | public void setIgnore(boolean ignore) {
42 | this.ignore = ignore;
43 | }
44 |
45 | public boolean isForce() {
46 | return force;
47 | }
48 |
49 | public void setForce(boolean force) {
50 | this.force = force;
51 | }
52 | }
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitClient.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import io.sqlman.vcs.VcsClient;
4 | import org.eclipse.jgit.api.Git;
5 | import org.eclipse.jgit.api.PullResult;
6 | import org.eclipse.jgit.api.errors.GitAPIException;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 |
11 | /**
12 | * Git客户端
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/9/10 13:53
16 | */
17 | public class GitClient implements VcsClient {
18 | private final GitConfig config;
19 | private volatile Git git;
20 |
21 | public GitClient(GitConfig config) {
22 | this.config = config;
23 | }
24 |
25 | @Override
26 | public void init(File directory) throws IOException {
27 | try {
28 | git = Git.cloneRepository()
29 | .setDirectory(directory)
30 | .setCredentialsProvider(config.credentialsProvider)
31 | .setTimeout(config.timeout)
32 | .setURI(config.uri)
33 | .setGitDir(config.clone == null ? null : config.clone.gitDir)
34 | .setBare(config.clone != null && config.clone.bare)
35 | .setFs(config.clone == null ? null : config.clone.fs)
36 | .setRemote(config.clone == null ? null : config.clone.remote)
37 | .setBranch(config.clone == null ? null : config.clone.branch)
38 | .setCloneAllBranches(config.clone != null && config.clone.cloneAllBranches)
39 | .setCloneSubmodules(config.clone != null && config.clone.cloneSubmodules)
40 | .setNoCheckout(config.clone != null && config.clone.noCheckout)
41 | .setBranchesToClone(config.clone == null ? null : config.clone.branchesToClone)
42 | .call();
43 | } catch (Exception ex) {
44 | git = Git.open(directory);
45 | }
46 | }
47 |
48 | @Override
49 | public void checkout(String branch) throws IOException {
50 | try {
51 | git.checkout()
52 | .setName(branch)
53 | .setForceRefUpdate(config.checkout != null && config.checkout.forceRefUpdate)
54 | .setForced(config.checkout != null && config.checkout.forced)
55 | .setCreateBranch(config.checkout != null && config.checkout.createBranch)
56 | .setOrphan(config.checkout != null && config.checkout.orphan)
57 | .setUpstreamMode(config.checkout == null ? null : config.checkout.upstreamMode)
58 | .setStartPoint(config.checkout == null ? null : config.checkout.startPoint)
59 | .setStage(config.checkout == null ? null : config.checkout.checkoutStage)
60 | .setAllPaths(config.checkout != null && config.checkout.checkoutAllPaths)
61 | .call();
62 | } catch (GitAPIException ex) {
63 | throw new IOException(ex);
64 | }
65 | }
66 |
67 | @Override
68 | public void clean() throws IOException {
69 | try {
70 | git.clean()
71 | .setPaths(config.clean == null ? null : config.clean.paths)
72 | .setDryRun(config.clean != null && config.clean.dryRun)
73 | .setCleanDirectories(config.clean != null && config.clean.directories)
74 | .setIgnore(config.clean == null || config.clean.ignore)
75 | .setForce(config.clean != null && config.clean.force)
76 | .call();
77 | } catch (GitAPIException ex) {
78 | throw new IOException(ex);
79 | }
80 | }
81 |
82 | @Override
83 | public void pull() throws IOException {
84 | try {
85 | PullResult result = git.pull()
86 | .setCredentialsProvider(config.credentialsProvider)
87 | .setTimeout(config.timeout)
88 | .setRebase(config.pull == null ? null : config.pull.pullRebaseMode)
89 | .setRemote(config.pull == null ? null : config.pull.remote)
90 | .setRemoteBranchName(config.pull == null ? null : config.pull.remoteBranchName)
91 | .setTagOpt(config.pull == null ? null : config.pull.tagOption)
92 | .setFastForward(config.pull == null ? null : config.pull.fastForwardMode)
93 | .setRecurseSubmodules(config.pull == null ? null : config.pull.submoduleRecurseMode)
94 | .call();
95 | if (!result.isSuccessful()) {
96 | throw new IOException(result.toString());
97 | }
98 | } catch (GitAPIException ex) {
99 | throw new IOException(ex);
100 | }
101 | }
102 |
103 | @Override
104 | public void close() {
105 | if (git != null) {
106 | git.close();
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitClientFactory.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import io.sqlman.vcs.VcsClient;
4 | import io.sqlman.vcs.VcsClientFactory;
5 |
6 | import java.io.IOException;
7 |
8 | /**
9 | * Git客户端工厂
10 | *
11 | * @author Payne 646742615@qq.com
12 | * 2019/9/10 17:03
13 | */
14 | public class GitClientFactory implements VcsClientFactory {
15 | private final GitConfig config;
16 |
17 | public GitClientFactory(GitConfig config) {
18 | this.config = config;
19 | }
20 |
21 | @Override
22 | public VcsClient produce() {
23 | return new GitClient(config);
24 | }
25 |
26 | @Override
27 | public void release(VcsClient vcsClient) {
28 | try {
29 | vcsClient.close();
30 | } catch (IOException e) {
31 | // ignored
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitCloneConfig.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import org.eclipse.jgit.lib.Constants;
4 | import org.eclipse.jgit.util.FS;
5 |
6 | import java.io.File;
7 | import java.util.Collection;
8 |
9 | public class GitCloneConfig {
10 | File gitDir;
11 | boolean bare;
12 | FS fs;
13 | String remote = Constants.DEFAULT_REMOTE_NAME;
14 | String branch = Constants.HEAD;
15 | boolean cloneAllBranches;
16 | boolean cloneSubmodules;
17 | boolean noCheckout;
18 | Collection branchesToClone;
19 |
20 | public File getGitDir() {
21 | return gitDir;
22 | }
23 |
24 | public void setGitDir(File gitDir) {
25 | this.gitDir = gitDir;
26 | }
27 |
28 | public boolean isBare() {
29 | return bare;
30 | }
31 |
32 | public void setBare(boolean bare) {
33 | this.bare = bare;
34 | }
35 |
36 | public FS getFs() {
37 | return fs;
38 | }
39 |
40 | public void setFs(FS fs) {
41 | this.fs = fs;
42 | }
43 |
44 | public String getRemote() {
45 | return remote;
46 | }
47 |
48 | public void setRemote(String remote) {
49 | this.remote = remote;
50 | }
51 |
52 | public String getBranch() {
53 | return branch;
54 | }
55 |
56 | public void setBranch(String branch) {
57 | this.branch = branch;
58 | }
59 |
60 | public boolean isCloneAllBranches() {
61 | return cloneAllBranches;
62 | }
63 |
64 | public void setCloneAllBranches(boolean cloneAllBranches) {
65 | this.cloneAllBranches = cloneAllBranches;
66 | }
67 |
68 | public boolean isCloneSubmodules() {
69 | return cloneSubmodules;
70 | }
71 |
72 | public void setCloneSubmodules(boolean cloneSubmodules) {
73 | this.cloneSubmodules = cloneSubmodules;
74 | }
75 |
76 | public boolean isNoCheckout() {
77 | return noCheckout;
78 | }
79 |
80 | public void setNoCheckout(boolean noCheckout) {
81 | this.noCheckout = noCheckout;
82 | }
83 |
84 | public Collection getBranchesToClone() {
85 | return branchesToClone;
86 | }
87 |
88 | public void setBranchesToClone(Collection branchesToClone) {
89 | this.branchesToClone = branchesToClone;
90 | }
91 | }
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitConfig.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import org.eclipse.jgit.transport.CredentialsProvider;
4 |
5 | /**
6 | * Git配置
7 | *
8 | * @author Payne 646742615@qq.com
9 | * 2019/9/10 15:30
10 | */
11 | public class GitConfig {
12 | final String uri;
13 | final CredentialsProvider credentialsProvider;
14 | final int timeout;
15 | final GitCloneConfig clone;
16 | final GitCheckoutConfig checkout;
17 | final GitCleanConfig clean;
18 | final GitPullConfig pull;
19 |
20 | public GitConfig(String uri, CredentialsProvider credentialsProvider, int timeout, GitCloneConfig clone, GitCheckoutConfig checkout, GitCleanConfig clean, GitPullConfig pull) {
21 | this.uri = uri;
22 | this.credentialsProvider = credentialsProvider;
23 | this.timeout = timeout;
24 | this.clone = clone;
25 | this.checkout = checkout;
26 | this.clean = clean;
27 | this.pull = pull;
28 | }
29 |
30 | public String getUri() {
31 | return uri;
32 | }
33 |
34 | public CredentialsProvider getCredentialsProvider() {
35 | return credentialsProvider;
36 | }
37 |
38 | public int getTimeout() {
39 | return timeout;
40 | }
41 |
42 | public GitCloneConfig getClone() {
43 | return clone;
44 | }
45 |
46 | public GitCheckoutConfig getCheckout() {
47 | return checkout;
48 | }
49 |
50 | public GitCleanConfig getClean() {
51 | return clean;
52 | }
53 |
54 | public GitPullConfig getPull() {
55 | return pull;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/GitPullConfig.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git;
2 |
3 | import org.eclipse.jgit.api.MergeCommand;
4 | import org.eclipse.jgit.lib.BranchConfig;
5 | import org.eclipse.jgit.lib.SubmoduleConfig;
6 | import org.eclipse.jgit.transport.TagOpt;
7 |
8 | public class GitPullConfig {
9 | BranchConfig.BranchRebaseMode pullRebaseMode;
10 | String remote;
11 | String remoteBranchName;
12 | TagOpt tagOption;
13 | MergeCommand.FastForwardMode fastForwardMode;
14 | SubmoduleConfig.FetchRecurseSubmodulesMode submoduleRecurseMode;
15 |
16 | public BranchConfig.BranchRebaseMode getPullRebaseMode() {
17 | return pullRebaseMode;
18 | }
19 |
20 | public void setPullRebaseMode(BranchConfig.BranchRebaseMode pullRebaseMode) {
21 | this.pullRebaseMode = pullRebaseMode;
22 | }
23 |
24 | public String getRemote() {
25 | return remote;
26 | }
27 |
28 | public void setRemote(String remote) {
29 | this.remote = remote;
30 | }
31 |
32 | public String getRemoteBranchName() {
33 | return remoteBranchName;
34 | }
35 |
36 | public void setRemoteBranchName(String remoteBranchName) {
37 | this.remoteBranchName = remoteBranchName;
38 | }
39 |
40 | public TagOpt getTagOption() {
41 | return tagOption;
42 | }
43 |
44 | public void setTagOption(TagOpt tagOption) {
45 | this.tagOption = tagOption;
46 | }
47 |
48 | public MergeCommand.FastForwardMode getFastForwardMode() {
49 | return fastForwardMode;
50 | }
51 |
52 | public void setFastForwardMode(MergeCommand.FastForwardMode fastForwardMode) {
53 | this.fastForwardMode = fastForwardMode;
54 | }
55 |
56 | public SubmoduleConfig.FetchRecurseSubmodulesMode getSubmoduleRecurseMode() {
57 | return submoduleRecurseMode;
58 | }
59 |
60 | public void setSubmoduleRecurseMode(SubmoduleConfig.FetchRecurseSubmodulesMode submoduleRecurseMode) {
61 | this.submoduleRecurseMode = submoduleRecurseMode;
62 | }
63 | }
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/source/GitSourceProvider.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git.source;
2 |
3 | import io.sqlman.core.SqlNamingStrategy;
4 | import io.sqlman.vcs.VcsClientFactory;
5 | import io.sqlman.vcs.source.VcsSourceProvider;
6 |
7 | /**
8 | * Git资源提供器
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/9/11 16:19
12 | */
13 | public class GitSourceProvider extends VcsSourceProvider {
14 |
15 | public GitSourceProvider() {
16 | }
17 |
18 | public GitSourceProvider(VcsClientFactory clientFactory) {
19 | super(clientFactory);
20 | }
21 |
22 | public GitSourceProvider(SqlNamingStrategy namingStrategy, VcsClientFactory clientFactory) {
23 | super(namingStrategy, clientFactory);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/spring/GitProviderConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git.spring;
2 |
3 | import io.sqlman.core.SqlNamingStrategy;
4 | import io.sqlman.core.SqlSourceProvider;
5 | import io.sqlman.git.GitClientFactory;
6 | import io.sqlman.git.GitConfig;
7 | import io.sqlman.git.source.GitSourceProvider;
8 | import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
12 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
13 | import org.springframework.context.annotation.Bean;
14 | import org.springframework.context.annotation.Configuration;
15 |
16 | import javax.annotation.Resource;
17 |
18 | /**
19 | * Git资源提供器配置
20 | *
21 | * @author Payne 646742615@qq.com
22 | * 2019/9/11 16:17
23 | */
24 | @Configuration
25 | @EnableConfigurationProperties(GitProviderProperties.class)
26 | @ConditionalOnClass(GitSourceProvider.class)
27 | @ConditionalOnProperty(prefix = "sqlman.script", name = "provider", havingValue = "git")
28 | public class GitProviderConfiguration {
29 |
30 | @Resource
31 | private GitProviderProperties properties;
32 |
33 | @Resource
34 | private SqlNamingStrategy sqlNamingStrategy;
35 |
36 | @Bean
37 | @ConditionalOnMissingBean(SqlSourceProvider.class)
38 | public GitSourceProvider sqlmanGitScriptProvider() {
39 | UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(
40 | properties.getUsername(),
41 | properties.getPassword()
42 | );
43 | GitConfig config = new GitConfig(
44 | properties.getUri(),
45 | credentialsProvider,
46 | properties.getTimeout(),
47 | properties.getClone(),
48 | properties.getCheckout(),
49 | properties.getClean(),
50 | properties.getPull()
51 | );
52 | GitClientFactory clientFactory = new GitClientFactory(config);
53 | GitSourceProvider sourceProvider = new GitSourceProvider(sqlNamingStrategy, clientFactory);
54 | if (properties.getDirectory() != null) {
55 | sourceProvider.setDirectory(properties.getDirectory());
56 | }
57 | if (properties.getBranch() != null) {
58 | sourceProvider.setBranch(properties.getBranch());
59 | }
60 | if (properties.getUpdateStrategy() != null) {
61 | sourceProvider.setUpdateStrategy(properties.getUpdateStrategy());
62 | }
63 | if (properties.getLocation() != null) {
64 | sourceProvider.setScriptLocation(properties.getLocation());
65 | }
66 | return sourceProvider;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/java/io/sqlman/git/spring/GitProviderProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.git.spring;
2 |
3 | import io.sqlman.git.GitCheckoutConfig;
4 | import io.sqlman.git.GitCleanConfig;
5 | import io.sqlman.git.GitCloneConfig;
6 | import io.sqlman.git.GitPullConfig;
7 | import io.sqlman.vcs.spring.VcsProviderProperties;
8 | import org.springframework.boot.context.properties.ConfigurationProperties;
9 |
10 | /**
11 | * Git资源提供器配置属性
12 | *
13 | * @author Payne 646742615@qq.com
14 | * 2019/9/11 16:15
15 | */
16 | @ConfigurationProperties(prefix = "sqlman.script")
17 | public class GitProviderProperties extends VcsProviderProperties {
18 | private String uri;
19 | private String username;
20 | private String password;
21 | private int timeout;
22 | private GitCloneConfig clone;
23 | private GitCheckoutConfig checkout;
24 | private GitCleanConfig clean;
25 | private GitPullConfig pull;
26 |
27 | public String getUri() {
28 | return uri;
29 | }
30 |
31 | public void setUri(String uri) {
32 | this.uri = uri;
33 | }
34 |
35 | public String getUsername() {
36 | return username;
37 | }
38 |
39 | public void setUsername(String username) {
40 | this.username = username;
41 | }
42 |
43 | public String getPassword() {
44 | return password;
45 | }
46 |
47 | public void setPassword(String password) {
48 | this.password = password;
49 | }
50 |
51 | public int getTimeout() {
52 | return timeout;
53 | }
54 |
55 | public void setTimeout(int timeout) {
56 | this.timeout = timeout;
57 | }
58 |
59 | public GitCloneConfig getClone() {
60 | return clone;
61 | }
62 |
63 | public void setClone(GitCloneConfig clone) {
64 | this.clone = clone;
65 | }
66 |
67 | public GitCheckoutConfig getCheckout() {
68 | return checkout;
69 | }
70 |
71 | public void setCheckout(GitCheckoutConfig checkout) {
72 | this.checkout = checkout;
73 | }
74 |
75 | public GitCleanConfig getClean() {
76 | return clean;
77 | }
78 |
79 | public void setClean(GitCleanConfig clean) {
80 | this.clean = clean;
81 | }
82 |
83 | public GitPullConfig getPull() {
84 | return pull;
85 | }
86 |
87 | public void setPull(GitPullConfig pull) {
88 | this.pull = pull;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/sqlman-git/src/main/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2 | io.sqlman.git.spring.GitProviderConfiguration
--------------------------------------------------------------------------------
/sqlman-svn/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | sqlman
7 | io.sqlman
8 | v1.2.1
9 |
10 | 4.0.0
11 | sqlman-svn
12 | sqlman-svn
13 |
14 |
15 | UTF-8
16 | 1.7
17 | 1.7
18 |
19 |
20 |
21 |
22 | io.sqlman
23 | sqlman-vcs
24 | v1.2.1
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/sqlman-test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | sqlman
7 | io.sqlman
8 | v1.2.1
9 |
10 | 4.0.0
11 | sqlman-test
12 | sqlman-test
13 |
14 |
15 |
16 | io.sqlman
17 | sqlman-core
18 | v1.2.1
19 |
20 |
21 | io.sqlman
22 | sqlman-git
23 | v1.2.1
24 |
25 |
26 |
27 | junit
28 | junit
29 | 4.12
30 | test
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-starter-jdbc
35 | 2.0.1.RELEASE
36 | test
37 |
38 |
39 | org.yaml
40 | snakeyaml
41 | 1.19
42 | test
43 |
44 |
45 | org.slf4j
46 | slf4j-simple
47 | 1.7.26
48 | test
49 |
50 |
51 | mysql
52 | mysql-connector-java
53 | 6.0.6
54 | test
55 |
56 |
57 | org.xerial
58 | sqlite-jdbc
59 | 3.27.2.1
60 | test
61 |
62 |
63 | com.microsoft.sqlserver
64 | mssql-jdbc
65 | 7.0.0.jre8
66 | test
67 |
68 |
69 | com.oracle
70 | ojdbc6
71 | 11.2.0.4.0
72 | system
73 | ${basedir}/src/test/lib/ojdbc6-11.2.0.4.0.jar
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/sqlman-test/src/test/java/io/sqlman/core/spring/SqlmanTestApplication.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.spring;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | /**
7 | * Sqlman测试应用
8 | *
9 | * @author Payne 646742615@qq.com
10 | * 2019/5/25 9:59
11 | */
12 | @SpringBootApplication
13 | public class SqlmanTestApplication {
14 |
15 | public static void main(String... args) {
16 | SpringApplication.run(SqlmanTestApplication.class);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/sqlman-test/src/test/java/io/sqlman/core/test/MySQLSupportTest.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.test;
2 |
3 | import com.alibaba.druid.pool.DruidDataSource;
4 | import com.alibaba.druid.util.JdbcUtils;
5 | import io.sqlman.core.SqlLogger;
6 | import io.sqlman.core.dialect.MySQLDialectSupport;
7 | import io.sqlman.core.logger.Slf4jLoggerSupplier;
8 | import io.sqlman.core.naming.StandardNamingStrategy;
9 | import io.sqlman.core.script.DruidScriptResolver;
10 | import io.sqlman.core.source.ClasspathSourceProvider;
11 | import io.sqlman.core.version.JdbcVersionManager;
12 | import org.junit.Test;
13 |
14 | /**
15 | * MySQL测试
16 | *
17 | * @author Payne 646742615@qq.com
18 | * 2019/5/24 13:17
19 | */
20 | public class MySQLSupportTest {
21 |
22 | @Test
23 | public void test() throws Exception {
24 | JdbcVersionManager manager = null;
25 | try {
26 | DruidDataSource dataSource = new DruidDataSource();
27 | dataSource.setUrl("jdbc:mysql://localhost:3306/sqlman?serverTimezone=GMT%2B8&useSSL=false");
28 | dataSource.setUsername("root");
29 | dataSource.setPassword("root");
30 | manager = new JdbcVersionManager(dataSource);
31 | manager.setDataSource(dataSource);
32 | manager.setSourceProvider(new ClasspathSourceProvider("sqlman/**/*.sql", new StandardNamingStrategy()));
33 | manager.setScriptResolver(new DruidScriptResolver(JdbcUtils.MYSQL, "UTF-8"));
34 | manager.setDialectSupport(new MySQLDialectSupport("sqlman_schema_version"));
35 | manager.setLoggerSupplier(new Slf4jLoggerSupplier(SqlLogger.Level.INFO));
36 | manager.upgrade();
37 | } finally {
38 | if (manager != null) {
39 | manager.remove();
40 | }
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/sqlman-test/src/test/java/io/sqlman/core/test/OracleSupportTest.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.test;
2 |
3 | import com.alibaba.druid.pool.DruidDataSource;
4 | import com.alibaba.druid.util.JdbcUtils;
5 | import io.sqlman.core.dialect.OracleDialectSupport;
6 | import io.sqlman.core.script.DruidScriptResolver;
7 | import io.sqlman.core.source.ClasspathSourceProvider;
8 | import io.sqlman.core.version.JdbcVersionManager;
9 | import org.junit.Test;
10 |
11 | /**
12 | * Oracle测试
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/5/26 11:20
16 | */
17 | public class OracleSupportTest {
18 |
19 | @Test
20 | public void test() throws Exception {
21 | JdbcVersionManager manager = null;
22 | try {
23 | DruidDataSource dataSource = new DruidDataSource();
24 | dataSource.setUrl("jdbc:oracle:thin:@//localhost:1521/sqlman");
25 | dataSource.setUsername("root");
26 | dataSource.setPassword("root");
27 | manager = new JdbcVersionManager(dataSource);
28 | manager.setDataSource(dataSource);
29 | manager.setDialectSupport(new OracleDialectSupport());
30 | manager.setScriptResolver(new DruidScriptResolver(JdbcUtils.ORACLE));
31 | manager.setSourceProvider(new ClasspathSourceProvider("sqlman/**/*.sql"));
32 | manager.upgrade();
33 | } finally {
34 | if (manager != null) {
35 | manager.remove();
36 | }
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/sqlman-test/src/test/java/io/sqlman/core/test/SQLServerSupportTest.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.test;
2 |
3 | import com.alibaba.druid.pool.DruidDataSource;
4 | import com.alibaba.druid.util.JdbcUtils;
5 | import io.sqlman.core.dialect.SQLServerDialectSupport;
6 | import io.sqlman.core.script.DruidScriptResolver;
7 | import io.sqlman.core.source.ClasspathSourceProvider;
8 | import io.sqlman.core.version.JdbcVersionManager;
9 | import org.junit.Test;
10 |
11 | /**
12 | * SQLServer测试
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/5/26 9:28
16 | */
17 | public class SQLServerSupportTest {
18 |
19 | @Test
20 | public void test() throws Exception {
21 | JdbcVersionManager manager = null;
22 | try {
23 | DruidDataSource dataSource = new DruidDataSource();
24 | dataSource.setUrl("jdbc:sqlserver://localhost;database=sqlman");
25 | dataSource.setUsername("root");
26 | dataSource.setPassword("root");
27 | manager = new JdbcVersionManager(dataSource);
28 | manager.setDataSource(dataSource);
29 | manager.setDialectSupport(new SQLServerDialectSupport());
30 | manager.setScriptResolver(new DruidScriptResolver(JdbcUtils.SQL_SERVER));
31 | manager.setSourceProvider(new ClasspathSourceProvider("sqlman/**/*.sql"));
32 | manager.upgrade();
33 | } finally {
34 | if (manager != null) {
35 | manager.remove();
36 | }
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/sqlman-test/src/test/java/io/sqlman/core/test/SQLiteSupportTest.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.core.test;
2 |
3 | import com.alibaba.druid.pool.DruidDataSource;
4 | import com.alibaba.druid.util.JdbcUtils;
5 | import io.sqlman.core.dialect.SQLiteDialectSupport;
6 | import io.sqlman.core.script.DruidScriptResolver;
7 | import io.sqlman.core.source.ClasspathSourceProvider;
8 | import io.sqlman.core.version.JdbcVersionManager;
9 | import org.junit.Test;
10 |
11 | /**
12 | * MySQL测试
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/5/24 13:17
16 | */
17 | public class SQLiteSupportTest {
18 |
19 | @Test
20 | public void test() throws Exception {
21 | JdbcVersionManager manager = null;
22 | try {
23 | DruidDataSource dataSource = new DruidDataSource();
24 | dataSource.setUrl("jdbc:sqlite:target/sqlman.db?date_string_format=yyyy-MM-dd HH:mm:ss&date_class=TEXT&journal_mode=WAL");
25 | dataSource.setUsername("root");
26 | dataSource.setPassword("root");
27 | manager = new JdbcVersionManager(dataSource);
28 | manager.setDataSource(dataSource);
29 | manager.setDialectSupport(new SQLiteDialectSupport());
30 | manager.setScriptResolver(new DruidScriptResolver(JdbcUtils.SQLITE));
31 | manager.setSourceProvider(new ClasspathSourceProvider("sqlman/**/*.sql"));
32 | manager.upgrade();
33 | } finally {
34 | if (manager != null) {
35 | manager.remove();
36 | }
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/sqlman-test/src/test/lib/ojdbc6-11.2.0.4.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/core-lib/sqlman/c0e18ad0802465196db278aedca3e1dc09aad8aa/sqlman-test/src/test/lib/ojdbc6-11.2.0.4.0.jar
--------------------------------------------------------------------------------
/sqlman-test/src/test/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8080
3 |
4 | spring:
5 | application:
6 | name: sqlman
7 | datasource:
8 | driverClassName: com.mysql.cj.jdbc.Driver
9 | url: jdbc:mysql://localhost:3306/sqlman?serverTimezone=GMT%2B8&useSSL=false
10 | username: root
11 | password: root
12 |
13 | sqlman:
14 | script:
15 | provider: git
16 | location: "**/*.sql"
17 | uri: https://new.xiniuboss.com:27480/Juniu/nwhs.git
18 | username: ycp
19 | password: 123456
--------------------------------------------------------------------------------
/sqlman-test/src/test/resources/sqlman/v1.0.0!创建表.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE SQLMAN_TEST1 (
2 | ID INT
3 | );
4 |
5 | CREATE TABLE SQLMAN_TEST2 (
6 | ID INT
7 | );
8 |
9 | CREATE TABLE SQLMAN_TEST3 (
10 | ID INT
11 | );
--------------------------------------------------------------------------------
/sqlman-test/src/test/resources/sqlman/v1.0.1-ATOMIC-SERIALIZABLE!插入数据.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO SQLMAN_TEST1 (ID)
2 | VALUES (1);
3 | INSERT INTO SQLMAN_TEST2 (ID)
4 | VALUES (2);
5 | INSERT INTO SQLMAN_TEST3 (ID)
6 | VALUES (3);
--------------------------------------------------------------------------------
/sqlman-test/src/test/resources/sqlman/v1.0.2!删除表.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE SQLMAN_TEST1;
2 | DROP TABLE SQLMAN_TEST2;
3 | DROP TABLE SQLMAN_TEST3;
--------------------------------------------------------------------------------
/sqlman-vcs/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 | sqlman
7 | io.sqlman
8 | v1.2.1
9 |
10 | 4.0.0
11 | sqlman-vcs
12 | sqlman-vcs
13 |
14 |
15 |
16 | io.sqlman
17 | sqlman-core
18 | v1.2.1
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/sqlman-vcs/src/main/java/io/sqlman/vcs/VcsClient.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.vcs;
2 |
3 | import java.io.Closeable;
4 | import java.io.File;
5 | import java.io.IOException;
6 |
7 | /**
8 | * 版本控制系统客户端
9 | *
10 | * @author Payne 646742615@qq.com
11 | * 2019/9/10 11:37
12 | */
13 | public interface VcsClient extends Closeable {
14 |
15 | /**
16 | * 初始化
17 | *
18 | * @param directory 根目录
19 | * @throws IOException I/O 异常
20 | */
21 | void init(File directory) throws IOException;
22 |
23 | /**
24 | * 检出
25 | *
26 | * @param branch 分支
27 | * @throws IOException I/O 异常
28 | */
29 | void checkout(String branch) throws IOException;
30 |
31 | /**
32 | * 清理,删除没有版本追踪的文件。
33 | *
34 | * @throws IOException I/O 异常
35 | */
36 | void clean() throws IOException;
37 |
38 | /**
39 | * 拉取更新
40 | *
41 | * @throws IOException I/O 异常
42 | */
43 | void pull() throws IOException;
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/sqlman-vcs/src/main/java/io/sqlman/vcs/VcsClientFactory.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.vcs;
2 |
3 | /**
4 | * 版本管理系统客户端工厂
5 | *
6 | * @author Payne 646742615@qq.com
7 | * 2019/9/10 13:42
8 | */
9 | public interface VcsClientFactory {
10 |
11 | /**
12 | * 生产
13 | *
14 | * @return VCS客户端
15 | */
16 | VcsClient produce();
17 |
18 | /**
19 | * 回收
20 | *
21 | * @param vcsClient VCS客户端
22 | */
23 | void release(VcsClient vcsClient);
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/sqlman-vcs/src/main/java/io/sqlman/vcs/source/VcsSource.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.vcs.source;
2 |
3 | import io.sqlman.core.SqlSource;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.net.URL;
8 | import java.util.Set;
9 |
10 |
11 | /**
12 | * 标准脚本资源
13 | *
14 | * @author Payne 646742615@qq.com
15 | * 2019/5/24 17:57
16 | */
17 | public class VcsSource implements SqlSource {
18 | private final String name;
19 | private final String version;
20 | private final Set parameters;
21 | private final String description;
22 | private final URL url;
23 |
24 | public VcsSource(String name, String version, Set parameters, String description, URL url) {
25 | if (name == null) {
26 | throw new IllegalArgumentException("name must not be null");
27 | }
28 | if (version == null) {
29 | throw new IllegalArgumentException("version must not be null");
30 | }
31 | if (parameters == null) {
32 | throw new IllegalArgumentException("parameters must not be null");
33 | }
34 | if (description == null) {
35 | throw new IllegalArgumentException("description must not be null");
36 | }
37 | if (url == null) {
38 | throw new IllegalArgumentException("url must not be null");
39 | }
40 | this.name = name;
41 | this.version = version;
42 | this.parameters = parameters;
43 | this.description = description;
44 | this.url = url;
45 | }
46 |
47 | @Override
48 | public String name() {
49 | return name;
50 | }
51 |
52 | @Override
53 | public String version() {
54 | return version;
55 | }
56 |
57 | @Override
58 | public Set parameters() {
59 | return parameters;
60 | }
61 |
62 | @Override
63 | public String description() {
64 | return description;
65 | }
66 |
67 | @Override
68 | public InputStream open() throws IOException {
69 | return url.openStream();
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return url.toString();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/sqlman-vcs/src/main/java/io/sqlman/vcs/source/VcsSourceProvider.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.vcs.source;
2 |
3 | import io.loadkit.Loaders;
4 | import io.loadkit.Resource;
5 | import io.sqlman.core.SqlNaming;
6 | import io.sqlman.core.SqlNamingStrategy;
7 | import io.sqlman.core.SqlSource;
8 | import io.sqlman.core.exception.DuplicatedVersionException;
9 | import io.sqlman.core.exception.MalformedNameException;
10 | import io.sqlman.core.source.AbstractSourceProvider;
11 | import io.sqlman.vcs.VcsClient;
12 | import io.sqlman.vcs.VcsClientFactory;
13 |
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.util.*;
17 |
18 | /**
19 | * 版本控制系统资源提供器
20 | *
21 | * @author Payne 646742615@qq.com
22 | * 2019/9/10 13:25
23 | */
24 | public abstract class VcsSourceProvider extends AbstractSourceProvider {
25 | protected VcsClientFactory clientFactory;
26 | protected File directory = new File(System.getProperty("java.io.tmpdir"), UUID.randomUUID().toString());
27 | protected String branch;
28 | protected VcsUpdateStrategy updateStrategy = VcsUpdateStrategy.CLEAN_TO_UPDATE;
29 | protected String scriptLocation = "**/*.sql";
30 |
31 | public VcsSourceProvider() {
32 | }
33 |
34 | public VcsSourceProvider(VcsClientFactory clientFactory) {
35 | this.clientFactory = clientFactory;
36 | }
37 |
38 | public VcsSourceProvider(SqlNamingStrategy namingStrategy, VcsClientFactory clientFactory) {
39 | super(namingStrategy);
40 | this.clientFactory = clientFactory;
41 | }
42 |
43 | protected void update() throws IOException {
44 | VcsClient vcsClient = clientFactory.produce();
45 | try {
46 | if (!directory.exists() && !directory.mkdirs() && !directory.exists()) {
47 | throw new IOException("could not make directories for " + directory);
48 | }
49 | updateStrategy.update(vcsClient, directory, branch);
50 | } finally {
51 | clientFactory.release(vcsClient);
52 | }
53 | }
54 |
55 | @Override
56 | public Enumeration acquire() throws MalformedNameException, DuplicatedVersionException, IOException {
57 | update();
58 | Enumeration resources = Loaders.ant(Loaders.file(directory)).load(scriptLocation);
59 | Set sources = new TreeSet<>(new Comparator() {
60 | @Override
61 | public int compare(SqlSource o1, SqlSource o2) {
62 | return namingStrategy.compare(o1.version(), o2.version());
63 | }
64 | });
65 | while (resources.hasMoreElements()) {
66 | Resource resource = resources.nextElement();
67 | String name = resource.getName();
68 | SqlNaming naming = namingStrategy.parse(name);
69 | SqlSource source = new VcsSource(naming.getName(), naming.getVersion(), naming.getParameters(), naming.getDescription(), resource.getUrl());
70 | if (!sources.add(source)) {
71 | throw new DuplicatedVersionException("duplicate SQL script version: " + source.version(), source.version());
72 | }
73 | }
74 | return Collections.enumeration(sources);
75 | }
76 |
77 | @Override
78 | public Enumeration acquire(String version, boolean included) throws MalformedNameException, DuplicatedVersionException, IOException {
79 | update();
80 | Enumeration resources = Loaders.ant(Loaders.file(directory)).load(scriptLocation);
81 | Set sources = new TreeSet<>(new Comparator() {
82 | @Override
83 | public int compare(SqlSource o1, SqlSource o2) {
84 | return namingStrategy.compare(o1.version(), o2.version());
85 | }
86 | });
87 | while (resources.hasMoreElements()) {
88 | Resource resource = resources.nextElement();
89 | String name = resource.getName();
90 | SqlNaming naming = namingStrategy.parse(name);
91 | int comparision = namingStrategy.compare(naming.getVersion(), version);
92 | SqlSource source = new VcsSource(naming.getName(), naming.getVersion(), naming.getParameters(), naming.getDescription(), resource.getUrl());
93 | boolean newer = comparision > 0 || (comparision == 0 && included);
94 | if (newer && !sources.add(source)) {
95 | throw new DuplicatedVersionException("duplicate SQL script version: " + source.version(), source.version());
96 | }
97 | }
98 | return Collections.enumeration(sources);
99 | }
100 |
101 | public VcsClientFactory getClientFactory() {
102 | return clientFactory;
103 | }
104 |
105 | public void setClientFactory(VcsClientFactory clientFactory) {
106 | this.clientFactory = clientFactory;
107 | }
108 |
109 | public File getDirectory() {
110 | return directory;
111 | }
112 |
113 | public void setDirectory(File directory) {
114 | this.directory = directory;
115 | }
116 |
117 | public String getBranch() {
118 | return branch;
119 | }
120 |
121 | public void setBranch(String branch) {
122 | this.branch = branch;
123 | }
124 |
125 | public VcsUpdateStrategy getUpdateStrategy() {
126 | return updateStrategy;
127 | }
128 |
129 | public void setUpdateStrategy(VcsUpdateStrategy updateStrategy) {
130 | this.updateStrategy = updateStrategy;
131 | }
132 |
133 | public String getScriptLocation() {
134 | return scriptLocation;
135 | }
136 |
137 | public void setScriptLocation(String scriptLocation) {
138 | this.scriptLocation = scriptLocation;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/sqlman-vcs/src/main/java/io/sqlman/vcs/source/VcsUpdateStrategy.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.vcs.source;
2 |
3 | import io.sqlman.core.SqlUtils;
4 | import io.sqlman.vcs.VcsClient;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | /**
10 | * 版本控制系统资源更新策略
11 | *
12 | * @author Payne 646742615@qq.com
13 | * 2019/9/10 16:24
14 | */
15 | public enum VcsUpdateStrategy {
16 |
17 | /**
18 | * 清空再更新:即将本地库清空再克隆最新副本。
19 | */
20 | CLEAR_TO_UPDATE {
21 | @Override
22 | public void update(VcsClient client, File directory, String branch) throws IOException {
23 | SqlUtils.delete(directory, true);
24 | client.init(directory);
25 | client.checkout(branch);
26 | client.pull();
27 | }
28 | },
29 |
30 | /**
31 | * 清理再更新:即将本地库的未版本追踪文件删除再更新。
32 | */
33 | CLEAN_TO_UPDATE {
34 | @Override
35 | public void update(VcsClient client, File directory, String branch) throws IOException {
36 | client.init(directory);
37 | client.clean();
38 | client.checkout(branch);
39 | client.pull();
40 | }
41 | },
42 |
43 | /**
44 | * 永远不更新:即程序不自动更新,需要人工更新。
45 | */
46 | NEVER_DO_UPDATE {
47 | @Override
48 | public void update(VcsClient client, File directory, String branch) {
49 | // do nothing!!!
50 | }
51 | };
52 |
53 | /**
54 | * 更新本地库
55 | *
56 | * @param client VCS客户端
57 | */
58 | public abstract void update(VcsClient client, File directory, String branch) throws IOException;
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/sqlman-vcs/src/main/java/io/sqlman/vcs/spring/VcsProviderProperties.java:
--------------------------------------------------------------------------------
1 | package io.sqlman.vcs.spring;
2 |
3 | import io.sqlman.core.spring.script.AbstractProviderProperties;
4 | import io.sqlman.vcs.source.VcsUpdateStrategy;
5 |
6 | import java.io.File;
7 |
8 | /**
9 | * VCS资源提供器配置属性
10 | *
11 | * @author Payne 646742615@qq.com
12 | * 2019/9/11 13:53
13 | */
14 | public abstract class VcsProviderProperties extends AbstractProviderProperties {
15 | /**
16 | * VCS Local Directory
17 | */
18 | private File directory;
19 | /**
20 | * VCS Remote Branch
21 | */
22 | private String branch;
23 | /**
24 | * VCS Update Strategy
25 | */
26 | private VcsUpdateStrategy updateStrategy = VcsUpdateStrategy.CLEAN_TO_UPDATE;
27 | /**
28 | * SQL script location
29 | */
30 | private String location = "**/*.sql";
31 |
32 | public File getDirectory() {
33 | return directory;
34 | }
35 |
36 | public void setDirectory(File directory) {
37 | this.directory = directory;
38 | }
39 |
40 | public String getBranch() {
41 | return branch;
42 | }
43 |
44 | public void setBranch(String branch) {
45 | this.branch = branch;
46 | }
47 |
48 | public VcsUpdateStrategy getUpdateStrategy() {
49 | return updateStrategy;
50 | }
51 |
52 | public void setUpdateStrategy(VcsUpdateStrategy updateStrategy) {
53 | this.updateStrategy = updateStrategy;
54 | }
55 |
56 | public String getLocation() {
57 | return location;
58 | }
59 |
60 | public void setLocation(String location) {
61 | this.location = location;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------