Mybatis 動態執行SQL語句

FH-Admin發表於2021-07-29

有很多的介面都只是執行個SQL查詢之後就直接返回給前端,那麼我們能不能把這些SQL儲存在資料庫中,呼叫一個固定的介面就能根據傳參查詢出想要的資料呢?或者當為了加減個欄位就得修改程式碼重啟服務的痛苦能不能減少點呢?下面就是方案。

呼叫直接傳入SQL語句(可以選擇存資料庫)和引數,SQL語句寫法和在XML內的寫法保持一致即可,包括Mybatis標籤等等,引數選擇使用通用的Map,可以從介面接收任何引數,方法的返回值是List。

<dependency>
   <groupId>org.ow2.asm</groupId>
   <artifactId>asm</artifactId>
   <version>7.0</version>
</dependency>
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;

//from fhadmin.cn
@Component
@Slf4j
public class SqlExecutor {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public void parserString(String originSql,Map<String,Object> param) {
        StringBuilder builder = new StringBuilder();
        builder.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
        builder.append("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\r\n");
        builder.append("<mapper namespace=\"cn.video.asm.TestMapper\">\r\n");
        builder.append("     <select id=\"queryById\"  resultType=\"java.util.Map\">\r\n");
        builder.append(originSql);
        builder.append("     </select>\r\n");
        builder.append("</mapper>");
        InputStream inputStream = new ByteArrayInputStream(builder.toString().getBytes());
        Configuration baseConfig = sqlSessionFactory.getConfiguration();
        // 不能使用原有的config物件載入,否則下次就不會重複載入導致傳入的SQL語句不能切換
        // 也可以在這裡指定資料來源,從對應的資料來源做查詢動作
        Configuration configuration = new Configuration(baseConfig.getEnvironment());
        String resource = "resource";
        ErrorContext.instance().resource(resource);
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,resource ,configuration.getSqlFragments());
        mapperParser.parse();
        SqlSession sqlSessionXML = new DefaultSqlSessionFactory(configuration).openSession();
        Object result = null;
        try {
            // 使用自定義的ClassLoader
            MyClassLoader loader = new MyClassLoader();
            // 生成二進位制位元組碼
            byte[] bytes = MyClassLoader.dump();
            // 載入我們生成的 Mapper類
            Class<?> clazz = loader.defineClass("cn.video.asm.TestMapper", bytes);
            // 將生成的類物件載入到configuration中
            configuration.addMapper(clazz);
            Method query = clazz.getMethod("queryById", Map.class);
            // 這裡就是透過類物件從configuration中獲取對應的Mapper
            Object testMapper = sqlSessionXML.getMapper(clazz);
            result = query.invoke(testMapper, param);
        } catch (Exception e) {
            log.error("",e);
        }
        System.out.println("dyn : " + result);
    }
}
package cn.video.common;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;

import static jdk.internal.org.objectweb.asm.Opcodes.*;

//from fhadmin.cn
public class MyClassLoader extends ClassLoader {

    public static byte[] dump() {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(52, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "cn/video/asm/TestMapper", null, "java/lang/Object", null);
        cw.visitSource("TestMapper.java", null);
        {
            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "queryById", "(Ljava/util/Map;)Ljava/util/List;", "(Ljava/util/Map;)Ljava/util/List<Ljava/lang/Object;>;", null);
            mv.visitEnd();
        }
        cw.visitEnd();
        return cw.toByteArray();
    }

    public Class<?> defineClass(String name, byte[] b) {
        // ClassLoader是個抽象類,而ClassLoader.defineClass 方法是protected的
        // 所以我們需要定義一個子類將這個方法暴露出來
        Class<?> clazz = super.findLoadedClass(name);
        if (clazz != null) {
            return clazz;
        }
        return super.defineClass(name, b, 0, b.length);
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章