使用场景: 让mybatis帮忙管理mapper.xml文件,使用时感知不到conf.xml的存在; 灵活获取mapper文件中指定动态sql。
使用示例:
@Test
public void test() {
//只用指定mapper文件的路径即可
MappersParser parser = new MappersParser("classpath:mappers/*.xml");
//解析文件
parser.parse();
/**
* class Param{
* private Integer id;
*
* public Integer getId(){
* return id;
* }
* public Integer setId(Integer id){
* this.id = id;
* }
* }
*/
//设置参数
Param param = new Param();
param.setId(12);
//根据mapper文件中的id获取指定的sql, 如:<select id="queryOne" resultType="java.util.Map">
MappersParser.SqlModel sql = parser.getAndFlatParam("queryOne", param);
System.out.println("sql="+sql.getSql());
System.out.println("params="+sql.getParams());
}
目录结构:
|--resources
|----mappers
|----demo.xml
demo.xml 文件实例
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.demo.abc.hello">
<!--suppress MybatisMapperXmlInspection -->
<select id="queryOne" resultType="java.util.Map">
select * from demo where id=#{id}
</select>
</mapper>
MapperParser.java 文件内容:
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author mrli
* @date 2020/8/3
*/
public class MappersParser {
private static final Logger log = LogManager.getLogger(MappersParser.class);
private String mappersPath;
private Configuration configuration;
public MappersParser(String mappersPath){
this.mappersPath = mappersPath;
}
public MappersParser(Configuration configuration){
this.configuration = configuration;
}
public void parse(){
try {
this.configuration = innerParse();
} catch (Exception e) {
e.printStackTrace();
}
}
public Configuration getConfiguration() {
return configuration;
}
/**
* 构造配置文件
*/
private Configuration innerParse() throws Exception{
if(StringUtils.isBlank(mappersPath)){
throw new RuntimeException("mapperLocations is null");
}
StringBuilder sbd = new StringBuilder();
sbd.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
sbd.append("<!DOCTYPE configuration PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-config.dtd\">");
sbd.append("<configuration>");
//下划线转驼峰
sbd.append("<settings>");
sbd.append("<setting name=\"mapUnderscoreToCamelCase\" value=\"true\"/>");
sbd.append("</settings>");
sbd.append(" <mappers>");
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
Resource[] res = resourceResolver.getResources(mappersPath);
for (Resource resource : res) {
String fileClasspath = pickClasspath(resource.getURI());
sbd.append("<mapper resource=\""+fileClasspath+"\"/>");
}
sbd.append(" </mappers>");
sbd.append("</configuration>");
XMLConfigBuilder builder = new XMLConfigBuilder(new StringReader(sbd.toString()));
return builder.parse();
}
private static String pickClasspath(URI fileUri){
String uri = fileUri.toString();
uri = StringUtils.substringAfterLast(uri, "/classes");
return StringUtils.substringAfter(uri, "/");
}
/**
* 将对象解析为,sql语句中对应的变量
* @param id
* @param param
* @return
*/
public SqlModel getAndFlatParam(String id, Object param){
log.debug("[DB] id="+id+", databseId="+configuration.getDatabaseId());
MappedStatement ms = configuration.getMappedStatement(id);
Assert.notNull(ms, "未找到id为"+id+"的sql定义");
BoundSql boundSql = ms.getBoundSql(param);
Assert.notNull(boundSql, "未找到id为"+id+",且参数类型为"+param.getClass()+"的sql定义");
String sql = boundSql.getSql();
Class<?> clazz = param.getClass();
List<Object> paramList = new ArrayList<>();
if(Map.class.isAssignableFrom(clazz)){
Map mapTmp = (Map)param;
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
String property = parameterMapping.getProperty();
paramList.add(mapTmp.get(property));
}
}else{
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
String property = parameterMapping.getProperty();
try{
Field field = getDeclaredFieldInFamily(clazz, property);
if(field==null){
throw new RuntimeException("can't found property "+property+" from Class "+clazz);
}
field.setAccessible(true);
paramList.add(field.get(param));
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
Object[] params = paramList.toArray(new Object[0]);
return new SqlModel(sql, params);
}
//反射获取属性
public static final Field getDeclaredFieldInFamily(Class target, String fieldName) throws Exception{
Field field = getDeclaredField(target, fieldName);
if(field!=null){
return field;
}
Class clazz = target.getSuperclass();
while (clazz!=Object.class){
field = getDeclaredField(clazz, fieldName);
if(field!=null){
return field;
}
clazz = clazz.getSuperclass();
}
return null;
}
public static final Field getDeclaredField(Class target, String fieldName){
Field[] fields = target.getDeclaredFields();
for (Field field : fields) {
if(field.getName().equals(fieldName)){
return field;
}
}
return null;
}
public static class SqlModel{
private String sql;
private Object[] params;
public SqlModel(String sql, Object[] params) {
this.sql = sql;
this.params = params;
}
public String getSql() {
return this.sql;
}
public Object[] getParams() {
return this.params;
}
public void setSql(String sql) {
this.sql = sql;
}
public void setParams(Object[] params) {
this.params = params;
}
public String toString() {
return "MappersParser.SqlModel(sql=" + this.getSql() +
", params=" + java.util.Arrays.deepToString(this.getParams()) + ")";
}
}
}