《你不知道的 JAVA》💘 一种失传已久的 SQL 多表连接查询的写法。

从 Left Join 说起

假设你有这样一个 n2n 的关系表,代表用户和角色之间的关系。

通常通过 left join 去连接这三张表,来查询出用户及其角色的信息。

SELECT 
    u.id AS user_id,
    u.name AS user_name,
    r.name AS role_name
FROM 
    "user" u
LEFT JOIN 
    "user_role_map" urm ON u.id = urm.user_id
LEFT JOIN 
    "role" r ON urm.role_id = r.id;
user_id user_name role_name
1 Alice Admin
1 Alice User
2 Bob User
3 Charlie Guest

查询出的结果中,Alice 这个用户出现了两次。这是显而易见的,因为这是一个 “Flatten” 的结果。

这样的结果是无法返回给客户端直接使用的。你需要进行处理,把重复的用户归纳到一起以后再返回给客户端进行展示,比如像下面这样:

user_id user_name user’s_role_array 备注
1 Alice [(Admin),(User<List>),(Vip<List>),…(n)] 试想任意节点都可能嵌套高度为 n 的子树的情况,各节点需直接返回 List<Map> 的形式供前端在 html 的 <li></li><select></select> 节点中展示。
2 Bob User
3 Charlie Guest

不幸的是这样的处理非常麻烦,你 join 的表越多这个代码越不好写,不相信你可以试试。

Group_contact

看到这里,你可能会觉得 agg_string 和 group_contact 等聚合函数一定程度上能实现这个需求。但是聚合函数人如其名,作用为「聚合」。
回到上面的例子,不要局限于例子中树的高度,试想任意节点都可能嵌套一颗高度为 n 的子树,并且你的业务逻辑还需要对子树的节点做数据结构的转换。显然,字符串的「聚合」在解决这样复杂树结构的问题时显得力量不足。

这样的复杂树结构是否很常见?不,它不常见,但是它也不少见。因为除了互联网,还有很多行业也在使用数据库支撑他们的业务。

谈谈 ORM

有没有方便的方法来获取这个「嵌套」的结果呢?使用 Hibernate 这样的 ORM 框架是个不错的主意:

@Entity
public class User {
    @Id
    private int id;

    @Column(name = "name", nullable = false)
    private String name;

    @ManyToMany
    @JoinTable(
        name = "user_role_map",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles;
}

@Entity
public class Role {
    @Id
    private int id;

    @Column(name = "name", nullable = false)
    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users;
}

Hibernate 直接帮你把数据库的结果映射到了嵌套结果集中。现在你可以直接把 List<User> 返回给客户端了,因为这个结果现在展示为:

user_id user_name user’s_role_array 备注
1 Alice [(Admin),(User<List>),(Vip<List>),…(n)] 无论树的形状和高度,Hibernate 把各节直接映射为 List<Map> 的形式供前端在 html 的 <li></li><select></select> 节点中展示。
2 Bob User
3 Charlie Guest

Hibernate 的问题

使用 Hibernate 的代价就是你的心智负担很大。除了要学习很多注解以外,还有很多夸张的概念需要深入理解,才能够写出能正常运行的代码。

那么,有没有一种简单的方法,可以通过 SQL 的方式,直接查询出这种嵌套的结果集, 然后扔给客户端进行处理呢?答案是有的。

全新的解决方案

public static void main(String[] args) {
        UserRoleEntity userRoleEntity = select(
                USER.ID,
                USER.NAME,
                array(select(ROLE.ID, ROLE.NAME)
                        .from(ROLE)
                        .join(USER_ROLE_MAP).on(ROLE.ID.eq(USER_ROLE_MAP.ROLE_ID))
                        .where(USER_ROLE_MAP.USER_ID.eq(USER.ID))
                ).as("roles")
        ).from(USER);
        System.out.println(userRoleEntity);
    }


class UserRoleEntity {
    private Long id;
    private String name;
    private List<Role> roles;
}

是的,如你所见,通过在 Java 的 main 方法里面用 Java 语言来编写「类型安全的 SQL」并通过 Array 方法一键转换为嵌套对象,免去了学习 Hibernate 的烦恼。

什么样的 Java 框架可以实现这样的功能?点击下方链接获取详情 :backhand_index_pointing_down:
https://linux.do/t/topic/195717/

108 Likes

sql语句是真的掉头发 :tieba_087: ps:没写过java

7 Likes

前排围观支持一下

5 Likes

sql还是可以解决的,一个group by u.name,和group_concat(r.name)解决

14 Likes

感谢分享这个写法

2 Likes

谢谢宝子支持

写在代码,出问题就知道爽了

2 Likes

看起来和 MyBatis-Flex差不多,对代码侵入性很大

2 Likes

所以怎么写查询所有用户最新的一条数据的时间 :slightly_smiling_face:

2 Likes

前排围观支持一下

1 Like

是的,所以需要类型安全的,可以 debug 的 java 来处理本身 sql 不具备的编译时类型安全的问题,另外还可以为 sql 提供 debug 的功能。这些都是 sql 欠缺的。

mybatis flex 借鉴了并实现了这个框架的一小部分的内容

mybatis flex 比你这个早吧

1 Like

这种不都是用group_concat函数吗

1 Like

Jooq QueryDSL ?

3 Likes

这个框架第一个版本是 2009 年发布的奥。

1 Like

感谢佬分享,现实中的确遇到过很多这种复杂的sql查询,有时候不得不用嵌套子查询来解决.

1 Like

是的。这个特性确实很好用,不过支持这种特性的数据库挺少的。array 这种数据结构几乎只有 postgresql 支持。
幸好 Jooq 这种库提供了大多数数据库该特性的运行时模拟,省去了不少麻烦,不然就只能用 hibernate 啦。

1 Like

sorry 宝,我重新编辑了一下帖子,之前的 table 中的 content 可能会引起一些误会。
这里描述的是假设客户端希望拿到一个嵌套的数组数据结构,而不是字符串的拼接。

1 Like

#算法添加#快问快答移除