MyBatis调用自定义类型存储过程
因业务及性能需要,有时会将一些复杂的数据查询场景通过数据库存储过程实现,通过Java+Mybatis实现调用。本文档描述了这种方案的基本使用。其中使用了自定义类型、自定义类型数组作为入参,且能返回自定义类型数组数据给调用方。
以下以Oracle数据库为例,做以说明。
数据库对象创建
自定义对象类型
-- 事件数据
type EVENT_DATA as object
(
ID number,
TERMINAL_ID varchar2(10),
VIN varchar2(17),
ICID varchar2(7),
VEHICLESTATE number,
LAT number,
LNG number,
MILEAGE number,
DATATIME timestamp,
SAVETIME timestamp,
STATE number,
DO_UPDATE number,
SALE_ID number,
CAR_STATE number
);
-- 事件参数
type EVENT_PARAM as object (
saleId number(10),
vin varchar2(17),
seq number(10),
status number(1),
time varchar2(20),
doUpdate number(1),
carState number(1),
icCard varchar2(20)
);
复制代码
自定义对象数组类型
-- 事件数据数组
type EVENT_DATA_ARR as table of EVENT_DATA;
-- 事件参数数组
type EVENT_PARAM_ARR as table of EVENT_PARAM
复制代码
自定义存储过程
-- 拼接查询sql
CREATE OR REPLACE procedure query_iccard_sql(vinParam in varchar2, seqId in number, dateTime in timestamp, out_return out SYS_REFCURSOR) is
str clob;
begin
-- 拼接sql
str:= 'select ID,
TERMINAL_ID,
VIN,
ICID,
VEHICLESTATE,
LAT,LNG,
MILEAGE,
DATATIME,
SAVETIME,
:seqId
from t_s_iccardevent e
where e.vin =:vinParam
and not exists (select 1 from beibos.t_b_Lifecycle_Aftersale a where a.vin=:vinParam
and e.id = a.SRC_START_SEQ and e.id != a.SRC_END_SEQ)
and vehiclestate in (3, 4) and e.STATE = 1 ';
-- 追加时间条件
if dateTime is not null then
str := str || ' and e.DATATIME >= :dateTime and e.id > :seqId order by e.id asc ';
dbms_output.put_line('sql = ' || str);
open out_return for str using seqId, vinParam, vinParam, dateTime, seqId;
else
str := str || ' and e.id > :seqId order by e.id asc ';
dbms_output.put_line('sql = ' || str);
dbms_output.put_line('');
open out_return for str using seqId, vinParam, vinParam, seqId;
end if;
end;
-- 查询事件数据
CREATE OR REPLACE procedure query_iccard_event(i_event_params in event_param_arr, o_event_datas out event_data_arr) is
-- 存储生成的sql游标
cur1 SYS_REFCURSOR;
-- IC卡事件数据存储对象
iccardevent t_s_iccardevent%rowtype;
-- 计数器
count_num number;
-- 数组索引
i number;
--j number;
-- 事件数据
ed event_data;
-- 事件数据集合
edArr event_data_arr := event_data_arr();
--filterSrcEvent event_data_arr := event_data_arr();
begin
-- 游标索引初始化
i:= 1;
--j:= 1;
-- 计数器初始化
count_num:=1;
FOR i in 1..i_event_params.count LOOP
BEGIN
query_iccard_sql(i_event_params(i).vin, i_event_params(i).seq,
to_date(i_event_params(i).time, 'yyyy-MM-dd hh24:mi:ss'), cur1);
loop
fetch cur1
into iccardevent;
exit when cur1%notfound;
edArr.extend;
ed := event_data(null, null, null, null, null, null, null, null, null, null, null, null, null, null);
ed.id := iccardevent.id;
ed.terminal_id := iccardevent.TERMINAL_ID;
ed.vin := iccardevent.VIN;
ed.icid := iccardevent.ICID;
ed.vehiclestate := iccardevent.VEHICLESTATE;
ed.lat := iccardevent.LAT;
ed.lng := iccardevent.LNG;
ed.mileage := iccardevent.MILEAGE;
ed.datatime := iccardevent.DATATIME;
ed.savetime := iccardevent.SAVETIME;
ed.state := iccardevent.state;
ed.do_update := i_event_params(i).doUpdate;
ed.sale_id := i_event_params(i).saleId;
ed.car_state := i_event_params(i).carState;
edArr(count_num) := ed;
count_num := count_num+1;
end loop;
close cur1;
END;
END LOOP;
o_event_datas:=edArr;
end;
-- 测试调用
declare
ep event_param := event_param(null, null, null, null, null, null, null, null);
ep2 event_param := event_param(null, null, null, null, null, null, null, null);
epArr event_param_arr := event_param_arr();
edArr event_data_arr := event_data_arr();
iq number;
begin
epArr.extend(2);
ep.vin := 'LBZ44DEB2HA012563';
ep.seq := 11945;
ep.status := 4;
ep.time := '2018-04-19 10:51:07';
ep.doupdate := 1;
ep.carState := 3;
ep.saleId := 130131;
ep.icCard := null;
epArr(1) := ep;
ep2.vin := 'LBZ44DEB2HA012563';
ep2.seq := 11925;
ep2.status := 4;
ep2.time := '2018-04-19 09:57:27';
ep2.doupdate := 1;
ep2.carState := 3;
ep2.saleId := 130138;
ep2.icCard := null;
epArr(2) := ep2;
query_iccard_event(epArr, edArr);
iq := 1;
for iq in 1 .. edArr.count LOOP
dbms_output.put_line('ID = ' || edArr(iq).id
|| ', TERMINAL_ID = '
|| edArr(iq).terminal_id
|| ', VIN = '
|| edArr(iq).vin
|| ', ICID = '
|| edArr(iq).icid
|| ', VEHICLESTATE = '
|| edArr(iq).vehiclestate
|| ', LAT = '
|| edArr(iq).lat
|| ', LNG = '
|| edArr(iq).lng
|| ', MILEAGE = '
|| edArr(iq).MILEAGE
|| ', DATATIME = '
|| to_char(edArr(iq).DATATIME, 'yyyy-MM-dd HH:mi:ss')
|| ', SAVETIME = '
|| to_char(edArr(iq).SAVETIME, 'yyyy-MM-dd HH:mi:ss')
|| ', STATE = '
|| edArr(iq).STATE
|| ', DO_UPDATE = '
|| edArr(iq).DO_UPDATE
|| ', SALE_ID = '
|| edArr(iq).SALE_ID
|| ', CAR_STATE = '
|| edArr(iq).CAR_STATE);
END LOOP;
end;
/
复制代码
数据库对象创建部分到此已经完成。
Java代码部分
自定义类型解析器
事件查询参数数组解析器
用于处理事件查询参数映射。重点关注setParameter方法实现。
@MappedJdbcTypes(JdbcType.ARRAY)
public class EventParamArrTypeHandler implements TypeHandler {
@Override
public void setParameter(PreparedStatement ps, int i, Object paramsObj, JdbcType jdbcType) throws SQLException {
Connection conn = null;
try {
ArrayList params = (ArrayList)paramsObj;
if(CollectionUtils.isNotEmpty(params)) {
DataSource dataSource = SpringContextUtil.getBean("masterDataSource");
if(dataSource != null) {
conn = dataSource.getConnection();
}
if(null != conn) {
conn = conn.unwrap(OracleConnection.class);
STRUCT[] structArr = new STRUCT[params.size()];
for(int j = 0; j < params.size(); j++) {
EventParam param = params.get(j);
// EVENT_PARAM 为数据库自定义类型名称,需大写
StructDescriptor sd = StructDescriptor .createDescriptor("EVENT_PARAM", conn);
Object[] o = new Object[8];
o[0] = param.getSaleId();
o[1] = param.getVin();
o[2] = param.getSeq();
o[3] = param.getStatus();
o[4] = param.getTime();
o[5] = param.getDoUpdate();
o[6] = param.getCarState();
o[7] = param.getIcCard();
structArr[j] = new STRUCT(sd, conn, o);
}
// EVENT_PARAM_ARR 为数据库自定义数组类型名称,需大写
ArrayDescriptor arrayDes = ArrayDescriptor.createDescriptor("EVENT_PARAM_ARR", conn);
ARRAY array = new ARRAY(arrayDes, conn, structArr);
ps.setArray(i, array);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if(null != conn){
conn.close();
}
}
}
@Override
public Object getResult(ResultSet resultSet, String s) throws SQLException {
return null;
}
@Override
public Object getResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public Object getResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
复制代码
事件数据数组解析器
用于处理存储过程返回值业务。重点关注getResult方法实现。
@MappedJdbcTypes(JdbcType.ARRAY)
public class EventDataArrTypeHandler implements TypeHandler {
@Override
public void setParameter(PreparedStatement ps, int i, Object obj, JdbcType jdbcType) throws SQLException {
}
@Override
public Object getResult(ResultSet resultSet, String s) throws SQLException {
return null;
}
@Override
public Object getResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public Object getResult(CallableStatement cs, int i) throws SQLException {
Connection conn = null;
List objList = new ArrayList<>();
try {
conn = cs.getConnection();
if(null != conn) {
ResultSet rs = cs.getArray(i).getResultSet();
while (rs.next()) {
Datum[] d = ((STRUCT)rs.getObject(2)).getOracleAttributes();
IcCardEvent event = new IcCardEvent();
event.setId(d[0].longValue());
event.setTerminalId(d[1].stringValue());
event.setVin(d[2].stringValue());
event.setIcid(d[3].stringValue());
event.setVehiclestate(d[4].intValue());
event.setLat(d[5].longValue());
event.setLng(d[6].longValue());
event.setMileage(d[7].longValue());
event.setDatatime(d[8].dateValue());
event.setSavetime(d[9].dateValue());
event.setSaleId(d[10].intValue());
event.setDoUpdate(d[11].intValue());
event.setCarState(d[12].intValue());
objList.add(event);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if(null != conn){
conn.close();
}
}
return objList;
}
}
复制代码
Mapper接口声明
Mapper接口声明。
List queryEventByParams(@Param("map") Map map) ;
复制代码
Mapper XML配置
Mapper SQL映射部分。 需通过typeHandler
属性配置具体的解析器。jdbcType类型使用ARRAY
。
<select id="queryEventByParams" statementType="CALLABLE" resultMap="BaseResultMap">
{
call query_iccard_event(
#{map.params,mode=IN,jdbcType=ARRAY,typeHandler=com.hopechart.micro.typehandler.EventParamArrTypeHandler},
#{map.events,mode=OUT,jdbcType=ARRAY, jdbcTypeName="EVENT_DATA_ARR", typeHandler=com.hopechart.micro.typehandler.EventDataArrTypeHandler}
)
}
select>
复制代码
业务接口调用
public List queryEventByParams(List params) throws BusinessException {
try {
List events = new ArrayList<>();
Map map = new HashMap();
map.put("params", params);
map.put("events", events);
// 调用
icCardEventMapper.queryEventByParams(map);
events = (List) map.get("events");
return events;
} catch (Exception e) {
LOGGER.error("查询原始IC卡时间数据异常", e);
throw e;
}
}
复制代码
至此,在Java中通过Mybatis调用自定义存储过程,实现自定义类型数组输入、自定义类型数组输出的方法已经阐述完毕。 希望此文档对有需要的同学能带来帮助,如有不妥之处还请指正,谢谢!
作者:代江