需求
需求是这样的,我们有一个数据服务平台的产品,用户先将数据源信息保存到平台上,一个数据源可以提供多个接口服务,而每个接口服务在数据库中存一个具有mybatis语法的sql片段。这样的话,对于一些简单的业务只需要编写好sql保存到数据库中然后提供一个接口文档就可以实现了。我们只需要对外提供一个http接口,http接口参数是接口服务ID和sql的参数。
保存在数据库中的xml片段大致如下:
select * from student as s left join score as sc on s.sno = sc.sno
<where>
<if sname != null and sname != ''>
s.sname = #{sname}
</if>
</where>
思路
从数据库中获取到xml文本片段后,如果手动去解析mybatis的各种标签和其中的OGNL表达式的话,无疑是一件很累人的事情。所以换种思路,既然功能是按照mybatis的规范来做的,那么mybatis有没有现成的API提供给我们使用呢?当然是有的,示例如下:
实现
-
先定义一套查询规范,也就是Mapper接口
import java.util.List; import java.util.Map; /** * @author m */ public interface CommonMapper { /** * 查询列表 * * @param params * @return */ List<Map<String, Object>> selectList(Map<String, Object> params); /** * 查询单个 * * @param params * @return */ Map<String, Object> selectOne(Map<String, Object> params); }
-
再定义一套业务规范,也就是Service接口
import java.util.List; import java.util.Map; /** * @author m */ public interface CommonService { /** * 查询列表 * * @param sql * @param params * @return */ List<Map<String, Object>> selectList(String sql, Map<String, Object> params); /** * 查询单个 * * @param params * @return */ Map<String, Object> selectOne(Map<String, Object> params); }
-
实现业务规范,在这里我们只实现查询列表的功能,其他的也就类似了。这里是没有实现数据源的切换和分页功能的,可以查询一下mybatis怎么切换数据源、PageHelper怎么去适配多数据源下的分页功能。当然也可以在评论区留言或者给我私信的。
import com.demo.common.mapper.CommonMapper; import com.demo.common.service.CommonService; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Map; @Service public class CommonServiceImpl implements CommonService { @Resource private SqlSessionFactory sqlSessionFactory; /** * 查询列表 * * @param params * @return */ @Override public List<Map<String, Object>> selectList(String sql, Map<String, Object> params) { String sqlXml = wrapSql2SelectListXml(sql); InputStream inputStream = new ByteArrayInputStream(sqlXml.getBytes(StandardCharsets.UTF_8)); // 手动加载 XML 配置到 MyBatis XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, sqlSessionFactory.getConfiguration(), "dynamic-mapper", sqlSessionFactory.getConfiguration().getSqlFragments()); xmlMapperBuilder.parse(); // 获取 SqlSession 并执行查询 try (SqlSession session = sqlSessionFactory.openSession()) { CommonMapper mapper = session.getMapper(CommonMapper.class); return mapper.selectList(params); } catch (Exception e) { e.printStackTrace(); } return Collections.emptyList(); } /** * 查询单个 * * @param params * @return */ @Override public Map<String, Object> selectOne(Map<String, Object> params) { // TODO 待实现 return Collections.emptyMap(); } /** * 将sql封装为一个完整的xml,对应CommonMapper::selectList接口 * * @param sql * @return */ private String wrapSql2SelectListXml(String sql) { String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">" + "<mapper namespace='com.demo.common.mapper.CommonMapper'>" + " <select id='selectList' resultType='java.util.Map'>" + sql + " </select>" + "</mapper>"; return xml; } }
-
测试,经过测试是OK的。
import com.demo.common.service.CommonService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; import java.util.Map; @RestController @RequestMapping("test") public class TestController { @Resource private CommonService commonService; @RequestMapping("list") public Object test(@RequestParam Map<String, Object> params) { List<Map<String, Object>> maps = commonService.selectList("SELECT * FROM datagov.dg_alg WHERE alg_id = #{algId}", params); System.out.println(maps); return maps; } }
总结
通过以上实现,不难发现,以上解决方案无非就是改变了Mybatis生成代理类的时机而已。在平常,Mybatis是通过扫描指定的xml目录和mapper接口,然后在容器启动时生成代理对象。而此时,我们是在方法执行的时候动态获取的xml并生成的动态的代理对象。两者使用起来是没有什么差别的。
当然以上只是平台一部分功能,像参数和结果集的提取,参数的校验,接口的鉴权、精细化控制、限流、负载均衡、熔断、幂等性,数据的分页处理、缓存等,在这里就不一一赘述了。