001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.db.model; 017 018import io.jboot.db.dialect.JbootMysqlDialect; 019import io.jboot.db.dialect.JbootSqlServerDialect; 020import io.jboot.utils.StrUtil; 021 022import java.io.Serializable; 023import java.util.*; 024 025/** 026 * Column 的工具类,用于方便组装sql 027 */ 028public class Columns implements Serializable { 029 030 public static final Columns EMPTY = Columns.create(); 031 032 private List<Column> cols; 033 034 /** 035 * 在很多场景下,只会根据字段来查询,如果字段值为 null 的情况,Columns 会直接忽略 null 值,此时会造成结果不准确的情况 036 * <p> 037 * 比如 : 038 * ``` 039 * public ShopInfo findFirstByAccountId(BigInteger accountId) { 040 * return findFirstByColumns(Columns.create("account_id", accountId)); 041 * } 042 * ``` 043 * 根据账户 id 来查询该账户对应的 ShopInfo,此时 如果传入 null 值,则返回了 第一个 ShopInfo,这个 ShopInfo 可能并不是该账户的。 044 * <p> 045 * 在这种场景下,我们就不应该允许用户传入 null 值进行查询,当传入 null 的时候直接抛出异常即可 。 046 * <p> 047 * 此时,我们可以使用如下代码进行查询。 048 * <p> 049 * ``` 050 * public ShopInfo findFirstByAccountId(BigInteger accountId) { 051 * return findFirstByColumns(Columns.safeMode().eq("account_id", accountId)); 052 * } 053 * ``` 054 */ 055 private boolean useSafeMode = false; 056 057 058 public static Columns create() { 059 return new Columns(); 060 } 061 062 063 public static Columns create(Column column) { 064 Columns that = new Columns(); 065 that.add(column); 066 return that; 067 068 } 069 070 public static Columns create(List<Column> columns) { 071 Columns that = new Columns(); 072 that.cols = columns; 073 return that; 074 075 } 076 077 public static Columns create(String name, Object value) { 078 return create().eq(name, value); 079 } 080 081 082 public static Columns safeMode() { 083 return new Columns().useSafeMode(); 084 } 085 086 087 public static Columns safeCreate(String name, Object value) { 088 return safeMode().eq(name, value); 089 } 090 091 092 /** 093 * add new column in Columns 094 * 095 * @param column 096 */ 097 public Columns add(Column column) { 098 099 //do not add null value column 100 if (column.hasPara() && column.getValue() == null) { 101 return this; 102 } 103 104 if (this.cols == null) { 105 this.cols = new LinkedList<>(); 106 } 107 108 this.cols.add(column); 109 return this; 110 } 111 112 113 /** 114 * add new column in Columns 115 * 116 * @param column 117 */ 118 public Columns addToFirst(Column column) { 119 120 //do not add null value column 121 if (column.hasPara() && column.getValue() == null) { 122 return this; 123 } 124 125 if (this.cols == null) { 126 this.cols = new LinkedList<>(); 127 } 128 129 this.cols.add(0, column); 130 return this; 131 } 132 133 134 /** 135 * add Columns 136 * 137 * @param columns 138 * @return 139 */ 140 public Columns add(Columns columns) { 141 return append(columns); 142 } 143 144 145 /** 146 * add Columns To First 147 * 148 * @param columns 149 * @return 150 */ 151 public Columns addToFirst(Columns columns) { 152 if (columns != null && !columns.isEmpty()) { 153 for (Column column : columns.getList()) { 154 addToFirst(column); 155 } 156 } 157 return this; 158 } 159 160 161 /** 162 * equals 163 * 164 * @param name 165 * @param value 166 * @return 167 */ 168 public Columns eq(String name, Object value) { 169 Util.checkNullParas(this, name, value); 170 return add(Column.create(name, value)); 171 } 172 173 /** 174 * not equals != 175 * 176 * @param name 177 * @param value 178 * @return 179 */ 180 public Columns ne(String name, Object value) { 181 Util.checkNullParas(this, name, value); 182 return add(Column.create(name, value, Column.LOGIC_NOT_EQUALS)); 183 } 184 185 186 /** 187 * like 188 * 189 * @param name 190 * @param value 191 * @return 192 */ 193 public Columns like(String name, Object value) { 194 Util.checkNullParas(this, name, value); 195 return add(Column.create(name, value, Column.LOGIC_LIKE)); 196 } 197 198 /** 199 * 自动添加两边 % 的like 200 * 201 * @param name 202 * @param value 203 * @return 204 */ 205 public Columns likeAppendPercent(String name, Object value) { 206 Util.checkNullParas(this, name, value); 207 if (value == null || (value instanceof String && StrUtil.isBlank((String) value))) { 208 return this; 209 } 210 return add(Column.create(name, "%" + value + "%", Column.LOGIC_LIKE)); 211 } 212 213 /** 214 * 大于 great than 215 * 216 * @param name 217 * @param value 218 * @return 219 */ 220 public Columns gt(String name, Object value) { 221 Util.checkNullParas(this, name, value); 222 return add(Column.create(name, value, Column.LOGIC_GT)); 223 } 224 225 /** 226 * 大于等于 great or equal 227 * 228 * @param name 229 * @param value 230 * @return 231 */ 232 public Columns ge(String name, Object value) { 233 Util.checkNullParas(this, name, value); 234 return add(Column.create(name, value, Column.LOGIC_GE)); 235 } 236 237 /** 238 * 小于 less than 239 * 240 * @param name 241 * @param value 242 * @return 243 */ 244 public Columns lt(String name, Object value) { 245 Util.checkNullParas(this, name, value); 246 return add(Column.create(name, value, Column.LOGIC_LT)); 247 } 248 249 /** 250 * 小于等于 less or equal 251 * 252 * @param name 253 * @param value 254 * @return 255 */ 256 public Columns le(String name, Object value) { 257 Util.checkNullParas(this, name, value); 258 return add(Column.create(name, value, Column.LOGIC_LE)); 259 } 260 261 262 /** 263 * IS NULL 264 * 265 * @param name 266 * @return 267 */ 268 public Columns isNull(String name) { 269 return add(Column.create(name, null, Column.LOGIC_IS_NULL)); 270 } 271 272 273 /** 274 * @param name 275 * @param condition 276 * @return 277 */ 278 public Columns isNullIf(String name, Boolean condition) { 279 if (condition != null && condition) { 280 add(Column.create(name, null, Column.LOGIC_IS_NULL)); 281 } 282 return this; 283 } 284 285 286 /** 287 * IS NOT NULL 288 * 289 * @param name 290 * @return 291 */ 292 public Columns isNotNull(String name) { 293 return add(Column.create(name, null, Column.LOGIC_IS_NOT_NULL)); 294 } 295 296 297 /** 298 * IS NOT NULL 299 * 300 * @param name 301 * @param condition 302 * @return 303 */ 304 public Columns isNotNullIf(String name, Boolean condition) { 305 if (condition != null && condition) { 306 add(Column.create(name, null, Column.LOGIC_IS_NOT_NULL)); 307 } 308 return this; 309 } 310 311 312 /** 313 * in arrays 314 * 315 * @param name 316 * @param arrays 317 * @return 318 */ 319 public Columns in(String name, Object... arrays) { 320 Util.checkNullParas(this, name, arrays); 321 322 //忽略 columns.in("name", null) 的情况 323 if (arrays != null && arrays.length == 1 && arrays[0] == null) { 324 return this; 325 } 326 return add(Column.create(name, arrays, Column.LOGIC_IN)); 327 } 328 329 330 /** 331 * in Collection 332 * 333 * @param name 334 * @param collection 335 * @return 336 */ 337 public Columns in(String name, Collection<?> collection) { 338 Util.checkNullParas(this, collection); 339 if (collection != null && !collection.isEmpty()) { 340 in(name, collection.toArray()); 341 } 342 return this; 343 } 344 345 /** 346 * not int arrays 347 * 348 * @param name 349 * @param arrays 350 * @return 351 */ 352 public Columns notIn(String name, Object... arrays) { 353 Util.checkNullParas(this, name, arrays); 354 355 //忽略 columns.notIn("name", null) 的情况 356 if (arrays != null && arrays.length == 1 && arrays[0] == null) { 357 return this; 358 } 359 return add(Column.create(name, arrays, Column.LOGIC_NOT_IN)); 360 } 361 362 363 /** 364 * not in Collection 365 * 366 * @param name 367 * @param collection 368 * @return 369 */ 370 public Columns notIn(String name, Collection<?> collection) { 371 Util.checkNullParas(this, collection); 372 if (collection != null && !collection.isEmpty()) { 373 notIn(name, collection.toArray()); 374 } 375 return this; 376 } 377 378 379 /** 380 * between 381 * 382 * @param name 383 * @param start 384 * @param end 385 * @return 386 */ 387 public Columns between(String name, Object start, Object end) { 388 Util.checkNullParas(this, name, start, end); 389 return add(Column.create(name, new Object[]{start, end}, Column.LOGIC_BETWEEN)); 390 } 391 392 /** 393 * not between 394 * 395 * @param name 396 * @param start 397 * @param end 398 * @return 399 */ 400 public Columns notBetween(String name, Object start, Object end) { 401 Util.checkNullParas(this, name, start, end); 402 return add(Column.create(name, new Object[]{start, end}, Column.LOGIC_NOT_BETWEEN)); 403 } 404 405 406 /** 407 * group 408 * 409 * @param columns 410 * @return 411 */ 412 public Columns group(Columns columns) { 413 if (columns == this) { 414 throw new IllegalArgumentException("Columns.group(...) need a new Columns"); 415 } 416 if (!columns.isEmpty()) { 417 add(new Group(columns)); 418 } 419 return this; 420 } 421 422 423 /** 424 * @param columns 425 * @param condition 426 * @return 427 */ 428 public Columns groupIf(Columns columns, Boolean condition) { 429 if (columns == this) { 430 throw new IllegalArgumentException("Columns.group(...) need a new Columns"); 431 } 432 if (condition != null && condition && !columns.isEmpty()) { 433 add(new Group(columns)); 434 } 435 return this; 436 } 437 438 /** 439 * @param name 440 * @return 441 */ 442 public Columns groupBy(String name) { 443 add(new GroupBy(name)); 444 return this; 445 } 446 447 /** 448 * @param name 449 * @return 450 */ 451 public Columns having(String name) { 452 add(new Having(name)); 453 return this; 454 } 455 456 457 /** 458 * @param sql 459 * @return 460 */ 461 public Columns having(String sql, Object... paras) { 462 add(new Having(sql, paras)); 463 return this; 464 } 465 466 467 /** 468 * @param columns 469 * @return 470 */ 471 public Columns having(Columns columns) { 472 add(new Having(columns)); 473 return this; 474 } 475 476 477 /** 478 * customize string sql 479 * 480 * @param sql 481 * @return 482 */ 483 public Columns sqlPart(String sql) { 484 if (StrUtil.isNotBlank(sql)) { 485 add(new SqlPart(sql)); 486 } 487 return this; 488 } 489 490 /** 491 * customize string sql 492 * 493 * @param sql 494 * @param paras 495 * @return 496 */ 497 public Columns sqlPart(String sql, Object... paras) { 498 Util.checkNullParas(this, paras); 499 if (StrUtil.isNotBlank(sql)) { 500 add(new SqlPart(sql, paras)); 501 } 502 return this; 503 } 504 505 /** 506 * customize string sql 507 * 508 * @param sql 509 * @param condition 510 * @return 511 */ 512 public Columns sqlPartIf(String sql, Boolean condition) { 513 if (condition != null && condition && StrUtil.isNotBlank(sql)) { 514 add(new SqlPart(sql)); 515 } 516 return this; 517 } 518 519 /** 520 * customize string sql 521 * 522 * @param sql 523 * @param condition 524 * @param paras 525 * @return 526 */ 527 public Columns sqlPartIf(String sql, Boolean condition, Object... paras) { 528 Util.checkNullParas(this, paras); 529 if (condition != null && condition && StrUtil.isNotBlank(sql)) { 530 add(new SqlPart(sql, paras)); 531 } 532 return this; 533 } 534 535 /** 536 * customize string sql 537 * 538 * @param sql 539 * @return 540 */ 541 public Columns sqlPartWithoutLink(String sql) { 542 if (StrUtil.isNotBlank(sql)) { 543 add(new SqlPart(sql, true)); 544 } 545 return this; 546 } 547 548 /** 549 * customize string sql 550 * 551 * @param sql 552 * @param paras 553 * @return 554 */ 555 public Columns sqlPartWithoutLink(String sql, Object... paras) { 556 Util.checkNullParas(this, paras); 557 if (StrUtil.isNotBlank(sql)) { 558 add(new SqlPart(sql, paras, true)); 559 } 560 return this; 561 } 562 563 /** 564 * customize string sql 565 * 566 * @param sql 567 * @param condition 568 * @return 569 */ 570 public Columns sqlPartWithoutLinkIf(String sql, Boolean condition) { 571 if (condition != null && condition && StrUtil.isNotBlank(sql)) { 572 add(new SqlPart(sql, true)); 573 } 574 return this; 575 } 576 577 /** 578 * customize string sql 579 * 580 * @param sql 581 * @param condition 582 * @param paras 583 * @return 584 */ 585 public Columns sqlPartWithoutLinkIf(String sql, Boolean condition, Object... paras) { 586 Util.checkNullParas(this, paras); 587 if (condition != null && condition && StrUtil.isNotBlank(sql)) { 588 add(new SqlPart(sql, paras, true)); 589 } 590 return this; 591 } 592 593 594 public Columns or() { 595 add(new Or()); 596 return this; 597 } 598 599 600 public Columns ors(String name, String logic, Object... values) { 601 Util.checkNullParas(this, name, values); 602 603 Columns columns = new Columns(); 604 for (int i = 0; i < values.length; i++) { 605 Object value = values[i]; 606 if (value != null) { 607 columns.add(Column.create(name, value, logic)); 608 if (i != values.length - 1) { 609 columns.add(new Or()); 610 } 611 } 612 } 613 614 return group(columns); 615 } 616 617 618 public Columns orEqs(String name, Object... values) { 619 return ors(name, Column.LOGIC_EQUALS, values); 620 } 621 622 623 /** 624 * 追加 新的 columns 625 * 626 * @param columns 627 * @return 628 */ 629 public Columns append(Columns columns) { 630 if (columns != null && !columns.isEmpty()) { 631 for (Column column : columns.getList()) { 632 add(column); 633 } 634 } 635 return this; 636 } 637 638 639 /** 640 * 追加 新的 columns 641 * 642 * @param columns 643 * @return 644 */ 645 public Columns appendIf(Columns columns, Boolean condition) { 646 if (condition != null && condition) { 647 append(columns); 648 } 649 return this; 650 } 651 652 public boolean isUseSafeMode() { 653 return useSafeMode; 654 } 655 656 public Columns useSafeMode() { 657 this.useSafeMode = true; 658 return this; 659 } 660 661 public Columns unUseSafeMode() { 662 this.useSafeMode = false; 663 return this; 664 } 665 666 public boolean isEmpty() { 667 return cols == null || cols.isEmpty(); 668 } 669 670 671 public Object[] getValueArray() { 672 return Util.getValueArray(cols); 673 } 674 675 676 public List<Column> getList() { 677 return cols; 678 } 679 680 public boolean containsName(String name) { 681 if (isEmpty()) { 682 return false; 683 } 684 685 for (Column col : cols) { 686 if (col.getName() != null && col.getName().equals(name)) { 687 return true; 688 } 689 } 690 return false; 691 } 692 693 694 public String getCacheKey() { 695 if (isEmpty()) { 696 return null; 697 } 698 699 List<Column> columns = new ArrayList<>(cols); 700 StringBuilder s = new StringBuilder(); 701 buildCacheKey(s, columns); 702 703 return s.toString(); 704 } 705 706 private static final char SQL_CACHE_SEPARATOR = '-'; 707 708 private void buildCacheKey(StringBuilder s, List<Column> columns) { 709 for (int i = 0; i < columns.size(); i++) { 710 711 Column column = columns.get(i); 712 713 if (column instanceof Or) { 714 Column before = i > 0 ? columns.get(i - 1) : null; 715 if (before != null && !(before instanceof Or)) { 716 s.append("or").append(SQL_CACHE_SEPARATOR); 717 } 718 } else if (column instanceof Group) { 719 s.append('('); 720 buildCacheKey(s, ((Group) column).getColumns().getList()); 721 s.append(')').append(SQL_CACHE_SEPARATOR); 722 } else if (column instanceof SqlPart) { 723 String sqlpart = ((SqlPart) column).getSql(); 724 Object value = column.getValue(); 725 if (value != null) { 726 if (value.getClass().isArray()) { 727 Object[] values = (Object[]) value; 728 for (Object v : values) { 729 sqlpart = Util.replaceSqlPara(sqlpart, v); 730 } 731 } else { 732 sqlpart = Util.replaceSqlPara(sqlpart, value); 733 } 734 } 735 s.append(Util.deleteWhitespace(sqlpart)).append(SQL_CACHE_SEPARATOR); 736 } else { 737 s.append(column.getName()) 738 .append(SQL_CACHE_SEPARATOR) 739 .append(getLogicString(column.getLogic())) 740 .append(SQL_CACHE_SEPARATOR); 741 Object value = column.getValue(); 742 if (value != null) { 743 if (value.getClass().isArray()) { 744 s.append(Util.array2String((Object[]) value)); 745 } else { 746 s.append(column.getValue()); 747 } 748 s.append(SQL_CACHE_SEPARATOR); 749 } 750 } 751 } 752 s.deleteCharAt(s.length() - 1); 753 } 754 755 756 /** 757 * @param logic 758 * @return 759 */ 760 private static String getLogicString(String logic) { 761 switch (logic) { 762 case Column.LOGIC_LIKE: 763 return "lk"; 764 case Column.LOGIC_GT: 765 return "gt"; 766 case Column.LOGIC_GE: 767 return "ge"; 768 case Column.LOGIC_LT: 769 return "lt"; 770 case Column.LOGIC_LE: 771 return "le"; 772 case Column.LOGIC_EQUALS: 773 return "eq"; 774 case Column.LOGIC_NOT_EQUALS: 775 return "neq"; 776 case Column.LOGIC_IS_NULL: 777 return "isn"; 778 case Column.LOGIC_IS_NOT_NULL: 779 return "nn"; 780 case Column.LOGIC_IN: 781 return "in"; 782 case Column.LOGIC_NOT_IN: 783 return "nin"; 784 case Column.LOGIC_BETWEEN: 785 return "bt"; 786 case Column.LOGIC_NOT_BETWEEN: 787 return "nbt"; 788 default: 789 return ""; 790 } 791 } 792 793 794 /** 795 * 输出 where 后面的 sql 部分,风格是 mysql 的风格 SQL 796 * 797 * @return 798 */ 799 public String toWherePartSql() { 800 return toWherePartSql('`', false); 801 } 802 803 804 /** 805 * 输出 where 后面的 sql 部分,风格是 mysql 的风格 SQL 806 * 807 * @param withWhereKeyword 是否带上 where 关键字 808 * @return 809 */ 810 public String toWherePartSql(boolean withWhereKeyword) { 811 return toWherePartSql('`', withWhereKeyword); 812 } 813 814 815 /** 816 * 输出 where 部分的 sql 817 * 818 * @param separator 字段分隔符 819 * @param withWhereKeyword 是否带上 "where 关键字" 820 * @return 821 */ 822 public String toWherePartSql(char separator, boolean withWhereKeyword) { 823 StringBuilder sb = new StringBuilder(); 824 SqlBuilder.buildWhereSql(sb, getList(), separator, withWhereKeyword); 825 return sb.toString(); 826 } 827 828 829 @Override 830 public String toString() { 831 String cacheKey = getCacheKey(); 832 return StrUtil.isNotBlank(cacheKey) ? cacheKey : "{}"; 833 } 834 835 836 public static void main(String[] args) { 837 838 Columns columns = Columns.create().useSafeMode().or().or().or().eq("aa", "bb").or().or().or().notIn("aaa", 123, 456, 789).like("titile", "a"); 839 columns.group(Columns.create().or().or().sqlPart("aa=bb")); 840 columns.group(Columns.create("aa", "bb").eq("cc", "dd") 841 .group(Columns.create("aa", "bb").eq("cc", "dd")) 842 .group(Columns.create("aa", "bb").eq("cc", "dd").group(Columns.create("aa", "bb").eq("cc", "dd")))); 843 844 columns.ge("age", 10); 845 columns.or(); 846 columns.or(); 847 columns.or(); 848 columns.or(); 849 columns.sqlPart("user.id != ? and xxx= ?", 1, "abc2"); 850 columns.sqlPart("user.id != ? and xxx= ?", 1, "abc2"); 851 852 columns.or(); 853 columns.or(); 854 columns.or(); 855 columns.group(Columns.create().likeAppendPercent("name", "null").or().or().or() 856 .eq("age", "18").eq("ddd", null)); 857 858 columns.or(); 859 columns.or(); 860 861 columns.group(Columns.create().or().or().sqlPart("name = ?", "zhangsan")); 862 columns.or(); 863 columns.or(); 864 columns.or(); 865 866 columns.between("name", "123", "1233"); 867 columns.between("name", "123", "1233"); 868 columns.or(); 869 870// columns.sqlPartWithoutLink("group by xxx"); 871 columns.groupBy("aaa").having(Columns.create("aaa", "bbb").ge("ccc", 111)); 872// columns.or(); 873// columns.or(); 874// columns.or(); 875 876 System.out.println(columns.getCacheKey()); 877 System.out.println(Arrays.toString(columns.getValueArray())); 878 System.out.println(columns.toMysqlSql()); 879 System.out.println("-----------"); 880 System.out.println(columns.toWherePartSql('"', true)); 881 882 } 883 884 /** 885 * 这个只是用于调试 886 * 887 * @return 888 */ 889 private String toMysqlSql() { 890 JbootMysqlDialect dialect = new JbootMysqlDialect(); 891 return dialect.forFindByColumns(null, null, "table", "*", getList(), null, null); 892 } 893 894 895 /** 896 * 这个只是用于调试 897 * 898 * @return 899 */ 900 private String toSqlServerSql() { 901 JbootSqlServerDialect dialect = new JbootSqlServerDialect(); 902 return dialect.forFindByColumns(null, null, "table", "*", getList(), null, null); 903 } 904 905}