java实现类SQL的join操作


SQL是结构化查询语言的简称,给数据库操作带来很大的方便。

随着大数据的流行,hive、spark、flink都开始支持SQL的操作。

但在java内部并没有对SQL操作的支持,使用java stream API进行数据关联操作的代码惨不忍睹。

因此,我基于Google guava的Table,封装了常用的join操作。分享并记录在这里。

目前支持的join类型包括如下几类,基本上常见的join都已经覆盖到了。

public enum JoinStrategy {
    INNER_JOIN,//内连接
    LEFT_JOIN,//左连接
    RIGHT_JOIN,//右连接
    LEFT_SEMI_JOIN,//左半连接,结果只取左表,连接条件是左表和右表的交集
    LEFT_ANTI_JOIN,//左半连接,结果只取左表,连接条件是左表与右表的差集
    FULL_JOIN;//全连接
}

闲言少叙,下面就是JoinUtils的硬代码

一、构造Table对象

由于大部分人拿到的数据要么并不是Table类型,所以我首先提供了从这两个对象转Table对象的方法

由于从jdbc查出来的数据,默认格式就是一个List>,下面这个createTable方法可以将List>生成一个Table对象

因为我们后面要进行join操作,所以需要提供一个id作为每一行的rowKey.

/**
     * 基于List创建一个表对象,将使用用户提供的id的在map中对应的value值作为rowKey
     *
     * @param listMap
     * @param id
     * @param 
     * @param 
     * @param 
     * @return
     */
    public static  Table createTable(List> listMap, R id) {
        Table table = HashBasedTable.create();
        for (Map map : listMap) {
            for (Map.Entry entry : map.entrySet()) {
                table.put((R) map.get(id), entry.getKey(), entry.getValue());
            }
        }
        return table;
    }

有些情况下,我们的数据并不是List>,而是别人给我们传递的一组对象,这个情况我也考虑到了。

下面是一个从List对象生成Table对象的方法。

 public static  Table createTable(List objects, Class aClass, String primaryKey) {
        Table table = HashBasedTable.create();
        List fieldNames = getFieldNames(aClass);
        for (T t : objects) {
            Object primaryKeyValue = getFieldValue(aClass, t, primaryKey);
            for (String field : fieldNames) {
                Object fieldValue = getFieldValue(aClass, t, field);
                table.put((R) primaryKeyValue, (C) field, (V) fieldValue);
            }
        }
        return table;
    }

二、join操作

对外我暴露了一个join工厂方法,用来基于用户提供的策略选择不同的底层实现来操作。

public static  Table join(Table left, Table right, JoinStrategy strategy) {
        switch (strategy) {
            case LEFT_JOIN:
                return leftJoin(left, right);
            case RIGHT_JOIN:
                return rightJoin(left, right);
            case LEFT_SEMI_JOIN:
                return leftSemiJoin(left, right);
            case LEFT_ANTI_JOIN:
                return leftAntiJoin(left, right);
            case FULL_JOIN:
                return fullJoin(left, right);
            default:
                return innerJoin(left, right);
        }
    }

测试一下

//测试代码:
class JoinUtilsTest {

    private Table leftTable;
    private Table rightTable;

    @BeforeEach
    void createTable() {
        Map map = new HashMap<>();
        Map map1 = new HashMap<>();
        Map map2 = new HashMap<>();
        map.put("id", "a");
        map.put("name", "x");
        map1.put("id", "b");
        map1.put("name", "y");
        map2.put("id", "c");
        map2.put("name", "z");
        List> list = new ArrayList<>();
        list.add(map);
        list.add(map1);
        list.add(map2);
        leftTable = JoinUtils.createTable(list, "id");

        Map map3 = new HashMap<>();
        Map map4 = new HashMap<>();
        Map map5 = new HashMap<>();
        map3.put("id", "a");
        map3.put("val", "o");
        map4.put("id", "b");
        map4.put("val", "p");
        map5.put("id", "d");
        map5.put("val", "q");
        List> list2 = new ArrayList<>();
        list2.add(map3);
        list2.add(map4);
        list2.add(map5);
        rightTable = JoinUtils.createTable(list2, "id");
        System.out.println("LeftTable");
        JoinUtils.printTable(leftTable);
        System.out.println("RightTable");
        JoinUtils.printTable(rightTable);
    }


    @Test
    void testCreateTable2() {
        User tom = new User(1, "tom", false);
        User john = new User(2, "john", true);
        User pet = new User(3, "pet", true);
        ArrayList users = new ArrayList<>();
        users.add(tom);
        users.add(john);
        users.add(pet);
        Table userTable = JoinUtils.createTable(users, User.class, "name");
        JoinUtils.printTable(userTable);
    }

