需求
需求是这样的,我们有一个数据服务平台的产品,用户先将数据源信息保存到平台上,一个数据源可以提供多个接口服务,而每个接口服务在数据库中存一个具有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 {@Resourceprivate SqlSessionFactory sqlSessionFactory;/*** 查询列表** @param params* @return*/@Overridepublic 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 配置到 MyBatisXMLMapperBuilder 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*/@Overridepublic 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 {@Resourceprivate 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并生成的动态的代理对象。两者使用起来是没有什么差别的。
当然以上只是平台一部分功能,像参数和结果集的提取,参数的校验,接口的鉴权、精细化控制、限流、负载均衡、熔断、幂等性,数据的分页处理、缓存等,在这里就不一一赘述了。