9.3 调用存储过程
MyBatis 对存储过程提供了调用功能,并且支持对游标数据的转化功能,让我们在这里学习它们。
9.3.1 存储过程 in 和 out 参数的使用
MyBatis 支持存储过程,并且对它们进行封装。让我们看看 MyBatis 是如何实现对存储过程支持的。这里笔者采用了 Oracle 数据库,我们先来新建一个存储过程,如代码清单 9-10 所示。
代码清单 9-10:新建存储过程
CREATE OR REPLACE PROCEDURE count_role( p_role_name in varchar, count_total out int, exec_date out date ) IS BEGIN select count(*) into count_total from t_role where role_name like '%' ||p_role_name || '%' ; select sysdate into exec_date from dual; End
我们新建了一个按角色名称模糊查询的存储过程,这里的存储过程中存在一个 in 参数,两个 out 参数。in 参数是一个输入的参数,而 out 参数则是一个输出的参数。首先我们把模糊查询的结果保存到 count_total 这个 out 参数中,并且将当前日期保存在 out_date 这个参数中,然后结束过程。
我们首先定义一个 POJO 来反映这个存储过程的参数,如代码清单 9-11 所示。
代码清单 9-11:存储过程参数 POJO
public class ProcedurePojo { private String roleName = null; private int result = 0; private Date execDate; public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public int getResult() { return result; } public void setResult(int result) { this.result = result; } public Date getExecDate() { return execDate; } public void setExecDate(Date execDate) { this.execDate = execDate; } }
这里我们可以轻易看到 POJO 和存储过程参数的对应关系。那么还需要在 XML 映射器中配置它们,如代码清单 9-12 所示。
代码清单 9-12:配置存储过程
<select id="count" parameterType="com.learn.chapter9.pojo.ProcedurePojo" statementType="CALLABLE"> {call count_role ( #{roleName, mode=IN, jdbcType=VARCHAR}, #{result, mode=OUT, jdbcType=INTEGER}, #{execDate, mode=OUT, jdbcType=DATE} )} </select>
这里的 statementType="CALLABLE"告诉 MyBatis 我们将用存储过程的方式去执行它。如果不声明它,程序将会抛出异常。参数定义,mode=IN 的时候为输入参数,mode=OUT 的时候为输出参数,jdbcType 定义为数据库的类型。当我们这样写的时候,MyBatis 会帮我们回填 result 和 execDate。当然也可以使用 Map,但是我们不推荐那么做,因为 Map 将失去业务可读性。为了测试需要请声明一下 Mapper 接口,如代码清单 9-13 所示。
代码清单 9-13:存储过程接口
public interface ProcedureMapper { public void count(ProcedurePojo pojo); }
这样我们便能够测试一下这个接口,让我们看看测试代码,如代码清单 9-14 所示。
代码清单 9-14:测试存储过程 in 和 out 参数
sqlSession= SqlSessionFactoryUtil.openSqlSession(); ProcedureMapper procedureMapper = sqlSession.getMapper(ProcedureMapper.class); int result = 0; ProcedurePojo pojo = new ProcedurePojo(); pojo.setRoleName("role"); procedureMapper.count(pojo); System.err.println(pojo.getRoleName() + "\t" + pojo.getResult() + "\t"); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); System.err.println(df.format(pojo.getExecDate()));
我们这里只是传递一个 roleName 参数到过程中,再通过接口调度过程,最后打印一下返回的其他属性。让我们看看测试结果。
==> Preparing: {call count_role ( ?, ?, ? )} DEBUG 2015-12-21 10:36:53,731 org.apache.ibatis.logging.jdbc.BaseJdbc Logger: ==> Parameters: role(String) role 6 2015-12-21 DEBUG 2015-12-21 10:36:53,750 org.apache.ibatis.transaction.jdbc. JdbcTransaction: Resetting autocommit to true on JDBC Connection [oracle.jdbc.driver.T4CConnection@3d99d22e] ......
我们通过日志可以发现过程已经被我们调用了,而结果也正确打印出来了。这样我们就可以轻松使用存储过程来获取我们想要的数据了。
9.3.2 存储过程游标
我们在 9.3.1 节看到了 in 和 out 参数的使用过程,还是比较简单的,但是在存储过程中往往还需要返回游标。MyBatis 对存储过程的游标提供了一个 JdbcType=CURSOR 的支持,它可以智能地把游标读到的数据通过配置的映射关系映射到某个类型的 POJO 上,方便了我们的使用,让我们看看它的用法。
这里我们使用角色名称 p_role_name 查询角色,但是往往查询需要考虑分页的效果,所以新加了 p_start 和 p_end 参数来确定从数据库的第几行到第几行,从而确定分页。而分页还需要一个总数,我们用存储过程的 out 参数 r_count 记录,而查询到的具体角色用游标 ref_cur 来记录,遍历它将可以得到对应查询出来的记录。编写过程如代码清单 9-15 所示。
代码清单 9-15:使用游标的存储过程
create or replace procedure find_role( p_role_name in varchar, p_start in int, p_end in int, r_count out int, ref_cur out sys_refcursor) AS BEGIN select count(*) into r_count from t_role where role_name like '%' ||p_role_name|| '%' ; open ref_cur for select role_no, role_name, note, create_date from (SELECT role_no, role_name, note, create_date, rownum as row1 FROM t_role a where a.role_name like '%' ||p_role_name|| '%' and rownum <=p_end) where row1> p_start; end find_role;
这里统计了满足条件的总数,并用游标打开返回满足条件的模糊查询记录。这个游标中每行的数据需要一个 POJO 进行保存,我们先定义游标返回的 POJO,如代码清单 9-16 所示。
代码清单 9-16:定义游标返回的 POJO
public class TRole { private String roleNo; private String roleName; private String note; private Date createDate; public String getRoleNo() { return roleNo; } public void setRoleNo(String roleNo) { this.roleNo = roleNo; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } }
显然 POJO 和游标的返回值是一一对应的。但是返回的不单单是游标,还有另外一个总数和其他参数。那么让我们在游标的 POJO 的基础上再定义一个 POJO,如代码清单 9-17 所示。
代码清单 9-17:游标参数和结果 POJO
public class PageRole{ private int start; private int end; private int count; private String roleName; private List<TRole> roleList; public int getStart() { return start; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public void setStart(int start) { this.start = start; } public int getEnd() { return end; } public void setEnd(int end) { this.end = end; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public List<TRole> getRoleList() { return roleList; } public void setRoleList(List<TRole> roleList) { this.roleList = roleList; } }
这里我们是以 roleList 来保存游标的数据的,count 代表总数。然后定义游标返回的映射规则,如代码清单 9-18 所示。
代码清单 9-18:定义游标返回的映射规则
<resultMap id="roleMap" type="com.learn.chapter9.pojo.TRole"> <id property="roleName" column="ROLE_NAME" /> <result property="roleNo" column="USER_NAME" /> <result property="note" column="NOTE" /> <result property="createDate" column="CREATE_DATE"/> </resultMap> <select id="findRole" parameterType="com.learn.chapter9.pojo. PageRole" statementType="CALLABLE"> {call find_role( #{roleName, mode=IN, jdbcType=VARCHAR}, #{start, mode=IN, jdbcType=INTEGER}, #{end, mode=IN, jdbcType=INTEGER}, #{count, mode=OUT, jdbcType=INTEGER}, #{roleList,mode=OUT,jdbcType=CURSOR, javaType=ResultSet,resultMap= roleMap} )} </select>
首先我们定义了一个 roleMap,它能满足游标返回对 POJO 的映射,这样我们就在过程游标输出参数里面定义了。
jdbcType=CURSOR, javaType=ResultSet,resultMap=roleMap
此时 MyBatis 就知道游标的数据集可以依赖于 roleMap 定义的规则去转化为 roleList 列表对象。其他的参数规则与 9.3.1 节讲到的 in 和 out 参数规则相同。
最后,我们定义接口,如代码清单 9-19 所示。
代码清单 9-19:定义游标映射器接口
public void findRole(PageRole pageRole);
现在测试这段代码,如代码清单 9-20 所示。
代码清单 9-20:测试带游标的存储过程
sqlSession= SqlSessionFactoryUtil.openSqlSession(); ProcedureMapper procedureMapper = sqlSession.getMapper (ProcedureMapper.class); int result = 0; PageRole pageRole = new PageRole(); pageRole.setRoleName("role"); pageRole.setStart(0); pageRole.setEnd(5); pageRole.setCount(0); pageRole.setRoleList(new ArrayList<TRole>()); procedureMapper.findRole(pageRole); System.err.println(pageRole.getCount()); for (TRole role : pageRole.getRoleList()) { System.err.println("role_no=>" + role.getRoleNo() + ", role_name = >" + role.getRoleName()); }
运行一下代码,我们便可以得到想要的结果。
DEBUG 2015-12-21 13:44:04,475 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j. Slf4jImpl' adapter. ...... DEBUG 2015-12-21 13:44:04,801 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: {call find_role( ?, ?, ?, ?, ? )} DEBUG 2015-12-21 13:44:04,874 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: role(String), 0(Integer), 5(Integer) 6 role_no=>null, role_name = >role_name1 role_no=>null, role_name = >role_name2 role_no=>null, role_name = >role_name3 role_no=>null, role_name = >role_name4 role_no=>null, role_name = >role_name5 ...... DEBUG 2015-12-21 13:44:04,914 org.apache.ibatis.datasource.pooled. PooledDataSource: Returned connection 215145189 to pool.
一个游标便被映射成了我们想要的 POJO 对象返回给调用者了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论