├── .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 [![Maven central](https://maven-badges.herokuapp.com/maven-central/cn.mybatisboost/mybatis-boost/badge.svg)](https://maven-badges.herokuapp.com/maven-central/cn.mybatisboost/mybatis-boost) [![Build Status](https://www.travis-ci.org/zhang-rf/mybatis-boost.svg?branch=master)](https://www.travis-ci.org/zhang-rf/mybatis-boost) [![Coverage Status](https://coveralls.io/repos/github/zhang-rf/mybatis-boost/badge.svg)](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> 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 { 23 | 24 | private static Logger logger = LoggerFactory.getLogger(JsonTypeHandler.class); 25 | static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); 26 | static ThreadLocal> tlProperties = ThreadLocal.withInitial(ArrayList::new); 27 | static ThreadLocal> tlResults = ThreadLocal.withInitial(ArrayList::new); 28 | 29 | @Override 30 | public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) 31 | throws SQLException { 32 | try { 33 | ps.setString(i, objectMapper.writeValueAsString(parameter)); 34 | } catch (JsonProcessingException e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | 39 | @Override 40 | public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { 41 | List resultNames = tlProperties.get(); 42 | String property = PropertyUtils.normalizeProperty(columnName); 43 | if (!resultNames.contains(property)) { 44 | resultNames.add(property); 45 | } 46 | tlResults.get().add(rs.getString(columnName)); 47 | return null; 48 | } 49 | 50 | @Override 51 | public Object getNullableResult(ResultSet rs, int columnIndex) { 52 | logger.warn("Json type is not supported when using CallableStatement"); 53 | return null; 54 | } 55 | 56 | @Override 57 | public Object getNullableResult(CallableStatement cs, int columnIndex) { 58 | logger.warn("Json type is not supported when using CallableStatement"); 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/lang/LanguageSqlProvider.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.lang; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.lang.provider.*; 7 | import org.apache.ibatis.mapping.BoundSql; 8 | import org.apache.ibatis.mapping.MappedStatement; 9 | import org.apache.ibatis.reflection.MetaObject; 10 | 11 | import java.sql.Connection; 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | public class LanguageSqlProvider implements SqlProvider { 17 | 18 | private Configuration configuration; 19 | private List providers; 20 | 21 | public LanguageSqlProvider(Configuration configuration) { 22 | this.configuration = configuration; 23 | initProviders(); 24 | } 25 | 26 | protected void initProviders() { 27 | providers = Collections.unmodifiableList(Arrays.asList(new InsertEnhancement(), new UpdateEnhancement(), 28 | new TableEnhancement(), new NullEnhancement(), new ListParameterEnhancement())); 29 | for (SqlProvider p : providers) { 30 | if (p instanceof ConfigurationAware) { 31 | ((ConfigurationAware) p).setConfiguration(configuration); 32 | } 33 | } 34 | } 35 | 36 | @Override 37 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 38 | providers.forEach(p -> p.replace(connection, metaObject, mappedStatement, boundSql)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/lang/provider/InsertEnhancement.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.lang.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import cn.mybatisboost.util.MyBatisUtils; 9 | import cn.mybatisboost.util.SqlUtils; 10 | import cn.mybatisboost.util.tuple.BinaryTuple; 11 | import org.apache.ibatis.mapping.BoundSql; 12 | import org.apache.ibatis.mapping.MappedStatement; 13 | import org.apache.ibatis.mapping.SqlCommandType; 14 | import org.apache.ibatis.reflection.MetaObject; 15 | 16 | import java.sql.Connection; 17 | import java.util.*; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | 21 | public class InsertEnhancement implements SqlProvider, ConfigurationAware { 22 | 23 | private static final Pattern PATTERN_LITERAL_COLUMNS = Pattern.compile("((NOT|not) )?(\\w+, ?)*\\w+|\\*"); 24 | private Configuration configuration; 25 | 26 | @Override 27 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 28 | String sql = boundSql.getSql(); 29 | String sqlUpperCase = sql.toUpperCase(); 30 | if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT && 31 | !sqlUpperCase.startsWith("INSERT INTO ") && sqlUpperCase.startsWith("INSERT ")) { 32 | Matcher matcher = PATTERN_LITERAL_COLUMNS.matcher(sql = sql.substring(7)); 33 | if (!matcher.find()) { 34 | throw new IllegalStateException("Found INSERT statement but no column is specified"); 35 | } 36 | 37 | String literalColumns = matcher.group(); 38 | Class entityType = MapperUtils.getEntityTypeFromMapper 39 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 40 | boolean mapUnderscoreToCamelCase = (boolean) 41 | metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase"); 42 | BinaryTuple, List> propertiesAndColumns = 43 | SqlUtils.getPropertiesAndColumnsFromLiteralColumns(literalColumns, entityType, mapUnderscoreToCamelCase); 44 | 45 | List entities = boundSql.getParameterObject() instanceof Map ? 46 | (List) ((Map) boundSql.getParameterObject()).get("param1") : 47 | Collections.singletonList(Objects.requireNonNull(boundSql.getParameterObject(), 48 | "ParameterObject mustn't be null")); 49 | if (entities.isEmpty()) { 50 | throw new IllegalArgumentException("Can't insert empty list"); 51 | } else { 52 | String additionalStatement = sql.substring(literalColumns.length()); 53 | org.apache.ibatis.session.Configuration configuration = 54 | (org.apache.ibatis.session.Configuration) metaObject.getValue("delegate.configuration"); 55 | Object parameterObject = buildParameterObject(entities); 56 | metaObject.setValue("delegate.boundSql.sql", 57 | buildSql(entityType, propertiesAndColumns.second(), entities.size(), additionalStatement)); 58 | metaObject.setValue("delegate.boundSql.parameterMappings", 59 | MyBatisUtils.getListParameterMappings(configuration, propertiesAndColumns.first(), entities.size())); 60 | metaObject.setValue("delegate.boundSql.parameterObject", parameterObject); 61 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler")) 62 | .setValue("parameterObject", parameterObject); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | public void setConfiguration(Configuration configuration) { 69 | this.configuration = configuration; 70 | } 71 | 72 | private Object buildParameterObject(List entities) { 73 | Map map = new HashMap<>(); 74 | map.put("list", entities); 75 | map.put("collection", entities); 76 | return map; 77 | } 78 | 79 | private String buildSql(Class entityType, List columns, int batchSize, String additionalStatement) { 80 | StringBuilder sqlBuilder = new StringBuilder(); 81 | sqlBuilder.append("INSERT INTO ") 82 | .append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 83 | sqlBuilder.append(" ("); 84 | columns.forEach(c -> sqlBuilder.append(c).append(", ")); 85 | sqlBuilder.setLength(sqlBuilder.length() - 2); 86 | sqlBuilder.append(") VALUES "); 87 | for (int i = 0; i < batchSize; i++) { 88 | sqlBuilder.append("("); 89 | columns.forEach(c -> sqlBuilder.append("?, ")); 90 | sqlBuilder.setLength(sqlBuilder.length() - 2); 91 | sqlBuilder.append("), "); 92 | } 93 | sqlBuilder.setLength(sqlBuilder.length() - 2); 94 | sqlBuilder.append(additionalStatement); 95 | return sqlBuilder.toString(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/lang/provider/ListParameterEnhancement.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.lang.provider; 2 | 3 | import cn.mybatisboost.core.SqlProvider; 4 | import cn.mybatisboost.util.MyBatisUtils; 5 | import cn.mybatisboost.util.SqlUtils; 6 | import org.apache.ibatis.mapping.BoundSql; 7 | import org.apache.ibatis.mapping.MappedStatement; 8 | import org.apache.ibatis.mapping.ParameterMapping; 9 | import org.apache.ibatis.reflection.MetaObject; 10 | import org.apache.ibatis.scripting.xmltags.DynamicSqlSource; 11 | import org.apache.ibatis.scripting.xmltags.ForEachSqlNode; 12 | import org.apache.ibatis.scripting.xmltags.SqlNode; 13 | import org.apache.ibatis.session.Configuration; 14 | 15 | import java.lang.reflect.Field; 16 | import java.sql.Connection; 17 | import java.util.*; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.ConcurrentMap; 20 | import java.util.regex.Matcher; 21 | 22 | public class ListParameterEnhancement implements SqlProvider { 23 | 24 | private static ConcurrentMap filterCache = new ConcurrentHashMap<>(); 25 | 26 | @Override 27 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 28 | if (filter(mappedStatement)) { 29 | org.apache.ibatis.session.Configuration configuration = 30 | (org.apache.ibatis.session.Configuration) metaObject.getValue("delegate.configuration"); 31 | 32 | Map> listMap = getLists(boundSql.getParameterObject(), boundSql.getParameterMappings()); 33 | if (!listMap.isEmpty()) { 34 | boolean isEmpty = listMap.values().stream().anyMatch(List::isEmpty); 35 | if (isEmpty) { 36 | throw new IllegalArgumentException("Can't enhance empty list"); 37 | } 38 | 39 | StringBuilder sqlBuilder = new StringBuilder(boundSql.getSql()); 40 | replacePlaceholders(listMap, sqlBuilder); 41 | metaObject.setValue("delegate.boundSql.sql", 42 | String.format(sqlBuilder.toString(), buildNewPlaceholders(listMap))); 43 | refreshParameterMappings(boundSql.getParameterMappings(), configuration, listMap); 44 | } 45 | } 46 | } 47 | 48 | private boolean filter(MappedStatement mappedStatement) { 49 | return !(mappedStatement.getSqlSource() instanceof DynamicSqlSource) || 50 | filterCache.computeIfAbsent(mappedStatement.getId(), k -> filter(Collections.singletonList((SqlNode) 51 | MyBatisUtils.getMetaObject(mappedStatement.getSqlSource()).getValue("rootSqlNode")))); 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | private boolean filter(List contents) { 56 | for (SqlNode content : contents) { 57 | if (content instanceof ForEachSqlNode) return false; 58 | for (Field field : content.getClass().getDeclaredFields()) { 59 | field.setAccessible(true); 60 | try { 61 | if (SqlNode.class.isAssignableFrom(field.getType())) { 62 | if (!filter(Collections.singletonList((SqlNode) field.get(content)))) return false; 63 | } else if (Objects.equals(field.getGenericType().getTypeName(), 64 | "java.util.List")) { 65 | if (!filter((List) field.get(content))) return false; 66 | } 67 | } catch (IllegalAccessException e) { 68 | throw new RuntimeException(e); 69 | } 70 | } 71 | } 72 | return true; 73 | } 74 | 75 | private Map> getLists(Object parameterObject, List parameterMappings) { 76 | if (parameterMappings.isEmpty()) { 77 | return Collections.emptyMap(); 78 | } else { 79 | Map> listMap = new HashMap<>(); 80 | MetaObject parameterMetaObject = MyBatisUtils.getMetaObject(parameterObject); 81 | for (int i = 0; i < parameterMappings.size(); i++) { 82 | try { 83 | Object property = parameterMetaObject.getValue(parameterMappings.get(i).getProperty()); 84 | if (property instanceof List) { 85 | listMap.put(i, (List) property); 86 | } 87 | } catch (Exception ignored) { 88 | return Collections.emptyMap(); 89 | } 90 | } 91 | return listMap; 92 | } 93 | } 94 | 95 | private void replacePlaceholders(Map> listMap, StringBuilder sqlBuilder) { 96 | Matcher matcher = SqlUtils.PATTERN_PLACEHOLDER.matcher(sqlBuilder.toString()); 97 | int previousIndex = 0; 98 | for (Integer nextIndex : listMap.keySet()) { 99 | for (int i = previousIndex; i <= nextIndex; i++) { 100 | if (!matcher.find()) { 101 | throw new IndexOutOfBoundsException("SQL Placeholder not found"); 102 | } 103 | } 104 | int start = matcher.start() + previousIndex; 105 | previousIndex = nextIndex + 1; 106 | sqlBuilder.replace(start, start + 1, "%s"); 107 | } 108 | } 109 | 110 | private Object[] buildNewPlaceholders(Map> listMap) { 111 | Object[] placeholders = new Object[listMap.size()]; 112 | StringBuilder placeholderBuilder = new StringBuilder(); 113 | 114 | int index = 0; 115 | for (List list : listMap.values()) { 116 | placeholderBuilder.setLength(0); 117 | 118 | placeholderBuilder.append('('); 119 | list.forEach(i -> placeholderBuilder.append("?, ")); 120 | placeholderBuilder.setLength(placeholderBuilder.length() - 2); 121 | placeholderBuilder.append(')'); 122 | placeholders[index++] = placeholderBuilder.toString(); 123 | } 124 | return placeholders; 125 | } 126 | 127 | private void refreshParameterMappings(List parameterMappings, 128 | Configuration configuration, Map> listMap) { 129 | int offset = 0; 130 | for (Integer i : listMap.keySet()) { 131 | String property = parameterMappings.remove(i + offset).getProperty(); 132 | 133 | int n = 0; 134 | for (Object ignored : listMap.get(i)) { 135 | parameterMappings.add(i + offset++, new ParameterMapping.Builder 136 | (configuration, property + '[' + n++ + ']', Object.class).build()); 137 | } 138 | offset--; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/lang/provider/NullEnhancement.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.lang.provider; 2 | 3 | import cn.mybatisboost.core.SqlProvider; 4 | import cn.mybatisboost.util.MyBatisUtils; 5 | import cn.mybatisboost.util.SqlUtils; 6 | import org.apache.ibatis.mapping.BoundSql; 7 | import org.apache.ibatis.mapping.MappedStatement; 8 | import org.apache.ibatis.mapping.ParameterMapping; 9 | import org.apache.ibatis.mapping.SqlCommandType; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | import java.util.Iterator; 14 | import java.util.regex.Matcher; 15 | 16 | public class NullEnhancement implements SqlProvider { 17 | 18 | @Override 19 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 20 | if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT || 21 | mappedStatement.getSqlCommandType() == SqlCommandType.DELETE) { 22 | String sql = boundSql.getSql(); 23 | 24 | Matcher matcher = SqlUtils.PATTERN_PLACEHOLDER.matcher(sql); 25 | Iterator iterator = boundSql.getParameterMappings().iterator(); 26 | MetaObject parameterMetaObject = MyBatisUtils.getMetaObject(boundSql.getParameterObject()); 27 | boolean isUpperCase = Character.isUpperCase(sql.charAt(0)); 28 | 29 | int offset = 0; 30 | StringBuilder sqlBuilder = new StringBuilder(); 31 | while (matcher.find() & iterator.hasNext()) { 32 | try { 33 | if (parameterMetaObject.getValue(iterator.next().getProperty()) != null) continue; 34 | } catch (Exception ignored) { 35 | continue; 36 | } 37 | iterator.remove(); 38 | 39 | String substring = sql.substring(offset, matcher.end()); 40 | int before = substring.length(); 41 | substring = substring.replaceFirst(" ?!= *\\?$| ?<> *\\?$", 42 | isUpperCase ? " IS NOT NULL" : " is not null"); 43 | if (substring.length() == before) { 44 | substring = substring.replaceFirst(" ?= *\\?$", isUpperCase ? " IS NULL" : " is null"); 45 | } 46 | sqlBuilder.append(substring); 47 | offset = matcher.end(); 48 | } 49 | sqlBuilder.append(sql, offset, sql.length()); 50 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/lang/provider/TableEnhancement.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.lang.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import org.apache.ibatis.mapping.BoundSql; 9 | import org.apache.ibatis.mapping.MappedStatement; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | 14 | public class TableEnhancement implements SqlProvider, ConfigurationAware { 15 | 16 | private Configuration configuration; 17 | 18 | @Override 19 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 20 | String sql = boundSql.getSql(); 21 | if (sql.contains("#t")) { 22 | Class entityType = MapperUtils.getEntityTypeFromMapper 23 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 24 | metaObject.setValue("delegate.boundSql.sql", sql.replace("#t", 25 | EntityUtils.getTableName(entityType, configuration.getNameAdaptor()))); 26 | } 27 | } 28 | 29 | @Override 30 | public void setConfiguration(Configuration configuration) { 31 | this.configuration = configuration; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/lang/provider/UpdateEnhancement.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.lang.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import cn.mybatisboost.util.MyBatisUtils; 9 | import cn.mybatisboost.util.SqlUtils; 10 | import cn.mybatisboost.util.tuple.BinaryTuple; 11 | import org.apache.ibatis.mapping.BoundSql; 12 | import org.apache.ibatis.mapping.MappedStatement; 13 | import org.apache.ibatis.mapping.ParameterMapping; 14 | import org.apache.ibatis.mapping.SqlCommandType; 15 | import org.apache.ibatis.reflection.MetaObject; 16 | 17 | import java.sql.Connection; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | public class UpdateEnhancement implements SqlProvider, ConfigurationAware { 23 | 24 | private Configuration configuration; 25 | 26 | @Override 27 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 28 | String sql = boundSql.getSql(); 29 | if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE && 30 | sql.toUpperCase().startsWith("UPDATE SET ")) { 31 | String[] split = splitSql(sql); // split[0] = columns, split[1] = conditions(if there were) 32 | Class entityType = MapperUtils.getEntityTypeFromMapper 33 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 34 | boolean mapUnderscoreToCamelCase = (boolean) 35 | metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase"); 36 | 37 | BinaryTuple, List> propertiesAndColumns = 38 | SqlUtils.getPropertiesAndColumnsFromLiteralColumns(split[0], entityType, mapUnderscoreToCamelCase); 39 | List conditionProperties = getConditionProperties(entityType, boundSql.getParameterMappings()); 40 | propertiesAndColumns.first().removeAll(conditionProperties); 41 | propertiesAndColumns.second().removeAll(conditionProperties.stream() 42 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList())); 43 | 44 | metaObject.setValue("delegate.boundSql.sql", 45 | buildSQL(sql, entityType, propertiesAndColumns.second(), split)); 46 | metaObject.setValue("delegate.boundSql.parameterMappings", 47 | getParameterMappings(metaObject, boundSql, propertiesAndColumns.first())); 48 | } 49 | } 50 | 51 | @Override 52 | public void setConfiguration(Configuration configuration) { 53 | this.configuration = configuration; 54 | } 55 | 56 | private String[] splitSql(String sql) { 57 | String[] split = {sql.substring(11)}; 58 | if (split[0].contains(" where ")) { 59 | split = split[0].split(" where ", 2); 60 | } else if (split[0].contains(" WHERE ")) { 61 | split = split[0].split(" WHERE ", 2); 62 | } 63 | return split; 64 | } 65 | 66 | private List getConditionProperties(Class entityType, List parameterMappings) { 67 | List conditionProperties = parameterMappings.stream() 68 | .map(ParameterMapping::getProperty).collect(Collectors.toList()); 69 | try { 70 | String idProperty = EntityUtils.getIdProperty(entityType); 71 | if (!conditionProperties.contains(idProperty)) { 72 | conditionProperties.add(idProperty); 73 | } 74 | } catch (NoSuchFieldError ignored) { 75 | } 76 | return conditionProperties; 77 | } 78 | 79 | private String buildSQL(String sql, Class entityType, List columns, String[] split) { 80 | StringBuilder sqlBuilder = new StringBuilder(); 81 | sqlBuilder.append("UPDATE ") 82 | .append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())).append(" SET "); 83 | columns.forEach(c -> sqlBuilder.append(c).append(" = ?, ")); 84 | sqlBuilder.setLength(sqlBuilder.length() - 2); 85 | if (split.length == 2) { 86 | sqlBuilder.append(sql.contains(" WHERE ") ? " WHERE " : " where ").append(split[1]); 87 | } 88 | return sqlBuilder.toString(); 89 | } 90 | 91 | private List getParameterMappings(MetaObject metaObject, BoundSql boundSql, List properties) { 92 | org.apache.ibatis.session.Configuration configuration = (org.apache.ibatis.session.Configuration) 93 | metaObject.getValue("delegate.configuration"); 94 | List parameterMappings = boundSql.getParameterMappings(); 95 | Object parameterObject = boundSql.getParameterObject(); 96 | if (parameterObject instanceof Map) { 97 | for (int i = 1; i <= properties.size(); i++) { 98 | parameterMappings.add(i - 1, 99 | new ParameterMapping.Builder(configuration, "param" + i, Object.class).build()); 100 | } 101 | } else { 102 | parameterMappings.addAll(0, MyBatisUtils.getParameterMappings(configuration, properties)); 103 | } 104 | return parameterMappings; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/limiter/LimiterSqlProvider.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.limiter; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.limiter.provider.MySQL; 7 | import cn.mybatisboost.limiter.provider.PostgreSQL; 8 | import org.apache.ibatis.mapping.BoundSql; 9 | import org.apache.ibatis.mapping.MappedStatement; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | import java.sql.SQLException; 14 | import java.util.Arrays; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | public class LimiterSqlProvider implements SqlProvider { 19 | 20 | private Configuration configuration; 21 | private Map providerMap = new HashMap<>(); 22 | private volatile SqlProvider provider; 23 | 24 | public LimiterSqlProvider(Configuration configuration) { 25 | this.configuration = configuration; 26 | initProviders(); 27 | } 28 | 29 | protected void initProviders() { 30 | Arrays.asList(new MySQL(), new PostgreSQL()).forEach(p -> providerMap.put(p.toString(), p)); 31 | for (SqlProvider provider : providerMap.values()) { 32 | if (provider instanceof ConfigurationAware) { 33 | ((ConfigurationAware) provider).setConfiguration(configuration); 34 | } 35 | } 36 | } 37 | 38 | @Override 39 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 40 | SqlProvider provider = this.provider; 41 | if (provider == null || configuration.isMultipleDatasource()) { 42 | try { 43 | String databaseName = connection.getMetaData().getDatabaseProductName(); 44 | this.provider = provider = providerMap.get(databaseName); 45 | } catch (SQLException e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | if (provider != null) { 50 | provider.replace(connection, metaObject, mappedStatement, boundSql); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/limiter/provider/MySQL.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.limiter.provider; 2 | 3 | import cn.mybatisboost.core.SqlProvider; 4 | import cn.mybatisboost.util.MyBatisUtils; 5 | import cn.mybatisboost.util.SqlUtils; 6 | import org.apache.ibatis.mapping.BoundSql; 7 | import org.apache.ibatis.mapping.MappedStatement; 8 | import org.apache.ibatis.reflection.MetaObject; 9 | import org.apache.ibatis.session.RowBounds; 10 | 11 | import java.sql.Connection; 12 | 13 | public class MySQL implements SqlProvider { 14 | 15 | @Override 16 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 17 | metaObject.setValue("delegate.boundSql.sql", 18 | SqlUtils.appendLimit(boundSql.getSql(), (RowBounds) metaObject.getValue("delegate.rowBounds"))); 19 | metaObject.setValue("delegate.rowBounds", RowBounds.DEFAULT); 20 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.resultSetHandler")) 21 | .setValue("rowBounds", RowBounds.DEFAULT); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "MySQL"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/limiter/provider/PostgreSQL.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.limiter.provider; 2 | 3 | import cn.mybatisboost.core.SqlProvider; 4 | import cn.mybatisboost.util.MyBatisUtils; 5 | import cn.mybatisboost.util.SqlUtils; 6 | import org.apache.ibatis.mapping.BoundSql; 7 | import org.apache.ibatis.mapping.MappedStatement; 8 | import org.apache.ibatis.reflection.MetaObject; 9 | import org.apache.ibatis.session.RowBounds; 10 | 11 | import java.sql.Connection; 12 | 13 | public class PostgreSQL implements SqlProvider { 14 | 15 | @Override 16 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 17 | metaObject.setValue("delegate.boundSql.sql", 18 | SqlUtils.appendLimitOffset(boundSql.getSql(), (RowBounds) metaObject.getValue("delegate.rowBounds"))); 19 | metaObject.setValue("delegate.rowBounds", RowBounds.DEFAULT); 20 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.resultSetHandler")) 21 | .setValue("rowBounds", RowBounds.DEFAULT); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "PostgreSQL"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/CrudMapper.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper; 2 | 3 | import cn.mybatisboost.core.GenericMapper; 4 | import cn.mybatisboost.mapper.provider.Delete; 5 | import cn.mybatisboost.mapper.provider.*; 6 | import cn.mybatisboost.mapper.provider.Insert; 7 | import cn.mybatisboost.mapper.provider.Update; 8 | import org.apache.ibatis.annotations.*; 9 | import org.apache.ibatis.session.RowBounds; 10 | 11 | import java.util.List; 12 | 13 | public interface CrudMapper extends GenericMapper { 14 | 15 | @SelectProvider(type = SelectOrCount.class, method = "reserved") 16 | int count(T entity, String... conditionProperties); 17 | 18 | @SelectProvider(type = SelectOrCount.class, method = "reserved") 19 | T selectOne(T entity, String... conditionProperties); 20 | 21 | @SelectProvider(type = SelectOrCount.class, method = "reserved") 22 | List select(T entity, String... conditionProperties); 23 | 24 | @SelectProvider(type = SelectOrCount.class, method = "reserved") 25 | List selectWithRowBounds(T entity, RowBounds rowBounds, String... conditionProperties); 26 | 27 | @SelectProvider(type = SelectOrCountAll.class, method = "reserved") 28 | int countAll(); 29 | 30 | @SelectProvider(type = SelectOrCountAll.class, method = "reserved") 31 | List selectAll(); 32 | 33 | @SelectProvider(type = SelectOrCountAll.class, method = "reserved") 34 | List selectAllWithRowBounds(RowBounds rowBounds); 35 | 36 | @SelectProvider(type = SelectByIds.class, method = "reserved") 37 | T selectById(@Param("arg0") Object id); 38 | 39 | @SelectProvider(type = SelectByIds.class, method = "reserved") 40 | List selectByIds(Object... ids); 41 | 42 | @InsertProvider(type = Insert.class, method = "reserved") 43 | int insert(T entity, String... properties); 44 | 45 | @InsertProvider(type = Insert.class, method = "reserved") 46 | int batchInsert(List entities, String... properties); 47 | 48 | @InsertProvider(type = Insert.class, method = "reserved") 49 | int insertSelective(T entity, String... properties); 50 | 51 | @InsertProvider(type = Insert.class, method = "reserved") 52 | int batchInsertSelective(List entities, String... properties); 53 | 54 | @UpdateProvider(type = Update.class, method = "reserved") 55 | int update(T entity, String... conditionProperties); 56 | 57 | @UpdateProvider(type = Update.class, method = "reserved") 58 | int updatePartial(T entity, String[] properties, String... conditionProperties); 59 | 60 | @UpdateProvider(type = Update.class, method = "reserved") 61 | int updateSelective(T entity, String... conditionProperties); 62 | 63 | @UpdateProvider(type = Update.class, method = "reserved") 64 | int updatePartialSelective(T entity, String[] properties, String... conditionProperties); 65 | 66 | @DeleteProvider(type = Delete.class, method = "reserved") 67 | int delete(T entity, String... conditionProperties); 68 | 69 | @DeleteProvider(type = DeleteByIds.class, method = "reserved") 70 | int deleteByIds(Object... ids); 71 | } 72 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/MapperSqlProvider.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.MyBatisUtils; 7 | import cn.mybatisboost.util.function.UncheckedFunction; 8 | import org.apache.ibatis.mapping.BoundSql; 9 | import org.apache.ibatis.mapping.MappedStatement; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | import java.util.Objects; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.concurrent.ConcurrentMap; 16 | 17 | public class MapperSqlProvider implements SqlProvider { 18 | 19 | private Configuration configuration; 20 | private ConcurrentMap, SqlProvider> providerMap = new ConcurrentHashMap<>(); 21 | 22 | public MapperSqlProvider(Configuration configuration) { 23 | this.configuration = configuration; 24 | } 25 | 26 | @Override 27 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 28 | if (Objects.equals(boundSql.getSql(), SqlProvider.MYBATIS_BOOST)) { 29 | Class providerType = (Class) 30 | MyBatisUtils.getMetaObject(mappedStatement.getSqlSource()).getValue("providerType"); 31 | SqlProvider provider = providerMap.get(providerType); 32 | if (provider == null) { 33 | synchronized (providerType) { 34 | provider = providerMap.computeIfAbsent(providerType, UncheckedFunction.of(k -> { 35 | SqlProvider p = (SqlProvider) providerType.newInstance(); 36 | if (p instanceof ConfigurationAware) { 37 | ((ConfigurationAware) p).setConfiguration(configuration); 38 | } 39 | return p; 40 | })); 41 | } 42 | } 43 | if (provider != null) { 44 | provider.replace(connection, metaObject, mappedStatement, boundSql); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/MysqlCrudMapper.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper; 2 | 3 | import cn.mybatisboost.mapper.provider.mysql.Replace; 4 | import cn.mybatisboost.mapper.provider.mysql.Save; 5 | import org.apache.ibatis.annotations.InsertProvider; 6 | 7 | import java.util.List; 8 | 9 | public interface MysqlCrudMapper extends CrudMapper { 10 | 11 | @InsertProvider(type = Save.class, method = "reserved") 12 | int save(T entity, String... properties); 13 | 14 | @InsertProvider(type = Save.class, method = "reserved") 15 | int saveSelective(T entity, String... properties); 16 | 17 | @InsertProvider(type = Save.class, method = "reserved") 18 | int batchSave(List entity, String... properties); 19 | 20 | @InsertProvider(type = Save.class, method = "reserved") 21 | int batchSaveSelective(List entity, String... properties); 22 | 23 | @InsertProvider(type = Replace.class, method = "reserved") 24 | int replace(T entity, String... properties); 25 | 26 | @InsertProvider(type = Replace.class, method = "reserved") 27 | int replaceSelective(T entity, String... properties); 28 | 29 | @InsertProvider(type = Replace.class, method = "reserved") 30 | int batchReplace(List entity, String... properties); 31 | 32 | @InsertProvider(type = Replace.class, method = "reserved") 33 | int batchReplaceSelective(List entity, String... properties); 34 | } 35 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/Delete.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.*; 7 | import org.apache.ibatis.mapping.BoundSql; 8 | import org.apache.ibatis.mapping.MappedStatement; 9 | import org.apache.ibatis.mapping.ParameterMapping; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.stream.Collectors; 17 | 18 | public class Delete implements SqlProvider, ConfigurationAware { 19 | 20 | private Configuration configuration; 21 | 22 | @Override 23 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 24 | Class entityType = MapperUtils.getEntityTypeFromMapper 25 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 26 | StringBuilder sqlBuilder = new StringBuilder(); 27 | sqlBuilder.append("DELETE FROM ").append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 28 | 29 | Map parameterMap = (Map) boundSql.getParameterObject(); 30 | Object entity = parameterMap.get("param1"); 31 | List properties; 32 | String[] conditionalProperties = (String[]) parameterMap.get("param2"); 33 | if (conditionalProperties.length == 0) { 34 | properties = EntityUtils.getProperties(entity, true); 35 | } else { 36 | properties = Arrays.stream(conditionalProperties).map(PropertyUtils::normalizeProperty).collect(Collectors.toList()); 37 | } 38 | 39 | if (!properties.isEmpty()) { 40 | boolean mapUnderscoreToCamelCase = (boolean) 41 | metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase"); 42 | List columns = properties.stream() 43 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList()); 44 | SqlUtils.appendWhere(sqlBuilder, columns.stream()); 45 | } 46 | 47 | List parameterMappings = MyBatisUtils.getParameterMappings 48 | ((org.apache.ibatis.session.Configuration) 49 | metaObject.getValue("delegate.configuration"), properties); 50 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler")) 51 | .setValue("parameterObject", entity); 52 | metaObject.setValue("delegate.boundSql.parameterObject", entity); 53 | metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings); 54 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 55 | } 56 | 57 | @Override 58 | public void setConfiguration(Configuration configuration) { 59 | this.configuration = configuration; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/DeleteByIds.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import cn.mybatisboost.util.SqlUtils; 9 | import org.apache.ibatis.mapping.BoundSql; 10 | import org.apache.ibatis.mapping.MappedStatement; 11 | import org.apache.ibatis.mapping.ParameterMapping; 12 | import org.apache.ibatis.reflection.MetaObject; 13 | 14 | import java.sql.Connection; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | public class DeleteByIds implements SqlProvider, ConfigurationAware { 21 | 22 | private Configuration configuration; 23 | 24 | @Override 25 | @SuppressWarnings("unchecked") 26 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 27 | Class entityType = MapperUtils.getEntityTypeFromMapper 28 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 29 | StringBuilder sqlBuilder = new StringBuilder(); 30 | sqlBuilder.append("DELETE FROM ").append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 31 | 32 | Map parameterMap = (Map) boundSql.getParameterObject(); 33 | Object[] ids = (Object[]) parameterMap.get("param1"); 34 | parameterMap.clear(); 35 | if (ids.length > 0) { 36 | String idProperty = EntityUtils.getIdProperty(entityType); 37 | String idColumn = SqlUtils.normalizeColumn(idProperty, 38 | (boolean) metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase")); 39 | sqlBuilder.append(" WHERE ").append(idColumn).append(" IN ("); 40 | Arrays.stream(ids).forEach(c -> sqlBuilder.append("?, ")); 41 | sqlBuilder.setLength(sqlBuilder.length() - 2); 42 | sqlBuilder.append(')'); 43 | 44 | org.apache.ibatis.session.Configuration configuration = (org.apache.ibatis.session.Configuration) 45 | metaObject.getValue("delegate.configuration"); 46 | List parameterMappings = new ArrayList<>(ids.length); 47 | for (int i = 0; i < ids.length; i++) { 48 | parameterMap.put(idProperty + i, ids[i]); 49 | parameterMappings.add(new ParameterMapping.Builder(configuration, 50 | idProperty + i, Object.class).build()); 51 | } 52 | metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings); 53 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 54 | } 55 | } 56 | 57 | @Override 58 | public void setConfiguration(Configuration configuration) { 59 | this.configuration = configuration; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/Insert.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.*; 7 | import org.apache.ibatis.mapping.BoundSql; 8 | import org.apache.ibatis.mapping.MappedStatement; 9 | import org.apache.ibatis.mapping.ParameterMapping; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | 19 | public class Insert implements SqlProvider, ConfigurationAware { 20 | 21 | private Configuration configuration; 22 | 23 | @Override 24 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 25 | Class entityType = MapperUtils.getEntityTypeFromMapper 26 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 27 | StringBuilder sqlBuilder = new StringBuilder(); 28 | sqlBuilder.append("INSERT INTO ").append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 29 | 30 | Map parameterMap = (Map) boundSql.getParameterObject(); 31 | Object entity = parameterMap.get("param1"); 32 | List entities; 33 | if (entity instanceof List) { 34 | entities = (List) entity; 35 | if (entities.isEmpty()) { 36 | throw new IllegalArgumentException("Can't insert empty list"); 37 | } 38 | entity = entities.iterator().next(); 39 | } else { 40 | entities = Collections.singletonList(entity); 41 | } 42 | 43 | boolean selective = mappedStatement.getId().endsWith("Selective"); 44 | String[] candidateProperties = (String[]) parameterMap.get("param2"); 45 | List properties; 46 | if (candidateProperties.length == 0) { 47 | if (!selective || !configuration.isIterateSelectiveInBatch()) { 48 | properties = EntityUtils.getProperties(entity, selective); 49 | } else { 50 | properties = EntityUtils.getProperties(entities); 51 | } 52 | } else { 53 | properties = PropertyUtils.buildPropertiesWithCandidates(candidateProperties, entity, selective); 54 | } 55 | 56 | List parameterMappings = Collections.emptyList(); 57 | if (!properties.isEmpty()) { 58 | boolean mapUnderscoreToCamelCase = (boolean) 59 | metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase"); 60 | List columns = properties.stream() 61 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList()); 62 | 63 | StringBuilder subSqlBuilder = new StringBuilder(); 64 | columns.forEach(property -> subSqlBuilder.append(property).append(", ")); 65 | subSqlBuilder.setLength(subSqlBuilder.length() - 2); 66 | sqlBuilder.append('(').append(subSqlBuilder).append(") VALUES "); 67 | 68 | subSqlBuilder.setLength(0); 69 | for (int n = 0; n < entities.size(); n++) { 70 | subSqlBuilder.append('('); 71 | for (int i = 0, size = columns.size(); i < size; i++) { 72 | subSqlBuilder.append("?, "); 73 | } 74 | subSqlBuilder.setLength(subSqlBuilder.length() - 2); 75 | subSqlBuilder.append("), "); 76 | } 77 | subSqlBuilder.setLength(subSqlBuilder.length() - 2); 78 | sqlBuilder.append(subSqlBuilder); 79 | 80 | if (entities.size() > 1) { 81 | entity = Collections.singletonMap("list", entities); 82 | parameterMappings = new ArrayList<>(properties.size() * entities.size()); 83 | 84 | for (int i = 0; i < entities.size(); i++) { 85 | org.apache.ibatis.session.Configuration configuration = 86 | (org.apache.ibatis.session.Configuration) 87 | metaObject.getValue("delegate.configuration"); 88 | for (String property : properties) { 89 | parameterMappings.add(new ParameterMapping.Builder(configuration, 90 | "list[" + i + "]." + property, Object.class).build()); 91 | } 92 | } 93 | } else { 94 | parameterMappings = MyBatisUtils.getParameterMappings((org.apache.ibatis.session.Configuration) 95 | metaObject.getValue("delegate.configuration"), properties); 96 | } 97 | } 98 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler")) 99 | .setValue("parameterObject", entity); 100 | metaObject.setValue("delegate.boundSql.parameterObject", entity); 101 | metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings); 102 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 103 | } 104 | 105 | @Override 106 | public void setConfiguration(Configuration configuration) { 107 | this.configuration = configuration; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/SelectByIds.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import cn.mybatisboost.util.MyBatisUtils; 9 | import cn.mybatisboost.util.SqlUtils; 10 | import org.apache.ibatis.mapping.BoundSql; 11 | import org.apache.ibatis.mapping.MappedStatement; 12 | import org.apache.ibatis.mapping.ParameterMapping; 13 | import org.apache.ibatis.reflection.MetaObject; 14 | 15 | import java.sql.Connection; 16 | import java.util.*; 17 | 18 | public class SelectByIds implements SqlProvider, ConfigurationAware { 19 | 20 | private Configuration configuration; 21 | 22 | @Override 23 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 24 | Class entityType = MapperUtils.getEntityTypeFromMapper 25 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 26 | StringBuilder sqlBuilder = new StringBuilder(); 27 | sqlBuilder.append("SELECT * FROM ").append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 28 | 29 | String idProperty = EntityUtils.getIdProperty(entityType); 30 | String idColumn = SqlUtils.normalizeColumn(idProperty, 31 | (boolean) metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase")); 32 | boolean multipleIds = mappedStatement.getId().endsWith("Ids"); 33 | List parameterMappings = Collections.emptyList(); 34 | if (!multipleIds) { 35 | sqlBuilder.append(" WHERE ").append(idColumn).append(" = ?"); 36 | parameterMappings = Collections.singletonList(new ParameterMapping.Builder 37 | ((org.apache.ibatis.session.Configuration) metaObject.getValue("delegate.configuration"), 38 | "param1", Object.class).build()); 39 | } else { 40 | Map parameterMap = (Map) boundSql.getParameterObject(); 41 | Object[] ids = (Object[]) parameterMap.get("param1"); 42 | if (ids.length > 0) { 43 | sqlBuilder.append(" WHERE ").append(idColumn).append(" IN ("); 44 | Arrays.stream(ids).forEach(c -> sqlBuilder.append("?, ")); 45 | sqlBuilder.setLength(sqlBuilder.length() - 2); 46 | sqlBuilder.append(')'); 47 | 48 | org.apache.ibatis.session.Configuration configuration = (org.apache.ibatis.session.Configuration) 49 | metaObject.getValue("delegate.configuration"); 50 | Map newParameterMap = new HashMap<>(ids.length); 51 | parameterMappings = new ArrayList<>(ids.length); 52 | for (int i = 0; i < ids.length; i++) { 53 | newParameterMap.put(idProperty + i, ids[i]); 54 | parameterMappings.add(new ParameterMapping.Builder(configuration, 55 | idProperty + i, Object.class).build()); 56 | } 57 | parameterMap = newParameterMap; 58 | } else { 59 | parameterMap = Collections.emptyMap(); 60 | } 61 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler")) 62 | .setValue("parameterObject", parameterMap); 63 | metaObject.setValue("delegate.boundSql.parameterObject", parameterMap); 64 | } 65 | metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings); 66 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 67 | } 68 | 69 | @Override 70 | public void setConfiguration(Configuration configuration) { 71 | this.configuration = configuration; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/SelectOrCount.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import cn.mybatisboost.util.MyBatisUtils; 9 | import cn.mybatisboost.util.SqlUtils; 10 | import org.apache.ibatis.mapping.BoundSql; 11 | import org.apache.ibatis.mapping.MappedStatement; 12 | import org.apache.ibatis.mapping.ParameterMapping; 13 | import org.apache.ibatis.reflection.MetaObject; 14 | 15 | import java.sql.Connection; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.stream.Collectors; 20 | 21 | public class SelectOrCount implements SqlProvider, ConfigurationAware { 22 | 23 | private Configuration configuration; 24 | 25 | @Override 26 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 27 | Class entityType = MapperUtils.getEntityTypeFromMapper 28 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 29 | StringBuilder sqlBuilder = new StringBuilder(); 30 | sqlBuilder.append(mappedStatement.getId().endsWith("count") ? "SELECT COUNT(*) FROM " : "SELECT * FROM ") 31 | .append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 32 | 33 | Map parameterMap = (Map) boundSql.getParameterObject(); 34 | Object entity = parameterMap.get("param1"); 35 | List properties; 36 | String[] conditionalProperties = (String[]) (parameterMap.containsKey("param2") ? 37 | parameterMap.get("param2") : parameterMap.get("param3")); 38 | if (conditionalProperties.length == 0) { 39 | properties = EntityUtils.getProperties(entity, true); 40 | } else { 41 | properties = Arrays.asList(conditionalProperties); 42 | } 43 | 44 | if (!properties.isEmpty()) { 45 | boolean mapUnderscoreToCamelCase = (boolean) 46 | metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase"); 47 | List columns = properties.stream() 48 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList()); 49 | SqlUtils.appendWhere(sqlBuilder, columns.stream()); 50 | } 51 | 52 | List parameterMappings = MyBatisUtils.getParameterMappings 53 | ((org.apache.ibatis.session.Configuration) 54 | metaObject.getValue("delegate.configuration"), properties); 55 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler")) 56 | .setValue("parameterObject", entity); 57 | metaObject.setValue("delegate.boundSql.parameterObject", entity); 58 | metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings); 59 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 60 | } 61 | 62 | @Override 63 | public void setConfiguration(Configuration configuration) { 64 | this.configuration = configuration; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/SelectOrCountAll.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.EntityUtils; 7 | import cn.mybatisboost.util.MapperUtils; 8 | import org.apache.ibatis.mapping.BoundSql; 9 | import org.apache.ibatis.mapping.MappedStatement; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | 14 | public class SelectOrCountAll implements SqlProvider, ConfigurationAware { 15 | 16 | private Configuration configuration; 17 | 18 | @Override 19 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 20 | String tableName = EntityUtils.getTableName(MapperUtils.getEntityTypeFromMapper 21 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))), 22 | configuration.getNameAdaptor()); 23 | metaObject.setValue("delegate.boundSql.sql", (mappedStatement.getId().endsWith("countAll") ? 24 | "SELECT COUNT(*) FROM " : "SELECT * FROM ") + tableName); 25 | } 26 | 27 | @Override 28 | public void setConfiguration(Configuration configuration) { 29 | this.configuration = configuration; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/Update.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.core.ConfigurationAware; 5 | import cn.mybatisboost.core.SqlProvider; 6 | import cn.mybatisboost.util.*; 7 | import org.apache.ibatis.mapping.BoundSql; 8 | import org.apache.ibatis.mapping.MappedStatement; 9 | import org.apache.ibatis.mapping.ParameterMapping; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | 12 | import java.sql.Connection; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.stream.Collectors; 16 | 17 | public class Update implements SqlProvider, ConfigurationAware { 18 | 19 | private Configuration configuration; 20 | 21 | @Override 22 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 23 | Class entityType = MapperUtils.getEntityTypeFromMapper 24 | (mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf('.'))); 25 | StringBuilder sqlBuilder = new StringBuilder(); 26 | sqlBuilder.append("UPDATE ").append(EntityUtils.getTableName(entityType, configuration.getNameAdaptor())); 27 | 28 | boolean partial = mappedStatement.getId().contains("Partial"); 29 | boolean selective = mappedStatement.getId().contains("Selective"); 30 | 31 | Map parameterMap = (Map) boundSql.getParameterObject(); 32 | Object entity = parameterMap.get("param1"); 33 | List properties; 34 | String[] conditionalProperties; 35 | if (!partial) { 36 | properties = EntityUtils.getProperties(entity, selective); 37 | conditionalProperties = (String[]) parameterMap.get("param2"); 38 | } else { 39 | String[] candidateProperties = (String[]) parameterMap.get("param2"); 40 | properties = PropertyUtils.buildPropertiesWithCandidates(candidateProperties, entity, selective); 41 | conditionalProperties = (String[]) parameterMap.get("param3"); 42 | } 43 | if (conditionalProperties.length == 0) { 44 | conditionalProperties = new String[]{EntityUtils.getIdProperty(entityType)}; 45 | } 46 | PropertyUtils.rebuildPropertiesWithConditions(properties, entityType, conditionalProperties); 47 | 48 | if (!properties.isEmpty()) { 49 | boolean mapUnderscoreToCamelCase = (boolean) 50 | metaObject.getValue("delegate.configuration.mapUnderscoreToCamelCase"); 51 | List columns = properties.stream() 52 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList()); 53 | sqlBuilder.append(" SET "); 54 | columns.stream().limit(columns.size() - conditionalProperties.length) 55 | .forEach(c -> sqlBuilder.append(c).append(" = ?, ")); 56 | sqlBuilder.setLength(sqlBuilder.length() - 2); 57 | SqlUtils.appendWhere(sqlBuilder, columns.stream().skip(columns.size() - conditionalProperties.length)); 58 | } 59 | 60 | List parameterMappings = MyBatisUtils.getParameterMappings 61 | ((org.apache.ibatis.session.Configuration) 62 | metaObject.getValue("delegate.configuration"), properties); 63 | MyBatisUtils.getMetaObject(metaObject.getValue("delegate.parameterHandler")) 64 | .setValue("parameterObject", entity); 65 | metaObject.setValue("delegate.boundSql.parameterObject", entity); 66 | metaObject.setValue("delegate.boundSql.parameterMappings", parameterMappings); 67 | metaObject.setValue("delegate.boundSql.sql", sqlBuilder.toString()); 68 | } 69 | 70 | @Override 71 | public void setConfiguration(Configuration configuration) { 72 | this.configuration = configuration; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/mysql/Replace.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider.mysql; 2 | 3 | import cn.mybatisboost.mapper.provider.Insert; 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 | 10 | public class Replace extends Insert { 11 | 12 | @Override 13 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 14 | super.replace(connection, metaObject, mappedStatement, boundSql); 15 | String sql = boundSql.getSql(); 16 | metaObject.setValue("delegate.boundSql.sql", sql.startsWith("INSERT") ? 17 | sql.replaceFirst("INSERT", "REPLACE") : 18 | sql.replaceFirst("insert", "replace")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/mapper/provider/mysql/Save.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.mapper.provider.mysql; 2 | 3 | import cn.mybatisboost.mapper.provider.Insert; 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.Arrays; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | public class Save extends Insert { 14 | 15 | private static final Pattern PATTERN_COLUMNS = 16 | Pattern.compile("INSERT INTO \\w+ ?\\((.*?)\\)", Pattern.CASE_INSENSITIVE); 17 | 18 | @Override 19 | public void replace(Connection connection, MetaObject metaObject, MappedStatement mappedStatement, BoundSql boundSql) { 20 | super.replace(connection, metaObject, mappedStatement, boundSql); 21 | String sql = boundSql.getSql(); 22 | Matcher matcher = PATTERN_COLUMNS.matcher(sql); 23 | if (matcher.find()) { 24 | StringBuilder builder = new StringBuilder(sql); 25 | builder.append(" ON DUPLICATE KEY UPDATE "); 26 | Arrays.stream(matcher.group(1).split(", ")) 27 | .forEach(it -> builder.append(it).append(" = VALUES(").append(it).append("), ")); 28 | builder.setLength(builder.length() - 2); 29 | metaObject.setValue("delegate.boundSql.sql", builder.toString()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/metric/MetricInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.metric; 2 | 3 | import cn.mybatisboost.core.Configuration; 4 | import cn.mybatisboost.util.MyBatisUtils; 5 | import org.apache.commons.lang3.time.StopWatch; 6 | import org.apache.ibatis.executor.statement.StatementHandler; 7 | import org.apache.ibatis.mapping.BoundSql; 8 | import org.apache.ibatis.mapping.ParameterMapping; 9 | import org.apache.ibatis.plugin.*; 10 | import org.apache.ibatis.reflection.MetaObject; 11 | import org.apache.ibatis.session.ResultHandler; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.sql.Statement; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Properties; 20 | import java.util.function.BiConsumer; 21 | 22 | @Intercepts({ 23 | @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}), 24 | @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), 25 | @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})}) 26 | public class MetricInterceptor implements Interceptor { 27 | 28 | private static Logger logger = LoggerFactory.getLogger(MetricInterceptor.class); 29 | private Configuration configuration; 30 | 31 | public MetricInterceptor(Configuration configuration) { 32 | this.configuration = configuration; 33 | } 34 | 35 | @Override 36 | public Object intercept(Invocation invocation) throws Throwable { 37 | BoundSql boundSql = ((StatementHandler) invocation.getTarget()).getBoundSql(); 38 | 39 | String sql = boundSql.getSql().replaceAll("\\s*\\n\\s*", " "); 40 | List parameters = new ArrayList<>(); 41 | if (configuration.isShowQueryWithParameters()) { 42 | List parameterMappings = boundSql.getParameterMappings(); 43 | Object parameterObject = boundSql.getParameterObject(); 44 | MetaObject metaObject = MyBatisUtils.getMetaObject(parameterObject); 45 | if (parameterMappings.size() == 1 && !(parameterObject instanceof Map) && 46 | !metaObject.hasGetter(parameterMappings.get(0).getProperty())) { 47 | parameters.add(parameterObject); 48 | } else { 49 | parameterMappings.forEach(pm -> parameters.add(metaObject.getValue(pm.getProperty()))); 50 | } 51 | } 52 | 53 | StopWatch stopWatch = StopWatch.createStarted(); 54 | Object proceed = invocation.proceed(); 55 | long time = stopWatch.getTime(); 56 | if (time > configuration.getSlowQueryThresholdInMillis()) { 57 | if (parameters.isEmpty()) { 58 | logger.error(String.format("[SLOW Query took %s ms] %s", time, sql)); 59 | } else { 60 | logger.error(String.format("[SLOW Query took %s ms, Parameters: %s] %s ", time, parameters, sql)); 61 | } 62 | BiConsumer slowSqlHandler = configuration.getSlowQueryHandler(); 63 | if (slowSqlHandler != null) { 64 | slowSqlHandler.accept(sql, time); 65 | } 66 | } else if (configuration.isShowQuery()) { 67 | if (parameters.isEmpty()) { 68 | logger.info(String.format("[Query took %s ms] %s", time, sql)); 69 | } else { 70 | logger.info(String.format("[Query took %s ms, Parameters: %s] %s ", time, parameters, sql)); 71 | } 72 | } 73 | return proceed; 74 | } 75 | 76 | @Override 77 | public Object plugin(Object target) { 78 | return Plugin.wrap(target, this); 79 | } 80 | 81 | @Override 82 | public void setProperties(Properties properties) { 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/nosql/MapperInstrument.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.nosql; 2 | 3 | import org.apache.ibatis.javassist.*; 4 | import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute; 5 | import org.apache.ibatis.javassist.bytecode.ConstPool; 6 | import org.apache.ibatis.javassist.bytecode.MethodInfo; 7 | import org.apache.ibatis.javassist.bytecode.annotation.Annotation; 8 | import org.apache.ibatis.javassist.bytecode.annotation.ArrayMemberValue; 9 | import org.apache.ibatis.javassist.bytecode.annotation.MemberValue; 10 | import org.apache.ibatis.javassist.bytecode.annotation.StringMemberValue; 11 | import org.apache.ibatis.annotations.Mapper; 12 | import org.apache.ibatis.session.RowBounds; 13 | 14 | import java.util.Set; 15 | import java.util.UUID; 16 | import java.util.concurrent.ConcurrentSkipListSet; 17 | 18 | public class MapperInstrument { 19 | 20 | private static Set modifiedClassNames = new ConcurrentSkipListSet<>(); 21 | 22 | public static boolean modify(String className, boolean mapUnderscoreToCamelCase) { 23 | synchronized (className.intern()) { 24 | if (modifiedClassNames.contains(className)) return true; 25 | try { 26 | boolean modified = false; 27 | CtClass ctClass = ClassPool.getDefault().get(className); 28 | for (CtMethod ctMethod : ctClass.getDeclaredMethods()) { 29 | if (ctMethod.hasAnnotation(Mapper.class)) { 30 | MethodNameParser parser = 31 | new MethodNameParser(ctMethod.getName(), "#t", mapUnderscoreToCamelCase); 32 | addQueryAnnotation(ctMethod, parser.toSql()); 33 | addRowBoundsParameter(ctMethod, parser.toRowBounds()); 34 | modified = true; 35 | } 36 | } 37 | if (modified) { 38 | ctClass.toClass(MapperInstrument.class.getClassLoader(), 39 | MapperInstrument.class.getProtectionDomain()); 40 | modifiedClassNames.add(className); 41 | } 42 | 43 | for (CtClass i : ctClass.getInterfaces()) { 44 | modified |= modify(i.getName(), mapUnderscoreToCamelCase); 45 | } 46 | return modified; 47 | } catch (CannotCompileException | NotFoundException e) { 48 | throw new RuntimeException(e); 49 | } 50 | } 51 | } 52 | 53 | private static void addQueryAnnotation(CtMethod ctMethod, String sql) { 54 | AnnotationsAttribute attribute = (AnnotationsAttribute) 55 | ctMethod.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag); 56 | ConstPool constPool = attribute.getConstPool(); 57 | Annotation annotation = new Annotation(ctMethod.getName().startsWith("delete") ? 58 | "org.apache.ibatis.annotations.Delete" : "org.apache.ibatis.annotations.Select", constPool); 59 | ArrayMemberValue memberValue = new ArrayMemberValue(new StringMemberValue(constPool), constPool); 60 | memberValue.setValue(new MemberValue[]{new StringMemberValue(sql, constPool)}); 61 | annotation.addMemberValue("value", memberValue); 62 | attribute.addAnnotation(annotation); 63 | } 64 | 65 | private static void addRowBoundsParameter(CtMethod ctMethod, RowBounds rowBounds) { 66 | if (rowBounds == RowBounds.DEFAULT) return; 67 | try { 68 | String newMethodName = ctMethod.getName() + "$" + 69 | UUID.randomUUID().toString().replace("-", ""); 70 | CtMethod ctNewMethod = CtNewMethod.copy(ctMethod, newMethodName, ctMethod.getDeclaringClass(), null); 71 | ctNewMethod.addParameter(ClassPool.getDefault().get("org.apache.ibatis.session.RowBounds")); 72 | ctNewMethod.getMethodInfo().addAttribute 73 | (ctMethod.getMethodInfo().removeAttribute(AnnotationsAttribute.visibleTag)); 74 | 75 | String body; 76 | if (rowBounds.getLimit() == 1) { 77 | MethodInfo methodInfo = ctNewMethod.getMethodInfo(); 78 | String descriptor = methodInfo.getDescriptor(); 79 | String returnType = descriptor.substring(descriptor.lastIndexOf(')') + 1); 80 | methodInfo.setDescriptor(descriptor = 81 | descriptor.substring(0, descriptor.length() - returnType.length()) + "Ljava/util/List;"); 82 | ctNewMethod.setGenericSignature 83 | (descriptor.substring(0, descriptor.length() - 1) + "<" + returnType + ">;"); 84 | 85 | body = "{ java.util.List list = %s($$, new org.apache.ibatis.session.RowBounds(%s, %s));" + 86 | "return !list.isEmpty() ? (%s) list.get(0) : null; }"; 87 | body = String.format(body, newMethodName, rowBounds.getOffset(), rowBounds.getLimit(), 88 | returnType.substring(1, returnType.length() - 1).replace('/', '.')); 89 | } else { 90 | String signature = ctMethod.getGenericSignature(); 91 | int index = signature.lastIndexOf(')'); 92 | ctNewMethod.setGenericSignature(signature.substring(0, index) + 93 | "Lorg/apache/ibatis/session/RowBounds;" + signature.substring(index)); 94 | body = String.format("{ return %s($$, new org.apache.ibatis.session.RowBounds(%s, %s)); }", 95 | newMethodName, rowBounds.getOffset(), rowBounds.getLimit()); 96 | } 97 | ctMethod.getDeclaringClass().addMethod(ctNewMethod); 98 | ctMethod.setBody(body); 99 | } catch (CannotCompileException | NotFoundException e) { 100 | throw new RuntimeException(e); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/nosql/Method.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.nosql; 2 | 3 | import java.util.Arrays; 4 | 5 | public enum Method { 6 | Select("SELECT * FROM"), Count("SELECT COUNT(*) FROM"), Delete("DELETE FROM"); 7 | 8 | private String sqlFragment; 9 | 10 | Method(String sqlFragment) { 11 | this.sqlFragment = sqlFragment; 12 | } 13 | 14 | public String sqlFragment() { 15 | return sqlFragment; 16 | } 17 | 18 | public static Method of(String name) { 19 | return Arrays.stream(values()).filter(it -> name.startsWith(it.name())).findFirst() 20 | .orElseThrow(() -> new IllegalArgumentException("Illegal method type")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/nosql/MethodNameParser.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.nosql; 2 | 3 | import cn.mybatisboost.util.SqlUtils; 4 | import cn.mybatisboost.util.tuple.BinaryTuple; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.apache.ibatis.session.RowBounds; 7 | 8 | import java.util.*; 9 | import java.util.regex.Pattern; 10 | 11 | public class MethodNameParser { 12 | 13 | private static final Pattern PATTERN_ORDER_BY_SEPARATOR = Pattern.compile(" (?!ASC|DESC)"); 14 | 15 | private final String methodName, tableName; 16 | private final boolean mapUnderscoreToCamelCase; 17 | private String parsedSql; 18 | private int offset, limit; 19 | 20 | public MethodNameParser(String methodName, String tableName, boolean mapUnderscoreToCamelCase) { 21 | this.methodName = methodName; 22 | this.tableName = tableName; 23 | this.mapUnderscoreToCamelCase = mapUnderscoreToCamelCase; 24 | } 25 | 26 | public String toSql() { 27 | if (parsedSql != null) return parsedSql; 28 | StringBuilder sqlBuilder = new StringBuilder(); 29 | Method method = Method.of(StringUtils.capitalize(methodName)); 30 | sqlBuilder.append(method.sqlFragment()).append(' ').append(tableName).append(' '); 31 | 32 | String expression = this.methodName.substring(method.name().length()); 33 | expression = prepare(sqlBuilder, expression); 34 | if (expression.isEmpty()) { 35 | return parsedSql = sqlBuilder.toString().trim(); 36 | } 37 | 38 | Map keywordMap = new TreeMap<>(); 39 | for (String kw : Predicate.keywords()) { 40 | int index = -1; 41 | while ((index = expression.indexOf(kw, index + 1)) >= 0) { 42 | if (index + kw.length() == expression.length() || 43 | Character.isUpperCase(expression.charAt(index + kw.length()))) { 44 | keywordMap.put(index, kw); 45 | } 46 | } 47 | } 48 | for (String it : keywordMap.values()) { 49 | expression = expression.replace(it, "?"); 50 | } 51 | 52 | int offset = 0, predicateIndex; 53 | Iterator iterator = keywordMap.values().iterator(); 54 | boolean containsOrderBy = false; 55 | int orderByIndex = 0; 56 | while ((predicateIndex = expression.indexOf("?", offset)) >= 0) { 57 | Predicate predicate = Predicate.of(iterator.next()); 58 | 59 | if (predicateIndex > offset) { 60 | sqlBuilder.append(SqlUtils.normalizeColumn 61 | (expression.substring(offset, predicateIndex), mapUnderscoreToCamelCase)).append(' '); 62 | if (!predicate.containsParameters()) { 63 | sqlBuilder.append("= ? "); 64 | } 65 | } 66 | sqlBuilder.append(predicate.sqlFragment()).append(' '); 67 | offset = predicateIndex + 1; 68 | 69 | if (predicate == Predicate.OrderBy) { 70 | containsOrderBy = true; 71 | orderByIndex = sqlBuilder.length(); 72 | } 73 | } 74 | if (offset < expression.length() - 1) { 75 | sqlBuilder.append(SqlUtils.normalizeColumn 76 | (expression.substring(offset), mapUnderscoreToCamelCase)); 77 | if (!containsOrderBy) { 78 | sqlBuilder.append(" = ?"); 79 | } 80 | } 81 | if (containsOrderBy) { 82 | String correctedOrderByExpression = PATTERN_ORDER_BY_SEPARATOR.matcher 83 | (sqlBuilder.substring(orderByIndex).trim()).replaceAll(", "); 84 | sqlBuilder.replace(orderByIndex, sqlBuilder.length(), correctedOrderByExpression); 85 | } 86 | return parsedSql = sqlBuilder.toString().trim(); 87 | } 88 | 89 | public RowBounds toRowBounds() { 90 | if (parsedSql == null) toSql(); 91 | return offset > 0 || limit > 0 ? new RowBounds(offset, limit) : RowBounds.DEFAULT; 92 | } 93 | 94 | private String prepare(StringBuilder sqlBuilder, String expression) { 95 | if (expression.startsWith("All")) { 96 | expression = expression.substring(3); 97 | } else { 98 | if (expression.startsWith("First")) { 99 | limit = 1; 100 | expression = expression.substring(5); 101 | } else if (expression.startsWith("Top")) { 102 | Optional> optional = 103 | extractKeyNumber(expression, "Top", false); 104 | if (optional.isPresent()) { 105 | BinaryTuple tuple = optional.get(); 106 | limit = tuple.second(); 107 | expression = tuple.first(); 108 | } 109 | } 110 | } 111 | if (expression.startsWith("By")) { 112 | sqlBuilder.append("WHERE "); 113 | expression = expression.substring(2); 114 | } 115 | 116 | Optional> optional = 117 | extractKeyNumber(expression, "Limit", true); 118 | if (optional.isPresent()) { 119 | BinaryTuple tuple = optional.get(); 120 | limit = tuple.second(); 121 | expression = tuple.first(); 122 | 123 | optional = extractKeyNumber(expression, "Offset", true); 124 | if (optional.isPresent()) { 125 | tuple = optional.get(); 126 | offset = tuple.second(); 127 | expression = tuple.first(); 128 | } 129 | } 130 | return expression; 131 | } 132 | 133 | private Optional> extractKeyNumber(String s, String key, boolean fromEnd) { 134 | int index = fromEnd ? s.lastIndexOf(key) : s.indexOf(key); 135 | if (index < 0) return Optional.empty(); 136 | 137 | char[] chars = s.toCharArray(); 138 | for (int i = index + key.length(); i < chars.length; i++) { 139 | if (!Character.isDigit(chars[i]) || i + 1 == chars.length) { 140 | if (i + 1 == chars.length) i++; 141 | char[] copy = Arrays.copyOfRange(chars, index + key.length(), i); 142 | return Optional.of(new BinaryTuple<> 143 | (new StringBuilder(s).replace(index, i, "").toString(), 144 | Integer.parseInt(new String(copy)))); 145 | } 146 | } 147 | throw new IllegalArgumentException("Invalid string"); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/nosql/Predicate.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.nosql; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.Stream; 6 | 7 | public enum Predicate { 8 | And("And", "AND", false), 9 | Or("Or", "OR", false), 10 | Is("Is", "= ?", true), 11 | Equals("E", "= ?", true), 12 | Between("Bw", "BETWEEN ? AND ?", true), 13 | NotBetween("Nbw", "NOT BETWEEN ? AND ?", true), 14 | LessThan("Lt", "< ?", true), 15 | LessThanEqual("Lte", "<= ?", true), 16 | GreaterThan("Gt", "> ?", true), 17 | GreaterThanEqual("Gte", ">= ?", true), 18 | After("Af", "> ?", true), 19 | Before("Bf", "< ?", true), 20 | IsNull("N", "IS NULL", true), 21 | IsNotNull("Nn", "IS NOT NULL", true), 22 | IsEmpty("E", "= ''", true), 23 | IsNotEmpty("Ne", "!= ''", true), 24 | Like("L", "LIKE ?", true), 25 | NotLike("Nl", "NOT LIKE ?", true), 26 | OrderBy("Ob", "ORDER BY", false), 27 | Not("Not", "!= ?", true), 28 | In("In", "IN ?", true), 29 | NotIn("Ni", "NOT IN ?", true), 30 | IsTrue("T", "= TRUE", true), 31 | IsFalse("F", "= FALSE", true), 32 | Asc("Asc", "ASC", true), 33 | Desc("Desc", "DESC", true); 34 | 35 | private static List keywords; 36 | private static Map aliasMap = new HashMap<>(); 37 | private String abbr, sqlFragment; 38 | private boolean containsParameters; 39 | 40 | static { 41 | keywords = Stream.concat(Arrays.stream(values()).map(Predicate::name), Arrays.stream(values()).map(Predicate::alias)) 42 | .distinct().sorted(Comparator.comparingInt(it -> -it.length())).collect(Collectors.toList()); 43 | keywords = Collections.unmodifiableList(keywords); 44 | Arrays.stream(values()).forEach(it -> aliasMap.put(it.abbr, it)); 45 | } 46 | 47 | Predicate(String abbr, String sqlFragment, boolean containsParameters) { 48 | this.abbr = abbr; 49 | this.sqlFragment = sqlFragment; 50 | this.containsParameters = containsParameters; 51 | } 52 | 53 | public String alias() { 54 | return abbr; 55 | } 56 | 57 | public String sqlFragment() { 58 | return sqlFragment; 59 | } 60 | 61 | public boolean containsParameters() { 62 | return containsParameters; 63 | } 64 | 65 | public static Predicate of(String name) { 66 | return aliasMap.compute(name, (k, v) -> v != null ? v : valueOf(name)); 67 | } 68 | 69 | public static List keywords() { 70 | return keywords; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/support/JsonType.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.support; 2 | 3 | public abstract class JsonType { 4 | } 5 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/EntityUtils.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import cn.mybatisboost.core.adaptor.NameAdaptor; 4 | import cn.mybatisboost.util.function.UncheckedFunction; 5 | import cn.mybatisboost.util.function.UncheckedPredicate; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import javax.persistence.Table; 11 | import java.lang.reflect.Field; 12 | import java.lang.reflect.Modifier; 13 | import java.util.*; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | import java.util.concurrent.ConcurrentMap; 16 | import java.util.stream.Collectors; 17 | 18 | public abstract class EntityUtils { 19 | 20 | private static ConcurrentMap, String> tableNameCache = new ConcurrentHashMap<>(); 21 | private static ConcurrentMap, String> idPropertyCache = new ConcurrentHashMap<>(); 22 | private static ConcurrentMap, List> propertiesCache = new ConcurrentHashMap<>(); 23 | private static ConcurrentMap, List> generatedFieldCache = new ConcurrentHashMap<>(); 24 | 25 | public static String getTableName(Class type, NameAdaptor converter) { 26 | return tableNameCache.computeIfAbsent(type, k -> { 27 | if (type.isAnnotationPresent(Table.class)) { 28 | Table table = type.getAnnotation(Table.class); 29 | String catalog = table.catalog(); 30 | if (StringUtils.isEmpty(catalog)) { 31 | catalog = table.schema(); 32 | } 33 | if (StringUtils.isEmpty(catalog)) { 34 | return table.name(); 35 | } else { 36 | return String.format("`%s`.`%s`", catalog, table.name()); 37 | } 38 | } else { 39 | return converter.adapt(type.getSimpleName()); 40 | } 41 | }); 42 | } 43 | 44 | public static String getIdProperty(Class type) { 45 | return idPropertyCache.computeIfAbsent(type, k -> { 46 | try { 47 | return type.getDeclaredField("id").getName(); 48 | } catch (NoSuchFieldException e) { 49 | return Arrays.stream(type.getDeclaredFields()).filter(f -> f.isAnnotationPresent(Id.class)) 50 | .findFirst().map(Field::getName) 51 | .orElseThrow(() -> new NoSuchFieldError("Id property not found in " + type.getName())); 52 | } 53 | }); 54 | } 55 | 56 | public static List getProperties(Class type) { 57 | return new ArrayList<>(propertiesCache.computeIfAbsent(type, k -> 58 | Collections.unmodifiableList(Arrays.stream(type.getDeclaredFields()) 59 | .filter(f -> !Modifier.isStatic(f.getModifiers())) 60 | .filter(f -> !Modifier.isTransient(f.getModifiers())) 61 | .map(Field::getName).collect(Collectors.toList())))); 62 | } 63 | 64 | public static List getProperties(Object entity, boolean selective) { 65 | Class type = entity.getClass(); 66 | List properties = getProperties(type); 67 | if (selective) { 68 | properties = properties.stream() 69 | .map(UncheckedFunction.of(type::getDeclaredField)) 70 | .peek(f -> f.setAccessible(true)) 71 | .filter(UncheckedPredicate.of(f -> f.get(entity) != null)) 72 | .map(UncheckedFunction.of(Field::getName)) 73 | .collect(Collectors.toList()); 74 | } 75 | return properties; 76 | } 77 | 78 | public static List getProperties(List entities) { 79 | if (entities.isEmpty()) return Collections.emptyList(); 80 | 81 | Class type = entities.get(0).getClass(); 82 | List properties = getProperties(type); 83 | Iterator iterator = properties.iterator(); 84 | while (iterator.hasNext()) { 85 | try { 86 | Field field = type.getDeclaredField(iterator.next()); 87 | boolean nonNull = entities.stream() 88 | .anyMatch(UncheckedPredicate.of(it -> field.get(it) != null)); 89 | if (!nonNull) iterator.remove(); 90 | } catch (NoSuchFieldException e) { 91 | throw new RuntimeException(e); 92 | } 93 | } 94 | return properties; 95 | } 96 | 97 | public static List getGeneratedFields(Class type) { 98 | return generatedFieldCache.computeIfAbsent(type, k -> 99 | Collections.unmodifiableList(Arrays.stream(type.getDeclaredFields()) 100 | .filter(it -> it.isAnnotationPresent(GeneratedValue.class)) 101 | .map(ReflectionUtils::makeAccessible).collect(Collectors.toList()))); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/MapperUtils.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import cn.mybatisboost.core.GenericMapper; 4 | 5 | import java.lang.reflect.ParameterizedType; 6 | import java.lang.reflect.Type; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.ConcurrentMap; 9 | 10 | public abstract class MapperUtils { 11 | 12 | private static ConcurrentMap> entityTypeCache = new ConcurrentHashMap<>(); 13 | 14 | public static Class getEntityTypeFromMapper(String mapperClassName) { 15 | return entityTypeCache.computeIfAbsent(mapperClassName, k -> { 16 | try { 17 | Class type = Class.forName(mapperClassName); 18 | Class[] interfaces = type.getInterfaces(); 19 | Type[] genericInterfaces = type.getGenericInterfaces(); 20 | for (int i = 0; i < interfaces.length; i++) { 21 | if (GenericMapper.class.isAssignableFrom(interfaces[i])) { 22 | return (Class) ((ParameterizedType) genericInterfaces[i]).getActualTypeArguments()[0]; 23 | } 24 | } 25 | throw new ClassNotFoundException("GenericMapper interface not found on the mapper class"); 26 | } catch (ClassNotFoundException e) { 27 | throw new RuntimeException(e); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/MultipleMapKey.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Objects; 5 | 6 | public class MultipleMapKey { 7 | 8 | private final Object[] keys; 9 | 10 | public MultipleMapKey(Object... keys) { 11 | this.keys = keys; 12 | } 13 | 14 | public Object[] getKeys() { 15 | return keys; 16 | } 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (this == o) return true; 21 | if (o == null || getClass() != o.getClass()) return false; 22 | MultipleMapKey multipleMapKey = (MultipleMapKey) o; 23 | if (keys.length != multipleMapKey.keys.length) return false; 24 | return Arrays.equals(keys, multipleMapKey.keys); 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return Objects.hash(keys); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "MultipleMapKey" + Arrays.toString(keys); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/MyBatisUtils.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import org.apache.ibatis.mapping.ParameterMapping; 4 | import org.apache.ibatis.reflection.DefaultReflectorFactory; 5 | import org.apache.ibatis.reflection.MetaObject; 6 | import org.apache.ibatis.reflection.SystemMetaObject; 7 | import org.apache.ibatis.session.Configuration; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | public abstract class MyBatisUtils { 14 | 15 | private static final DefaultReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory(); 16 | 17 | public static MetaObject getMetaObject(Object target) { 18 | MetaObject metaObject; 19 | while ((metaObject = forObject(target)).hasGetter("h")) { 20 | target = metaObject.getValue("h.target"); 21 | } 22 | return metaObject; 23 | } 24 | 25 | private static MetaObject forObject(Object object) { 26 | return MetaObject.forObject(object, SystemMetaObject.DEFAULT_OBJECT_FACTORY, 27 | SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY); 28 | } 29 | 30 | public static List getParameterMappings(Configuration configuration, List properties) { 31 | return properties.stream() 32 | .map(property -> new ParameterMapping.Builder(configuration, property, Object.class).build()) 33 | .collect(Collectors.toCollection(() -> new ArrayList<>(properties.size()))); 34 | } 35 | 36 | public static List getListParameterMappings 37 | (Configuration configuration, List properties, int size) { 38 | List parameterMappings = new ArrayList<>(properties.size() * size); 39 | for (int i = 0; i < size; i++) { 40 | for (String property : properties) { 41 | parameterMappings.add(new ParameterMapping.Builder 42 | (configuration, "list[" + i + "]." + property, Object.class).build()); 43 | } 44 | } 45 | return parameterMappings; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/PropertyUtils.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.stream.Collectors; 9 | 10 | public abstract class PropertyUtils { 11 | 12 | public static String normalizeProperty(String property) { 13 | return StringUtils.uncapitalize 14 | (Arrays.stream(property.split("_")).map(StringUtils::capitalize).collect(Collectors.joining())); 15 | } 16 | 17 | public static List buildPropertiesWithCandidates 18 | (String[] candidateProperties, Object entity, boolean selective) { 19 | if (candidateProperties.length > 0 && !Objects.equals(candidateProperties[0], "!")) { 20 | return Arrays.stream(candidateProperties) 21 | .map(PropertyUtils::normalizeProperty).collect(Collectors.toList()); 22 | } else { 23 | List properties = EntityUtils.getProperties(entity, selective); 24 | if (candidateProperties.length > 0) { 25 | properties.removeAll(Arrays.stream(candidateProperties) 26 | .map(PropertyUtils::normalizeProperty).collect(Collectors.toList())); 27 | } 28 | return properties; 29 | } 30 | } 31 | 32 | public static void rebuildPropertiesWithConditions 33 | (List properties, Class type, String[] conditionalProperties) { 34 | if (conditionalProperties.length == 0) { 35 | conditionalProperties = new String[]{EntityUtils.getIdProperty(type)}; 36 | } 37 | List conditionalPropertyList = Arrays.stream(conditionalProperties) 38 | .map(PropertyUtils::normalizeProperty).collect(Collectors.toList()); 39 | properties.removeAll(conditionalPropertyList); 40 | properties.addAll(conditionalPropertyList); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import java.lang.reflect.AccessibleObject; 4 | 5 | public abstract class ReflectionUtils { 6 | 7 | public static T makeAccessible(T accessibleObject) { 8 | accessibleObject.setAccessible(true); 9 | return accessibleObject; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/SafeProperty.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.ConcurrentMap; 5 | 6 | public abstract class SafeProperty { 7 | 8 | private static ConcurrentMap cache = new ConcurrentHashMap<>(); 9 | 10 | public static String[] of(Class type, String... properties) { 11 | cache.computeIfAbsent(new MultipleMapKey(type, properties), k -> { 12 | for (String property : properties) { 13 | try { 14 | type.getDeclaredField(property); 15 | } catch (NoSuchFieldException e) { 16 | throw new RuntimeException(e); 17 | } 18 | } 19 | return true; 20 | }); 21 | return properties; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/SqlUtils.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util; 2 | 3 | import cn.mybatisboost.util.tuple.BinaryTuple; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.ibatis.session.RowBounds; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.ConcurrentMap; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | public abstract class SqlUtils { 18 | 19 | public static final Pattern PATTERN_PLACEHOLDER = Pattern.compile("(? placeholderCountCache = new ConcurrentHashMap<>(); 21 | 22 | public static StringBuilder appendWhere(StringBuilder sqlBuilder, Stream stream) { 23 | sqlBuilder.append(" WHERE "); 24 | stream.forEach(c -> sqlBuilder.append(c).append(" = ? AND ")); 25 | sqlBuilder.setLength(sqlBuilder.length() - 5); 26 | return sqlBuilder; 27 | } 28 | 29 | public static String appendLimit(String sql, RowBounds rowBounds) { 30 | if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET || rowBounds.getLimit() != RowBounds.NO_ROW_LIMIT) { 31 | sql += " LIMIT " + rowBounds.getOffset() + ", " + rowBounds.getLimit(); 32 | } 33 | return sql; 34 | } 35 | 36 | public static String appendLimitOffset(String sql, RowBounds rowBounds) { 37 | if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET || rowBounds.getLimit() != RowBounds.NO_ROW_LIMIT) { 38 | sql += " LIMIT " + rowBounds.getLimit() + " OFFSET " + rowBounds.getOffset(); 39 | } 40 | return sql; 41 | } 42 | 43 | public static int countPlaceholders(String sql) { 44 | return placeholderCountCache.computeIfAbsent(sql, k -> { 45 | int count = 0; 46 | Matcher matcher = PATTERN_PLACEHOLDER.matcher(sql); 47 | while (matcher.find()) count++; 48 | return count; 49 | }); 50 | } 51 | 52 | public static String normalizeColumn(String column, boolean mapUnderscoreToCamelCase) { 53 | Stream stream = Arrays.stream(StringUtils.splitByCharacterTypeCamelCase(column)) 54 | .filter(it -> !Objects.equals(it, "_")); 55 | if (mapUnderscoreToCamelCase) { 56 | return stream.map(StringUtils::uncapitalize).collect(Collectors.joining("_")); 57 | } else { 58 | return stream.map(StringUtils::capitalize).collect(Collectors.joining()); 59 | } 60 | } 61 | 62 | public static BinaryTuple, List> getPropertiesAndColumnsFromLiteralColumns 63 | (String literalColumns, Class entityType, boolean mapUnderscoreToCamelCase) { 64 | List properties, columns; 65 | if (Objects.equals(literalColumns, "*") || literalColumns.toUpperCase().startsWith("NOT ")) { 66 | properties = EntityUtils.getProperties(entityType); 67 | if (literalColumns.toUpperCase().startsWith("NOT ")) { 68 | properties.removeAll 69 | (Arrays.stream(literalColumns.substring(4).split(",")) 70 | .map(String::trim).map(PropertyUtils::normalizeProperty) 71 | .collect(Collectors.toList())); 72 | } 73 | columns = properties.stream() 74 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList()); 75 | } else { 76 | columns = Arrays.stream(literalColumns.split(",")).map(String::trim) 77 | .map(it -> SqlUtils.normalizeColumn(it, mapUnderscoreToCamelCase)).collect(Collectors.toList()); 78 | properties = columns.stream().map(PropertyUtils::normalizeProperty).collect(Collectors.toList()); 79 | } 80 | return new BinaryTuple<>(properties, columns); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/function/UncheckedConsumer.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util.function; 2 | 3 | import java.util.function.Consumer; 4 | 5 | @FunctionalInterface 6 | public interface UncheckedConsumer { 7 | 8 | void accept(T t) throws Throwable; 9 | 10 | static Consumer of(UncheckedConsumer consumer) { 11 | return t -> { 12 | try { 13 | consumer.accept(t); 14 | } catch (Throwable e) { 15 | throw sneakyThrow(e); 16 | } 17 | }; 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | static E sneakyThrow(Throwable e) throws E { 22 | return (E) e; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/function/UncheckedFunction.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util.function; 2 | 3 | import java.util.function.Function; 4 | 5 | @FunctionalInterface 6 | public interface UncheckedFunction { 7 | 8 | R apply(T t) throws Throwable; 9 | 10 | static Function of(UncheckedFunction function) { 11 | return t -> { 12 | try { 13 | return function.apply(t); 14 | } catch (Throwable e) { 15 | throw sneakyThrow(e); 16 | } 17 | }; 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | static E sneakyThrow(Throwable e) throws E { 22 | return (E) e; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/function/UncheckedPredicate.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util.function; 2 | 3 | import java.util.function.Predicate; 4 | 5 | @FunctionalInterface 6 | public interface UncheckedPredicate { 7 | 8 | boolean test(T t) throws Throwable; 9 | 10 | static Predicate of(UncheckedPredicate predicate) { 11 | return t -> { 12 | try { 13 | return predicate.test(t); 14 | } catch (Throwable e) { 15 | throw sneakyThrow(e); 16 | } 17 | }; 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | static E sneakyThrow(Throwable e) throws E { 22 | return (E) e; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mybatis-boost-core/src/main/java/cn/mybatisboost/util/tuple/BinaryTuple.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.util.tuple; 2 | 3 | import java.util.Objects; 4 | 5 | public class BinaryTuple { 6 | 7 | private T1 first; 8 | private T2 second; 9 | 10 | public BinaryTuple(T1 first, T2 second) { 11 | this.first = first; 12 | this.second = second; 13 | } 14 | 15 | public T1 first() { 16 | return first; 17 | } 18 | 19 | public T2 second() { 20 | return second; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | BinaryTuple that = (BinaryTuple) o; 28 | return Objects.equals(first, that.first) && 29 | Objects.equals(second, that.second); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(first, second); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "BinaryTuple{" + 40 | "first=" + first + 41 | ", second=" + second + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-autoconfigure/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-spring-boot-autoconfigure 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.0.0.RELEASE 21 | pom 22 | import 23 | 24 | 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-autoconfigure 31 | true 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-configuration-processor 36 | true 37 | 38 | 39 | 40 | cn.mybatisboost 41 | mybatis-boost-core 42 | 43 | 44 | 45 | org.mybatis 46 | mybatis 47 | true 48 | 49 | 50 | org.mybatis.spring.boot 51 | mybatis-spring-boot-starter 52 | true 53 | 54 | 55 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-autoconfigure/src/main/java/cn/mybatisboost/spring/boot/autoconfigure/EnvironmentHolder.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.spring.boot.autoconfigure; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.env.EnvironmentPostProcessor; 5 | import org.springframework.core.env.ConfigurableEnvironment; 6 | 7 | public class EnvironmentHolder implements EnvironmentPostProcessor { 8 | 9 | private static volatile ConfigurableEnvironment environment; 10 | 11 | public static ConfigurableEnvironment getEnvironment() { 12 | if (environment == null) { 13 | throw new EnvironmentNotSetException(); 14 | } 15 | return environment; 16 | } 17 | 18 | @Override 19 | public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { 20 | EnvironmentHolder.environment = environment; 21 | } 22 | 23 | public static class EnvironmentNotSetException extends RuntimeException { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-autoconfigure/src/main/java/cn/mybatisboost/spring/boot/autoconfigure/MybatisBoostAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.spring.boot.autoconfigure; 2 | 3 | import cn.mybatisboost.core.DispatcherInterceptor; 4 | import cn.mybatisboost.core.adaptor.NoopNameAdaptor; 5 | import cn.mybatisboost.core.preprocessor.AutoParameterMappingPreprocessor; 6 | import cn.mybatisboost.core.preprocessor.MybatisCacheRemovingPreprocessor; 7 | import cn.mybatisboost.core.preprocessor.ParameterNormalizationPreprocessor; 8 | import cn.mybatisboost.generator.GeneratingSqlProvider; 9 | import cn.mybatisboost.json.JsonResultSetsHandler; 10 | import cn.mybatisboost.json.JsonTypeHandler; 11 | import cn.mybatisboost.lang.LanguageSqlProvider; 12 | import cn.mybatisboost.limiter.LimiterSqlProvider; 13 | import cn.mybatisboost.mapper.MapperSqlProvider; 14 | import cn.mybatisboost.metric.MetricInterceptor; 15 | import org.apache.ibatis.session.SqlSessionFactory; 16 | import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; 17 | import org.springframework.beans.factory.annotation.Value; 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 19 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 21 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | import org.springframework.context.annotation.Import; 25 | 26 | @ConditionalOnClass(SqlSessionFactory.class) 27 | @Configuration 28 | @EnableConfigurationProperties(MybatisBoostProperties.class) 29 | @Import(NosqlConfiguration.class) 30 | public class MybatisBoostAutoConfiguration { 31 | 32 | private final MybatisBoostProperties properties; 33 | 34 | @Value("${mybatisboost.mapper.enabled:true}") 35 | private boolean isMapperEnabled; 36 | @Value("${mybatisboost.lang.enabled:true}") 37 | private boolean isLangEnabled; 38 | @Value("${mybatisboost.limiter.enabled:true}") 39 | private boolean isLimiterEnabled; 40 | 41 | public MybatisBoostAutoConfiguration(MybatisBoostProperties properties) { 42 | this.properties = properties; 43 | } 44 | 45 | @Bean 46 | @ConditionalOnMissingBean 47 | public cn.mybatisboost.core.Configuration configuration() 48 | throws IllegalAccessException, InstantiationException { 49 | cn.mybatisboost.core.Configuration.Builder builder = 50 | cn.mybatisboost.core.Configuration.builder() 51 | .setMultipleDatasource(properties.isMultipleDatasource()) 52 | .setIterateSelectiveInBatch(properties.isIterateSelectiveInBatch()) 53 | .setShowQuery(properties.isShowQuery()) 54 | .setShowQueryWithParameters(properties.isShowQueryWithParameters()) 55 | .setSlowQueryThresholdInMillis(properties.getSlowQueryThresholdInMillis()); 56 | if (properties.getNameAdaptor() != null) { 57 | builder.setNameAdaptor(properties.getNameAdaptor().newInstance()); 58 | } else { 59 | builder.setNameAdaptor(new NoopNameAdaptor()); 60 | } 61 | if (properties.getSlowQueryHandler() != null) { 62 | builder.setSlowQueryHandler(properties.getSlowQueryHandler().newInstance()); 63 | } 64 | return builder.build(); 65 | } 66 | 67 | @Bean 68 | @ConditionalOnMissingBean 69 | @ConditionalOnProperty("mybatisboost.metric.enabled") 70 | public MetricInterceptor metricInterceptor(cn.mybatisboost.core.Configuration configuration) { 71 | return new MetricInterceptor(configuration); 72 | } 73 | 74 | @Bean 75 | @ConditionalOnMissingBean 76 | public DispatcherInterceptor mybatisBoostInterceptor(cn.mybatisboost.core.Configuration configuration) { 77 | DispatcherInterceptor dispatcherInterceptor = new DispatcherInterceptor(configuration); 78 | dispatcherInterceptor.appendPreprocessor(new MybatisCacheRemovingPreprocessor()); 79 | dispatcherInterceptor.appendPreprocessor(new ParameterNormalizationPreprocessor()); 80 | dispatcherInterceptor.appendPreprocessor(new AutoParameterMappingPreprocessor()); 81 | dispatcherInterceptor.appendProvider(new GeneratingSqlProvider()); 82 | if (isMapperEnabled) { 83 | dispatcherInterceptor.appendProvider(new MapperSqlProvider(configuration)); 84 | } 85 | if (isLangEnabled) { 86 | dispatcherInterceptor.appendProvider(new LanguageSqlProvider(configuration)); 87 | } 88 | if (isLimiterEnabled) { 89 | dispatcherInterceptor.appendProvider(new LimiterSqlProvider(configuration)); 90 | } 91 | return dispatcherInterceptor; 92 | } 93 | 94 | @ConditionalOnClass(ConfigurationCustomizer.class) 95 | @ConditionalOnProperty(name = "mybatisboost.json.enabled", matchIfMissing = true) 96 | @Configuration 97 | public static class JsonConfiguration implements ConfigurationCustomizer { 98 | 99 | @Bean 100 | public JsonResultSetsHandler jsonInterceptor() { 101 | return new JsonResultSetsHandler(); 102 | } 103 | 104 | @Override 105 | public void customize(org.apache.ibatis.session.Configuration configuration) { 106 | configuration.getTypeHandlerRegistry().register(JsonTypeHandler.class); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-autoconfigure/src/main/java/cn/mybatisboost/spring/boot/autoconfigure/MybatisBoostProperties.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.spring.boot.autoconfigure; 2 | 3 | import cn.mybatisboost.core.adaptor.NameAdaptor; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | import java.util.function.BiConsumer; 7 | 8 | @ConfigurationProperties("mybatisboost") 9 | public class MybatisBoostProperties { 10 | 11 | private Class nameAdaptor; 12 | private boolean multipleDatasource; 13 | private boolean iterateSelectiveInBatch; 14 | private boolean showQuery; 15 | private boolean showQueryWithParameters; 16 | private long slowQueryThresholdInMillis = Long.MAX_VALUE; 17 | private Class> slowQueryHandler; 18 | 19 | public Class getNameAdaptor() { 20 | return nameAdaptor; 21 | } 22 | 23 | public void setNameAdaptor(Class nameAdaptor) { 24 | this.nameAdaptor = nameAdaptor; 25 | } 26 | 27 | public boolean isMultipleDatasource() { 28 | return multipleDatasource; 29 | } 30 | 31 | public void setMultipleDatasource(boolean multipleDatasource) { 32 | this.multipleDatasource = multipleDatasource; 33 | } 34 | 35 | public boolean isIterateSelectiveInBatch() { 36 | return iterateSelectiveInBatch; 37 | } 38 | 39 | public MybatisBoostProperties setIterateSelectiveInBatch(boolean iterateSelectiveInBatch) { 40 | this.iterateSelectiveInBatch = iterateSelectiveInBatch; 41 | return this; 42 | } 43 | 44 | public boolean isShowQuery() { 45 | return showQuery; 46 | } 47 | 48 | public MybatisBoostProperties setShowQuery(boolean showQuery) { 49 | this.showQuery = showQuery; 50 | return this; 51 | } 52 | 53 | public boolean isShowQueryWithParameters() { 54 | return showQueryWithParameters; 55 | } 56 | 57 | public MybatisBoostProperties setShowQueryWithParameters(boolean showQueryWithParameters) { 58 | this.showQueryWithParameters = showQueryWithParameters; 59 | return this; 60 | } 61 | 62 | public long getSlowQueryThresholdInMillis() { 63 | return slowQueryThresholdInMillis; 64 | } 65 | 66 | public MybatisBoostProperties setSlowQueryThresholdInMillis(long slowQueryThresholdInMillis) { 67 | this.slowQueryThresholdInMillis = slowQueryThresholdInMillis; 68 | return this; 69 | } 70 | 71 | public Class> getSlowQueryHandler() { 72 | return slowQueryHandler; 73 | } 74 | 75 | public MybatisBoostProperties setSlowQueryHandler(Class> slowQueryHandler) { 76 | this.slowQueryHandler = slowQueryHandler; 77 | return this; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-autoconfigure/src/main/java/cn/mybatisboost/spring/boot/autoconfigure/NosqlConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.spring.boot.autoconfigure; 2 | 3 | import org.apache.ibatis.javassist.ClassPool; 4 | import org.apache.ibatis.javassist.CtClass; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 10 | import org.springframework.core.type.AnnotationMetadata; 11 | 12 | @ConditionalOnProperty(name = "mybatisboost.nosql.enabled", matchIfMissing = true) 13 | public class NosqlConfiguration implements ImportBeanDefinitionRegistrar { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(NosqlConfiguration.class); 16 | private static boolean mapUnderscoreToCamelCase; 17 | 18 | static { 19 | try { 20 | mapUnderscoreToCamelCase = EnvironmentHolder.getEnvironment() 21 | .getProperty("mybatis.configuration.map-underscore-to-camel-case", 22 | boolean.class, false); 23 | } catch (EnvironmentHolder.EnvironmentNotSetException e) { 24 | logger.warn("Use default mapUnderscoreToCamelCase value, because Environment wasn't set"); 25 | } 26 | try { 27 | CtClass ctClass = ClassPool.getDefault().get("org.mybatis.spring.mapper.ClassPathMapperScanner"); 28 | ctClass.getDeclaredMethod("checkCandidate").insertAfter("if ($_) " + 29 | "cn.mybatisboost.nosql.MapperInstrument.modify" + 30 | "($2.getBeanClassName(), " + mapUnderscoreToCamelCase + ");"); 31 | ctClass.toClass(NosqlConfiguration.class.getClassLoader(), 32 | NosqlConfiguration.class.getProtectionDomain()); 33 | } catch (Exception e) { 34 | logger.error("Exception happened when configuring mybatisboost.nosql", e); 35 | } 36 | } 37 | 38 | @Override 39 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 40 | // Noop 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | cn.mybatisboost.spring.boot.autoconfigure.MybatisBoostAutoConfiguration 3 | org.springframework.boot.env.EnvironmentPostProcessor=\ 4 | cn.mybatisboost.spring.boot.autoconfigure.EnvironmentHolder -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-starter/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-spring-boot-starter 14 | 15 | 16 | 17 | cn.mybatisboost 18 | mybatis-boost-spring-boot-autoconfigure 19 | 20 | 21 | -------------------------------------------------------------------------------- /mybatis-boost-spring-boot-starter/src/main/resources/META-INF/spring.provides: -------------------------------------------------------------------------------- 1 | provides: mybatis-boost-spring-boot-autoconfigure 2 | -------------------------------------------------------------------------------- /mybatis-boost-test/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-test 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.0.0.RELEASE 21 | pom 22 | import 23 | 24 | 25 | 26 | 27 | 28 | 29 | cn.mybatisboost 30 | mybatis-boost-spring-boot-starter 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | org.mybatis.spring.boot 40 | mybatis-spring-boot-starter 41 | 1.2.1 42 | 43 | 44 | mysql 45 | mysql-connector-java 46 | runtime 47 | 48 | 49 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/main/java/cn/mybatisboost/test/Project.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import javax.persistence.GeneratedValue; 4 | 5 | public class Project { 6 | 7 | private Integer id; 8 | @GeneratedValue(generator = "cn.mybatisboost.generator.UuidGenerator") 9 | private String uuid; 10 | private String groupId; 11 | private String artifactId; 12 | private String license; 13 | private String scm; 14 | private String developer; 15 | private Website website; 16 | 17 | public Project() { 18 | } 19 | 20 | public Project(String groupId, String artifactId, String license, String scm, String developer, Website website) { 21 | this.groupId = groupId; 22 | this.artifactId = artifactId; 23 | this.license = license; 24 | this.scm = scm; 25 | this.developer = developer; 26 | this.website = website; 27 | } 28 | 29 | public Integer getId() { 30 | return id; 31 | } 32 | 33 | public Project setId(Integer id) { 34 | this.id = id; 35 | return this; 36 | } 37 | 38 | public String getUuid() { 39 | return uuid; 40 | } 41 | 42 | public void setUuid(String uuid) { 43 | this.uuid = uuid; 44 | } 45 | 46 | public String getGroupId() { 47 | return groupId; 48 | } 49 | 50 | public Project setGroupId(String groupId) { 51 | this.groupId = groupId; 52 | return this; 53 | } 54 | 55 | public String getArtifactId() { 56 | return artifactId; 57 | } 58 | 59 | public Project setArtifactId(String artifactId) { 60 | this.artifactId = artifactId; 61 | return this; 62 | } 63 | 64 | public String getLicense() { 65 | return license; 66 | } 67 | 68 | public Project setLicense(String license) { 69 | this.license = license; 70 | return this; 71 | } 72 | 73 | public String getScm() { 74 | return scm; 75 | } 76 | 77 | public Project setScm(String scm) { 78 | this.scm = scm; 79 | return this; 80 | } 81 | 82 | public String getDeveloper() { 83 | return developer; 84 | } 85 | 86 | public Project setDeveloper(String developer) { 87 | this.developer = developer; 88 | return this; 89 | } 90 | 91 | public Website getWebsite() { 92 | return website; 93 | } 94 | 95 | public void setWebsite(Website website) { 96 | this.website = website; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "Project{" + 102 | "id=" + id + 103 | ", uuid='" + uuid + '\'' + 104 | ", groupId='" + groupId + '\'' + 105 | ", artifactId='" + artifactId + '\'' + 106 | ", license='" + license + '\'' + 107 | ", scm='" + scm + '\'' + 108 | ", developer='" + developer + '\'' + 109 | ", website=" + website + 110 | '}'; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/main/java/cn/mybatisboost/test/ProjectMapper.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import cn.mybatisboost.mapper.CrudMapper; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | import java.util.List; 7 | 8 | @Mapper 9 | public interface ProjectMapper extends CrudMapper { 10 | 11 | @Select("select * from #t") 12 | List selectFromT(); 13 | 14 | @Select("select * from project where id in #{ids}") 15 | List selectRange(@Param("ids") List ids); 16 | 17 | @Select("select * from #t where id in ?") 18 | List selectRange2(List list); 19 | 20 | @Select("select * from #t where id = ?") 21 | Project selectOneFromT(Integer id); 22 | 23 | @Select("select * from #t where group_id != ? and artifact_id = ? and scm = ?") 24 | Project selectNullable(String groupId, String artifactId, String scm); 25 | 26 | @Insert("insert *") 27 | @Options(useGeneratedKeys = true) 28 | int insertSome(List list); 29 | 30 | @Insert("insert group_id") 31 | @Options(useGeneratedKeys = true) 32 | int insertOne1(Project project); 33 | 34 | @Insert("insert NOT artifact_id") 35 | @Options(useGeneratedKeys = true) 36 | int insertOne2(Project project); 37 | 38 | @Update("update set group_id where id = ?") 39 | int updateGroupId(String groupId, int id); 40 | 41 | @Update("update set not group_id") 42 | int updateNotGroupId(Project project); 43 | 44 | @Update("update set *") 45 | int updateAll(Project project); 46 | } 47 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/main/java/cn/mybatisboost/test/ProjectNosqlMapper.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import cn.mybatisboost.core.GenericMapper; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | import java.util.List; 7 | 8 | @Mapper 9 | public interface ProjectNosqlMapper extends GenericMapper { 10 | 11 | @Mapper 12 | int deleteAll(); 13 | 14 | @Mapper 15 | Project selectFirst(); 16 | 17 | @Mapper 18 | List selectTop2(); 19 | 20 | @Mapper 21 | Project selectAllOffset1Limit1(); 22 | 23 | @Mapper 24 | List selectByGroupIdAndArtifactId(String groupId, String artifactId); 25 | 26 | @Mapper 27 | List selectByGroupIdOrArtifactId(String groupId, String artifactId); 28 | 29 | @Mapper 30 | List selectByArtifactIdNot(String artifactId); 31 | 32 | @Mapper 33 | List selectAllOrderByGroupIdDesc(); 34 | } 35 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/main/java/cn/mybatisboost/test/Website.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import cn.mybatisboost.support.JsonType; 4 | 5 | public class Website extends JsonType { 6 | 7 | private String protocol; 8 | private String host; 9 | private short port; 10 | 11 | public Website() { 12 | } 13 | 14 | public Website(String protocol, String host, short port) { 15 | this.protocol = protocol; 16 | this.host = host; 17 | this.port = port; 18 | } 19 | 20 | public String getProtocol() { 21 | return protocol; 22 | } 23 | 24 | public void setProtocol(String protocol) { 25 | this.protocol = protocol; 26 | } 27 | 28 | public String getHost() { 29 | return host; 30 | } 31 | 32 | public void setHost(String host) { 33 | this.host = host; 34 | } 35 | 36 | public short getPort() { 37 | return port; 38 | } 39 | 40 | public void setPort(short port) { 41 | this.port = port; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/test/java/cn/mybatisboost/test/CrudMapperTest.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import cn.mybatisboost.util.SafeProperty; 4 | import org.apache.ibatis.session.RowBounds; 5 | import org.junit.After; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.jdbc.core.JdbcTemplate; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import java.util.Collections; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootApplication 20 | @SpringBootTest(classes = ProjectMapper.class) 21 | public class CrudMapperTest { 22 | 23 | @Autowired 24 | private ProjectMapper mapper; 25 | @Autowired 26 | private JdbcTemplate jdbcTemplate; 27 | 28 | @After 29 | public void tearDown() { 30 | jdbcTemplate.execute("delete from project"); 31 | } 32 | 33 | @Test 34 | public void count() { 35 | assertEquals(0, mapper.count(new Project())); 36 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost')"); 37 | assertEquals(1, mapper.count(new Project())); 38 | assertEquals(1, mapper.count(new Project().setGroupId("cn.mybatisboost"))); 39 | assertEquals(0, mapper.count(new Project().setGroupId("whatever"))); 40 | assertEquals(1, mapper.count(new Project().setGroupId("cn.mybatisboost"), 41 | SafeProperty.of(Project.class, "groupId"))); 42 | assertEquals(0, mapper.count(new Project().setGroupId("cn.mybatisboost").setArtifactId("whatever"), 43 | SafeProperty.of(Project.class, "groupId", "artifactId"))); 44 | assertEquals(1, mapper.count(new Project().setGroupId("cn.mybatisboost").setArtifactId("whatever"), 45 | SafeProperty.of(Project.class, "groupId"))); 46 | } 47 | 48 | @Test 49 | public void selectOne() { 50 | assertNull(mapper.selectOne(new Project())); 51 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost')"); 52 | assertEquals("cn.mybatisboost", 53 | mapper.selectOne(new Project().setGroupId("cn.mybatisboost")).getGroupId()); 54 | assertNull(mapper.selectOne(new Project().setGroupId("whatever"))); 55 | } 56 | 57 | @Test 58 | public void select() { 59 | assertTrue(mapper.select(new Project()).isEmpty()); 60 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost')"); 61 | assertEquals("cn.mybatisboost", 62 | mapper.select(new Project().setGroupId("cn.mybatisboost")).get(0).getGroupId()); 63 | assertTrue(mapper.select(new Project().setGroupId("whatever")).isEmpty()); 64 | } 65 | 66 | @Test 67 | public void selectWithRowBounds() { 68 | assertTrue(mapper.selectWithRowBounds(new Project(), RowBounds.DEFAULT).isEmpty()); 69 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost1')"); 70 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost2')"); 71 | assertEquals(2, mapper.selectWithRowBounds(new Project(), RowBounds.DEFAULT).size()); 72 | assertEquals(1, mapper.selectWithRowBounds(new Project(), new RowBounds(1, 1)).size()); 73 | assertEquals("cn.mybatisboost2", 74 | mapper.selectWithRowBounds(new Project(), new RowBounds(1, 1)).get(0).getGroupId()); 75 | } 76 | 77 | @Test 78 | public void countAll() { 79 | assertEquals(0, mapper.countAll()); 80 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost')"); 81 | assertEquals(1, mapper.countAll()); 82 | } 83 | 84 | @Test 85 | public void selectAll() { 86 | assertEquals(0, mapper.selectAll().size()); 87 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost')"); 88 | assertEquals(1, mapper.selectAll().size()); 89 | } 90 | 91 | @Test 92 | public void selectAllWithRowBounds() { 93 | assertTrue(mapper.selectAllWithRowBounds(RowBounds.DEFAULT).isEmpty()); 94 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost1')"); 95 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost2')"); 96 | assertEquals(2, mapper.selectAllWithRowBounds(RowBounds.DEFAULT).size()); 97 | assertEquals(1, mapper.selectAllWithRowBounds(new RowBounds(1, 1)).size()); 98 | assertEquals("cn.mybatisboost2", 99 | mapper.selectAllWithRowBounds(new RowBounds(1, 1)).get(0).getGroupId()); 100 | } 101 | 102 | @Test 103 | public void selectById() { 104 | assertNull(mapper.selectById(123)); 105 | jdbcTemplate.execute("insert into project (id, group_id) values (123, 'cn.mybatisboost')"); 106 | assertEquals(123, mapper.selectById(123).getId().intValue()); 107 | } 108 | 109 | @Test 110 | public void selectByIds() { 111 | assertTrue(mapper.selectByIds(123).isEmpty()); 112 | jdbcTemplate.execute("insert into project (id, group_id) values (123, 'cn.mybatisboost')"); 113 | assertEquals(123, mapper.selectByIds(123).get(0).getId().intValue()); 114 | } 115 | 116 | @Test 117 | public void selectNullable() { 118 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost', 'mybatis-boost')"); 119 | assertNotNull(mapper.selectNullable(null, "mybatis-boost", null)); 120 | assertNull(mapper.selectNullable("cn.mybatisboost", null, null)); 121 | assertNull(mapper.selectNullable(null, null, null)); 122 | } 123 | 124 | @Test 125 | public void insert() { 126 | try { 127 | assertEquals(1, mapper.insert(new Project(null, "mybatis-boost", 128 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null))); 129 | fail(); 130 | } catch (Exception ignored) { 131 | // normally, exception would happen because "group_id" column is declared NOT NULL 132 | } 133 | } 134 | 135 | @Test 136 | public void batchInsert() { 137 | try { 138 | assertEquals(1, mapper.batchInsert(Collections.singletonList( 139 | new Project(null, "mybatis-boost", 140 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null)))); 141 | fail(); 142 | } catch (Exception ignored) { 143 | // normally, exception would happen because "group_id" column is declared NOT NULL 144 | } 145 | } 146 | 147 | @Test 148 | public void insertSelective() { 149 | assertEquals(1, mapper.insertSelective(new Project("cn.mybatisboost", "mybatis-boost", 150 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null))); 151 | jdbcTemplate.query("select * from project", resultSet -> { 152 | assertEquals(1, resultSet.getRow()); 153 | assertNotNull(resultSet.getString("uuid")); 154 | }); 155 | } 156 | 157 | @Test 158 | public void batchInsertSelective() { 159 | assertEquals(1, mapper.batchInsertSelective(Collections.singletonList( 160 | new Project("cn.mybatisboost", "mybatis-boost", 161 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null)))); 162 | jdbcTemplate.query("select * from project", resultSet -> { 163 | assertEquals(1, resultSet.getRow()); 164 | assertNotNull(resultSet.getString("uuid")); 165 | }); 166 | } 167 | 168 | @Test 169 | public void update() { 170 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 171 | assertEquals(1, mapper.update(new Project().setId(123).setGroupId("cn.mybatisboost2"))); 172 | jdbcTemplate.query("select * from project", resultSet -> { 173 | assertEquals(1, resultSet.getRow()); 174 | assertEquals("cn.mybatisboost2", resultSet.getString("group_id")); 175 | assertNull(resultSet.getString("artifact_id")); 176 | }); 177 | } 178 | 179 | @Test 180 | public void updatePartial() { 181 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 182 | assertEquals(1, mapper.updatePartial(new Project().setId(123).setGroupId("cn.mybatisboost2"), 183 | SafeProperty.of(Project.class, "groupId"))); 184 | jdbcTemplate.query("select * from project", resultSet -> { 185 | assertEquals(1, resultSet.getRow()); 186 | assertEquals("cn.mybatisboost2", resultSet.getString("group_id")); 187 | assertEquals("mybatis-boost", resultSet.getString("artifact_id")); 188 | }); 189 | } 190 | 191 | @Test 192 | public void updateSelective() { 193 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 194 | assertEquals(1, mapper.updateSelective(new Project().setId(123).setGroupId("cn.mybatisboost2"))); 195 | jdbcTemplate.query("select * from project", resultSet -> { 196 | assertEquals(1, resultSet.getRow()); 197 | assertEquals("cn.mybatisboost2", resultSet.getString("group_id")); 198 | assertEquals("mybatis-boost", resultSet.getString("artifact_id")); 199 | }); 200 | } 201 | 202 | @Test 203 | public void updatePartialSelective() { 204 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 205 | assertEquals(1, mapper.updatePartialSelective(new Project().setId(123).setGroupId("cn.mybatisboost2"), 206 | SafeProperty.of(Project.class, "groupId"))); 207 | jdbcTemplate.query("select * from project", resultSet -> { 208 | assertEquals(1, resultSet.getRow()); 209 | assertEquals("cn.mybatisboost2", resultSet.getString("group_id")); 210 | assertEquals("mybatis-boost", resultSet.getString("artifact_id")); 211 | }); 212 | } 213 | 214 | @Test 215 | public void delete() { 216 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 217 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (456, 'cn.mybatisboost2', 'mybatis-boost')"); 218 | assertEquals(2, mapper.delete(new Project())); 219 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 220 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (456, 'cn.mybatisboost2', 'mybatis-boost')"); 221 | assertEquals(2, mapper.delete(new Project().setArtifactId("mybatis-boost"))); 222 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 223 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (456, 'cn.mybatisboost2', 'mybatis-boost')"); 224 | assertEquals(0, mapper.delete(new Project().setGroupId("cn.mybatisboost"))); 225 | } 226 | 227 | @Test 228 | public void deleteByIds() { 229 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost')"); 230 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (456, 'cn.mybatisboost2', 'mybatis-boost')"); 231 | assertEquals(0, mapper.deleteByIds(0)); 232 | assertEquals(2, mapper.deleteByIds(123, 456)); 233 | } 234 | } -------------------------------------------------------------------------------- /mybatis-boost-test/src/test/java/cn/mybatisboost/test/EnhancementTest.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import org.junit.After; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.jdbc.core.JdbcTemplate; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import java.util.Arrays; 13 | import java.util.Collections; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootApplication 19 | @SpringBootTest(classes = ProjectMapper.class) 20 | public class EnhancementTest { 21 | 22 | @Autowired 23 | private ProjectMapper mapper; 24 | @Autowired 25 | private JdbcTemplate jdbcTemplate; 26 | 27 | @After 28 | public void tearDown() { 29 | jdbcTemplate.execute("delete from project"); 30 | } 31 | 32 | @Test 33 | public void selectFromT() { 34 | assertTrue(mapper.selectFromT().isEmpty()); 35 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost1')"); 36 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost2')"); 37 | assertEquals(2, mapper.selectFromT().size()); 38 | } 39 | 40 | @Test 41 | public void selectRange() { 42 | assertTrue(mapper.selectRange(Arrays.asList(123, 456)).isEmpty()); 43 | jdbcTemplate.execute("insert into project (id, group_id) values (123, 'cn.mybatisboost1')"); 44 | jdbcTemplate.execute("insert into project (id, group_id) values (456, 'cn.mybatisboost2')"); 45 | assertEquals(2, mapper.selectRange(Arrays.asList(123, 456)).size()); 46 | assertEquals(123, (int) mapper.selectRange(Collections.singletonList(123)).get(0).getId()); 47 | assertEquals(456, (int) mapper.selectRange(Collections.singletonList(456)).get(0).getId()); 48 | assertEquals(2, mapper.selectRange2(Arrays.asList(123, 456)).size()); 49 | } 50 | 51 | @Test 52 | public void selectOneFromT() { 53 | assertNull(mapper.selectOneFromT(123)); 54 | jdbcTemplate.execute("insert into project (id, group_id) values (123, 'cn.mybatisboost1')"); 55 | jdbcTemplate.execute("insert into project (id, group_id) values (456, 'cn.mybatisboost2')"); 56 | assertEquals(123, (int) mapper.selectOneFromT(123).getId()); 57 | } 58 | 59 | @Test 60 | public void insertSome() { 61 | assertEquals(1, mapper.insertSome(Collections.singletonList(new Project( 62 | "cn.mybatisboost", "mybatis-boost", 63 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null)))); 64 | jdbcTemplate.query("select * from project", resultSet -> { 65 | assertEquals(1, resultSet.getRow()); 66 | assertEquals("cn.mybatisboost", resultSet.getString("group_id")); 67 | assertEquals("mybatis-boost", resultSet.getString("artifact_id")); 68 | assertNotNull(resultSet.getString("uuid")); 69 | }); 70 | } 71 | 72 | @Test 73 | public void insertOne1() { 74 | assertEquals(1, mapper.insertOne1(new Project("cn.mybatisboost", "mybatis-boost", 75 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null))); 76 | jdbcTemplate.query("select * from project", resultSet -> { 77 | assertEquals(1, resultSet.getRow()); 78 | assertEquals("cn.mybatisboost", resultSet.getString("group_id")); 79 | assertNull(resultSet.getString("artifact_id")); 80 | }); 81 | } 82 | 83 | @Test 84 | public void insertOne2() { 85 | assertEquals(1, mapper.insertOne2(new Project("cn.mybatisboost", "mybatis-boost", 86 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null))); 87 | jdbcTemplate.query("select * from project", resultSet -> { 88 | assertEquals(1, resultSet.getRow()); 89 | assertEquals("cn.mybatisboost", resultSet.getString("group_id")); 90 | assertNull(resultSet.getString("artifact_id")); 91 | assertNotNull(resultSet.getString("uuid")); 92 | }); 93 | } 94 | 95 | @Test 96 | public void updateGroupId() { 97 | jdbcTemplate.execute("insert into project (id, group_id) values (123, 'cn.mybatisboost1')"); 98 | assertEquals(1, mapper.updateGroupId("cn.mybatisboost2", 123)); 99 | jdbcTemplate.query("select * from project", resultSet -> { 100 | assertEquals(1, resultSet.getRow()); 101 | assertEquals("cn.mybatisboost2", resultSet.getString("group_id")); 102 | assertNull(resultSet.getString("artifact_id")); 103 | }); 104 | } 105 | 106 | @Test 107 | public void updateNotGroupId() { 108 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost1')"); 109 | assertEquals(1, mapper.updateNotGroupId(new Project("cn.mybatisboost", "mybatis-boost", 110 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null))); 111 | jdbcTemplate.query("select * from project", resultSet -> { 112 | assertEquals(1, resultSet.getRow()); 113 | assertEquals("cn.mybatisboost1", resultSet.getString("group_id")); 114 | assertEquals("mybatis-boost", resultSet.getString("artifact_id")); 115 | assertEquals("MIT", resultSet.getString("license")); 116 | assertEquals("https://github.com/zhang-rf/mybatis-boost", resultSet.getString("scm")); 117 | assertEquals("zhangrongfan", resultSet.getString("developer")); 118 | }); 119 | } 120 | 121 | @Test 122 | public void updateAll() { 123 | jdbcTemplate.execute("insert into project (group_id) values ('cn.mybatisboost1')"); 124 | assertEquals(1, mapper.updateAll(new Project("cn.mybatisboost", "mybatis-boost", 125 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", null))); 126 | jdbcTemplate.query("select * from project", resultSet -> { 127 | assertEquals(1, resultSet.getRow()); 128 | assertEquals("cn.mybatisboost", resultSet.getString("group_id")); 129 | assertEquals("mybatis-boost", resultSet.getString("artifact_id")); 130 | assertEquals("MIT", resultSet.getString("license")); 131 | assertEquals("https://github.com/zhang-rf/mybatis-boost", resultSet.getString("scm")); 132 | assertEquals("zhangrongfan", resultSet.getString("developer")); 133 | }); 134 | } 135 | } -------------------------------------------------------------------------------- /mybatis-boost-test/src/test/java/cn/mybatisboost/test/JsonTest.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.junit.After; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.jdbc.core.JdbcTemplate; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | @RunWith(SpringRunner.class) 17 | @SpringBootApplication 18 | @SpringBootTest(classes = ProjectMapper.class) 19 | public class JsonTest { 20 | 21 | @Autowired 22 | private ProjectMapper mapper; 23 | @Autowired 24 | private JdbcTemplate jdbcTemplate; 25 | private ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); 26 | 27 | @After 28 | public void tearDown() { 29 | jdbcTemplate.execute("delete from project"); 30 | } 31 | 32 | @Test 33 | public void testSave() { 34 | Project project = new Project("cn.mybatisboost", "mybatis-boost", 35 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", 36 | new Website("HTTPS", "mybatisboost.cn", (short) 80)); 37 | mapper.insert(project); 38 | jdbcTemplate.query("select * from project", resultSet -> { 39 | try { 40 | assertEquals(objectMapper.writeValueAsString(project.getWebsite()), resultSet.getString("website")); 41 | } catch (JsonProcessingException e) { 42 | fail(); 43 | } 44 | }); 45 | } 46 | 47 | @Test 48 | public void testQuery() throws Exception { 49 | Project project = new Project("cn.mybatisboost", "mybatis-boost", 50 | "MIT", "https://github.com/zhang-rf/mybatis-boost", "zhangrongfan", 51 | new Website("HTTPS", "mybatisboost.cn", (short) 80)); 52 | jdbcTemplate.execute("insert into project (id, group_id, website) values (999, 'cn.mybatisboost', '" + objectMapper.writeValueAsString(project.getWebsite()) + "')"); 53 | project = mapper.selectById(999); 54 | assertNotNull(project); 55 | assertEquals("HTTPS", project.getWebsite().getProtocol()); 56 | assertEquals("mybatisboost.cn", project.getWebsite().getHost()); 57 | assertEquals(80, project.getWebsite().getPort()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/test/java/cn/mybatisboost/test/NosqlTest.java: -------------------------------------------------------------------------------- 1 | package cn.mybatisboost.test; 2 | 3 | import cn.mybatisboost.core.GenericMapper; 4 | import org.junit.After; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.jdbc.core.JdbcTemplate; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.lang.reflect.Method; 16 | import java.util.List; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | /** 21 | * We use reflection to test Nosql feature 22 | * mainly because we mustn't but have to load mapper classes before there were scanned by Mybatis 23 | */ 24 | @RunWith(SpringRunner.class) 25 | @SpringBootApplication 26 | @SpringBootTest(classes = GenericMapper.class) 27 | @SuppressWarnings("unchecked") 28 | public class NosqlTest { 29 | 30 | @Autowired 31 | @Qualifier("projectNosqlMapper") 32 | private GenericMapper mapper; 33 | @Autowired 34 | private JdbcTemplate jdbcTemplate; 35 | 36 | @Before 37 | public void setUp() { 38 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (123, 'cn.mybatisboost1', 'mybatis-boost1')"); 39 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (456, 'cn.mybatisboost2', 'mybatis-boost2')"); 40 | jdbcTemplate.execute("insert into project (id, group_id, artifact_id) values (789, 'cn.mybatisboost3', 'mybatis-boost3')"); 41 | } 42 | 43 | @After 44 | public void tearDown() { 45 | jdbcTemplate.execute("delete from project"); 46 | } 47 | 48 | @Test 49 | public void deleteAll() throws Exception { 50 | Method deleteAll = mapper.getClass().getDeclaredMethod("deleteAll"); 51 | assertEquals(3, deleteAll.invoke(mapper)); 52 | jdbcTemplate.query("select * from project", resultSet -> { 53 | assertEquals(0, resultSet.getRow()); 54 | }); 55 | } 56 | 57 | @Test 58 | public void selectFirst() throws Exception { 59 | Method selectFirst = mapper.getClass().getDeclaredMethod("selectFirst"); 60 | assertEquals(123, (int) ((Project) selectFirst.invoke(mapper)).getId()); 61 | } 62 | 63 | @Test 64 | public void selectTop2() throws Exception { 65 | Method selectTop2 = mapper.getClass().getDeclaredMethod("selectTop2"); 66 | List list = (List) selectTop2.invoke(mapper); 67 | assertEquals(2, list.size()); 68 | assertEquals(123, (int) list.get(0).getId()); 69 | assertEquals(456, (int) list.get(1).getId()); 70 | } 71 | 72 | @Test 73 | public void selectAllOffset1Limit1() throws Exception { 74 | Method selectAllOffset1Limit1 = mapper.getClass().getDeclaredMethod("selectAllOffset1Limit1"); 75 | assertEquals(456, (int) ((Project) selectAllOffset1Limit1.invoke(mapper)).getId()); 76 | } 77 | 78 | @Test 79 | public void selectByGroupIdAndArtifactId() throws Exception { 80 | Method selectByGroupIdAndArtifactId = mapper.getClass().getDeclaredMethod("selectByGroupIdAndArtifactId", String.class, String.class); 81 | assertEquals(123, (int) ((List) selectByGroupIdAndArtifactId.invoke(mapper, "cn.mybatisboost1", "mybatis-boost1")).get(0).getId()); 82 | } 83 | 84 | @Test 85 | public void selectByGroupIdOrArtifactId() throws Exception { 86 | Method selectByGroupIdOrArtifactId = mapper.getClass().getDeclaredMethod("selectByGroupIdOrArtifactId", String.class, String.class); 87 | List list = (List) selectByGroupIdOrArtifactId.invoke(mapper, "cn.mybatisboost1", "mybatis-boost2"); 88 | assertEquals(2, list.size()); 89 | assertEquals(123, (int) list.get(0).getId()); 90 | assertEquals(456, (int) list.get(1).getId()); 91 | } 92 | 93 | @Test 94 | public void selectByNotArtifactId() throws Exception { 95 | Method selectByArtifactIdNot = mapper.getClass().getDeclaredMethod("selectByArtifactIdNot", String.class); 96 | List list = (List) selectByArtifactIdNot.invoke(mapper, "mybatis-boost1"); 97 | assertEquals(2, list.size()); 98 | assertEquals(456, (int) list.get(0).getId()); 99 | assertEquals(789, (int) list.get(1).getId()); 100 | } 101 | 102 | @Test 103 | public void selectAllOrderByGroupIdDesc() throws Exception { 104 | Method selectAllOrderByGroupIdDesc = mapper.getClass().getDeclaredMethod("selectAllOrderByGroupIdDesc"); 105 | List list = (List) selectAllOrderByGroupIdDesc.invoke(mapper); 106 | assertEquals(3, list.size()); 107 | assertEquals(789, (int) list.get(0).getId()); 108 | assertEquals(456, (int) list.get(1).getId()); 109 | assertEquals(123, (int) list.get(2).getId()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /mybatis-boost-test/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost/test 2 | spring.datasource.username=root 3 | mybatis.configuration.map-underscore-to-camel-case=true 4 | mybatisboost.name-adaptor=cn.mybatisboost.core.adaptor.SnakeCaseNameAdaptor 5 | mybatisboost.metric.enabled=true 6 | mybatisboost.show-query=true 7 | mybatisboost.show-query-with-parameters=true -------------------------------------------------------------------------------- /mybatis-boost-test/src/test/resources/project.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS test; 2 | CREATE TABLE `test`.`project` 3 | ( 4 | id int PRIMARY KEY NOT NULL AUTO_INCREMENT, 5 | uuid char(32), 6 | group_id varchar(100) NOT NULL, 7 | artifact_id varchar(100), 8 | license varchar(100), 9 | scm varchar(100), 10 | developer varchar(100), 11 | website text, 12 | _create_time timestamp NOT NULL default current_timestamp, 13 | _last_modify_time timestamp NOT NULL 14 | on update current_timestamp 15 | ); -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cn.mybatisboost 8 | mybatis-boost 9 | 2.3.3-SNAPSHOT 10 | pom 11 | 12 | MybatisBoost 13 | Boost your mybatis sql developing experience 14 | https://www.mybatisboost.cn 15 | 16 | 17 | 18 | The MIT License 19 | https://opensource.org/licenses/MIT 20 | repo 21 | 22 | 23 | 24 | 25 | https://github.com/zhang-rf/mybatis-boost 26 | scm:git:git@github.com:zhang-rf/mybatis-boost.git 27 | 28 | 29 | 30 | 31 | zhangrongfan 32 | 610384825@qq.com 33 | suniper 34 | 35 | 36 | 37 | 38 | mybatis-boost-core 39 | mybatis-boost-spring-boot-autoconfigure 40 | mybatis-boost-spring-boot-starter 41 | mybatis-boost-test 42 | 43 | 44 | 45 | 1.8 46 | 1.8 47 | 1.8 48 | 1.8 49 | UTF-8 50 | UTF-8 51 | 52 | 53 | 54 | 55 | 56 | cn.mybatisboost 57 | mybatis-boost-core 58 | ${project.version} 59 | 60 | 61 | cn.mybatisboost 62 | mybatis-boost-spring-boot-autoconfigure 63 | ${project.version} 64 | 65 | 66 | cn.mybatisboost 67 | mybatis-boost-spring-boot-starter 68 | ${project.version} 69 | 70 | 71 | 72 | javax.persistence 73 | persistence-api 74 | 1.0.2 75 | 76 | 77 | org.mybatis 78 | mybatis 79 | 3.4.6 80 | 81 | 82 | org.mybatis.spring.boot 83 | mybatis-spring-boot-starter 84 | 1.3.2 85 | 86 | 87 | org.apache.commons 88 | commons-lang3 89 | 3.7 90 | 91 | 92 | org.slf4j 93 | slf4j-api 94 | 1.7.25 95 | 96 | 97 | com.fasterxml.jackson.datatype 98 | jackson-datatype-jdk8 99 | 2.9.8 100 | 101 | 102 | com.fasterxml.jackson.datatype 103 | jackson-datatype-jsr310 104 | 2.9.8 105 | 106 | 107 | com.fasterxml.jackson.module 108 | jackson-module-parameter-names 109 | 2.9.8 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | maven-source-plugin 118 | 3.0.1 119 | 120 | 121 | attach-sources 122 | 123 | jar-no-fork 124 | 125 | 126 | 127 | 128 | 129 | maven-javadoc-plugin 130 | 3.0.1 131 | 132 | 133 | attach-javadocs 134 | 135 | jar 136 | 137 | 138 | 139 | 140 | 141 | org.eluder.coveralls 142 | coveralls-maven-plugin 143 | 4.3.0 144 | 145 | 146 | org.jacoco 147 | jacoco-maven-plugin 148 | 0.7.6.201602180812 149 | 150 | 151 | prepare-agent 152 | 153 | prepare-agent 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | ossrh 164 | 165 | 166 | ossrh 167 | https://oss.sonatype.org/content/repositories/snapshots 168 | 169 | 170 | ossrh 171 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 172 | 173 | 174 | 175 | 176 | 177 | maven-gpg-plugin 178 | 1.6 179 | 180 | 181 | sign-artifacts 182 | verify 183 | 184 | sign 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | --------------------------------------------------------------------------------