导读:当我们在实际开发中遇到对接多渠道多接口场景时需频繁改动持久层代码并重启服务,这显得比较繁琐而且不太灵活。这时我们可考虑实现灵活可配置的 SQL 。思路是将 SQL 语句保存到数据库中,当要用时取出并运行,而当业务发生变化时只需修改 SQL 语句即可,不用改动代码及重启服务,这样会显得更加灵活。下面本文将分享笔者的实现灵活可配置 SQL 的思路,希望对各位有所帮助。
MyBatis 实现灵活可配置 SQL
笔者实现灵活可配置 SQL 主要借助以下几个 MyBatis 的核心组件:
- Configuration:用于描述 MyBatis 主配置文件信息,MyBatis 框架在启动时会加载主配置文件,将配置信息转换为 Configuration 对象。
- SqlSession:面向用户的 API,是 MyBatis 与数据库交互的接口。
- MappedStatement:用于描述 SQL 配置信息,MyBatis 框架启动时,XML 文件或者注解配置的 SQL 信息会被转换为 MappedStatement 对象注册到 Configuration 组件中。
// 灵活可配置的 SQL Demo
public class DynamicTest {
@Autowired
private SqlSessionFactory sqlSessionFactory;
private Configuration configuration;
private SqlSession session;
@Before
public void setUp() {
this.configuration = sqlSessionFactory.getConfiguration();
this.session = sqlSessionFactory.openSession();
}
@Test
public void insertTest() throws IOException {
String boundSql =
"INSERT INTO datahub_crontask.gateway_channel(" +
"channel_code" +
",mode" +
") values(" +
"#{channel_code}" +
",#{mode}" +
")";
String statementId = "com.xxx.launch.mapper.test@" + Md5Util.md5(boundSql);
XMLLanguageDriver languageDriver = new XMLLanguageDriver();
SqlSource source = languageDriver.createSqlSource(configuration,"<script>" + boundSql + "</script>", MapperMethod.ParamMap.class);
ResultMap resultMap = new ResultMap.Builder(configuration, statementId, Long.class, new ArrayList()).build();
MappedStatement ms = new MappedStatement.Builder(configuration, statementId, source, SqlCommandType.INSERT)
.resultMaps(Lists.newArrayList(resultMap))
.build();
configuration.addMappedStatement(ms);
this.session.insert(statementId, ImmutableMap.of(
"channel_code", "TEST",
"mode", "DB"
));
}
}
步骤解析:
- 配置数据源 DataSourceConfig,并将 SqlSessionFactory 放入 IOC 容器中。(关于 DataSourceConfig 配置里就不再赘述)。
- 使用 XMLLanguageDriver 构建 SqlSource。XMLLanguageDriver 为 XML 语言驱动,实现了动态 SQL 的功能,也就是说可以利用 MyBatis 提供的 XML 标签(常用的、等标签)结合OGNL表达式语法来实现动态的条件判断。
- 注册 MapperStatement 到 Configuration 中。
- 通过 SqlSessionFactory 创建一个 SqlSession 并执行操作。
这里分析一下以下这句代码:
SqlSource source = languageDriver.createSqlSource(configuration,"<script>" + boundSql + "</script>", MapperMethod.ParamMap.class);
我们先看看 MyBatis 源码 XMLLanguageDriver.createSqlSource
createSqlSource() 方法用于处理 Java 注解中配置的 SQL 信息,该方法中首先判断 SQL 配置是否是以 <script> 标签开头。如果是则以 XML 方式处理 Java 注解中配置的 SQL 信息;否则只是简单处理,替换 SQL 中的全局参数即可。如果 SQL 中仍然包含 ${} 参数占位符,则 SQL 语句仍然需要根据传递的参数动态生成,所以使用 DynamicSqlSource 对象描述 SQL 资源,否则说明 SQL 语句不需要根据参数动态生成,使用 RawSqlSource 对象描述 SQL 资源。
基于以上知识,整体灵活可配置 SQL的设计思路如下:
- 将对应渠道服务的 SQL 存入到数据库中
- 调用渠道服务后将其返回结果转为 Map 类型
- 应用以上的核心代码执行 SQL 将结果集插入到数据库中,这样当有新的渠道服务时我们只需建表及新增一条 SQL语句,而不需要改动持久层代码及重启服务。
拓展
对于 SQL 语句,我们可以做一些拓展的行为。如借助 Druid 的 SQL Parse 与 Visitor 解析处理 SQL,实现 UDF 自定义处理方法等。
@Test
public void testDruidVisitor() {
String statement = "select * from datahub_crontask.gateway_channel";
SQLStatement sqlStatement = SQLUtils.parseSingleStatement(statement, JdbcConstants.MYSQL);
StringBuilder dynamicSql = new StringBuilder();
MySqlOutputVisitor visitor = new MySqlOutputVisitor(dynamicSql);
sqlStatement.accept(visitor);
}
解析:
- 使用 Druid SQL Parser 解析 SQL 语句并形成 AST 语法树
- 使用 Visitor 遍历 AST 语法树,每个元素被 Visitor 中对应的 visit() 处理
这里简单提供了一些思路,感兴趣的朋友可以查阅 Druid 相关知识并结合应用。
// Druid 使用手册
https://www.bookstack.cn/read/Druid/561a8f205346f80b.md
最后
以上就是笔者对于关于灵活可配置 SQL 的设计思路,主要借助 MyBatis 的一些核心组件来实现。希望对各位有所帮助,如果实现逻辑有纰漏或有更好的实现方式欢迎提出,共同讨论和进步。
感谢您的阅读,如果喜欢本文欢迎关注和转发,本头条号将坚持持续分享IT技术知识。对于文章内容有其他想法或意见建议等,欢迎提出共同讨论共同进步。
参考文章:
https://www.bookstack.cn/read/Druid/561a8f205346f80b.md
https://www.cnblogs.com/Howinfun/p/12973902.html
https://blog.csdn.net/qq_25104587/article/details/105757507