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<Map<>>,下面这个createTable方法可以将List<Map<>>生成一个Table对象
因为我们后面要进行join操作,所以需要提供一个id作为每一行的rowKey.
/** * 基于List<Map>创建一个表对象,将使用用户提供的id的在map中对应的value值作为rowKey * * @param listMap * @param id * @param <R> * @param <C> * @param <V> * @return */ public static <R, C, V> Table<R, C, V> createTable(List<Map<C, V>> listMap, R id) { Table<R, C, V> table = HashBasedTable.create(); for (Map<C, V> map : listMap) { for (Map.Entry<C, V> entry : map.entrySet()) { table.put((R) map.get(id), entry.getKey(), entry.getValue()); } } return table; }
有些情况下,我们的数据并不是List<Map<>>,而是别人给我们传递的一组对象,这个情况我也考虑到了。
下面是一个从List<T>对象生成Table对象的方法。
public static <T, R, C, V> Table<R, C, V> createTable(List<T> objects, Class<T> aClass, String primaryKey) { Table<R, C, V> table = HashBasedTable.create(); List<String> 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 <R, C, V> Table<R, C, V> join(Table<R, C, V> left, Table<R, C, V> 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<String, String, Object> leftTable; private Table<String, String, Object> rightTable; @BeforeEach void createTable() { Map<String, Object> map = new HashMap<>(); Map<String, Object> map1 = new HashMap<>(); Map<String, Object> 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<Map<String, Object>> list = new ArrayList<>(); list.add(map); list.add(map1); list.add(map2); leftTable = JoinUtils.createTable(list, "id"); Map<String, Object> map3 = new HashMap<>(); Map<String, Object> map4 = new HashMap<>(); Map<String, Object> 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<Map<String, Object>> 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<User> users = new ArrayList<>(); users.add(tom); users.add(john); users.add(pet); Table<Integer, String, Object> 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 <R, C, V> Table<R, C, V> innerJoin(Table<R, C, V> left, Table<R, C, V> right) { Table<R, C, V> result = HashBasedTable.create(); Set<R> leftRowKey = Sets.newHashSet(left.rowKeySet()); Set<R> rightRowKey = Sets.newHashSet(right.rowKeySet()); leftRowKey.retainAll(rightRowKey); for (R key : leftRowKey) { Map<C, V> leftRow = left.row(key); Map<C, V> rightRow = right.row(key); for (Map.Entry<C, V> leftEntry : leftRow.entrySet()) { result.put(key, leftEntry.getKey(), leftEntry.getValue()); } for (Map.Entry<C, V> rightEntry : rightRow.entrySet()) { result.put(key, rightEntry.getKey(), rightEntry.getValue()); } } return result; } private static <R, C, V> Table<R, C, V> leftJoin(Table<R, C, V> left, Table<R, C, V> right) { Table<R, C, V> result = HashBasedTable.create(); Set<R> leftRowKey = Sets.newHashSet(left.rowKeySet()); for (R key : leftRowKey) { Map<C, V> leftRow = left.row(key); Map<C, V> rightRow = right.row(key); for (Map.Entry<C, V> leftEntry : leftRow.entrySet()) { result.put(key, leftEntry.getKey(), leftEntry.getValue()); } for (Map.Entry<C, V> rightEntry : rightRow.entrySet()) { result.put(key, rightEntry.getKey(), rightEntry.getValue()); } } return result; } private static <R, C, V> Table<R, C, V> rightJoin(Table<R, C, V> left, Table<R, C, V> right) { return leftJoin(right, left); } private static <R, C, V> Table<R, C, V> leftSemiJoin(Table<R, C, V> left, Table<R, C, V> right) { Table<R, C, V> result = HashBasedTable.create(); Set<R> leftRowKey = Sets.newHashSet(left.rowKeySet()); Set<R> rightRowKey = Sets.newHashSet(right.rowKeySet()); leftRowKey.retainAll(rightRowKey); for (R key : leftRowKey) { Map<C, V> leftRow = left.row(key); for (Map.Entry<C, V> leftEntry : leftRow.entrySet()) { result.put(key, leftEntry.getKey(), leftEntry.getValue()); } } return result; } private static <R, C, V> Table<R, C, V> leftAntiJoin(Table<R, C, V> left, Table<R, C, V> right) { Table<R, C, V> result = HashBasedTable.create(); Set<R> leftRowKey = Sets.newHashSet(left.rowKeySet()); Set<R> rightRowKey = Sets.newHashSet(right.rowKeySet()); for (R key : leftRowKey) { if (!rightRowKey.contains(key)) { Map<C, V> leftRow = left.row(key); for (Map.Entry<C, V> leftEntry : leftRow.entrySet()) { result.put(key, leftEntry.getKey(), leftEntry.getValue()); } } } return result; } private static <R, C, V> Table<R, C, V> fullJoin(Table<R, C, V> left, Table<R, C, V> right) { Table<R, C, V> result = HashBasedTable.create(); Set<R> leftRowKey = Sets.newHashSet(left.rowKeySet()); Set<R> rightRowKey = Sets.newHashSet(right.rowKeySet()); Set<R> union = new HashSet<>(leftRowKey); union.addAll(rightRowKey); for (R key : union) { Map<C, V> leftRow = left.row(key); Map<C, V> rightRow = right.row(key); for (Map.Entry<C, V> leftEntry : leftRow.entrySet()) { result.put(key, leftEntry.getKey(), leftEntry.getValue()); } for (Map.Entry<C, V> rightEntry : rightRow.entrySet()) { result.put(key, rightEntry.getKey(), rightEntry.getValue()); } } return result; }