Mybatis调用存储过程返回查询结果的通用接口(Oracle 存储过程 游标)

背景:

        在通常的业务处理中,需要前端展示一个表单,后端通常要写配套的返回实体类 VO好将Mybatis查询的结构映射为实体类,即便是简单的单表查询也需要写常用的三层结构。如果返回的结构需要变动,例如加上一列,前后端几乎每个层级都需要改动添加上新的返回字段。

        本文主要是为了业务大量存放于存储过程的项目服务,如果是单纯为了方便可以稍作修改,将Mybatis调用存储过程,修改为普通的SQL。其余的思路也是相通的。

如下,Java通常的三层结构对于SQL较为简单或者业务写在存储过程的查询实在是太繁重了!! 

//实体类
public class SysUser extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 用户ID */
    @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号")
    private Long userId;
....
//Service接口
public interface ISysUserService
{
    /**
     * 根据条件分页查询用户列表
....
//imp
@Service
public class SysUserServiceImpl implements ISysUserService
{
    private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
    @Autowired
    private SysUserMapper userMapper;
....
//Controller
@RestController
@RequestMapping("/system/user")
public class SysUserController extends BaseController
{
    @Autowired
    private ISysUserService userService;
    @GetMapping("/list")
    public TableDataInfo list(SysUser user)
    {
        startPage();
        List<SysUser> list = userService.selectUserList(user);
        return getDataTable(list);
    }
....
//Mapper
public interface SysUserMapper
    public List<SysUser> selectUserList(SysUser sysUser);
//XML
	<sql id="selectUserVo">
        select * from sys_user u
    </sql>

存储过程 

将SQL写入存储过程中,Oracle版本为11g,注释为测试存储过程多参数的情况

create or replace package pkg_package

as

type type_cursor is ref cursor;

end;
create or replace procedure p_temp_procedure

(
--v_a varchar,
--v_b varchar,
cur_out_arg out pkg_package.type_cursor

)

is

begin

open cur_out_arg for select 
--v_a,v_b  ,
a.* from sys_user a;

end;

JDBC

        先使用JDBC进行测试,注释部分为存储过程单参数的情况。使用JDBC预编译,将需要的变量填入?之中 。将获取的游标转换为List<Map> 的形式,和平常的List<User>一样,只不过将User干掉修改为Map类型。

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        List<Map<String, Object>> tableData = new ArrayList<Map<String, Object>>();
        List<String> columnName = new ArrayList<>();
        JSONObject res =new JSONObject();
        String driver = "oracle.jdbc.driver.OracleDriver";
        String url = "jdbc:oracle:thin:@127.0.0.1:1521/orcl";
        String username = "yam";
        String password = "******";
        Class.forName(driver);
        Connection con = DriverManager.getConnection(url, username, password);
//        CallableStatement callableStatement = con.prepareCall("{call p_temp_procedure(?)}");
        CallableStatement callableStatement = con.prepareCall("{call p_temp_procedure(?,?,?)}");
            callableStatement.setInt(1, 0);
            callableStatement.setInt(2, 10);
        callableStatement.registerOutParameter(3, OracleTypes.CURSOR);
//        callableStatement.registerOutParameter(1, OracleTypes.CURSOR);
        callableStatement.execute();
//        ResultSet rs = ((OracleCallableStatement) callableStatement).getCursor(1);
        ResultSet rs = ((OracleCallableStatement) callableStatement).getCursor(3);
        int cl = rs.getMetaData().getColumnCount();
        //columnName 可以配合前端如vantd之类的框架,进行表单的展示,不用前端再去写动态表单。根据实际情况进行改造
        for (int i = 0; i < cl; i++) {
            String key = rs.getMetaData().getColumnName(i + 1);
            columnName.add(key);
        }
        res.put("columnName",columnName);
        while (rs.next()) {
            Map<String, Object> each = new HashMap<String, Object>();
            for (int i = 0; i < cl; i++) {
                String key = rs.getMetaData().getColumnName(i + 1);
                Object val = rs.getObject(key);
                each.put(key, val);
            }
            tableData.add(each);
        }
        res.put("tableData",tableData);
        System.out.println("输出的结果是:" + res);
    }

      输出的结果是: {
    "columnName": [":B2", ":B1", "USER_ID", "DEPT_ID", ...],
    "tableData": [{
        "DEPT_ID": 103,
        "PHONENUMBER": "15888888888",
        "SEX": "1",
        ":B1": "10",
        "USER_ID": 1...
    },{
        "SEX": "0",
        "UPDATE_BY": "admin",
        ":B1": "10",
        "USER_ID": 100...
    }]

        上面输出字段不一样是因为推测是HashMap的value不能为空,所以在put的时候需要判断一下val是否为空,如果为空put “”即可。

        实践之后发现Map中是可以有存有null的value,而转换成json的时候json中也存在。原因是fastjson生成json后他的toString方法中Null属性不显示。

        解决方案有两个,一个是刚刚说的给value赋空字符串

                if (val ==null){
                    val="";
                }

        另一个是在toString时候指定参数为空也要写入。这个方法只能fastJson使用,fastJson2无效。

        String str = JSONObject.toJSONString(res,SerializerFeature.WriteMapNullValue);
        System.out.println("输出的结果是:" + str);

         如果不要toString的话,可以无视这些,直接debug看。

 

        

 Mybatis

        Mapper ,返回和输入的参数请务必定义为Map<String, Object>,不要@parm。本质是对输入的Map进行修改,添加新的Key-value。所以必须要有输入的Map!

    public Map<String, Object> getTable(Map<String, Object> result);

        XML,定义一个resultMap类型为HashMap进行映射,因为靠变量拼接存储过程名和变量名,所以只能用${},但是因为存储过程是已经在数据库中编译过的,所以不必担心SQL注入 。输出游标基本是定死,请不要修改。

    <resultMap id="cursorMap" type="java.util.HashMap">
    </resultMap>    
    <select id="getTable" statementType="CALLABLE" parameterType="map" >
        {call ${procedureName}(
                ${procedureVar},
                #{cur_out_arg, jdbcType=CURSOR, mode=OUT, javaType=ResultSet, resultMap=cursorMap}
            )}
    </select>

         Controller层,将存储过程的过程名字和变量数组传入,

    @GetMapping("/list")
    public R<JSONObject> userList() throws ClassNotFoundException, SQLException {
        JSONObject res =new JSONObject();
        Map tableData=new HashMap();
//    procedureName 为需要调用的存储过程名
        tableData.put("procedureName","p_temp_procedure");
//    procedureVar为需要调用的存储过程的输入变量
        tableData.put("procedureVar","09492,9512");
//    调用写好的通用Mapper,通过Mybatis的映射将结果写入输入的tableData中
        autoDBtoJSONMapper.getTable(tableData);
        res.put("tableData",tableData);
        return R.ok(res);
    }

        但是返回的Map怎么size不一样? 有东西什么东西漏了?

        

         和前面JDBC的转换一样,Mybatis也同样有问题,但是问题原因不一样,他在处理结果的Map中就直接缺少了空的字段。

        Mybatis查询数据映射数据类型使用的是Map,当字段值为null时,mybatis映射返回字段的时候会忽略。为了解决这个问题,先修改Mybatis的配置文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 全局参数 -->
    <settings>
        <!-- 使全局的映射器启用或禁用缓存 -->
        <setting name="cacheEnabled"             value="true"   />
        <!-- 允许JDBC 支持自动生成主键 -->
        <setting name="useGeneratedKeys"         value="false"   />
        <!-- 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新 -->
        <setting name="defaultExecutorType"      value="SIMPLE" />
		<!-- 指定 MyBatis 所用日志的具体实现 -->
        <setting name="logImpl"                  value="SLF4J"  />
        <!-- 使用驼峰命名法转换字段 -->
		<!-- <setting name="mapUnderscoreToCamelCase" value="true"/> -->
        <!-- 修改映射Map,返回所有值,即便是Null -->
        <setting name="callSettersOnNulls" value="true"/>
	</settings>

</configuration>

   经过Mybatis的配置后如果查询结果为空则传入null。如果要直接toString 的话也会遇到上面JDBC输出的问题,不过这里是放在controller中,直接把json返回给前端所以并无影响。

 

Mybatis调用Oracle存储过程返回多个游标的步骤如下: 1. 定义存储过程Oracle数据库中定义存储过程,该存储过程需要返回多个游标,例如: ``` CREATE OR REPLACE PROCEDURE get_multi_cursor( p_id IN NUMBER, p_cursor1 OUT SYS_REFCURSOR, p_cursor2 OUT SYS_REFCURSOR ) AS BEGIN OPEN p_cursor1 FOR SELECT * FROM table1 WHERE id = p_id; OPEN p_cursor2 FOR SELECT * FROM table2 WHERE id = p_id; END; ``` 2. 创建Java类 创建一个Java类,用于封装存储过程的参数和返回结果,例如: ```java public class MultiCursorResult { private List<Table1> table1List; private List<Table2> table2List; // getter and setter } ``` 3. 定义Mapper接口Mybatis的Mapper接口中定义调用存储过程的方法,例如: ```java public interface MultiCursorMapper { void getMultiCursor( @Param("id") Integer id, @Param("cursor1") ResultSet[] cursor1, @Param("cursor2") ResultSet[] cursor2 ); } ``` 4. 编写Mapper XML 在Mapper XML中编写调用存储过程SQL语句,例如: ```xml <select id="getMultiCursor" statementType="CALLABLE"> {call get_multi_cursor(#{id},#{cursor1,mode=OUT,jdbcType=CURSOR,javaType=ResultSet},#{cursor2,mode=OUT,jdbcType=CURSOR,javaType=ResultSet})} </select> ``` 5. 调用Mapper方法 在Java程序中调用Mapper方法,例如: ```java MultiCursorMapper mapper = sqlSession.getMapper(MultiCursorMapper.class); ResultSet[] cursor1 = new ResultSet[1]; ResultSet[] cursor2 = new ResultSet[1]; mapper.getMultiCursor(1, cursor1, cursor2); List<Table1> table1List = new ArrayList<>(); while (cursor1[0].next()) { Table1 table1 = new Table1(); table1.setId(cursor1[0].getInt("id")); table1.setName(cursor1[0].getString("name")); table1List.add(table1); } List<Table2> table2List = new ArrayList<>(); while (cursor2[0].next()) { Table2 table2 = new Table2(); table2.setId(cursor2[0].getInt("id")); table2.setName(cursor2[0].getString("name")); table2List.add(table2); } MultiCursorResult result = new MultiCursorResult(); result.setTable1List(table1List); result.setTable2List(table2List); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值