用Antlr4处理sql中的多语言字段

417 阅读2分钟

用Antlr4处理sql中的多语言字段

对于一个后端开发来说,在写带多语言字段的sql时,往往需要手动join下多语言表,查询对应内容。 例如现在有两个表

主book表

字段
id书id
book_name书名

多语言book_tl表

字段
id书id
lang语言code
book_name书名

当想查询书列表的时候,我们会这样写sql

select
b.id,
bk.book_name
from book b left join book_tl bt on b.id = bk.id and bk.lang = 'en_US'

写多了就觉得烦了,也不利于阅读,还容易漏。 那有没有一种方式,可以自动处理多语言内容? 在xml里只写

select
b.id,
b.book_name
from book b

然后执行的时候自动处理成

select
b.id,
bk.book_name
from book b left join book_tl bt on b.id = bk.id and bk.lang = 'en_US'

答案是可以。

用到的就是antlr4-强大的语法解析工具

关于antlr4的具体内容在这不做展开,可以阅读相关书例如《antlr4实战》

简单的来说,我们给antler4一个sql的语法规则,然后antlr4就能解析sql了,将sql解析未一颗语法树。 然后在遍历树的时候,我们就能做一些事情了,对于本文来说,就是自动给表join上多语言表

下面是主要的过程

首先需要给entity对象加一些元数据,作为后面替换的依据

比如

@Table(name = "student")//表名
@MultiLanguage(name = "student_tl") //多语言表名
public class Student {
    private Long id;
    @MultiLanguageField //代表这个字段是多语言字段
    private String bookName;

    .
    .
    .
}

然后写一个实现BeanPostProcessor的类,在服务启动的时候收集多语言元数据信息 收集到一个map,记录哪些表是多语言表,哪些字段是多语言字段

然后就是antlr4部分 准备一个解析sql的词法、语法规则,使用antlr4,将规则生成相应的java代码

然后就是遍历语法树,遇到表名节点时,通过前面收集的多语言元数据,以及select的列是否包含多语言字段,将join 多语言表的sql插入对应的节点,以及将字段改成多语言表的字段。

public class MultiLangSqlRecognizer extends SelectBaseVisitor<Mdata> {


    private final List<Mdata> mdataList = new ArrayList<>();

    public String apply(String originSql) throws IOException {
        InputStream is = new ByteArrayInputStream(originSql.toUpperCase().getBytes(StandardCharsets.UTF_8));
        CharStream stream = CharStreams.fromStream(is);
        // 词法分析
        SelectLexer lexer = new SelectLexer(stream);
        lexer.removeErrorListeners();
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        // 语法分析
        SelectParser parser = new SelectParser(tokens);
        parser.removeErrorListeners();
        ParseTree ast = parser.selectStatement();
        //访问语法树
        this.visit(ast);
        //处理多语言
        List<Pair<Pair<Integer, Integer>, String>> replaceInfos = recognizeLangInfo(mdataList);
        if (replaceInfos.size() > 0) {
            return handleReplace(originSql, replaceInfos);
        } else {
            return null;
        }
    }

通过antlr4,我们将得到一个转换后的sql 最后通过mybatis的拦截器,将这个转换过程应用上,即完成了多余sql的自动装配。