├── .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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> clazz) {
197 | withinTx(conn -> {
198 | create(conn, clazz, null);
199 | });
200 | }
201 |
202 | public void create(Class extends IEntity> clazz, String suffix) {
203 | withinTx(conn -> {
204 | create(conn, clazz, suffix);
205 | });
206 | }
207 |
208 | public void create(Connection conn, Class extends IEntity> clazz, String suffix) {
209 | withinPrepare(conn, createSQL(clazz, suffix), stmt -> {
210 | stmt.execute();
211 | });
212 | }
213 |
214 | private String createSQL(Class extends IEntity> 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 extends IEntity> clazz) {
230 | drop(clazz, null);
231 | }
232 |
233 | public void drop(Class extends IEntity> clazz, String suffix) {
234 | withinTx(conn -> {
235 | drop(conn, clazz, suffix);
236 | });
237 | }
238 |
239 | public void drop(Connection conn, Class extends IEntity> clazz, String suffix) {
240 | withinPrepare(conn, dropSQL(clazz, suffix), stmt -> {
241 | stmt.execute();
242 | });
243 | }
244 |
245 | private String dropSQL(Class extends IEntity> 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 extends IEntity> clazz) {
252 | truncate(clazz, null);
253 | }
254 |
255 | public void truncate(Class extends IEntity> clazz, String suffix) {
256 | withinTx(conn -> {
257 | truncate(conn, clazz, suffix);
258 | });
259 | }
260 |
261 | public void truncate(Connection conn, Class extends IEntity> clazz, String suffix) {
262 | withinPrepare(conn, truncateSQL(clazz, suffix), stmt -> {
263 | stmt.execute();
264 | });
265 | }
266 |
267 | private String truncateSQL(Class extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> clazz, Filterable filter, Object... values) {
409 | return count(clazz, null, filter, values);
410 | }
411 |
412 | public long count(Class extends IEntity> clazz, String suffix) {
413 | return count(clazz, suffix, null);
414 | }
415 |
416 | public long count(Class extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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 extends IEntity> 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