目标:将接口配置的sql存到mysql,还要支持mybaits的标签。
思路:在mybatisplus加载完成后。从数据把sql查出来后模仿mybatis加载xml配置文件过程加载。用asm框架生成mapper的接口中的方法。这样实现了配置mybatis。
注意:因为mybatis架构中存放xml配置几乎都是hashMap,所以在加载注意后面多线程读取或加载会有线程不安全的问题。翻了mybaits源码也没找到解决方案。
代码::https://gitee.com/kingqiu/springbootmybaitsplus
效果:
主要代码(文末有完整代码下载)
1、加载配置主要代码
package com.example.springbootmybaitsplus.asm;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.example.springbootmybaitsplus.asm.DTO.ChartConfigDTO;
import com.example.springbootmybaitsplus.asm.DTO.MapperClassDTO;
import com.example.springbootmybaitsplus.asm.DTO.MapperMethonDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
@Primary
@Slf4j
public class TolocalNewSqlToMybaitsService {
@Autowired
private MapperCreate mapperCreate;
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
// @Bean
// public SqlSessionTemplate getSqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
// log.info("构造SqlSessionTemplate");
// return new MySqlSessionTemplate(sqlSessionFactory);
// }
/*
* 执行sql
* */
public List<Map<String, Object>> dealChartSql(ChartConfigDTO chartConfigDTO, Object requestBody) {
try {
//获取mapper的class
Class<?> mapperClass = mapperCreate.loadClass(MybaitsSqlContext.mapperPathName);
if (ObjectUtil.isNull(mapperClass)) {
throw new RuntimeException("图标查询未初始化!");
}
Method query = mapperClass.
getMethod(chartConfigDTO.getTheme(), HashMap.class);
SqlSession sqlSession = getSqlSession();
Object mapper = sqlSession.getMapper(mapperClass);
List<Map<String, Object>> invoke = (List<Map<String, Object>>) query.invoke(mapper, requestBody);
sqlSession.close();
return invoke;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
* 刷新配置
*
* */
public void refashSqlConfig(List<ChartConfigDTO> chartConfigList) throws Exception {
//获取所有配置
log.info("获取到图标配置{}", chartConfigList);
//拼接出mapper的配置文件
String mapperXml = createMapperXML(chartConfigList);
//获取mybatisplus配置
SqlSession sqlSession = getSqlSession();
Configuration configuration = sqlSession.getConfiguration();
//加载自定义的sql
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
new ByteArrayInputStream(mapperXml.getBytes()),
configuration,
"resource",
configuration.getSqlFragments());
mapperParser.parse();
// 使用自定义的ClassLoader生成mapper并注册到mybatis中
List<MapperMethonDTO> mapperMethonDTOList = chartConfigList.stream().map(item ->
new MapperMethonDTO(item.getTheme(),
MybaitsSqlContext.descriptor,
MybaitsSqlContext.signature)).
collect(Collectors.toList());
byte[] byteClassData = mapperCreate.dump(new MapperClassDTO(MybaitsSqlContext.mapperName,
MybaitsSqlContext.mapperPath, mapperMethonDTOList));
Class<?> mapperClass = mapperCreate.defineClass(
MybaitsSqlContext.mapperPathName,
byteClassData);
configuration.addMapper(mapperClass);
sqlSession.close();
}
//创建mapper的xml文件
private String createMapperXML(List<ChartConfigDTO> chartConfigList) {
StringBuffer mapperSqlXML = new StringBuffer();
chartConfigList.forEach(item -> {
mapperSqlXML.append(StrUtil.format(MybaitsSqlContext.selectInitXml,
item.getTheme(), item.getChartSql()));
});
String mapperXml = StrUtil.format(MybaitsSqlContext.mapperInitXML,
MybaitsSqlContext.mapperPath,
MybaitsSqlContext.mapperName,
mapperSqlXML);
log.info("刷新mapper配置文件{}", mapperXml);
return mapperXml;
}
// /*
// * 启动后自动加载
// *
// * */
// @Override
// public void run(ApplicationArguments args) throws Exception {
// refashSqlConfig();
// }
/*
*
* 获取mybaits的SqlSession
* */
private SqlSession getSqlSession() {
return SqlSessionUtils.getSqlSession(sqlSessionTemplate.getSqlSessionFactory(),
sqlSessionTemplate.getExecutorType(),
sqlSessionTemplate.getPersistenceExceptionTranslator());
}
}
2、生成mapper接口代码
package com.example.springbootmybaitsplus.asm;
import cn.hutool.core.util.StrUtil;
import com.example.springbootmybaitsplus.asm.DTO.MapperClassDTO;
import com.example.springbootmybaitsplus.asm.DTO.MapperMethonDTO;
import lombok.extern.slf4j.Slf4j;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.springframework.stereotype.Component;
import static org.objectweb.asm.Opcodes.*;
@Component
@Slf4j
public class MapperCreate extends ClassLoader {
public byte[] dump(MapperClassDTO mapperClassDTO) throws Exception {
//生成class写入流
ClassWriter cw = new ClassWriter(0);
//设置class的基本信息
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
StrUtil.replace(mapperClassDTO.getPath()+"."+mapperClassDTO.getNameClase(), ".", "/"),
null,
"java/lang/Object",
null);
cw.visitSource(StrUtil.format("{}.java", mapperClassDTO.getNameClase()), null);
//根据chart配置信息生成方法
for (MapperMethonDTO mapperMethonDTO : mapperClassDTO.getMapperMethonDTOList()) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT,
mapperMethonDTO.getNameMethon(),
mapperMethonDTO.getDescriptor(),
mapperMethonDTO.getSignature(),
null);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
public Class<?> defineClass(String name, byte[] b) {
// ClassLoader是个抽象类,而ClassLoader.defineClass 方法是protected的
// 所以我们需要定义一个子类将这个方法暴露出来
return super.defineClass(name, b, 0, b.length);
}
}