只有 2000 行 Java 代码写出来的支持分库分表的 ORM 值不值得看 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
shellquery
V2EX    程序员

只有 2000 行 Java 代码写出来的支持分库分表的 ORM 值不值得看

  •  
  •   shellquery 2018-05-03 09:43:05 +08:00 3673 次点击
    这是一个创建于 2785 天前的主题,其中的信息可能已经有所发展或是发生改变。

    OrmKids

    https://github.com/pyloque/ormkids

    假期花了几天时间撸了一个支持分库分表的 MySQL 单表 ORM 框架,暂用于学习

    功能特性

    1. 代码简洁,没有任何依赖项
    2. 易于使用,无须复杂的配置
    3. 提供自动创建表功能
    4. 支持分库又分表,可以只分库,也可以只分表
    5. 支持 groupby/having
    6. 支持原生 SQL
    7. 支持事件回调,可用于服务跟踪调试和动态 sql 改写

    不支持多表关联

    1. 多表比较复杂,实现成本高,学习成本也高,容易出错
    2. 常用的多表的操作一般都可以使用多条单表操作组合实现
    3. 在分库分表的场合,很少使用多表操作
    4. 不使用外键,专注于 sql 逻辑

    db.withinTx

    对于复杂的多表查询和批量数据处理,可以使用该方法。 用户可以获得原生的 jdbc 链接,通过编写 jdbc 代码来实现。

    Q

    用户可以使用 Q 对象构建复杂的 SQL 查询

    其它数据库支持

    暂时没有

    分库分表

    @Table public class BookShelf implements IEntity { public final static int PARTITIOnS= 4; @Column(name = "user_id", type = "varchar(255)", nullable = false) private String userId; @Column(name = "book_id", type = "varchar(255)", nullable = false) private String bookId; @Column(name = "comment", type = "varchar(255)") private String comment; @Column(name = "created_at", type = "datetime", nullable = false, defaultValue = "now()") private Date createdAt; public BookShelf() { } public BookShelf(String userId, String bookId, String comment, Date createdAt) { this.userId = userId; this.bookId = bookId; this.comment = comment; this.createdAt = createdAt; } public String getUserId() { return userId; } public String getBookId() { return bookId; } public void setComment(String comment) { this.comment = comment; } public String getComment() { return comment; } public Date getCreatedAt() { return createdAt; } @Override public String table() { return "book_shelf"; } @Override public TableOptions options() { return new TableOptions().option("engine", "innodb"); } @Override public TableIndices indices() { return new TableIndices().primary("user_id", "book_id"); } /* * 分表策略 */ @Override public String suffix() { var crc32 = new CRC32(); crc32.update(userId.getBytes(Utils.UTF8)); return String.valueOf(Math.abs(crc32.getValue()) % PARTITIONS); } /** * 分库策略 */ public static class GridStrategy implements IGridable<BookShelf> { @Override public int select(int dbs, BookShelf t) { return Math.abs(t.getUserId().hashCode()) % dbs; } @Override public int select(int dbs, Object... params) { String userId = (String) params[0]; return Math.abs(userId.hashCode()) % dbs; } } } 

    定义单个数据库

    public class DemoDB extends DB { private DataSource ds; public DemoDB(String name, String uri) { this(name, new HashMap<>(), uri); } public DemoDB(String name, Map<Class<? extends IEntity>, Meta> metas, String uri) { super(name, metas); var ds = new MysqlConnectionPoolDataSource(); // 连接池 ds.setUrl(uri); this.ds = ds; } @Override protected Connection conn() { // 获取链接 try { return ds.getConnection(); } catch (SQLException e) { throw new KidsException(e); } } } 

    定义网格数据库分库

    public class GridDemoDB extends GridDB<DemoDB> { /** * 传进来多个 DB 对象 */ public GridDemoDB(DemoDB[] dbs) { super(dbs); this.registerGridables(); } /* * 注册实体类的分库策略 */ @Override public void registerGridables() { this.gridWith(BookShelf.class, new BookShelf.GridStrategy<DemoDB>()); } } 

    分库增删改查

    public class DemoSharding { private static DemoDB[] dbs = new DemoDB[3]; static { Map<Class<? extends IEntity>, Meta> metas = new HashMap<>(); dbs[0] = new DemoDB("demo-0", metas, "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"); dbs[1] = new DemoDB("demo-1", metas, "jdbc:mysql://localhost:3307/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"); dbs[2] = new DemoDB("demo-2", metas, "jdbc:mysql://localhost:3308/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"); } public static void main(String[] args) { var grid = new GridDemoDB(dbs); // 构造 Grid 实例 try { for (int k = 0; k < BookShelf.PARTITIONS; k++) { grid.create(BookShelf.class, String.valueOf(k)); // 创建所有分库中的分表 } var bss = new ArrayList<BookShelf>(); for (int i = 0; i < 100; i++) { var bs = new BookShelf("user" + i, "book" + i, "comment" + i, new Date()); bss.add(bs); grid.insert(bs); // 插入,自动分发到相应的分库中的分表 } for (int k = 0; k < grid.size(); k++) { for (int i = 0; i < BookShelf.PARTITIONS; i++) { System.out.printf("db %d partition %d count %d\n", k, i, grid.count(BookShelf.class, k, String.valueOf(i))); // 依次查询出所有分库的分表的行数 } } Random random = new Random(); for (var bs : bss) { bs.setComment("comment_update_" + random.nextInt(100)); grid.update(bs); // 更新,自动分发到相应的分库中的分表 } for (var bs : bss) { bs = grid.get(BookShelf.class, bs.getUserId(), bs.getBookId()); // 主键查询,自动分发到相应的分库中的分表 System.out.println(bs.getComment()); } for (var bs : bss) { grid.delete(bs); // 删除,自动分发到相应的分库中的分表 } for (int k = 0; k < grid.size(); k++) { for (int i = 0; i < BookShelf.PARTITIONS; i++) { System.out.printf("db %d partition %d count %d\n", k, i, grid.count(BookShelf.class, k, String.valueOf(i))); // 依次查询出所有分库的分表的行数 } } } finally { for (int k = 0; k < BookShelf.PARTITIONS; k++) { grid.drop(BookShelf.class, String.valueOf(k)); // 删除所有分库中的分表 } } } } 

    事件上下文对象

    public class Context { private DB db; // 数据库实例 private Connection conn; // 当前的链接 private Class<? extends IEntity> clazz; // 当前的实体类 private Q q; // 查询 sql private Object[] values; // 查询的绑定参数 private boolean before; // before or after private Exception error; // 异常 private long duration; // 耗时 microsecond } 

    事件回调,用于服务跟踪调试

    public class DemoEvent { private final static String URI = "jdbc:mysql://localhost:3306/mydrc?user=mydrc&password=mydrc&useUnicode=true&characterEncoding=UTF8"; public static void main(String[] args) { var db = new DemoDB("demo", URI); db.on(ctx -> { // 全局事件回调 System.out.printf("db=%s sql=%s cost=%dus\n", ctx.db().name(), ctx.q().sql(), ctx.duration()); return true; // 返回 false 会导致事件链终止,后续的 ORM 操作也不会执行 }); try { db.create(User.class); db.scope(ctx -> { // 范围回调,execute 方法内部的所有 ORM 操作都会回调 System.out.printf("db=%s sql=%s cost=%dus\n", ctx.db().name(), ctx.q().sql(), ctx.duration()); return true; }).execute(() -> { db.count(User.class); db.find(User.class); }); } finally { db.drop(User.class); // 删除表 } } } 

    鉴于文章太长花费 v 币太多,更多内容请阅读 github 的 README

    https://github.com/pyloque/ormkids

    13 条回复    2018-05-03 12:43:43 +08:00
    enhancer
        1
    enhancer  
       2018-05-03 09:51:43 +08:00
    支持分表之间的 JOIN 吗?
    shellquery
        2
    shellquery  
    OP
       2018-05-03 10:03:41 +08:00
    @enhancer 同是天涯沦落人,何苦为难自家人
    ylcc
        3
    ylcc  
       2018-05-03 10:13:30 +08:00   1
    看了楼主的 gayhub,觉得非常有趣,小孩有点多哈哈哈,赞学习精神
    micean
        4
    micean  
       2018-05-03 10:16:40 +08:00
    以前也造过轮子
    现在觉得小的用表分区就搞完了,大的就直接上中间件了……
    shellquery
        5
    shellquery  
    OP
       2018-05-03 10:23:46 +08:00 via Android
    @micean 中间件也是一种常见的方案
    admol
        6
    admol  
       2018-05-03 11:31:36 +08:00
    var 用的 JDK10 吗?
    shellquery
        7
    shellquery  
    OP
       2018-05-03 11:37:01 +08:00 via Android
    @admol 没错,提前尝鲜了
    lihongjie0209
        8
    lihongjie0209  
       2018-05-03 11:48:54 +08:00
    同样的文章为什么昨天发今天也发
    THP301
        9
    THP301  
       2018-05-03 11:49:11 +08:00   1
    难得见这么简洁优雅的代码,已经收藏了
    shellquery
        10
    shellquery  
    OP
       2018-05-03 11:53:53 +08:00 via Android
    @lihongjie0209 昨天的文章是依赖注入
    tianzx
        11
    tianzx  
    PRO
       2018-05-03 12:16:21 +08:00 via Android
    m
    sethverlo
        12
    sethverlo  
       2018-05-03 12:18:38 +08:00
    楼主的头像!

    good news everyone (自带音效)
    shellquery
        13
    shellquery  
    OP
       2018-05-03 12:43:43 +08:00 via Android
    @sethverlo 老司机
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3210 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 11:09 PVG 19:09 LAX 03:09 JFK 06:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86