有很多的接口都只是執行個SQL查詢之后就直接返回給前端,那么我們能不能把這些SQL保存在數據庫中,調用一個固定的接口就能根據傳參查詢出想要的數據呢?或者當為了加減個字段就得修改代碼重啟服務的痛苦能不能減少點呢?下面就是方案。
調用直接傳入SQL語句(可以選擇存數據庫)和參數,SQL語句寫法和在XML內的寫法保持一致即可,包括Mybatis標簽等等,參數選擇使用通用的Map,可以從接口接收任何參數,方法的返回值是List<Map>。
<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); } }