├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── ormkids │ ├── Column.java │ ├── Context.java │ ├── DB.java │ ├── E.java │ ├── GridDB.java │ ├── IEntity.java │ ├── IGridable.java │ ├── Index.java │ ├── KidsException.java │ ├── Meta.java │ ├── Option.java │ ├── Q.java │ ├── Utils.java │ └── demo │ ├── BookShelf.java │ ├── DemoComplexQuery.java │ ├── DemoCompoundPk.java │ ├── DemoDB.java │ ├── DemoEvent.java │ ├── DemoPartitioning.java │ ├── DemoSharding.java │ ├── DemoSimplePk.java │ ├── Exam.java │ ├── GridDemoDB.java │ ├── Member.java │ └── User.java └── resources └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .project 3 | .classpath 4 | *.class 5 | target 6 | deploy 7 | dependency-reduced-pom.xml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OrmKids 2 | == 3 | 支持分库分表的MySQL单表ORM框架,暂用于学习,后续会在生产环境进行检验 4 | 5 | ``` 6 | 7 | com.github.pyloque 8 | ormkids 9 | 0.0.4 10 | 11 | ``` 12 | 13 | 功能特性 14 | -- 15 | 1. 代码简洁,没有任何依赖项 16 | 2. 易于使用,无须复杂的配置 17 | 3. 提供自动创建表功能 18 | 4. 支持分库又分表,可以只分库,也可以只分表 19 | 5. 支持groupby/having/order by 20 | 6. 支持原生SQL 21 | 7. 支持事件回调,可用于服务跟踪调试和动态sql改写 22 | 23 | 不支持多表关联 24 | -- 25 | 1. 多表比较复杂,实现成本高,学习成本也高,容易出错 26 | 2. 常用的多表的操作一般都可以使用多条单表操作组合实现 27 | 3. 在分库分表的场合,很少使用多表操作 28 | 4. 不使用外键,专注于sql逻辑 29 | 30 | db.withinTx 31 | -- 32 | 对于复杂的多表查询和批量数据处理,可以使用该方法。 33 | 用户可以获得原生的jdbc链接,通过编写jdbc代码来实现。 34 | 35 | Q 36 | -- 37 | 用户可以使用Q对象构建复杂的SQL查询 38 | 39 | 40 | 其它数据库支持 41 | -- 42 | 暂时没有 43 | 44 | 45 | 实体接口 46 | -- 47 | 48 | ```java 49 | /** 50 | * 所有的实体类必须实现该接口 51 | */ 52 | public interface IEntity { 53 | 54 | /** 55 | * 表名 56 | * @return 57 | */ 58 | String table(); 59 | 60 | /** 61 | * 分表必须覆盖此方法 62 | * @return 63 | */ 64 | default String suffix() { 65 | return null; 66 | } 67 | 68 | default String tableWithSuffix() { 69 | return tableWith(suffix()); 70 | } 71 | 72 | default String tableWith(String suffix) { 73 | return Utils.tableWithSuffix(table(), suffix); 74 | } 75 | 76 | /** 77 | * 定义表的物理结构属性如engine=innodb,用于动态创建表 78 | * @return 79 | */ 80 | TableOptions options(); 81 | 82 | /** 83 | * 定义表的主键和索引信息,用于动态创建表 84 | * @return 85 | */ 86 | TableIndices indices(); 87 | 88 | } 89 | ``` 90 | 91 | 单表单主键 92 | -- 93 | 94 | ```java 95 | @Table 96 | public class User implements IEntity { 97 | 98 | @Column(name = "id", type = "int", autoincrement = true, nullable = false) 99 | private Integer id; 100 | @Column(name = "name", type = "varchar(255)", nullable = false) 101 | private String name; 102 | @Column(name = "nick", type = "varchar(255)", nullable = false) 103 | private String nick; 104 | @Column(name = "passwd", type = "varchar(255)") 105 | private String passwd; 106 | @Column(name = "created_at", type = "datetime", nullable = false, defaultValue = "now()") 107 | private Date createdAt; 108 | 109 | public User() { 110 | } 111 | 112 | public User(String name, String nick, String passwd) { 113 | this.name = name; 114 | this.nick = nick; 115 | this.passwd = passwd; 116 | } 117 | 118 | public Integer getId() { 119 | return id; 120 | } 121 | 122 | public String getName() { 123 | return name; 124 | } 125 | 126 | public String getNick() { 127 | return nick; 128 | } 129 | 130 | public String getPasswd() { 131 | return passwd; 132 | } 133 | 134 | public Date getCreatedAt() { 135 | return createdAt; 136 | } 137 | 138 | @Override 139 | public TableOptions options() { 140 | return new TableOptions().option("engine", "innodb"); 141 | } 142 | 143 | @Override 144 | public TableIndices indices() { 145 | return new TableIndices().primary("id").unique("name"); 146 | } 147 | 148 | @Override 149 | public String table() { 150 | return "user"; 151 | } 152 | 153 | } 154 | 155 | ``` 156 | 157 | 单表复合主键 158 | -- 159 | 160 | ```java 161 | @Table 162 | public class Member implements IEntity { 163 | 164 | @Column(name = "user_id", type = "int", nullable = false) 165 | private Integer userId; 166 | @Column(name = "group_id", type = "int", nullable = false) 167 | private Integer groupId; 168 | @Column(name = "title", type = "varchar(255)") 169 | private String title; 170 | @Column(name = "created_at", type = "datetime", nullable = false, defaultValue = "now()") 171 | private Date createdAt; 172 | 173 | public Member() { 174 | } 175 | 176 | public Member(Integer userId, Integer groupId, String title, Date createdAt) { 177 | this.userId = userId; 178 | this.groupId = groupId; 179 | this.title = title; 180 | this.createdAt = createdAt; 181 | } 182 | 183 | public Integer getUserId() { 184 | return userId; 185 | } 186 | 187 | public Integer getGroupId() { 188 | return groupId; 189 | } 190 | 191 | public String getTitle() { 192 | return title; 193 | } 194 | 195 | public Date getCreatedAt() { 196 | return createdAt; 197 | } 198 | 199 | @Override 200 | public TableOptions options() { 201 | return new TableOptions().option("engine", "innodb"); 202 | } 203 | 204 | @Override 205 | public TableIndices indices() { 206 | return new TableIndices().primary("user_id", "group_id"); 207 | } 208 | 209 | @Override 210 | public String table() { 211 | return "member"; 212 | } 213 | 214 | } 215 | ``` 216 | 217 | 分库接口 218 | -- 219 | 220 | ```java 221 | public interface IGridable { 222 | 223 | /** 224 | * 根据实体对象选择分库索引 225 | */ 226 | int select(int dbs, T t); 227 | 228 | /** 229 | * 根据特定参数选择分库索引 230 | */ 231 | int select(int dbs, Object... params); 232 | 233 | } 234 | ``` 235 | 236 | 分库分表 237 | -- 238 | 239 | ```java 240 | @Table 241 | public class BookShelf implements IEntity { 242 | 243 | public final static int PARTITIONS = 4; 244 | 245 | @Column(name = "user_id", type = "varchar(255)", nullable = false) 246 | private String userId; 247 | @Column(name = "book_id", type = "varchar(255)", nullable = false) 248 | private String bookId; 249 | @Column(name = "comment", type = "varchar(255)") 250 | private String comment; 251 | @Column(name = "created_at", type = "datetime", nullable = false, defaultValue = "now()") 252 | private Date createdAt; 253 | 254 | public BookShelf() { 255 | } 256 | 257 | public BookShelf(String userId, String bookId, String comment, Date createdAt) { 258 | this.userId = userId; 259 | this.bookId = bookId; 260 | this.comment = comment; 261 | this.createdAt = createdAt; 262 | } 263 | 264 | public String getUserId() { 265 | return userId; 266 | } 267 | 268 | public String getBookId() { 269 | return bookId; 270 | } 271 | 272 | public void setComment(String comment) { 273 | this.comment = comment; 274 | } 275 | 276 | public String getComment() { 277 | return comment; 278 | } 279 | 280 | public Date getCreatedAt() { 281 | return createdAt; 282 | } 283 | 284 | @Override 285 | public String table() { 286 | return "book_shelf"; 287 | } 288 | 289 | @Override 290 | public TableOptions options() { 291 | return new TableOptions().option("engine", "innodb"); 292 | } 293 | 294 | @Override 295 | public TableIndices indices() { 296 | return new TableIndices().primary("user_id", "book_id"); 297 | } 298 | 299 | /* 300 | * 分表策略 301 | */ 302 | @Override 303 | public String suffix() { 304 | var crc32 = new CRC32(); 305 | crc32.update(userId.getBytes(Utils.UTF8)); 306 | return String.valueOf(Math.abs(crc32.getValue()) % PARTITIONS); 307 | } 308 | 309 | /** 310 | * 分库策略 311 | */ 312 | public static class GridStrategy implements IGridable { 313 | 314 | @Override 315 | public int select(int dbs, BookShelf t) { 316 | return Math.abs(t.getUserId().hashCode()) % dbs; 317 | } 318 | 319 | @Override 320 | public int select(int dbs, Object... params) { 321 | String userId = (String) params[0]; 322 | return Math.abs(userId.hashCode()) % dbs; 323 | } 324 | 325 | } 326 | 327 | } 328 | ``` 329 | 330 | 定义单个数据库 331 | -- 332 | 333 | ```java 334 | public class DemoDB extends DB { 335 | 336 | private DataSource ds; 337 | 338 | public DemoDB(String name, String uri) { 339 | this(name, new HashMap<>(), uri); 340 | } 341 | 342 | public DemoDB(String name, Map, Meta> metas, String uri) { 343 | super(name, metas); 344 | var ds = new MysqlConnectionPoolDataSource(); // 连接池 345 | ds.setUrl(uri); 346 | this.ds = ds; 347 | } 348 | 349 | @Override 350 | protected Connection conn() { // 获取链接 351 | try { 352 | return ds.getConnection(); 353 | } catch (SQLException e) { 354 | throw new KidsException(e); 355 | } 356 | } 357 | 358 | } 359 | ``` 360 | 361 | 定义网格数据库——分库 362 | -- 363 | 364 | ```java 365 | public class GridDemoDB extends GridDB { 366 | 367 | /** 368 | * 传进来多个DB对象 369 | */ 370 | public GridDemoDB(DemoDB[] dbs) { 371 | super(dbs); 372 | this.registerGridables(); 373 | } 374 | 375 | /* 376 | * 注册实体类的分库策略 377 | */ 378 | @Override 379 | public void registerGridables() { 380 | this.gridWith(BookShelf.class, new BookShelf.GridStrategy()); 381 | } 382 | 383 | } 384 | 385 | ``` 386 | 387 | 单表单主键增删改查 388 | -- 389 | 390 | ```java 391 | public class DemoSimplePk { 392 | 393 | private final static String URI = "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"; 394 | 395 | public static void main(String[] args) { 396 | var db = new DemoDB("demo", URI); 397 | try { 398 | db.create(User.class); // 创建表 399 | var user = new User("test1", "nick1", "passwd1"); 400 | db.insert(user); // 插入 401 | System.out.println(user.getId()); 402 | user = db.get(User.class, user.getId()); // 主键查询 403 | System.out.printf("%s %s %s %s %s\n", user.getId(), user.getName(), user.getNick(), user.getPasswd(), 404 | user.getCreatedAt()); 405 | user = new User("test2", "nick2", "passwd2"); 406 | db.insert(user); // 再插入 407 | var count = db.count(User.class); // 查询总行数 408 | System.out.println(count); 409 | var users = db.find(User.class); // 列出所有行 410 | System.out.println(users.size()); 411 | for (var u : users) { 412 | System.out.printf("%s %s %s %s %s\n", u.getId(), u.getName(), u.getNick(), u.getPasswd(), 413 | u.getCreatedAt()); 414 | } 415 | users = db.find(User.class, Q.eq_("nick"), "nick2"); // 条件查询 416 | System.out.println(users.size()); 417 | var setters = new HashMap(); 418 | setters.put("passwd", "whatever"); 419 | db.update(User.class, setters, 2); // 修改 420 | users = db.find(User.class); // 再列出所有行 421 | System.out.println(users.size()); 422 | for (var u : users) { 423 | System.out.printf("%s %s %s %s %s\n", u.getId(), u.getName(), u.getNick(), u.getPasswd(), 424 | u.getCreatedAt()); 425 | } 426 | db.delete(User.class, 1); // 删除 427 | db.delete(User.class, 2); // 再删除 428 | count = db.count(User.class); // 统计所有行 429 | System.out.println(count); 430 | } finally { 431 | db.drop(User.class); // 删除表 432 | } 433 | } 434 | 435 | } 436 | ``` 437 | 438 | 单表复合主键增删改查 439 | -- 440 | 441 | ```java 442 | public class DemoCompoundPk { 443 | private final static String URI = "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"; 444 | 445 | public static void main(String[] args) { 446 | var db = new DemoDB("demo", URI); 447 | try { 448 | db.create(Member.class); // 建表 449 | var member = new Member(1, 2, "boss", null); 450 | db.insert(member); // 插入 451 | member = db.get(Member.class, 1, 2); // 主键查询 452 | System.out.println(member.getTitle()); 453 | member = new Member(2, 2, "manager", new Date()); 454 | db.insert(member); // 再插入 455 | var count = db.count(Member.class); // 获取总行数 456 | System.out.println(count); 457 | var members = db.find(Member.class); // 获取全部行 458 | for (var m : members) { 459 | System.out.printf("%d %d %s %s\n", m.getUserId(), m.getGroupId(), m.getTitle(), m.getCreatedAt()); 460 | } 461 | member = new Member(2, 3, "manager", new Date()); 462 | db.insert(member); // 再插入 463 | members = db.find(Member.class, Q.eq_("group_id"), 2); // 条件查询 464 | for (var m : members) { 465 | System.out.printf("%d %d %s %s\n", m.getUserId(), m.getGroupId(), m.getTitle(), m.getCreatedAt()); 466 | } 467 | var setters = new HashMap(); 468 | setters.put("title", "employee"); 469 | db.update(Member.class, setters, 2, 3); // 修改 470 | member = db.get(Member.class, 2, 3); // 主键查询 471 | System.out.println(member.getTitle()); 472 | db.delete(Member.class, 1, 2); // 删除 473 | db.delete(Member.class, 2, 2); // 删除 474 | db.delete(Member.class, 2, 3); // 删除 475 | count = db.count(Member.class); // 再获取总行数 476 | System.out.println(count); 477 | } finally { 478 | db.drop(Member.class); // 删表 479 | } 480 | } 481 | 482 | } 483 | ``` 484 | 485 | 复杂查询 486 | -- 487 | 488 | ```java 489 | public class DemoComplexQuery { 490 | private final static String URI = "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"; 491 | 492 | public static void main(String[] args) { 493 | var db = new DemoDB("demo", URI); 494 | try { 495 | db.create(Exam.class); // 建表 496 | var random = new Random(); 497 | for (var i = 0; i < 100; i++) { 498 | var userId = Math.abs(random.nextLong()); 499 | var exam = new Exam(userId, random.nextInt(100), random.nextInt(100), random.nextInt(100), 500 | random.nextInt(100), random.nextInt(100), random.nextInt(100)); 501 | db.insert(exam); // 插入 502 | } 503 | System.out.println(db.count(Exam.class)); // 查询总行数 504 | // math >= 50 505 | var exams = db.find(Exam.class, Q.ge_("math"), 50); // 条件查询 506 | System.out.println(exams.size()); 507 | var count = db.count(Exam.class, Q.ge_("math"), 50); // 条件总行数 508 | System.out.println(count); 509 | // math > 50 & english >= 50 510 | exams = db.find(Exam.class, Q.and(Q.gt_("math"), Q.ge_("english")), 50, 50); // 条件查询 511 | System.out.println(exams.size()); 512 | count = db.count(Exam.class, Q.and(Q.gt_("math"), Q.ge_("english")), 50, 50); // 条件总行数 513 | System.out.println(count); 514 | // math > 50 || english >= 50 515 | exams = db.find(Exam.class, Q.or(Q.gt_("math"), Q.ge_("english")), 50, 50); // 条件查询 516 | System.out.println(exams.size()); 517 | count = db.count(Exam.class, Q.or(Q.gt_("math"), Q.ge_("english")), 50, 50); // 条件总行数 518 | System.out.println(count); 519 | // math > 50 && (english >= 50 || chinese > 60) 520 | exams = db.find(Exam.class, Q.and(Q.gt_("math"), Q.or(Q.ge_("english"), Q.gt_("chinese"))), 50, 50, 60); // 条件查询 521 | System.out.println(exams.size()); 522 | count = db.count(Exam.class, Q.and(Q.gt_("math"), Q.or(Q.ge_("english"), Q.gt_("chinese"))), 50, 50, 60); // 条件总行数 523 | System.out.println(count); 524 | // math > 50 || physics between 60 and 80 || chemistry < 60 525 | exams = db.find(Exam.class, Q.or(Q.gt_("math"), Q.between_("physics"), Q.lt_("chemistry")), 50, 60, 80, 60); // 条件查询 526 | System.out.println(exams.size()); 527 | count = db.count(Exam.class, Q.or(Q.gt_("math"), Q.between_("physics"), Q.lt_("chemistry")), 50, 60, 80, 528 | 60); // 条件总行数 529 | System.out.println(count); 530 | // group by math / 10 531 | var q = Q.select().field("(math div 10) * 10 as mathx", "count(1)").table("exam").groupBy("mathx") 532 | .having(Q.gt_("count(1)")).orderBy("count(1)", "desc"); // 复杂sql构造 533 | var rank = new LinkedHashMap(); 534 | db.any(Exam.class, q, stmt -> { // 原生sql查询 535 | stmt.setInt(1, 0); 536 | ResultSet rs = stmt.executeQuery(); 537 | while (rs.next()) { 538 | rank.put(rs.getInt(1), rs.getInt(2)); 539 | } 540 | return rs; 541 | }); 542 | rank.forEach((mathx, c) -> { 543 | System.out.printf("[%d-%d) = %d\n", mathx, mathx + 10, c); 544 | }); 545 | } finally { 546 | db.drop(Exam.class); 547 | } 548 | } 549 | 550 | } 551 | ``` 552 | 553 | 分表 554 | -- 555 | 556 | ```java 557 | public class DemoPartitioning { 558 | private final static String URI = "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"; 559 | 560 | public static void main(String[] args) { 561 | var db = new DemoDB("demo", URI); 562 | try { 563 | for (int i = 0; i < BookShelf.PARTITIONS; i++) { 564 | db.create(BookShelf.class, String.valueOf(i)); // 创建所有分表 565 | } 566 | var bss = new ArrayList(); 567 | for (int i = 0; i < 100; i++) { 568 | var bs = new BookShelf("user" + i, "book" + i, "comment" + i, new Date()); 569 | bss.add(bs); 570 | db.insert(bs); // 插入,自动插入相应分表 571 | } 572 | for (int i = 0; i < BookShelf.PARTITIONS; i++) { 573 | System.out.printf("partition %d count %d\n", i, db.count(BookShelf.class, String.valueOf(i))); 574 | } 575 | Random random = new Random(); 576 | for (var bs : bss) { 577 | bs.setComment("comment_update_" + random.nextInt(100)); 578 | db.update(bs); // 更新,自动更新相应分表数据 579 | } 580 | bss = new ArrayList(); 581 | for (int i = 0; i < BookShelf.PARTITIONS; i++) { 582 | bss.addAll(db.find(BookShelf.class, String.valueOf(i))); // 指定分表列出所有行 583 | } 584 | for (var bs : bss) { 585 | System.out.println(bs.getComment()); 586 | } 587 | for (var bs : bss) { 588 | db.delete(bs); // 挨个删除,自动删除相应分表数据 589 | } 590 | } finally { 591 | for (int i = 0; i < BookShelf.PARTITIONS; i++) { 592 | db.drop(BookShelf.class, String.valueOf(i)); // 删除所有分表 593 | } 594 | } 595 | } 596 | 597 | } 598 | ``` 599 | 600 | 分库 601 | -- 602 | 603 | ```java 604 | public class DemoSharding { 605 | 606 | private static DemoDB[] dbs = new DemoDB[3]; 607 | static { 608 | Map, Meta> metas = new HashMap<>(); 609 | dbs[0] = new DemoDB("demo-0", metas, 610 | "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"); 611 | dbs[1] = new DemoDB("demo-1", metas, 612 | "jdbc:mysql://localhost:3307/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"); 613 | dbs[2] = new DemoDB("demo-2", metas, 614 | "jdbc:mysql://localhost:3308/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"); 615 | } 616 | 617 | public static void main(String[] args) { 618 | var grid = new GridDemoDB(dbs); // 构造Grid实例 619 | try { 620 | for (int k = 0; k < BookShelf.PARTITIONS; k++) { 621 | grid.create(BookShelf.class, String.valueOf(k)); // 创建所有分库中的分表 622 | } 623 | var bss = new ArrayList(); 624 | for (int i = 0; i < 100; i++) { 625 | var bs = new BookShelf("user" + i, "book" + i, "comment" + i, new Date()); 626 | bss.add(bs); 627 | grid.insert(bs); // 插入,自动分发到相应的分库中的分表 628 | } 629 | for (int k = 0; k < grid.size(); k++) { 630 | for (int i = 0; i < BookShelf.PARTITIONS; i++) { 631 | System.out.printf("db %d partition %d count %d\n", k, i, 632 | grid.count(BookShelf.class, k, String.valueOf(i))); // 依次查询出所有分库的分表的行数 633 | } 634 | } 635 | Random random = new Random(); 636 | for (var bs : bss) { 637 | bs.setComment("comment_update_" + random.nextInt(100)); 638 | grid.update(bs); // 更新,自动分发到相应的分库中的分表 639 | } 640 | for (var bs : bss) { 641 | bs = grid.get(BookShelf.class, bs.getUserId(), bs.getBookId()); // 主键查询,自动分发到相应的分库中的分表 642 | System.out.println(bs.getComment()); 643 | } 644 | for (var bs : bss) { 645 | grid.delete(bs); // 删除,自动分发到相应的分库中的分表 646 | } 647 | for (int k = 0; k < grid.size(); k++) { 648 | for (int i = 0; i < BookShelf.PARTITIONS; i++) { 649 | System.out.printf("db %d partition %d count %d\n", k, i, 650 | grid.count(BookShelf.class, k, String.valueOf(i))); // 依次查询出所有分库的分表的行数 651 | } 652 | } 653 | } finally { 654 | for (int k = 0; k < BookShelf.PARTITIONS; k++) { 655 | grid.drop(BookShelf.class, String.valueOf(k)); // 删除所有分库中的分表 656 | } 657 | } 658 | } 659 | 660 | } 661 | ``` 662 | 663 | 事件上下文对象 664 | -- 665 | 666 | ```java 667 | public class Context { 668 | 669 | private DB db; // 数据库实例 670 | private Connection conn; // 当前的链接 671 | private Class clazz; // 当前的实体类 672 | private Q q; // 查询sql 673 | private Object[] values; // 查询的绑定参数 674 | private boolean before; // before or after 675 | private Exception error; // 异常 676 | private long duration; // 耗时microsecond 677 | 678 | } 679 | ``` 680 | 681 | 事件回调 682 | -- 683 | 684 | ```java 685 | public class DemoEvent { 686 | 687 | private final static String URI = "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"; 688 | 689 | public static void main(String[] args) { 690 | var db = new DemoDB("demo", URI); 691 | db.on(ctx -> { // 全局事件回调 692 | System.out.printf("db=%s sql=%s cost=%dus\n", ctx.db().name(), ctx.q().sql(), ctx.duration()); 693 | return true; // 返回false会导致事件链终止,后续的ORM操作也不会执行 694 | }); 695 | try { 696 | db.create(User.class); 697 | db.scope(ctx -> { // 范围回调,execute方法内部的所有ORM操作都会回调 698 | System.out.printf("db=%s sql=%s cost=%dus\n", ctx.db().name(), ctx.q().sql(), ctx.duration()); 699 | return true; 700 | }).execute(() -> { 701 | db.count(User.class); 702 | db.find(User.class); 703 | }); 704 | } finally { 705 | db.drop(User.class); // 删除表 706 | } 707 | } 708 | 709 | } 710 | ``` 711 | 712 | 相关链接 713 | -- 714 | 撸web框架 https://github.com/pyloque/httpkids 715 | 716 | 撸rpc框架 https://github.com/pyloque/rpckids 717 | 718 | 撸依赖注入框架 https://github.com/pyloque/iockids 719 | 720 | 讨论 721 | -- 722 | 关注公众号「码洞」,跟大佬们一起撸 723 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.github.pyloque 4 | ormkids 5 | 0.0.5-SNAPSHOT 6 | 7 | ormkids 8 | http://maven.apache.org 9 | MySQL ORM framework for Java with Sharding supported 10 | 11 | 12 | 13 | The Apache Software License, Version 2.0 14 | http://www.apache.org/licenses/LICENSE-2.0.txt 15 | 16 | 17 | 18 | 19 | 20 | pyloque 21 | holycoder@163.com 22 | 23 | 24 | 25 | 26 | scm:git:https://github.com/pyloque/ormkids 27 | scm:git:git@github.com:pyloque/ormkids.git 28 | https://github.com/pyloque/ormkids 29 | HEAD 30 | 31 | 32 | 33 | 34 | snapshots 35 | https://oss.sonatype.org/content/repositories/snapshots/ 36 | 37 | 38 | releases 39 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 40 | 41 | 42 | 43 | 44 | UTF-8 45 | 46 | 47 | 48 | 49 | 50 | maven-compiler-plugin 51 | 3.1 52 | 53 | 10 54 | 10 55 | 56 | ormkids/demo/* 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-source-plugin 63 | 2.2.1 64 | 65 | 66 | package 67 | 68 | jar-no-fork 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-javadoc-plugin 76 | 3.0.0 77 | 78 | 79 | package 80 | 81 | jar 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-gpg-plugin 89 | 1.5 90 | 91 | 92 | sign-artifacts 93 | verify 94 | 95 | sign 96 | 97 | 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-release-plugin 103 | 2.5.3 104 | 105 | v@{project.version} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | org.slf4j 114 | slf4j-api 115 | 1.7.5 116 | true 117 | 118 | 119 | org.slf4j 120 | slf4j-log4j12 121 | 1.7.5 122 | true 123 | 124 | 125 | mysql 126 | mysql-connector-java 127 | 6.0.6 128 | true 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/java/ormkids/Column.java: -------------------------------------------------------------------------------- 1 | package ormkids; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class Column { 6 | private String name; 7 | private String type; 8 | private boolean primary; 9 | private boolean autoIncrement; 10 | private boolean nullable; 11 | private String defaultValue; 12 | 13 | private Field field; 14 | 15 | public Column(String name, String type) { 16 | this(name, type, false, true, null); 17 | } 18 | 19 | public Column(String name, String type, boolean autoIncrement, boolean nullable, String defaultValue) { 20 | this.name = name; 21 | this.type = type; 22 | this.autoIncrement = autoIncrement; 23 | this.nullable = nullable; 24 | this.defaultValue = defaultValue; 25 | } 26 | 27 | public Column field(Field field) { 28 | this.field = field; 29 | return this; 30 | } 31 | 32 | public void primary(boolean primary) { 33 | this.primary = primary; 34 | } 35 | 36 | public boolean primary() { 37 | return this.primary; 38 | } 39 | 40 | public Field field() { 41 | return this.field; 42 | } 43 | 44 | public String s() { 45 | StringBuilder builder = new StringBuilder(); 46 | builder.append(String.format("`%s`", name)); 47 | builder.append(" "); 48 | builder.append(type); 49 | if (!nullable) { 50 | builder.append(" not null"); 51 | } 52 | if (autoIncrement) { 53 | builder.append(" auto_increment"); 54 | } 55 | 56 | if (defaultValue != null && !defaultValue.isEmpty()) { 57 | builder.append(" default "); 58 | builder.append(defaultValue); 59 | } 60 | return builder.toString(); 61 | } 62 | 63 | public String name() { 64 | return name; 65 | } 66 | 67 | public String type() { 68 | return type; 69 | } 70 | 71 | public boolean autoIncrement() { 72 | return autoIncrement; 73 | } 74 | 75 | public boolean nullable() { 76 | return nullable; 77 | } 78 | 79 | public String defaultValue() { 80 | return defaultValue; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/ormkids/Context.java: -------------------------------------------------------------------------------- 1 | package ormkids; 2 | 3 | import java.sql.Connection; 4 | 5 | public class Context { 6 | 7 | @FunctionalInterface 8 | public static interface IEventListener { 9 | boolean on(Context ctx); 10 | } 11 | 12 | private DB db; 13 | private Connection conn; 14 | private Class clazz; 15 | private Q q; 16 | private Object[] values; 17 | private boolean before; 18 | private Exception error; 19 | private long duration; // microsecond 20 | 21 | private final static Object[] EmptyValues = new Object[] {}; 22 | 23 | public static Context before(DB db, Connection conn, Class clazz, Q q, Object[] values) { 24 | var ctx = new Context(); 25 | ctx.db = db; 26 | ctx.conn = conn; 27 | ctx.clazz = clazz; 28 | ctx.q = q; 29 | ctx.values = values; 30 | ctx.before = true; 31 | return ctx; 32 | } 33 | 34 | public static Context after(DB db, Connection conn, Class clazz, Q q, Object[] values, 35 | Exception error, long duration) { 36 | var ctx = new Context(); 37 | ctx.db = db; 38 | ctx.conn = conn; 39 | ctx.clazz = clazz; 40 | ctx.q = q; 41 | ctx.values = values; 42 | ctx.before = false; 43 | ctx.error = error; 44 | ctx.duration = duration; 45 | return ctx; 46 | } 47 | 48 | public Class clazz() { 49 | return clazz; 50 | } 51 | 52 | public Q q() { 53 | return q; 54 | } 55 | 56 | public DB db() { 57 | return db; 58 | } 59 | 60 | public Object[] values() { 61 | return values != null ? values : EmptyValues; 62 | } 63 | 64 | public boolean before() { 65 | return before; 66 | } 67 | 68 | public boolean after() { 69 | return !before; 70 | } 71 | 72 | public Connection conn() { 73 | return conn; 74 | } 75 | 76 | public Exception error() { 77 | return error; 78 | } 79 | 80 | public Context error(Exception error) { 81 | this.error = error; 82 | return this; 83 | } 84 | 85 | public long duration() { 86 | return duration; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/ormkids/DB.java: -------------------------------------------------------------------------------- 1 | package ormkids; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | import java.sql.Connection; 6 | import java.sql.PreparedStatement; 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.LinkedHashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import ormkids.Context.IEventListener; 17 | import ormkids.Q.Filterable; 18 | 19 | public abstract class DB { 20 | 21 | private Map, Meta> metas; 22 | private String name; 23 | 24 | public DB(String name) { 25 | this(name, new HashMap<>()); 26 | } 27 | 28 | public DB(String name, Map, Meta> metas) { 29 | this.name = name; 30 | this.metas = Collections.synchronizedMap(metas); 31 | } 32 | 33 | public String name() { 34 | return name; 35 | } 36 | 37 | private T empty(Class clazz) { 38 | try { 39 | return clazz.getDeclaredConstructor().newInstance(); 40 | } catch (Exception e) { 41 | throw new KidsException("entity class should provide default constructor"); 42 | } 43 | } 44 | 45 | private T empty(Class clazz, Object... ids) { 46 | var empty = empty(clazz); 47 | var meta = meta(clazz); 48 | var i = 0; 49 | for (var name : meta.indices.primary().columns()) { 50 | var column = meta.columns.get(name); 51 | try { 52 | column.field().set(empty, ids[i]); 53 | } catch (Exception e) { 54 | throw new KidsException("access field errror", e); 55 | } 56 | i++; 57 | } 58 | return empty; 59 | } 60 | 61 | static class Holder { 62 | T value; 63 | } 64 | 65 | private List listeners = Collections.synchronizedList(new ArrayList<>()); 66 | private ThreadLocal localListener = new ThreadLocal<>(); 67 | 68 | public DB on(IEventListener listener) { 69 | this.listeners.add(listener); 70 | return this; 71 | } 72 | 73 | public DB off(IEventListener listener) { 74 | this.listeners.remove(listener); 75 | return this; 76 | } 77 | 78 | @FunctionalInterface 79 | public static interface IExecutor { 80 | void execute(Runnable runnable); 81 | } 82 | 83 | public IExecutor scope(IEventListener listener) { 84 | return new IExecutor() { 85 | 86 | public void execute(Runnable runnable) { 87 | localListener.set(listener); 88 | try { 89 | runnable.run(); 90 | } finally { 91 | localListener.remove(); 92 | } 93 | } 94 | 95 | }; 96 | } 97 | 98 | public boolean invokeListeners(Context event) { 99 | if (localListener.get() != null) { 100 | if (!localListener.get().on(event)) { 101 | return false; 102 | } 103 | } 104 | for (var listener : listeners) { 105 | if (!listener.on(event)) { 106 | return false; 107 | } 108 | } 109 | return true; 110 | } 111 | 112 | @FunctionalInterface 113 | public static interface ITransactionOp { 114 | void execute(Connection conn); 115 | } 116 | 117 | public void withinTx(ITransactionOp op) { 118 | withinTx(op, true); 119 | } 120 | 121 | public void withinTx(ITransactionOp op, boolean tx) { 122 | var conn = conn(); 123 | try { 124 | op.execute(conn); 125 | if (tx) { 126 | conn.commit(); 127 | } 128 | } catch (SQLException e) { 129 | if (tx) { 130 | try { 131 | conn.rollback(); 132 | } catch (SQLException e1) { 133 | } 134 | } 135 | } finally { 136 | try { 137 | conn.close(); 138 | } catch (SQLException e) { 139 | } 140 | } 141 | } 142 | 143 | @FunctionalInterface 144 | public static interface IPrepareOp { 145 | void prepare(PreparedStatement stmt) throws SQLException; 146 | } 147 | 148 | public void withinPrepare(Connection conn, String sql, IPrepareOp op) { 149 | PreparedStatement stmt = null; 150 | try { 151 | stmt = conn.prepareStatement(sql); 152 | op.prepare(stmt); 153 | } catch (SQLException e) { 154 | throw new KidsException(e); 155 | } finally { 156 | if (stmt != null) { 157 | try { 158 | stmt.close(); 159 | } catch (SQLException e) { 160 | } 161 | } 162 | } 163 | } 164 | 165 | @FunctionalInterface 166 | public static interface IQueryOp { 167 | ResultSet query(PreparedStatement stmt) throws SQLException; 168 | } 169 | 170 | public void withinQuery(Connection conn, String sql, IQueryOp op) { 171 | PreparedStatement stmt = null; 172 | ResultSet result = null; 173 | try { 174 | stmt = conn.prepareStatement(sql); 175 | result = op.query(stmt); 176 | } catch (SQLException e) { 177 | throw new KidsException(e); 178 | } finally { 179 | if (result != null) { 180 | try { 181 | result.close(); 182 | } catch (SQLException e) { 183 | } 184 | } 185 | if (stmt != null) { 186 | try { 187 | stmt.close(); 188 | } catch (SQLException e) { 189 | } 190 | } 191 | } 192 | } 193 | 194 | protected abstract Connection conn(); 195 | 196 | public void create(Class clazz) { 197 | withinTx(conn -> { 198 | create(conn, clazz, null); 199 | }); 200 | } 201 | 202 | public void create(Class clazz, String suffix) { 203 | withinTx(conn -> { 204 | create(conn, clazz, suffix); 205 | }); 206 | } 207 | 208 | public void create(Connection conn, Class clazz, String suffix) { 209 | withinPrepare(conn, createSQL(clazz, suffix), stmt -> { 210 | stmt.execute(); 211 | }); 212 | } 213 | 214 | private String createSQL(Class clazz, String suffix) { 215 | var meta = meta(clazz); 216 | var q = Q.create().table(meta.name, suffix); 217 | meta.columns.forEach((name, column) -> { 218 | q.column(column.name(), column.type(), column.autoIncrement(), column.nullable(), column.defaultValue()); 219 | }); 220 | meta.indices.indices().forEach(index -> { 221 | q.index(index.primaryKey(), index.unique(), index.columns()); 222 | }); 223 | meta.options.options().forEach(option -> { 224 | q.option(option.key(), option.value()); 225 | }); 226 | return q.sql(); 227 | } 228 | 229 | public void drop(Class clazz) { 230 | drop(clazz, null); 231 | } 232 | 233 | public void drop(Class clazz, String suffix) { 234 | withinTx(conn -> { 235 | drop(conn, clazz, suffix); 236 | }); 237 | } 238 | 239 | public void drop(Connection conn, Class clazz, String suffix) { 240 | withinPrepare(conn, dropSQL(clazz, suffix), stmt -> { 241 | stmt.execute(); 242 | }); 243 | } 244 | 245 | private String dropSQL(Class clazz, String suffix) { 246 | var meta = meta(clazz); 247 | var q = Q.drop().table(meta.name, suffix); 248 | return q.sql(); 249 | } 250 | 251 | public void truncate(Class clazz) { 252 | truncate(clazz, null); 253 | } 254 | 255 | public void truncate(Class clazz, String suffix) { 256 | withinTx(conn -> { 257 | truncate(conn, clazz, suffix); 258 | }); 259 | } 260 | 261 | public void truncate(Connection conn, Class clazz, String suffix) { 262 | withinPrepare(conn, truncateSQL(clazz, suffix), stmt -> { 263 | stmt.execute(); 264 | }); 265 | } 266 | 267 | private String truncateSQL(Class clazz, String suffix) { 268 | var meta = meta(clazz); 269 | var q = Q.truncate().table(meta.name, suffix); 270 | return q.sql(); 271 | } 272 | 273 | private T translate(ResultSet rs, Class clazz) throws SQLException { 274 | var meta = this.meta(clazz); 275 | var rowMeta = rs.getMetaData(); 276 | try { 277 | var res = empty(clazz); 278 | for (int i = 0; i < rowMeta.getColumnCount(); i++) { 279 | String name = rowMeta.getColumnName(i + 1); 280 | var column = meta.columns.get(name.toLowerCase()); 281 | if (column != null) { 282 | column.field().setAccessible(true); 283 | column.field().set(res, rs.getObject(i + 1)); 284 | } 285 | } 286 | return res; 287 | } catch (Exception e) { 288 | throw new KidsException("entity class should provide default constructor"); 289 | } 290 | } 291 | 292 | public T get(Class clazz, Object... ids) { 293 | var holder = new Holder(); 294 | withinTx(conn -> { 295 | holder.value = get(conn, clazz, ids); 296 | }, false); 297 | return holder.value; 298 | } 299 | 300 | public T get(Connection conn, Class clazz, Object... ids) { 301 | var holder = new Holder(); 302 | withinQuery(conn, selectSQL(clazz, ids), stmt -> { 303 | for (int i = 0; i < ids.length; i++) { 304 | stmt.setObject(i + 1, ids[i]); 305 | } 306 | ResultSet result = stmt.executeQuery(); 307 | if (!result.next()) { 308 | return null; 309 | } 310 | holder.value = translate(result, clazz); 311 | return result; 312 | }); 313 | return holder.value; 314 | } 315 | 316 | private String selectSQL(Class clazz, Object... ids) { 317 | var meta = meta(clazz); 318 | var columns = meta.indices.primary().columns(); 319 | if (columns.length != ids.length) { 320 | throw new KidsException("ids length must match with primary columns"); 321 | } 322 | var filters = new Filterable[ids.length]; 323 | for (int i = 0; i < ids.length; i++) { 324 | filters[i] = Q.eq_(columns[i]); 325 | } 326 | var empty = empty(clazz, ids); 327 | var q = Q.select().field("*").table(empty.table(), empty.suffix()).where(Q.and(filters)); 328 | return q.sql(); 329 | } 330 | 331 | public List find(Class clazz) { 332 | return this.find(clazz, null, 0, 0, null, new Object[] {}); 333 | } 334 | 335 | public List find(Class clazz, Filterable filter, Object... values) { 336 | return this.find(clazz, null, 0, 0, filter, values); 337 | } 338 | 339 | public List find(Class clazz, String suffix, Object... values) { 340 | return this.find(clazz, suffix, 0, 0, null, values); 341 | } 342 | 343 | public List find(Class clazz, String suffix, Filterable filter, Object... values) { 344 | return this.find(clazz, suffix, 0, 0, filter, values); 345 | } 346 | 347 | public List find(Class clazz, String suffix, int offset, int limit, Filterable filter, 348 | Object... values) { 349 | var holder = new Holder>(); 350 | withinTx(conn -> { 351 | holder.value = find(conn, clazz, suffix, filter, offset, limit, values); 352 | }, false); 353 | return holder.value; 354 | } 355 | 356 | public List find(Connection conn, Class clazz, String suffix, Filterable filter, 357 | Object... values) { 358 | return this.find(conn, clazz, suffix, filter, 0, 0, values); 359 | } 360 | 361 | public List find(Connection conn, Class clazz, String suffix, Filterable filter, 362 | int offset, int limit, Object... values) { 363 | var holder = new Holder>(); 364 | withinQuery(conn, findSQL(clazz, suffix, filter, offset, limit), stmt -> { 365 | int i = 1; 366 | for (; i <= values.length; i++) { 367 | stmt.setObject(i, values[i - 1]); 368 | } 369 | if (offset > 0) { 370 | stmt.setObject(i++, offset); 371 | } 372 | if (limit > 0) { 373 | stmt.setObject(i++, limit); 374 | } 375 | ResultSet res = stmt.executeQuery(); 376 | var result = new ArrayList(); 377 | while (res.next()) { 378 | result.add(translate(res, clazz)); 379 | } 380 | holder.value = result; 381 | return res; 382 | }); 383 | return holder.value; 384 | } 385 | 386 | private String findSQL(Class clazz, String suffix, Filterable filter, int offset, int limit) { 387 | var meta = meta(clazz); 388 | var q = Q.select().field("*").table(meta.name, suffix); 389 | if (filter != null) { 390 | q.where(filter); 391 | } 392 | if (offset > 0) { 393 | q.offset_(); 394 | } 395 | if (limit > 0) { 396 | q.limit_(); 397 | } 398 | return q.sql(); 399 | } 400 | 401 | public long count(Class clazz) { 402 | // eliminate warnings 403 | String suffix = null; 404 | Filterable filter = null; 405 | return count(clazz, suffix, filter); 406 | } 407 | 408 | public long count(Class clazz, Filterable filter, Object... values) { 409 | return count(clazz, null, filter, values); 410 | } 411 | 412 | public long count(Class clazz, String suffix) { 413 | return count(clazz, suffix, null); 414 | } 415 | 416 | public long count(Class clazz, String suffix, Filterable filter, Object... values) { 417 | var holder = new Holder(); 418 | withinTx(conn -> { 419 | holder.value = count(conn, clazz, suffix, filter, values); 420 | }, false); 421 | return holder.value; 422 | } 423 | 424 | public long count(Connection conn, Class clazz, String suffix, Filterable filter, 425 | Object... values) { 426 | var holder = new Holder(); 427 | var q = countSQL(clazz, suffix, filter); 428 | var evt = Context.before(this, conn, clazz, q, values); 429 | if (!this.invokeListeners(evt)) { 430 | return -1; 431 | } 432 | long start = System.nanoTime(); 433 | Exception error = null; 434 | try { 435 | withinQuery(conn, q.sql(), stmt -> { 436 | var i = 1; 437 | for (; i <= values.length; i++) { 438 | stmt.setObject(i, values[i - 1]); 439 | } 440 | ResultSet res = stmt.executeQuery(); 441 | res.next(); 442 | holder.value = res.getLong(1); 443 | return res; 444 | }); 445 | } catch (RuntimeException e) { 446 | error = e; 447 | } 448 | long duration = (System.nanoTime() - start) / 1000; 449 | evt = Context.after(this, conn, clazz, q, values, error, duration); 450 | this.invokeListeners(evt); 451 | return holder.value; 452 | } 453 | 454 | private Q countSQL(Class clazz, String suffix, Filterable filter) { 455 | var meta = meta(clazz); 456 | var q = Q.select().field("count(1)").table(meta.name, suffix); 457 | if (filter != null) { 458 | q.where(filter); 459 | } 460 | return q; 461 | } 462 | 463 | public void any(Class clazz, Q q, IQueryOp op, Object... values) { 464 | this.withinTx(conn -> { 465 | any(conn, clazz, q, op, values); 466 | }, false); 467 | } 468 | 469 | public void any(Connection conn, Class clazz, Q q, IQueryOp op, Object... values) { 470 | var evt = Context.before(this, conn, clazz, q, values); 471 | if (!this.invokeListeners(evt)) { 472 | return; 473 | } 474 | long start = System.nanoTime(); 475 | Exception error = null; 476 | try { 477 | this.withinQuery(conn, q.sql(), op); 478 | } catch (RuntimeException e) { 479 | error = e; 480 | } 481 | long duration = (System.nanoTime() - start) / 1000; 482 | this.invokeListeners(Context.after(this, conn, clazz, q, values, error, duration)); 483 | } 484 | 485 | private Object[] ids(T t) { 486 | var meta = meta(t.getClass()); 487 | Object[] ids = new Object[meta.indices.primary().columns().length]; 488 | var i = 0; 489 | for (var name : meta.indices.primary().columns()) { 490 | var field = meta.columns.get(name).field(); 491 | try { 492 | ids[i++] = field.get(t); 493 | } catch (Exception e) { 494 | throw new KidsException("access field errror", e); 495 | } 496 | } 497 | return ids; 498 | } 499 | 500 | private Map values(T t) { 501 | var meta = meta(t.getClass()); 502 | var values = new HashMap(); 503 | meta.columns.forEach((name, column) -> { 504 | Field f = column.field(); 505 | try { 506 | values.put(f.getName(), f.get(t)); 507 | } catch (Exception e) { 508 | throw new KidsException("access field errror", e); 509 | } 510 | }); 511 | return values; 512 | } 513 | 514 | public int delete(T t) { 515 | return delete(t.getClass(), ids(t)); 516 | } 517 | 518 | public int delete(Class clazz, Object... ids) { 519 | var holder = new Holder(); 520 | withinTx(conn -> { 521 | holder.value = delete(conn, clazz, ids); 522 | }); 523 | return holder.value; 524 | } 525 | 526 | public int delete(Connection conn, Class clazz, Object... ids) { 527 | var holder = new Holder(); 528 | var q = deleteSQL(clazz, ids); 529 | var evt = Context.before(this, conn, clazz, q, ids); 530 | if (!this.invokeListeners(evt)) { 531 | return -1; 532 | } 533 | long start = System.nanoTime(); 534 | Exception error = null; 535 | try { 536 | withinPrepare(conn, q.sql(), stmt -> { 537 | for (int i = 0; i < ids.length; i++) { 538 | stmt.setObject(i + 1, ids[i]); 539 | } 540 | holder.value = stmt.executeUpdate(); 541 | }); 542 | } catch (RuntimeException e) { 543 | error = e; 544 | } 545 | long duration = (System.nanoTime() - start) / 1000; 546 | this.invokeListeners(Context.after(this, conn, clazz, q, ids, error, duration)); 547 | return holder.value; 548 | } 549 | 550 | private Q deleteSQL(Class clazz, Object... ids) { 551 | var meta = meta(clazz); 552 | var columns = meta.indices.primary().columns(); 553 | if (columns.length != ids.length) { 554 | throw new KidsException("ids length must match with primary columns"); 555 | } 556 | var filters = new Filterable[ids.length]; 557 | for (int i = 0; i < ids.length; i++) { 558 | filters[i] = Q.eq_(columns[i]); 559 | } 560 | var empty = empty(clazz, ids); 561 | var q = Q.delete().table(empty.table(), empty.suffix()).where(Q.and(filters)); 562 | return q; 563 | } 564 | 565 | public int update(T t) { 566 | return update(t.getClass(), values(t), ids(t)); 567 | } 568 | 569 | public int update(Class clazz, Map values, Object... ids) { 570 | var holder = new Holder(); 571 | var valuesModify = new HashMap<>(values); // copy it for modify 572 | var meta = meta(clazz); 573 | // remove pk values 574 | for (var name : meta.indices.primary().fields()) { 575 | valuesModify.remove(name); 576 | } 577 | withinTx(conn -> { 578 | holder.value = update(conn, clazz, valuesModify, ids); 579 | }); 580 | return holder.value; 581 | } 582 | 583 | public int update(Connection conn, Class clazz, Map values, Object... ids) { 584 | var holder = new Holder(); 585 | var q = updateSQL(clazz, values, ids); 586 | var hybridValues = new Object[values.size() + ids.length]; 587 | int i = 0; 588 | for (var value : values.values()) { 589 | hybridValues[i++] = value; 590 | } 591 | for (var id : ids) { 592 | hybridValues[i++] = id; 593 | } 594 | var evt = Context.before(this, conn, clazz, q, hybridValues); 595 | if (!this.invokeListeners(evt)) { 596 | return -1; 597 | } 598 | long start = System.nanoTime(); 599 | Exception error = null; 600 | try { 601 | withinPrepare(conn, q.sql(), stmt -> { 602 | for (int k = 0; k < hybridValues.length; k++) { 603 | stmt.setObject(k + 1, hybridValues[k]); 604 | } 605 | holder.value = stmt.executeUpdate(); 606 | }); 607 | } catch (RuntimeException e) { 608 | error = e; 609 | } 610 | long duration = (System.nanoTime() - start) / 1000; 611 | this.invokeListeners(Context.after(this, conn, clazz, q, hybridValues, error, duration)); 612 | return holder.value; 613 | } 614 | 615 | private Q updateSQL(Class clazz, Map values, Object... ids) { 616 | var meta = meta(clazz); 617 | var columns = meta.indices.primary().columns(); 618 | if (columns.length != ids.length) { 619 | throw new KidsException("ids length must match with primary columns"); 620 | } 621 | var filters = new Filterable[ids.length]; 622 | for (int i = 0; i < ids.length; i++) { 623 | filters[i] = Q.eq_(columns[i]); 624 | } 625 | 626 | var empty = empty(clazz, ids); 627 | var q = Q.update().table(empty.table(), empty.suffix()).where(Q.and(filters)); 628 | values.forEach((name, value) -> { 629 | var column = meta.fields.get(name); 630 | if (column == null || column.primary()) { 631 | return; 632 | } 633 | q.with_(column.name()); 634 | }); 635 | return q; 636 | } 637 | 638 | public boolean insert(T t) { 639 | var values = new LinkedHashMap(); 640 | var meta = meta(t.getClass()); 641 | Holder lastInsertId = null; 642 | Field lastInsertField = null; 643 | for (var entry : meta.columns.entrySet()) { 644 | var column = entry.getValue(); 645 | try { 646 | var field = column.field(); 647 | var o = field.get(t); 648 | if (column.autoIncrement() && o == null) { 649 | lastInsertId = new Holder(); 650 | lastInsertField = column.field(); 651 | continue; 652 | } 653 | values.put(field.getName(), o); 654 | } catch (Exception e) { 655 | throw new KidsException("access field errror", e); 656 | } 657 | } 658 | boolean res = insert(t, values, lastInsertId); 659 | if (res && lastInsertId != null && lastInsertId.value != null) { 660 | try { 661 | lastInsertField.set(t, lastInsertId.value); 662 | } catch (Exception e) { 663 | throw new KidsException("access field errror", e); 664 | } 665 | } 666 | return res; 667 | } 668 | 669 | public boolean insert(T t, Map values, Holder lastInsertId) { 670 | var holder = new Holder(); 671 | withinTx(conn -> { 672 | holder.value = insert(conn, t, values); 673 | if (lastInsertId != null) { 674 | var q = Q.select().field("last_insert_id()"); 675 | this.any(conn, t.getClass(), q, stmt -> { 676 | ResultSet rs = stmt.executeQuery(); 677 | if (rs.next()) { 678 | lastInsertId.value = rs.getInt(1); 679 | } 680 | return rs; 681 | }); 682 | } 683 | }); 684 | return holder.value; 685 | } 686 | 687 | public boolean insert(Connection conn, T t, Map values) { 688 | var meta = meta(t.getClass()); 689 | var q = insertSQL(t, values); 690 | var paramsList = new ArrayList<>(); 691 | for (var entry : meta.columns.entrySet()) { 692 | var name = meta.columns.get(entry.getKey()).field().getName(); 693 | var value = values.get(name); 694 | if (value != null) { 695 | paramsList.add(value); 696 | } 697 | } 698 | var params = paramsList.toArray(); 699 | var evt = Context.before(this, conn, t.getClass(), q, params); 700 | if (!this.invokeListeners(evt)) { 701 | return false; 702 | } 703 | long start = System.nanoTime(); 704 | Exception error = null; 705 | try { 706 | withinPrepare(conn, q.sql(), stmt -> { 707 | for (var k = 0; k < params.length; k++) { 708 | stmt.setObject(k + 1, params[k]); 709 | } 710 | stmt.executeUpdate(); 711 | }); 712 | } catch (RuntimeException e) { 713 | error = e; 714 | } 715 | long duration = (System.nanoTime() - start) / 1000; 716 | this.invokeListeners(Context.after(this, conn, t.getClass(), q, params, error, duration)); 717 | return true; 718 | } 719 | 720 | private Q insertSQL(T t, Map values) { 721 | var meta = meta(t.getClass()); 722 | var q = Q.insert().table(t.table(), t.suffix()); 723 | for (var entry : meta.columns.entrySet()) { 724 | var column = entry.getValue(); 725 | var value = values.get(column.field().getName()); 726 | if (value == null) { 727 | if (column.autoIncrement()) { 728 | continue; 729 | } 730 | if (column.nullable()) { 731 | continue; 732 | } 733 | if (!column.nullable() && column.defaultValue() != null && !column.defaultValue().isEmpty()) { 734 | continue; 735 | } 736 | } 737 | q.with_(entry.getKey()); 738 | } 739 | return q; 740 | } 741 | 742 | private Meta meta(Class clazz) { 743 | var meta = metas.get(clazz); 744 | if (meta != null) { 745 | return meta; 746 | } 747 | var entity = empty(clazz); 748 | meta = new Meta(); 749 | meta.name = entity.table(); 750 | meta.options = entity.options(); 751 | meta.indices = entity.indices(); 752 | if (meta.indices.primary() == null) { 753 | throw new KidsException("entity class should provide primary index"); 754 | } 755 | fillMeta(clazz, meta); 756 | for (String name : meta.indices.primary().columns()) { 757 | var column = meta.columns.get(name); 758 | column.primary(true); 759 | } 760 | for (var index : meta.indices.indices()) { 761 | var fields = new String[index.columns().length]; 762 | var i = 0; 763 | for (var column : index.columns()) { 764 | fields[i++] = meta.columns.get(column).field().getName(); 765 | } 766 | index.fields(fields); 767 | } 768 | metas.put(clazz, meta); 769 | return meta; 770 | } 771 | 772 | private void fillMeta(Class clazz, Meta meta) { 773 | for (Field field : clazz.getDeclaredFields()) { 774 | for (Annotation anno : field.getDeclaredAnnotations()) { 775 | if (anno instanceof E.Column) { 776 | if (field.getType().isPrimitive()) { 777 | throw new KidsException("column must not be primitive type"); 778 | } 779 | var columnDef = (E.Column) anno; 780 | var column = new Column(columnDef.name().toLowerCase(), columnDef.type(), columnDef.autoincrement(), 781 | columnDef.nullable(), columnDef.defaultValue()); 782 | column.field(field); 783 | field.setAccessible(true); 784 | meta.column(column); 785 | break; 786 | } 787 | } 788 | } 789 | if (meta.columns.isEmpty()) { 790 | throw new KidsException("entity class should provide at least one column"); 791 | } 792 | } 793 | 794 | } 795 | -------------------------------------------------------------------------------- /src/main/java/ormkids/E.java: -------------------------------------------------------------------------------- 1 | package ormkids; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class E { 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ ElementType.TYPE }) 14 | public @interface Table { 15 | } 16 | 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target({ ElementType.FIELD }) 19 | public @interface Column { 20 | String name(); 21 | 22 | String type(); 23 | 24 | boolean nullable() default true; 25 | 26 | boolean autoincrement() default false; 27 | 28 | String defaultValue() default ""; 29 | } 30 | 31 | public static class TableOptions { 32 | private List