├── 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/v/core-lib/sqlman.svg)](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 | --------------------------------------------------------------------------------