V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Joker123456789
V2EX  ›  Java

我一直都是一个 SQL 派,所以我开发了这个框架

  •  
  •   Joker123456789 · 2022-03-04 17:35:38 +08:00 · 2942 次点击
    这是一个创建于 1002 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目简介:

    • 项目名:Magician-JDBC
    • 开发语言:Java
    • 多数据源:支持
    • 实体映射:支持

    我一直都是一个 SQL 派,所以我开发了这个框架, 但我也不是愚昧的排斥无 sql 操作,所以在项目中,单表的操作不需要写 sql ,但是多表 或者 复杂的操作 还是以 sql 为准。

    自从 springboot 出来以后,war 包就进入历史了,现在流行的方式是 打 jar 包,这就有点意思了,一旦打了 jar 包 就意味着 mybatis 的 xml 文件也会被打进 jar 包,这种情况下 如果要改 sql 就必须去找源码; 就算是 war 包年代,也不可能直接改线上,肯定还是从源码开始修改,经过正规的一套流程后 才能上线。

    所以,把 sql 写在文件里的这种形式,我个人认为 意义已经非常弱了,基于这样的考虑,所以我没支持 xml ,而是直接在代码里写 sql 。

    这里大家肯会有疑问,那就是如何解决 用 StringBuilder 时,append 太多的情况,其实这个问题 JDK 已经帮解决了,从 JDK14 开始,就支持了这种语法:

    String sql = """
        可以把多行的字符串写在这里
        可以把多行的字符串写在这里
        可以把多行的字符串写在这里
        可以把多行的字符串写在这里
        """
    

    现在 虽然 jdk8 的钉子户还很多,但是 未来肯定会用上 14 ,甚至是 17,18,19,20 ,只是早晚的问题。

    废话不多说,直接上示例

    单表操作

    插入数据

    ParamPO paramPO = new ParamPO();
    paramPO.setUserName("a");
    paramPO.setUserEmail("[email protected]");
    
    int result = JDBCTemplate.get().insert("表名", paramPO);
    

    修改数据

    // 构建修改条件
    List<Condition> conditionList = ConditionBuilder.createCondition()
            .add("id = ?", 10)
            .add("and name = ?", "bee"))
            .build();
    
    // 构建修改数据
    ParamPO paramPO = new ParamPO();
    paramPO.setUserName("a");
    paramPO.setUserEmail("[email protected]");
    
    // 执行修改
    int result = JDBCTemplate.get().update("表名", paramPO, conditionList);
    

    删除数据

    // 构建删除条件
    List<Condition> conditionList = ConditionBuilder.createCondition()
            .add("id = ?", 10)
            .build();
    
    // 执行删除
    int result = JDBCTemplate.get().delete("表名", conditionList);
    

    查询数据

    // 构建查询条件
    List<Condition> conditionList = ConditionBuilder.createCondition()
                .add("id > ?", 10)
                .add("and (name = ? or age > ?)", "bee", 10))
                .add("order by create_time", Condition.NOT_WHERE))
                .build();
    
    // 执行查询
    List<ParamPO> result = JDBCTemplate.get().select("表名", conditionList, ParamPO.class);
    

    自定义 SQL 操作

    增删改

    ParamPO paramPO = new ParamPO();
    paramPO.setUserName("testTx222");
    paramPO.setUserEmail("[email protected]");
    paramPO.setId(4);
    
    // 采用{}占位符的写法
    int result = JDBCTemplate.get().exec("update xt_message_board set user_name = {user_name} , user_email = {user_email} where id = {id}", paramPO);
    
    // 采用 ? 占位符的写法
    int result = JDBCTemplate.get().exec("update xt_message_board set user_name = ? , user_email = ? where id = ?", new Object[]{"testTx222","[email protected]", 4});
    

    查询数据

    ParamPO paramPO = new ParamPO();
    paramPO.setId(5);
    paramPO.setUserName("a");
    
    // 采用{}占位符的写法
    List<ParamPO> result = JDBCTemplate.get("dataSource").selectList("select * from xt_message_board where id > {id} and user_name != {user_name}", paramPO, ParamPO.class);
    
    // 采用 ? 占位符的写法
    List<ParamPO> result = JDBCTemplate.get("dataSource").selectList("select * from xt_message_board where id > ? and user_name != ?", new Object[]{5, "a"}, ParamPO.class);
    

    分页查询

    / 查询条件
    ParamPO paramPO = new ParamPO();
    paramPO.setId(5);
    paramPO.setUserName("a");
    
    // 查询参数
    PageParamModel pageParamModel = new PageParamModel();
    pageParamModel.setCurrentPage(1);
    pageParamModel.setPageSize(10);
    pageParamModel.setParam(paramPO);
    
    // 使用默认 countSql 查询
    PageModel<ParamPO> pageModel =  JDBCTemplate.get().selectPage("select * from xt_message_board where id > {id} and user_name != {user_name}", pageParamModel, ParamPO.class);
    
    // 使用自定义 countSql 查询
    String countSql = "自己定义 countSql";
    
    PageModel<ParamPO> pageModel =  JDBCTemplate.get().selectPageCustomCountSql("select * from xt_message_board where id > {id} and user_name != {user_name}", countSql, pageParamModel, ParamPO.class);
    

    事务管理

    // 开启事务
    TransactionManager.beginTraction();
    
    try {
        ParamPO paramPO = new ParamPO();
        paramPO.setUserName("testTx222");
        paramPO.setUserEmail("[email protected]");
        paramPO.setId(4);
        int result = JDBCTemplate.get().exec("update xt_message_board set user_name = {user_name} , user_email = {user_email} where id = {id}", paramPO);
    
        // 提交
        TransactionManager.commit();
    } catch(Execption e){
        // 回滚
        TransactionManager.rollback();
    }
    

    访问官网了解更多

    https://magician-io.com/cn/

    20 条回复    2022-03-08 13:40:21 +08:00
    brust
        1
    brust  
       2022-03-04 18:01:52 +08:00
    就我觉得很奇怪吗
    Saurichthys
        2
    Saurichthys  
       2022-03-04 18:04:05 +08:00
    你这是实现一个类似 mybatis 的 querywrapper 功能,说实话没有什么特别之处啊
    liprais
        3
    liprais  
       2022-03-04 18:05:58 +08:00
    prepared statement 都不用......
    py2ex
        4
    py2ex  
       2022-03-04 18:09:40 +08:00
    8 的钉子户报到
    9c04C5dO01Sw5DNL
        5
    9c04C5dO01Sw5DNL  
       2022-03-04 18:21:06 +08:00
    “一旦打了 jar 包 就意味着 mybatis 的 xml 文件也会被打进 jar 包”

    但凡用 maven assembly 等插件配置一下,都不至于把 xml 也打进 jar 包。
    pocketz
        6
    pocketz  
       2022-03-05 09:51:19 +08:00
    是我孤陋寡闻了。。。现在嵌入式 tomcat 用的这么普遍吗
    ldyisbest
        7
    ldyisbest  
       2022-03-05 11:49:20 +08:00
    看着感觉和 mybatis 的 example 差不多
    msg7086
        8
    msg7086  
       2022-03-05 11:53:30 +08:00
    唔,恭喜你重新发明了半个 ORM ?
    msg7086
        9
    msg7086  
       2022-03-05 12:18:20 +08:00
    最理想的做法应该是根据输入数据构建 AST 然后把 AST 变形转换成对应的 SQL 语句,这是一般 ORM 的做法。
    你这个只是做了一个 StringBuffer 在那拼字符串。
    而且这么核心的组件没有基本的测试覆盖,是不是太草率了一些……

    (我十几年前倒是在 PHP4.4 上做过类似的项目,在 ADOdb 上面包一层字符串处理。可这都已经 2022 年了……)
    a0210077
        10
    a0210077  
       2022-03-05 17:58:23 +08:00
    “自从 springboot 出来以后,war 包就进入历史了,现在流行的方式是 打 jar 包,这就有点意思了,一旦打了 jar 包 就意味着 mybatis 的 xml 文件也会被打进 jar 包,这种情况下 如果要改 sql 就必须去找源码; 就算是 war 包年代,也不可能直接改线上,肯定还是从源码开始修改,经过正规的一套流程后 才能上线。”
    a0210077
        11
    a0210077  
       2022-03-05 18:03:30 +08:00
    这里不用 xml 的理由非常牵强,同意我 @giiiiiithub #5 说的,用 maven ,想怎么打包都可以
    Joker123456789
        12
    Joker123456789  
    OP
       2022-03-07 15:08:06 +08:00
    @liprais 你确定不是你眼花了?
    Joker123456789
        13
    Joker123456789  
    OP
       2022-03-07 15:11:11 +08:00
    @msg7086 ast 确实高端一点,但是 stringBuffer 也没什么 致命的弱点吧。
    Joker123456789
        14
    Joker123456789  
    OP
       2022-03-07 15:15:22 +08:00
    @a0210077 确实....... , 但是 我最后还有一句:线上不可能让你直接改的,你还是要回去改源码,然后 经过一套流程 才能上线。

    我个人认为,配置文件 是 外包界的产物, 去客户现场 安装 调试等,改起来方便。 对于自研的公司,配置文件存在的意义 只是一个归纳整理。 按照环境拆分,统一配置中心等 都不是非文件不可的。
    Joker123456789
        15
    Joker123456789  
    OP
       2022-03-07 15:19:39 +08:00
    @pocketz 是的,非常普遍,因为 几乎都在用 springboot 。 然后.... 我用的是嵌入式 netty
    Joker123456789
        16
    Joker123456789  
    OP
       2022-03-07 15:26:05 +08:00
    @Saurichthys 确实是的,用法上没什么特别的。 但是因为我是一个 sql 派,所以

    这种写法

    // 构建查询条件
    List<Condition> conditionList = ConditionBuilder.createCondition()
    .add("id > ?", 10)
    .add("and (name = ? or age > ?)", "bee", 10)) // 这里是一个条件的组合
    .add("order by create_time", Condition.NOT_WHERE))
    .build();

    我觉得比这种写法更容易上手

    QueryWrapper queryWrapper = new QueryWrapper();
    queryWrapper.eq("id", 10);

    因为前者是 原生 sql ,学习成本几乎为 0 ,而且 组合条件 也很好写。 而后者 需要熟练的记住 eq, lt, ge 等方法的意思。

    而且我这个 jar 包非常小,很小很小, 连源码都没几个类。
    msg7086
        17
    msg7086  
       2022-03-07 15:39:18 +08:00
    StringBuffer 意味着你只能以真正 SQL 的方式去写查询。
    如果用 AST 或者类似的技术,则不需要遵循 SQL 的顺序,也不需要逼着用户去加生硬的「 and 」。

    举个简单的例子:

    bob = User.where(email: "[email protected]").where(active: true)
    # => SELECT "users".* FROM "users" WHERE "users"."email" = '[email protected]' AND "users"."active" = 't'

    details = User.select(:id, :email, :first_name).order(id: :desc)
    # => SELECT "users"."id", "users"."email", "users"."first_name" FROM "users" ORDER BY "users"."id" DESC

    bob.merge(details).first
    # => SELECT "users"."id", "users"."email", "users"."first_name" FROM "users"
    # WHERE "users"."email" = '[email protected]' AND "users"."active" = 't'
    # ORDER BY "users"."id" DESC LIMIT 1

    这里的这种 bob.merge(details).first 的用法可以让代码变得非常干净且易于维护。
    你可以提前列出所有可能的查询条件,然后在最后一步根据输入参数或者具体需求进行拼装。
    coderwl
        18
    coderwl  
       2022-03-07 18:19:52 +08:00
    建议直接使用 jooq
    lichao
        19
    lichao  
       2022-03-08 13:24:06 +08:00
    @msg7086 用 ActiveRecord 去怼 Java 写的 ORM 有点不讲武德
    msg7086
        20
    msg7086  
       2022-03-08 13:40:21 +08:00 via Android
    @lichao 概念是类似的,不是添加字符串而是添加成 ast 然后从 ast 构建语句。我相信就算 Java 应该也能实现出来的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2590 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 03:25 · PVG 11:25 · LAX 19:25 · JFK 22:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.