    @Test
    void testStrategy() {
        JoinStrategy[] values = JoinStrategy.values();
        for (JoinStrategy strategy : values) {
            System.out.println(strategy);
            JoinUtils.printTable(JoinUtils.join(leftTable, rightTable, strategy));
        }
    }
} 

输出:

LeftTable
        name    id      
a       x       a       
b       y       b       
c       z       c       

RightTable
        val     id      
a       o       a       
b       p       b       
d       q       d       

INNER_JOIN
        name    val     id      
a       x       o       a       
b       y       p       b       

LEFT_JOIN
        name    val     id      
a       x       o       a       
b       y       p       b       
c       z       null    c       

RIGHT_JOIN
        val     name    id      
a       o       x       a       
b       p       y       b       
d       q       null    d       

LEFT_SEMI_JOIN
        name    id      
a       x       a       
b       y       b       

LEFT_ANTI_JOIN
        name    id      
c       z       c       

FULL_JOIN
        name    val     id      
a       x       o       a       
b       y       p       b       
c       z       null    c       
d       null    q       d       

具体join操作的实现代码,功能肯定是没问题的,性能还没有完全测试。

如果你有更靠谱的实现,请留言告诉我。

/**
     * 未指定连接key的内连接,以rowKey作为连接键
     *
     * @param left
     * @param right
     * @return
     */
    private static  Table innerJoin(Table left, Table right) {
        Table result = HashBasedTable.create();
        Set leftRowKey = Sets.newHashSet(left.rowKeySet());
        Set rightRowKey = Sets.newHashSet(right.rowKeySet());
        leftRowKey.retainAll(rightRowKey);
        for (R key : leftRowKey) {
            Map leftRow = left.row(key);
            Map rightRow = right.row(key);
            for (Map.Entry leftEntry : leftRow.entrySet()) {
                result.put(key, leftEntry.getKey(), leftEntry.getValue());
            }
            for (Map.Entry rightEntry : rightRow.entrySet()) {
                result.put(key, rightEntry.getKey(), rightEntry.getValue());
            }
        }
        return result;
    }


    private static  Table leftJoin(Table left, Table right) {
        Table result = HashBasedTable.create();
        Set leftRowKey = Sets.newHashSet(left.rowKeySet());
        for (R key : leftRowKey) {
            Map leftRow = left.row(key);
            Map rightRow = right.row(key);
            for (Map.Entry leftEntry : leftRow.entrySet()) {
                result.put(key, leftEntry.getKey(), leftEntry.getValue());
            }
            for (Map.Entry rightEntry : rightRow.entrySet()) {
                result.put(key, rightEntry.getKey(), rightEntry.getValue());
            }
        }
        return result;

    }


    private static  Table rightJoin(Table left, Table right) {
        return leftJoin(right, left);

    }


    private static  Table leftSemiJoin(Table left, Table right) {
        Table result = HashBasedTable.create();
        Set leftRowKey = Sets.newHashSet(left.rowKeySet());
        Set rightRowKey = Sets.newHashSet(right.rowKeySet());
        leftRowKey.retainAll(rightRowKey);
        for (R key : leftRowKey) {
            Map leftRow = left.row(key);
            for (Map.Entry leftEntry : leftRow.entrySet()) {
                result.put(key, leftEntry.getKey(), leftEntry.getValue());
            }
        }
        return result;
    }


    private static  Table leftAntiJoin(Table left, Table right) {
        Table result = HashBasedTable.create();
        Set leftRowKey = Sets.newHashSet(left.rowKeySet());
        Set rightRowKey = Sets.newHashSet(right.rowKeySet());
        for (R key : leftRowKey) {
            if (!rightRowKey.contains(key)) {
                Map leftRow = left.row(key);
                for (Map.Entry leftEntry : leftRow.entrySet()) {
                    result.put(key, leftEntry.getKey(), leftEntry.getValue());
                }
            }
        }
        return result;
    }


    private static  Table fullJoin(Table left, Table right) {
        Table result = HashBasedTable.create();
        Set leftRowKey = Sets.newHashSet(left.rowKeySet());
        Set rightRowKey = Sets.newHashSet(right.rowKeySet());
        Set union = new HashSet<>(leftRowKey);
        union.addAll(rightRowKey);
        for (R key : union) {
            Map leftRow = left.row(key);
            Map rightRow = right.row(key);
            for (Map.Entry leftEntry : leftRow.entrySet()) {
                result.put(key, leftEntry.getKey(), leftEntry.getValue());
            }
            for (Map.Entry rightEntry : rightRow.entrySet()) {
                result.put(key, rightEntry.getKey(), rightEntry.getValue());
            }
        }
        return result;

    }