V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
jimisun
V2EX  ›  问与答

Java 如何通用化查询逻辑应对复杂的查询需求?

  •  
  •   jimisun · 2022-04-17 15:42:06 +08:00 · 1645 次点击
    这是一个创建于 951 天前的主题,其中的信息可能已经有所发展或是发生改变。

    数据库有一些数据,如下模拟;

    
            ArrayList<User> objects = Lists.newArrayList();
            objects.add(new User("zhangsan", "123456", 18, 1));
            objects.add(new User("lisi", "123456", 22, 1));
            objects.add(new User("wangwu", "123456", 30, 1));
            objects.add(new User("zhaoliu", "123456", 43, 1));
            objects.add(new User("xiaoli", "xiaoli67889", 16, 0));
            objects.add(new User("xiaona", "nai324389", 22, 0));
    

    问题抛出

    针对这些数据的查询而言,通常会在 service 层定义许多查询接口,例如

    • 查询性别为男的用户
    • 查询性别为男年龄 20 以下的用户
    • 查询性别为男年龄 20 以下的并且默认密码是 123456 的用户
    • 随需求增加...

    面对需求的变化多样,总是需要增量添加 controller 方法,service 方法,甚至 dao 方法,如何解决这个问题呢?

    面向函数编程是否能满足呢?

    //dao
    public static List<User> getUserList() {
            ArrayList<User> objects = Lists.newArrayList();
            objects.add(new User("zhangsan", "123456", 18, 1));
            objects.add(new User("lisi", "123456", 22, 1));
            objects.add(new User("wangwu", "123456", 30, 1));
            objects.add(new User("zhaoliu", "123456", 43, 1));
            objects.add(new User("xiaoli", "xiaoli67889", 16, 0));
            objects.add(new User("xiaona", "nai324389", 22, 0));
            return objects;
            }
    
    //service
    public static Optional getUserListByPredicate(Predicate<User> predicate) {
            List<User> userList = UserDao.getUserList();
            ArrayList<Object> resultList = Lists.newArrayList();
            for (User user : userList) {
            if (predicate.test(user)) {
            resultList.add(user);
            }
            }
            return Optional.ofNullable(resultList);
            }
            
    
    //controller
    public static void main(String[] args) {
    
            //查询性别为男,年龄 20 以下,并且密码为默认密码 123456 的用户
            Optional<List> result = UserService.getUserListByPredicate((User user) -> {
            return user.getSex() == 1 && user.getAge() < 20 && user.getPassword().equals("123456");
            });
    
    
            result.get().stream().forEach(user -> System.out.println(user));
            }
    

    我的想法是,dao 层总是返回全量的数据(此处有问题性能问题),在 service 层对查询条件进行抽象,controller 只需要将查询条件传入即可。

    更进一步用 stream 处理

    //dao
    public static List<User> getUserList() {
    ArrayList<User> objects = Lists.newArrayList();
    objects.add(new User("zhangsan", "123456", 18, 1));
    objects.add(new User("lisi", "123456", 22, 1));
    objects.add(new User("wangwu", "123456", 30, 1));
    objects.add(new User("zhaoliu", "123456", 43, 1));
    objects.add(new User("xiaoli", "xiaoli67889", 16, 0));
    objects.add(new User("xiaona", "nai324389", 22, 0));
    return objects;
    }
    
    //controller 层
        /**
         * 查询性别为男,年龄 20 以下,并且密码为默认密码 123456 的用户
         *
         * @param args
         */
        public static void main(String[] args) {
            List<User> userList = UserDao.getUserList();
            userList.stream().filter(
                    user -> user.getSex() == 1 && user.getAge() < 20 && user.getPassword().equals("123456")
            ).forEach(user -> System.out.println(user));
        }
    

    我的想法是,controller 总是获取所有的数据,针对流根据业务进行业务操作。

    ——————————

    以上两种方法是否可行? 问题?

    13 条回复    2022-04-18 14:07:12 +08:00
    PerFectTime
        1
    PerFectTime  
       2022-04-17 16:06:08 +08:00
    我们是维护了一个数据列表的列视图权限,在此基础上通过列视图的数据类型(bit/string/字典)由可配置的高级查询功能可以对每一列的情况进行筛选
    eggoxygen
        2
    eggoxygen  
       2022-04-17 16:26:25 +08:00 via iPhone
    根据 ORM 框架不同有不同解决方案吧。
    比如 JPA 的 Specification / QueryDsl 。
    定义好需要查询的 Condition / Criteria 。
    查询时传入即可。
    Leviathann
        3
    Leviathann  
       2022-04-17 16:26:32 +08:00
    为什么不根据查询条件动态生成 sql
    xiangyuecn
        4
    xiangyuecn  
       2022-04-17 17:16:50 +08:00
    多写一条 if else 就要被抓取坐牢
    micean
        5
    micean  
       2022-04-17 17:52:35 +08:00
    比如某个男人的 apijson……
    lower
        6
    lower  
       2022-04-17 17:57:56 +08:00   ❤️ 2
    直接让前端传 sql 语句吧,他们爱查啥查啥……
    EscYezi
        7
    EscYezi  
       2022-04-17 19:08:36 +08:00 via iPhone   ❤️ 1
    看场景都是同一些数据同一些字段数值不同。controller 层只需要一个接口,定义一个含有多个字段的 vo ,本次查询用不到的字段和前端协商一个默认值就 ok
    dao 层 mybatis 的 xml 文件中 if 和 choose 根据各种情况拼接查询条件; mybatis-plus 也可以在拼接查询条件时增加 bool 参数指定是否拼接
    全部查询出来数据少还好,多起来一次拿几十万数据出来,数据库和 java 服务压力都很大
    letitbesqzr
        8
    letitbesqzr  
       2022-04-17 22:05:26 +08:00
    试试用 Aviator 之类的表达式解析工具? 让前端传表达式
    rehoni
        9
    rehoni  
       2022-04-18 08:29:55 +08:00
    定义一个通用的查询过滤器 Qo 数组,用作查询条件的拼接。当数组为空时,默认查全部 sql 为 select*;数组中每个对象对应一个条件,如年龄 20 以下、默认密码是 123456 ,拼接出来 sql 就是 select * where age < 20 ,pwd = ‘123456’; Qo 对象的 JSON 很明显包含字段名 age ,条件<,值 20 等,这是最基础的,然后在此 JSON 基础上还可以做一些拓展,如复杂字段建立驼峰关系或者建立映射,条件提供枚举,时间类型提供格式化,整体条件提供前端解决方案如指定为下拉框、时间选择器,再者就是可以利用框架特性来进行条件 Qo 的封装来实现通用效果,如 mybatis-plus 的条件构造器。
    whatevers
        10
    whatevers  
       2022-04-18 10:03:36 +08:00
    单表查询用 Mybatis 逆向工程生成 Mapper ,前端传参生成动态 sql
    li746224
        11
    li746224  
       2022-04-18 10:48:50 +08:00
    graphql?
    90d0n
        12
    90d0n  
       2022-04-18 11:17:51 +08:00
    1. mybatis, 用 myabtis-plus 的 QueryWrapper, 动态拼接一下查询条件.
    https://baomidou.com/pages/10c804

    2. jpa, 看文档 4.8.2 那一节, Querydsl Web Support.
    https://docs.spring.io/spring-data/jpa/docs/current/reference/html


    实现后接口大概是这样: http://your.api/user?name=Alice&page=0&size=20&sort=age,asc
    aguesuka
        13
    aguesuka  
       2022-04-18 14:07:12 +08:00
    前端到后端用 luence 语法, 后端映射到 sql
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1096 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:36 · PVG 07:36 · LAX 15:36 · JFK 18:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.