MyBatis - 动态调整xml配置

MyBatis 另类用法:动态载入 xml 配置,获取渲染好的 SQL 语句。

业务场景:

在制作报表系统的时候,经常会有一个很头疼的事情:
明明只是写一个 SQL 的事情,但是系统各种配置,客户用不明白,时间久了,我们自己忘得差不多;

这时候,我们就会去思考:要不直接开放写 SQL 的权限?
客户想怎么查怎么查,我们不再需要考虑“报表系统怎么做”,将工作重心放到数据权限就好了。

mybatis 是我们常用的工具,我们能不能通过什么手段让它变成动态的?
比如说:客户想增加一个查询,上传一份 xml 配置,我们动态载入。

几个疑问:

前面提出的解决方案肯定是可行的。

你可能会有疑问,xml不是要对应一个 java 类么(Mapper类),客户上传 xml,没有对应的 java 类不会报错嘛?
mybatis 可以只有 xml,java 类的确不是必要的。

xml 没有对应的 java 类的话,怎么调用查询呢?
直接获取 mybatis 渲染好的 SQL,将渲染好的 SQL 直接交给 JDBC 执行。

方案缺陷:

对于同名的 xml,mybatis 只会加载一次,第二次加载不会触发更新;
比如:我们有一份 xml 配置,叫做 test.xml,加载到 Configuration 之后,
我们修改 xml 文件的内容,再加载这一份文件, Configuration 是不会再去读取这个文件的。

或许可以使用 java 反射技术暴力修改,不过我还是推荐重新 new 一个 Configuration 对象,避免产生错误逻辑。

import cn.seaboot.commons.file.IOUtils;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;
import java.io.InputStream;

/**
 * mybatis 配置信息读取工具
 * <p>
 * mybatis 不支持动态修改配置,源码上直接做了限制,详见: {@link XMLMapperBuilder#parse()}
 * 如果期望 mybatis 支持动态修改配置,修改 xml 内容后,new 一个新的 Configuration 即可。
 *
 * @author Mr.css
 * @version 2023-06-06 16:18
 */
public class MyBatisConfiguration {
    private final Logger logger = LoggerFactory.getLogger(MyBatisConfiguration.class);
    private Configuration configuration = new Configuration();

    /**
     * 用于加载 xml 配置文件
     */
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

    /**
     * 清除所有配置信息
     */
    public void clean() {
        configuration = null;
        configuration = new Configuration();
    }

    /**
     * 获取 xml中的配置信息
     *
     * @param id sql ID
     * @return {{@link MappedStatement}}
     */
    public MappedStatement getMappedStatement(String id) {
        return configuration.getMappedStatement(id);
    }

    /**
     * 获取xml中的配置信息
     *
     * @param id sql ID
     * @return {{@link MappedStatement}}
     */
    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        return configuration.getMappedStatement(id, validateIncompleteStatements);
    }

    /**
     * 加载
     *
     * @param locations xml位置信息
     * @throws IOException 内容读取失败
     */
    public void resolve(String... locations) throws IOException {
        for (String location : locations) {
            Resource[] resources = this.getResources(location);
            for (Resource resource : resources) {
                logger.debug("resolve resource: " + resource.toString());
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
                        configuration, resource.toString(), configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            }
        }
    }

    /**
     * 载入配置
     *
     * @param name 资源名称,资源名称相同的情况下,不会覆盖历史配置
     * @param is   包含 xml 信息的输入流
     */
    public void resolve(String name, InputStream is) {
        logger.debug("resolve resource: " + name);
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(is,
                configuration, name, configuration.getSqlFragments());
        xmlMapperBuilder.parse();
    }

    /**
     * 载入配置
     *
     * @param name    资源名称,资源名称相同的情况下,不会覆盖历史配置
     * @param content xml 字符串内容
     */
    public void resolve(String name, String content) {
        this.resolve(name, IOUtils.stringToInputStream(content));
    }

    /**
     * 获取资源
     *
     * @param location 配置路径
     * @return Resource
     */
    private Resource[] getResources(String location) throws IOException {
        return resourceResolver.getResources(location);
    }
}

 

/**
 * @author Mr.css
 * @version 2021-01-13 17:40
 */
public class TestScanner {

    public static void main(String[] args) throws IOException {
        MyBatisConfiguration configuration = new MyBatisConfiguration();
        configuration.resolve("sys_menu", IOUtils.openFileInputStream("D:\\seaboot\\seaboot\\mybatis-proxy\\src\\main\\resources\\mapper/sys_menu.xml"));

        MappedStatement statement = configuration.getMappedStatement("cn.seaboot.db.test.dao.MenuDao.selectById");

        // 构建参数
        Map<String, Object> params = new HashMap<>();
        params.put("menuLevel", "3");
        params.put("pid", "1");
        params.put("path", "2");

        // 获取渲染的 SQL
        BoundSql sql = statement.getBoundSql(params);
        System.out.println(sql.getSql());
        System.out.println(sql.getParameterMappings());
        System.out.println(statement.getResultMaps().get(0).getResultMappings());
    }
}

 

posted on 2023-06-07 09:27  疯狂的妞妞  阅读(243)  评论(0编辑  收藏  举报

导航