├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── mybatis-boost-core
├── pom.xml
└── src
│ └── main
│ └── java
│ └── cn
│ └── mybatisboost
│ ├── core
│ ├── Configuration.java
│ ├── ConfigurationAware.java
│ ├── DispatcherInterceptor.java
│ ├── GenericMapper.java
│ ├── SqlProvider.java
│ ├── adaptor
│ │ ├── NameAdaptor.java
│ │ ├── NoopNameAdaptor.java
│ │ ├── SnakeCaseNameAdaptor.java
│ │ └── TPrefixedNameAdaptor.java
│ └── preprocessor
│ │ ├── AutoParameterMappingPreprocessor.java
│ │ ├── MybatisCacheRemovingPreprocessor.java
│ │ └── ParameterNormalizationPreprocessor.java
│ ├── generator
│ ├── GeneratingSqlProvider.java
│ ├── Snowflake.java
│ ├── UuidGenerator.java
│ └── ValueGenerator.java
│ ├── json
│ ├── JsonResultSetsHandler.java
│ └── JsonTypeHandler.java
│ ├── lang
│ ├── LanguageSqlProvider.java
│ └── provider
│ │ ├── InsertEnhancement.java
│ │ ├── ListParameterEnhancement.java
│ │ ├── NullEnhancement.java
│ │ ├── TableEnhancement.java
│ │ └── UpdateEnhancement.java
│ ├── limiter
│ ├── LimiterSqlProvider.java
│ └── provider
│ │ ├── MySQL.java
│ │ └── PostgreSQL.java
│ ├── mapper
│ ├── CrudMapper.java
│ ├── MapperSqlProvider.java
│ ├── MysqlCrudMapper.java
│ └── provider
│ │ ├── Delete.java
│ │ ├── DeleteByIds.java
│ │ ├── Insert.java
│ │ ├── SelectByIds.java
│ │ ├── SelectOrCount.java
│ │ ├── SelectOrCountAll.java
│ │ ├── Update.java
│ │ └── mysql
│ │ ├── Replace.java
│ │ └── Save.java
│ ├── metric
│ └── MetricInterceptor.java
│ ├── nosql
│ ├── MapperInstrument.java
│ ├── Method.java
│ ├── MethodNameParser.java
│ └── Predicate.java
│ ├── support
│ └── JsonType.java
│ └── util
│ ├── EntityUtils.java
│ ├── MapperUtils.java
│ ├── MultipleMapKey.java
│ ├── MyBatisUtils.java
│ ├── PropertyUtils.java
│ ├── ReflectionUtils.java
│ ├── SafeProperty.java
│ ├── SqlUtils.java
│ ├── function
│ ├── UncheckedConsumer.java
│ ├── UncheckedFunction.java
│ └── UncheckedPredicate.java
│ └── tuple
│ └── BinaryTuple.java
├── mybatis-boost-spring-boot-autoconfigure
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── cn
│ │ └── mybatisboost
│ │ └── spring
│ │ └── boot
│ │ └── autoconfigure
│ │ ├── EnvironmentHolder.java
│ │ ├── MybatisBoostAutoConfiguration.java
│ │ ├── MybatisBoostProperties.java
│ │ └── NosqlConfiguration.java
│ └── resources
│ └── META-INF
│ └── spring.factories
├── mybatis-boost-spring-boot-starter
├── pom.xml
└── src
│ └── main
│ └── resources
│ └── META-INF
│ └── spring.provides
├── mybatis-boost-test
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── cn
│ │ └── mybatisboost
│ │ └── test
│ │ ├── Project.java
│ │ ├── ProjectMapper.java
│ │ ├── ProjectNosqlMapper.java
│ │ └── Website.java
│ └── test
│ ├── java
│ └── cn
│ │ └── mybatisboost
│ │ └── test
│ │ ├── CrudMapperTest.java
│ │ ├── EnhancementTest.java
│ │ ├── JsonTest.java
│ │ └── NosqlTest.java
│ └── resources
│ ├── application.properties
│ └── project.sql
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
3 | ### IntelliJ IDEA ###
4 | .idea
5 | *.iws
6 | *.iml
7 | *.ipr
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | services: mysql
3 | before_script:
4 | - mysql < mybatis-boost-test/src/test/resources/project.sql
5 | after_success:
6 | - mvn clean test jacoco:report coveralls:report
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright © 2018 zhangrongfan
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5 | and associated documentation files (the “Software”), to deal in the Software without
6 | restriction, including without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or
11 | substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
17 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MybatisBoost [](https://maven-badges.herokuapp.com/maven-central/cn.mybatisboost/mybatis-boost) [](https://www.travis-ci.org/zhang-rf/mybatis-boost) [](https://coveralls.io/github/zhang-rf/mybatis-boost)
2 |
3 | Mybatis SQL开发神器MybatisBoost,为Mybatis带来诸多官方没有的高级特性,包含[通用CrudMapper](#通用CrudMapper)、[Mybatis语法增强](#Mybatis语法增强)、[字段生成](#字段生成)、[JSON映射](#JSON映射)、[智能方法查询](#智能方法查询)、[无感知分页](#无感知分页)、[SQL指标与监控](#SQL指标与监控)、流式查询(开发中...)等功能,使用MybatisBoost来提升开发效率,轻松编写SQL代码!
4 |
5 | 使用MybatisBoost的最低要求:
6 |
7 | * JDK 1.8+
8 | * MyBatis 3.4.4+
9 |
10 | ## 快速开始
11 |
12 | 基于Spring Boot以及mybatis-spring-boot-starter项目的快速开始。
13 |
14 | Maven:
15 | ```xml
16 |
17 | org.mybatis.spring.boot
18 | mybatis-spring-boot-starter
19 | 1.3.2
20 |
21 |
22 | cn.mybatisboost
23 | mybatis-boost-spring-boot-starter
24 | 2.3.3-SNAPSHOT
25 |
26 | ```
27 |
28 | Gradle:
29 | ```gradle
30 | compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
31 | compile 'cn.mybatisboost:mybatis-boost-spring-boot-starter:2.3.3-SNAPSHOT'
32 | ```
33 |
34 | 在手动创建SqlSessionFactory Bean的情况下,请确保MybatisBoost的Mybatis Plugin有被加载。
35 |
36 | ```java
37 | @Bean
38 | public SqlSessionFactory sqlSessionFactory(ObjectProvider interceptorsProvider) {
39 | SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
40 | sessionFactory.setPlugins(interceptorsProvider.getIfAvailable()); // 确保加载了所有的Mybatis Plugin
41 | sessionFactory.setTypeHandlers(new TypeHandler[]{new JsonTypeHandler()}); // 确保加载了JSON映射所需的Mybatis TypeHandler
42 | ...
43 | }
44 | ```
45 |
46 | 如果你的数据库Table名称与项目中的POJO类名一致,Table的列名称与POJO属性的名称命名方式也一致的话(大小写忽略),那么恭喜你,你已经成功引入了MybatisBoost,可以跳过下一章《名称映射》的内容。
47 |
48 | > 后续内容将使用术语“表”来代表数据库中的表,“列”来代表数据库表中的列,“POJO”来代表对应的实体类,“属性”和“字段”来代表POJO中的成员变量
49 |
50 | ## 名称映射
51 |
52 | 配置名称映射是为了使MybatisBoost能自动地找到POJO对应的表,以及POJO中的属性对应的列。名称映射分为自动映射和手动标注两种方案。
53 |
54 | 对于表名与POJO类名之间的自动映射,MybatisBoost内置有几个常用的表名映射器,如果内置的表名映射器无法满足你的需求,你也可以基于`NameAdaptor`接口实现自己的表名映射器。
55 |
56 | 表名映射器|POJO类名|映射到的表名
57 | -|-|-
58 | NoopNameAdaptor|DemoTable|DemoTable
59 | TPrefixedNameAdaptor|DemoTable|T_DemoTable
60 | SnakeCaseNameAdaptor|DemoTable|demo_table
61 |
62 | MybatisBoost默认使用`NoopNameAdaptor`表名映射器,对应的`application.properties`配置如下:
63 |
64 | ```
65 | mybatisboost.name-adaptor=cn.mybatisboost.core.adaptor.NoopNameAdaptor
66 | ```
67 |
68 | 对于列名与属性名之间的自动映射,MybatisBoost采用了Mybatis内置的MapUnderscoreToCamelCase功能,默认使用CamelCase命名方式。如果你的数据库列名命名方式为snake_case命名方式,请使用如下的`application.properties`配置:
69 |
70 | ```
71 | mybatis.configuration.map-underscore-to-camel-case=true
72 | ```
73 |
74 | 除了自动映射方案,MybatisBoost同样提供手动标注的功能。
75 |
76 | 现在假设你的表名为“DEMO_ThisTable”,你的POJO类名为“ThatTable”,表名和POJO类名之间并无任何联系,则可以使用JPA提供的标准注解进行手动标注。
77 |
78 | 同样地,主键也可以使用JPA提供的标准注解进行手动标注。
79 |
80 | 对于列名与POJO属性名之间的映射,MybatisBoost采用约定大于配置的思想,不提供手动标注的功能。
81 |
82 | ```java
83 | @Table(name = "DEMO_ThisTable")
84 | public class ThatTable {
85 |
86 | @Id // 默认以名称为“id”的字段作为主键,否则需要使用@Id注解手动标注
87 | private Long myId;
88 | private String myField;
89 | ...
90 | }
91 | ```
92 |
93 | 到此,你已经可以开始使用MybatisBoost了,下面将逐一介绍MybatisBoost的各种功能特性。
94 |
95 | ## 基础知识
96 |
97 | 为了使MybatisBoost生效,你的Mybatis Mapper接口必须直接或间接地继承`GenericMapper`接口,范型<T>代表此Mapper对应的POJO类。
98 |
99 | > MybatisBoost提供的所有通用Mapper都继承于`GenericMapper`接口
100 |
101 | ## 通用CrudMapper
102 |
103 | 继承于`CrudMapper`接口的Mybatis Mapper接口即自动拥有了CrudMapper接口的所有功能。
104 |
105 | CrudMapper接口中的方法使用POJO中所有的属性参与CRUD,但不包括以Selective结尾的方法,这些方法会过滤值为null的属性,即POJO中值为null的属性不参与CRUD。
106 |
107 | 带有`properties`参数的方法,可使用`properties`参数指定参与插入、更新的属性。如果`properties`参数的第一个字符串为`!`,则代表排除后续指定的属性,如`new String[]{"!", "id"}`则代表除“id”以外,其他属性都参与CRUD。
108 |
109 | 同样地,带有`conditionProperties`参数的方法,可使用`conditionProperties`参数指定用于WHERE条件的属性。
110 |
111 | CrudMapper中的所有方法如下:
112 |
113 | ```java
114 | public interface CrudMapper {
115 |
116 | int count(T entity, String... conditionProperties);
117 | T selectOne(T entity, String... conditionProperties);
118 | List select(T entity, String... conditionProperties);
119 | List selectWithRowBounds(T entity, RowBounds rowBounds, String... conditionProperties);
120 | int countAll();
121 | List selectAll();
122 | List selectAllWithRowBounds(RowBounds rowBounds);
123 | T selectById(Object id);
124 | List selectByIds(Object... ids);
125 | int insert(T entity, String... properties);
126 | int batchInsert(List entities, String... properties);
127 | int insertSelective(T entity, String... properties);
128 | int batchInsertSelective(List entities, String... properties);
129 | int update(T entity, String... conditionProperties);
130 | int updatePartial(T entity, String[] properties, String... conditionProperties);
131 | int updateSelective(T entity, String... conditionProperties);
132 | int updatePartialSelective(T entity, String[] properties, String... conditionProperties);
133 | int delete(T entity, String... conditionProperties);
134 | int deleteByIds(Object... ids);
135 | }
136 | ```
137 |
138 | 如果你不需要CrudMapper接口里的所有方法,可以把CrudMapper接口中你所需的方法复制到你自己的Mybatis Mapper里即可。(需要把方法上的注解也一并复制。)
139 |
140 | ## MySQL CrudMapper
141 |
142 | 除了通用的CrudMapper,MybatisBoost还提供专用于MySQL的`MysqlCrudMapper`接口,在CrudMapper的基础上,增加了几个支持MySQL特性的方法。
143 |
144 | ```java
145 | public interface MysqlCrudMapper extends CrudMapper {
146 |
147 | int save(T entity, String... properties);
148 | int saveSelective(T entity, String... properties);
149 | int batchSave(List entity, String... properties);
150 | int batchSaveSelective(List entity, String... properties);
151 | int replace(T entity, String... properties);
152 | int replaceSelective(T entity, String... properties);
153 | int batchReplace(List entity, String... properties);
154 | int batchReplaceSelective(List entity, String... properties);
155 | }
156 | ```
157 |
158 | 其中,save方法使用的是MySQL的“ON DUPLICATE KEY UPDATE”特性,replace方法使用的是“REPLACE INTO”特性。
159 |
160 | ## Mybatis语法增强
161 |
162 | 为了使SQL的编写变得更简单,MybatisBoost提供了数个Mybatis和SQL语法增强的功能,包括自动参数映射、INSERT语法增强、UPDATE语法增强、表名变量、空值检测和集合参数映射。每个增强都可以单独使用,也可以联合使用。
163 |
164 | ### 自动参数映射
165 |
166 | Mybatis设计之中的一个不合理之处,在于舍弃了JDBC原生的参数占位符(即`?`)。显而易见的是,简单的SQL语句根本没有必要使用Mybatis的`#{variable}`语法去做多余的映射,这种麻烦在编写INSERT和UPDATE语句的时候尤为明显。
167 |
168 | 为此,MybatisBoost恢复了JDBC原生的参数占位符功能,MybatisBoost会自动按照参数的声明顺序做出正确的映射。
169 |
170 | ```java
171 | @Update("UPDATE table SET column1 = ? WHERE condition1 = ?")
172 | int update(String a, String b);
173 | ```
174 |
175 | > 自动参数映射目前还不支持嵌套属性,即不支持自动映射到对象中的属性
176 |
177 | ### INSERT语法增强
178 |
179 | MybatisBoost提供了更为简洁的INSERT语法,使得SQL的编写变得更为简单。
180 |
181 | ```java
182 | @Insert("INSERT *")
183 | int insertOne1(T entity); // 插入一条记录,插入所有字段
184 |
185 | @Insert("INSERT column1, column2, column3")
186 | int insertOne2(T entity); // 插入一条记录,只插入column1、column2、column3三个字段
187 |
188 | @Insert("INSERT NOT column4, column5")
189 | int insertOne3(T entity); // 插入一条记录,插入除了column4、column5以外的所有字段
190 |
191 | @Insert("INSERT *")
192 | int insertMany(List entities); // 批量插入,插入POJO中的所有字段
193 | ```
194 |
195 | ### UPDATE语法增强
196 |
197 | 同样地,MybatisBoost提供了更为简洁的UPDATE语法。
198 |
199 | ```java
200 | @Update("UPDATE SET *")
201 | int update1(T entity); // 更新所有字段
202 |
203 | @Update("UPDATE SET column1, column2, column3")
204 | int update2(T entity); // 只更新column1、column2、column3三个字段
205 |
206 | @Update("UPDATE SET NOT column4, column5")
207 | int update3(T entity); // 更新除了column4、column5以外的所有字段
208 |
209 | @Update("UPDATE SET column1, column2 WHERE condition1 = ?")
210 | int update3(String a, String b, String c); // 更新column1、column2两个字段,并且条件是“condition1 = c”
211 | ```
212 |
213 | ### 表名变量
214 |
215 | 在编写SQL语句时,SQL中的表名可使用`#t`代替,MybatisBoost会自动替换成正确的表名。
216 |
217 | 此功能不仅简化了表名的编写,还使得SQL语句具有了可重用性。
218 |
219 | ```sql
220 | SELECT * FROM #t
221 | ```
222 |
223 | ### 空值检测
224 |
225 | 现有一条SQL语句如下:
226 |
227 | ```sql
228 | SELECT * FROM Post WHERE id = #{id} AND Name != #{name}
229 | ```
230 |
231 | 假设传入的id参数和name参数都为null,则SQL会自动重写为如下的形式:
232 |
233 | ```sql
234 | SELECT * FROM Post WHERE id IS NULL AND Name IS NOT NULL
235 | ```
236 |
237 | ### 集合参数映射
238 |
239 | 使用MybatisBoost之前,集合参数的映射方法:
240 |
241 | ```xml
242 |
249 | ```
250 |
251 | 使用MybatisBoost之后,集合参数的映射方法就如同普通参数一样:
252 |
253 | ```sql
254 | SELECT * FROM Post WHERE id IN #{list}
255 | ```
256 |
257 | ## 字段生成
258 |
259 | 在执行INSERT语句前,MybatisBoost会检查待插入的POJO,如果发现其中的字段被标记了JPA的`@GeneratedValue`注解,则会调用相应的`ValueGenerator`自动填充字段。
260 |
261 | 以下示例中,“id”字段使用了内置的`UuidGenerator`生成器生成主键:
262 |
263 | ```java
264 | public class Post {
265 |
266 | @GeneratedValue(generator = "cn.mybatisboost.generator.UuidGenerator")
267 | private String id;
268 | ...
269 | }
270 | ```
271 |
272 | 目前,MybatisBoost提供了一个UuidGenerator生成器和Snowflake算法的实现,你也可以基于`ValueGenerator`接口实现自己的字段生成器。
273 |
274 | ## JSON映射
275 |
276 | JSON映射功能可以自动地将嵌套对象序列化成JSON保存到数据库中,同样地,也可以自动地将数据库中的JSON反序列化成嵌套对象。
277 |
278 | ```java
279 | class Post {
280 |
281 | private String id;
282 | private Article article;
283 | private List articleList;
284 | private Map articleMap;
285 | ...
286 | }
287 |
288 | class Article extends JsonType {
289 |
290 | private String id;
291 | private String name;
292 | ...
293 | }
294 | ```
295 |
296 | 在插入Project对象时,article、articleList、articleMap三个字段会自动序列化成JSON字符串。相反地,在查询Project对象时,数据库中的JSON字符串会反序列化成相应的嵌套对象。
297 |
298 | > 嵌套对象必须继承于`JsonType`类
299 |
300 | ## 智能方法查询
301 |
302 | 简单的SQL语句千篇一律,能否不再编写那些显而易见的SQL语句呢?答案是肯定的。
303 |
304 | ```java
305 | public interface PostMapper extends GenericMapper {
306 |
307 | @org.apache.ibatis.annotations.Mapper
308 | List selectByPostIdAndPostDateBw(int a, Date b, Date c);
309 | }
310 | ```
311 |
312 | 以上代码片段是一个简单的方法查询,MybatisBoost会自动识别并生成对应的SQL语句。
313 |
314 | ```sql
315 | SELECT * FROM #t WHERE PostId = ? AND PostDate BETWEEN ? AND ?
316 | ```
317 |
318 | 只要以Mybatis的`@Mapper`注解标记的方法,MybatisBoost都会智能的生成相应SQL语句,让你的双手解放于编写千篇一律的简单SQL语句。
319 |
320 | 下面我们就以“selectByPostIdAndPostDateBw”为例来分析下如何编写智能方法查询,分解后的单词如下:select By PostId And PostDate Bw。其中“select”称为“方法词”,“By”称为“辅助词”,“PostId”和“PostDate”为POJO中的属性,“And”和“Bw”(BETWEEN的缩写)为SQL关键字,其中,方法词和辅助词都是必须的,其他的为可选项。
321 |
322 | 目前支持的方法词:select、count、delete。
323 |
324 | 目前支持的关键字:
325 |
326 | 关键字|缩写|对应的SQL片段
327 | -|-|-
328 | And|And|AND
329 | Or|Or|OR
330 | Is|Is|= ?
331 | Equals|E|= ?
332 | Between|Bw|BETWEEN ? AND ?
333 | NotBetween|Nbw|NOT BETWEEN ? AND ?
334 | LessThan|Lt|< ?
335 | LessThanEqual|Lte|<= ?
336 | GreaterThan|Gt|> ?
337 | GreaterThanEqual|Gte|>= ?
338 | After|Af|> ?
339 | Before|Bf|< ?
340 | IsNull|N|IS| NULL
341 | IsNotNull|Nn|IS NOT NULL
342 | IsEmpty|E|= ''
343 | IsNotEmpty|Ne|!= ''
344 | Like|L|LIKE ?
345 | NotLike|Nl|NOT LIKE ?
346 | OrderBy|Ob|ORDER BY
347 | Not|Not|!= ?
348 | In|In|IN| ?
349 | NotIn|Ni|NOT IN ?
350 | IsTrue|T|= TRUE
351 | IsFalse|F|= FALSE
352 | Asc|Asc|ASC
353 | Desc|Desc|DESC
354 |
355 | 同时,智能查询方法还支持分页功能:
356 |
357 | ```java
358 | public interface PostMapper extends GenericMapper {
359 |
360 | @Mapper
361 | List selectAllOffset10Limit100();
362 |
363 | @Mapper
364 | List selectTop3();
365 |
366 | @Mapper
367 | Post selectFirst();
368 | }
369 | ```
370 |
371 | ## 无感知分页
372 |
373 | Mybatis本身其实已经提供了分页功能,可惜它的实现并不优雅。为此,MybatisBoost在使用方法不变的前提下,透明的修改了实现,做到了真正的`物理分页`。
374 |
375 | ```java
376 | List selectAll(RowBounds rowBounds); // RowBounds内含offset和limit字段
377 | ```
378 |
379 | > 目前暂时只支持MySQL和PostgreSQL数据库,后续支持敬请期待
380 |
381 | ## SQL指标与监控
382 |
383 | 默认情况下不开启SQL指标与监控功能。话不多说,直接上配置,简单易懂。
384 |
385 | ```
386 | # 开启SQL指标与监控功能
387 | mybatisboost.metric.enabled=true
388 | # 在日志中打印SQL和执行时间
389 | mybatisboost.showQuery=true
390 |
391 | # 以下配置为可选配置
392 |
393 | # 打印SQL时是否同时打印SQL参数
394 | mybatisboost.showQueryWithParameters=boolean
395 | # 慢SQL阈值(默认情况下,慢SQL会打印在日志中)
396 | mybatisboost.slowQueryThresholdInMillis=long
397 | # 慢SQL回调处理器(参数一为SQL语句,参数二为执行时间ms),可编写代码实现一些自定义逻辑,比如报警
398 | mybatisboost.slowQueryHandler=Class extends BiConsumer>
399 | ```
400 |
401 | ## 欢迎使用
402 |
403 | 光看文档太抽象?mybatis-boost-test模块下有各种使用case,敬请参考。
404 |
405 | MybatisBoost中没有你想要的功能?亦或是MybatisBoost有BUG?欢迎各位提出issues!
--------------------------------------------------------------------------------
/mybatis-boost-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | cn.mybatisboost
9 | mybatis-boost
10 | 2.3.3-SNAPSHOT
11 |
12 |
13 | mybatis-boost-core
14 |
15 |
16 |
17 | javax.persistence
18 | persistence-api
19 |
20 |
21 | org.mybatis
22 | mybatis
23 | true
24 |
25 |
26 | org.slf4j
27 | slf4j-api
28 |
29 |
30 | org.apache.commons
31 | commons-lang3
32 |
33 |
34 | com.fasterxml.jackson.datatype
35 | jackson-datatype-jdk8
36 |
37 |
38 | com.fasterxml.jackson.datatype
39 | jackson-datatype-jsr310
40 |
41 |
42 | com.fasterxml.jackson.module
43 | jackson-module-parameter-names
44 |
45 |
46 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/Configuration.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core;
2 |
3 | import cn.mybatisboost.core.adaptor.NameAdaptor;
4 | import cn.mybatisboost.core.adaptor.NoopNameAdaptor;
5 |
6 | import java.util.function.BiConsumer;
7 |
8 | public class Configuration {
9 |
10 | private NameAdaptor nameAdaptor = new NoopNameAdaptor();
11 | private boolean multipleDatasource;
12 | private boolean iterateSelectiveInBatch;
13 | private boolean showQuery;
14 | private boolean showQueryWithParameters;
15 | private long slowQueryThresholdInMillis = Long.MAX_VALUE;
16 | private BiConsumer slowQueryHandler;
17 |
18 | public static Builder builder() {
19 | return new Builder();
20 | }
21 |
22 | public NameAdaptor getNameAdaptor() {
23 | return nameAdaptor;
24 | }
25 |
26 | public boolean isMultipleDatasource() {
27 | return multipleDatasource;
28 | }
29 |
30 | public boolean isIterateSelectiveInBatch() {
31 | return iterateSelectiveInBatch;
32 | }
33 |
34 | public boolean isShowQuery() {
35 | return showQuery;
36 | }
37 |
38 | public boolean isShowQueryWithParameters() {
39 | return showQueryWithParameters;
40 | }
41 |
42 | public long getSlowQueryThresholdInMillis() {
43 | return slowQueryThresholdInMillis;
44 | }
45 |
46 | public BiConsumer getSlowQueryHandler() {
47 | return slowQueryHandler;
48 | }
49 |
50 | public static class Builder {
51 |
52 | private Configuration configuration = new Configuration();
53 |
54 | public Configuration build() {
55 | return configuration;
56 | }
57 |
58 | public Builder setNameAdaptor(NameAdaptor nameAdaptor) {
59 | configuration.nameAdaptor = nameAdaptor;
60 | return this;
61 | }
62 |
63 | public Builder setMultipleDatasource(boolean multipleDatasource) {
64 | configuration.multipleDatasource = multipleDatasource;
65 | return this;
66 | }
67 |
68 | public Builder setIterateSelectiveInBatch(boolean iterateSelectiveInBatch) {
69 | configuration.iterateSelectiveInBatch = iterateSelectiveInBatch;
70 | return this;
71 | }
72 |
73 | public Builder setShowQuery(boolean showQuery) {
74 | configuration.showQuery = showQuery;
75 | return this;
76 | }
77 |
78 | public Builder setShowQueryWithParameters(boolean showQueryWithParameters) {
79 | configuration.showQueryWithParameters = showQueryWithParameters;
80 | return this;
81 | }
82 |
83 | public Builder setSlowQueryThresholdInMillis(long slowQueryThresholdInMillis) {
84 | configuration.slowQueryThresholdInMillis = slowQueryThresholdInMillis;
85 | return this;
86 | }
87 |
88 | public Builder setSlowQueryHandler(BiConsumer slowQueryHandler) {
89 | configuration.slowQueryHandler = slowQueryHandler;
90 | return this;
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/ConfigurationAware.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core;
2 |
3 | public interface ConfigurationAware {
4 |
5 | void setConfiguration(Configuration configuration);
6 | }
7 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/DispatcherInterceptor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core;
2 |
3 | import cn.mybatisboost.util.MyBatisUtils;
4 | import org.apache.ibatis.executor.statement.StatementHandler;
5 | import org.apache.ibatis.mapping.BoundSql;
6 | import org.apache.ibatis.mapping.MappedStatement;
7 | import org.apache.ibatis.plugin.*;
8 | import org.apache.ibatis.reflection.MetaObject;
9 |
10 | import java.sql.Connection;
11 | import java.util.List;
12 | import java.util.Properties;
13 | import java.util.concurrent.CopyOnWriteArrayList;
14 |
15 | @Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
16 | public class DispatcherInterceptor implements Interceptor {
17 |
18 | private Configuration configuration;
19 | private List preprocessors = new CopyOnWriteArrayList<>();
20 | private List providers = new CopyOnWriteArrayList<>();
21 |
22 | public DispatcherInterceptor(Configuration configuration) {
23 | this.configuration = configuration;
24 | }
25 |
26 | public void appendPreprocessor(SqlProvider provider) {
27 | preprocessors.add(provider);
28 | if (provider instanceof ConfigurationAware) {
29 | ((ConfigurationAware) provider).setConfiguration(configuration);
30 | }
31 | }
32 |
33 | public void appendProvider(SqlProvider provider) {
34 | providers.add(provider);
35 | }
36 |
37 | @Override
38 | public Object intercept(Invocation invocation) throws Throwable {
39 | Connection connection = (Connection) invocation.getArgs()[0];
40 | MetaObject metaObject = MyBatisUtils.getMetaObject(invocation.getTarget());
41 | MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
42 | BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
43 | preprocessors.forEach(p -> p.replace(connection, metaObject, mappedStatement, boundSql));
44 | providers.forEach(p -> p.replace(connection, metaObject, mappedStatement, boundSql));
45 | return invocation.proceed();
46 | }
47 |
48 | @Override
49 | public Object plugin(Object target) {
50 | return Plugin.wrap(target, this);
51 | }
52 |
53 | @Override
54 | public void setProperties(Properties properties) {
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/GenericMapper.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core;
2 |
3 | public interface GenericMapper {
4 | }
5 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/SqlProvider.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core;
2 |
3 | import org.apache.ibatis.mapping.BoundSql;
4 | import org.apache.ibatis.mapping.MappedStatement;
5 | import org.apache.ibatis.reflection.MetaObject;
6 |
7 | import java.sql.Connection;
8 |
9 | public interface SqlProvider {
10 |
11 | String MYBATIS_BOOST = "#MYBATIS_BOOST#";
12 |
13 | default String reserved() {
14 | return MYBATIS_BOOST;
15 | }
16 |
17 | void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql);
18 | }
19 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/adaptor/NameAdaptor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.adaptor;
2 |
3 | public interface NameAdaptor {
4 |
5 | String adapt(String name);
6 | }
7 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/adaptor/NoopNameAdaptor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.adaptor;
2 |
3 | public class NoopNameAdaptor implements NameAdaptor {
4 |
5 | @Override
6 | public String adapt(String name) {
7 | return name;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/adaptor/SnakeCaseNameAdaptor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.adaptor;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 |
5 | public class SnakeCaseNameAdaptor implements NameAdaptor {
6 |
7 | @Override
8 | public String adapt(String name) {
9 | return String.join("_", StringUtils.splitByCharacterTypeCamelCase(name)).toLowerCase();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/adaptor/TPrefixedNameAdaptor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.adaptor;
2 |
3 | public class TPrefixedNameAdaptor implements NameAdaptor {
4 |
5 | @Override
6 | public String adapt(String name) {
7 | return "T_" + name;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/preprocessor/AutoParameterMappingPreprocessor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.preprocessor;
2 |
3 | import cn.mybatisboost.core.SqlProvider;
4 | import cn.mybatisboost.util.SqlUtils;
5 | import org.apache.ibatis.mapping.BoundSql;
6 | import org.apache.ibatis.mapping.MappedStatement;
7 | import org.apache.ibatis.mapping.ParameterMapping;
8 | import org.apache.ibatis.reflection.MetaObject;
9 | import org.apache.ibatis.session.Configuration;
10 |
11 | import java.sql.Connection;
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | public class AutoParameterMappingPreprocessor implements SqlProvider {
16 |
17 | @Override
18 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) {
19 | List parameterMappings = boundSql.getParameterMappings();
20 | Object parameterObject = boundSql.getParameterObject();
21 | if (parameterMappings.isEmpty() && parameterObject instanceof Map) {
22 | int parameterCount = SqlUtils.countPlaceholders(boundSql.getSql());
23 | if (parameterCount > 0) {
24 | Configuration configuration = (Configuration) metaObject.getValue("delegate.configuration");
25 | for (int size = ((Map, ?>) parameterObject).size() / 2, i = size - parameterCount + 1; i <= size; i++) {
26 | parameterMappings.add(new ParameterMapping.Builder
27 | (configuration, "param" + i, Object.class).build());
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/preprocessor/MybatisCacheRemovingPreprocessor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.preprocessor;
2 |
3 | import cn.mybatisboost.core.SqlProvider;
4 | import org.apache.ibatis.mapping.BoundSql;
5 | import org.apache.ibatis.mapping.MappedStatement;
6 | import org.apache.ibatis.reflection.MetaObject;
7 |
8 | import java.sql.Connection;
9 | import java.util.ArrayList;
10 |
11 | public class MybatisCacheRemovingPreprocessor implements SqlProvider {
12 |
13 | @Override
14 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) {
15 | metaObject.setValue("delegate.boundSql.parameterMappings", new ArrayList<>(boundSql.getParameterMappings()));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/core/preprocessor/ParameterNormalizationPreprocessor.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.core.preprocessor;
2 |
3 | import cn.mybatisboost.core.SqlProvider;
4 | import cn.mybatisboost.util.MapperUtils;
5 | import cn.mybatisboost.util.MyBatisUtils;
6 | import org.apache.ibatis.mapping.BoundSql;
7 | import org.apache.ibatis.mapping.MappedStatement;
8 | import org.apache.ibatis.reflection.MetaObject;
9 |
10 | import java.sql.Connection;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 |
14 | public class ParameterNormalizationPreprocessor implements SqlProvider {
15 |
16 | @Override
17 | @SuppressWarnings("unchecked")
18 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) {
19 | Object parameterObject = boundSql.getParameterObject();
20 | if (parameterObject != null) {
21 | Class> entityType;
22 | try {
23 | entityType = MapperUtils.getEntityTypeFromMapper
24 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.')));
25 | } catch (Exception ignored) {
26 | return;
27 | }
28 | if (parameterObject.getClass() != entityType) {
29 | if (parameterObject instanceof Map) {
30 | Map parameterMap = (Map) parameterObject;
31 | if (parameterMap.size() == 2 &&
32 | parameterMap.containsKey("collection") && parameterMap.containsKey("list")) {
33 | Object collection = parameterMap.get("list");
34 | parameterMap.clear();
35 | parameterMap.put("arg0", collection);
36 | parameterMap.put("param1", collection);
37 | } else if (parameterMap.size() == 1 && parameterMap.containsKey("array")) {
38 | Object array = parameterMap.get("array");
39 | parameterMap.clear();
40 | parameterMap.put("arg0", array);
41 | parameterMap.put("param1", array);
42 | }
43 | } else {
44 | Map parameterMap = new HashMap<>();
45 | parameterMap.put("arg0", parameterObject);
46 | parameterMap.put("param1", parameterObject);
47 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler"))
48 | .setValue("parameterObject", parameterMap);
49 | metaObject.setValue("delegate.boundSql.parameterObject", parameterMap);
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/generator/GeneratingSqlProvider.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.generator;
2 |
3 | import cn.mybatisboost.core.SqlProvider;
4 | import cn.mybatisboost.util.EntityUtils;
5 | import cn.mybatisboost.util.MapperUtils;
6 | import cn.mybatisboost.util.function.UncheckedFunction;
7 | import org.apache.ibatis.mapping.BoundSql;
8 | import org.apache.ibatis.mapping.MappedStatement;
9 | import org.apache.ibatis.mapping.SqlCommandType;
10 | import org.apache.ibatis.reflection.MetaObject;
11 |
12 | import javax.persistence.GeneratedValue;
13 | import java.lang.reflect.Field;
14 | import java.sql.Connection;
15 | import java.util.Collections;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.concurrent.ConcurrentHashMap;
19 | import java.util.concurrent.ConcurrentMap;
20 |
21 | public class GeneratingSqlProvider implements SqlProvider {
22 |
23 | private static ConcurrentMap> generatorCache = new ConcurrentHashMap<>();
24 |
25 | @Override
26 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) {
27 | if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
28 | Class> entityType;
29 | try {
30 | entityType = MapperUtils.getEntityTypeFromMapper
31 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.')));
32 | } catch (Exception ignored) {
33 | return;
34 | }
35 | Object parameterObject = boundSql.getParameterObject();
36 | List parameterList = null;
37 | if (parameterObject instanceof Map) {
38 | parameterObject = ((Map) parameterObject).get("param1");
39 | if (parameterObject instanceof List) {
40 | parameterList = (List) parameterObject;
41 | }
42 | }
43 | if (parameterList == null) {
44 | if (parameterObject == null) return;
45 | parameterList = Collections.singletonList(parameterObject);
46 | } else if (parameterList.isEmpty() || parameterList.get(0).getClass() != entityType) return;
47 |
48 | List generatedFields = EntityUtils.getGeneratedFields(entityType);
49 | if (!generatedFields.isEmpty()) {
50 | try {
51 | for (Object parameter : parameterList) {
52 | for (Field field : generatedFields) {
53 | String generatorType = field.getAnnotation(GeneratedValue.class).generator();
54 | ValueGenerator> generator = generatorCache.computeIfAbsent(generatorType,
55 | UncheckedFunction.of(key -> (ValueGenerator>) Class.forName(key).newInstance()));
56 | field.set(parameter, generator.generateValue(entityType, field.getType()));
57 | }
58 | }
59 | } catch (IllegalAccessException e) {
60 | throw new RuntimeException(e);
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/generator/Snowflake.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.generator;
2 |
3 | public class Snowflake {
4 |
5 | private final long epoch;
6 | private final long workerId;
7 | private final int timestampShifting;
8 | private final int workerIdShifting;
9 | private final long maxSequence;
10 |
11 | private long lastTimestamp;
12 | private long sequence;
13 |
14 | public Snowflake(long workerId) {
15 | this(1545825894992L, workerId);
16 | }
17 |
18 | public Snowflake(long epoch, long workerId) {
19 | this(epoch, workerId, 10, 12);
20 | }
21 |
22 | public Snowflake(long epoch, long workerId, int workerIdBits, int sequenceBits) {
23 | this.epoch = epoch;
24 | this.workerId = workerId;
25 | timestampShifting = sequenceBits + workerIdBits;
26 | workerIdShifting = sequenceBits;
27 | maxSequence = ~(-1L << sequenceBits);
28 | }
29 |
30 | public synchronized long next() {
31 | long timestamp = System.currentTimeMillis() - epoch;
32 | if (timestamp != lastTimestamp) {
33 | if (timestamp < lastTimestamp)
34 | throw new IllegalStateException("Clock moved backwards");
35 | lastTimestamp = timestamp;
36 | sequence = 0;
37 | }
38 | long id = timestamp << timestampShifting;
39 | id |= workerId << workerIdShifting;
40 | if (sequence > maxSequence) {
41 | Thread.yield();
42 | return next();
43 | }
44 | id |= sequence++;
45 | return id;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/generator/UuidGenerator.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.generator;
2 |
3 | import java.util.UUID;
4 |
5 | public class UuidGenerator implements ValueGenerator {
6 |
7 | @Override
8 | public String generateValue(Class> type, Class> fieldType) {
9 | return UUID.randomUUID().toString().replace("-", "");
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/generator/ValueGenerator.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.generator;
2 |
3 | public interface ValueGenerator {
4 |
5 | T generateValue(Class> type, Class> fieldType);
6 | }
7 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/json/JsonResultSetsHandler.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.json;
2 |
3 | import cn.mybatisboost.util.ReflectionUtils;
4 | import cn.mybatisboost.util.function.UncheckedFunction;
5 | import org.apache.ibatis.executor.resultset.ResultSetHandler;
6 | import org.apache.ibatis.plugin.*;
7 |
8 | import java.lang.reflect.Field;
9 | import java.sql.Statement;
10 | import java.util.List;
11 | import java.util.Properties;
12 | import java.util.concurrent.ConcurrentHashMap;
13 | import java.util.concurrent.ConcurrentMap;
14 |
15 | @Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class))
16 | public class JsonResultSetsHandler implements Interceptor {
17 |
18 | private ConcurrentMap fieldCache = new ConcurrentHashMap<>();
19 |
20 | @Override
21 | public Object intercept(Invocation invocation) throws Throwable {
22 | try {
23 | List> proceed = (List>) invocation.proceed();
24 | List properties = JsonTypeHandler.tlProperties.get();
25 | if (proceed.isEmpty() || properties.isEmpty()) return proceed;
26 |
27 | Class> type = proceed.get(0).getClass();
28 | List results = JsonTypeHandler.tlResults.get();
29 | for (int i = 0; i < results.size(); i++) {
30 | String content = results.get(i);
31 | if (content != null) {
32 | Field field = fieldCache.computeIfAbsent(properties.get(i % properties.size()),
33 | UncheckedFunction.of(key -> ReflectionUtils.makeAccessible(type.getDeclaredField(key))));
34 | field.set(proceed.get(i / properties.size()), JsonTypeHandler.objectMapper.readValue(content,
35 | JsonTypeHandler.objectMapper.constructType(field.getGenericType())));
36 | }
37 | }
38 | return proceed;
39 | } finally {
40 | JsonTypeHandler.tlProperties.remove();
41 | JsonTypeHandler.tlResults.remove();
42 | }
43 | }
44 |
45 | @Override
46 | public Object plugin(Object target) {
47 | return Plugin.wrap(target, this);
48 | }
49 |
50 | @Override
51 | public void setProperties(Properties properties) {
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/mybatis-boost-core/src/main/java/cn/mybatisboost/json/JsonTypeHandler.java:
--------------------------------------------------------------------------------
1 | package cn.mybatisboost.json;
2 |
3 | import cn.mybatisboost.support.JsonType;
4 | import cn.mybatisboost.util.PropertyUtils;
5 | import com.fasterxml.jackson.core.JsonProcessingException;
6 | import com.fasterxml.jackson.databind.ObjectMapper;
7 | import org.apache.ibatis.type.BaseTypeHandler;
8 | import org.apache.ibatis.type.JdbcType;
9 | import org.apache.ibatis.type.MappedTypes;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.sql.CallableStatement;
14 | import java.sql.PreparedStatement;
15 | import java.sql.ResultSet;
16 | import java.sql.SQLException;
17 | import java.util.ArrayList;
18 | import java.util.List;
19 | import java.util.Map;
20 |
21 | @MappedTypes({JsonType.class, List.class, Map.class})
22 | public class JsonTypeHandler extends BaseTypeHandler