java项目数据库迁移的一点思路
有时我们的java项目一开始只支持一种数据库,也没有规划好框架以后怎么适配多数据库。很多SQL写的不够标准,而且写死在代码里面。导致修改起来难度大。
所以我们需要在jdbc层拦截并翻译SQL。
数据层有很多种实现,这里只讲JdbcTemplate的例子。
注入代码
如果你项目所有的JdbcTemplate是通过@Autowired注入。那么恭喜你,你大可自己写一个类继承JdbcTemplate,在这个类里面重写方法,然后通过@Bean 将JdbcTemplate注入成你的类。
如果不是的话,只能去spring仓库找JdbcTemplate的源码,然后将其复制到你的项目里面,必须保证包名类名完全一样,再在这个JdbcTemplate里面写你的处理逻辑。
JdbcTemplate源码地址 https://github.com/spring-projects/spring-framework/blob/master/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java
注意!需要选择你项目引用的spring一样的版本分支。
上面处理完还有一个问题,就是当我们insert后要返回自增的主键一般通过下面的写法
KeyHolder keyHolder = new GeneratedKeyHolder();
PreparedStatementCreator preparedStatementCreator = con -> {
PreparedStatement ps = con.prepareStatement("INSERT INTO table1 (col1, col2, col3) VALUES ('1','2','3')", Statement.RETURN_GENERATED_KEYS);
return ps;
};
int id = keyHolder.getKey().intValue()
由于con.prepareStatement是在各个数据库的jdbc里面实现的,有些库有源码有些库没有源码,改起来就很麻烦了,我也只能在调用con.prepareStatement的代码前面添加翻译逻辑了,真是丑陋的代码。
翻译SQL
怎么翻译是个技术活,这里不展开,只是讲讲思路。
- 1.替换字符串是最简单的处理方式,但是你必须保证你是通过参数化调用sql,而且替换后不会导致语义分歧。
- 2.通过解析SQL的AST抽象语法树来自行拼接SQL字符串。这里推荐druid来生成AST,代码如下
List<SQLStatement> statementList = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
虽然druid也提供了翻译功能,但是目前只支持oracle转mysql(由于业务不需要,所以具体效果我也没有测试过)。
String result = SQLUtils.translateOracleToMySql(sql);
你也可以参考https://github.com/alibaba/druid/blob/master/src/main/java/com/alibaba/druid/sql/dialect/oracle/visitor/OracleToMySqlOutputVisitor.java 实现自己的Visitor
最后给开发人员一点建议
虽然项目开始不需要适配多数据库,但是有些代码规范执行好,对后续维护还是有好处的。
- 1.能用注入就不要用new实例化。
- 2.SQL不要写死在代码里面。尽量抽离到外部文件中,并且文件命名友好。
- 3.写的SQL尽量标准点,越标准,迁移越简单。
- 4.函数语法[xxx()]是处理非标准SQL的最好语法。能用函数尽量用函数,各个数据库的函数虽然不太一致,但是不一致的可以通过自定义函数来抹平差异。