使用MyBatis和Oracle存储过程进行批量更新

时间:2019-07-01 11:23:35

标签: java oracle stored-procedures mybatis

研究使用MyBatis和Oracle存储过程进行批量数据库更新的旧式解决方案。当前版本的Mapper与此类似

@Mapper
public interface MyMapper {
   void doUpdate(@Param("in") Map<String, List> in,
                 @Param("out") Map<String, List> out);
}

这个想法是提供一个长度相同的列表的列表,其中字段值作为“ in”参数,以将这些列表用作调用这样的存储过程的参数

 <select id="doUpdate"
        statementType="CALLABLE">
    <![CDATA[
    {
    CALL doUpdate(
            #{in.field1, mode=IN, jdbcType=ARRAY, jdbcTypeName=MY_TYPE,     typeHandler=NumberTypeHandler },
            #{in.field2, mode=IN, jdbcType=ARRAY, jdbcTypeName=MY_TYPE,     typeHandler=NumberTypeHandler},
            #{in.field3, mode=IN, jdbcType=ARRAY, jdbcTypeName=MY_TYPE,     typeHandler=NumberTypeHandler},
            #{out.field1, mode=IN, jdbcType=ARRAY, jdbcTypeName=MY_TYPE,    typeHandler=NumberTypeHandler })}]]>
 </select>

然后在存储过程中遍历这些数组以逐一更新实体。

问题是我必须初始化所有Maps / Arrays并在调用之前手动填充它们,并且还必须将结果手动转换回Java对象。因此,现在看起来太复杂和冗长,我正在尝试找到更准确的解决方案。

所以问题是:有没有更简单的方法可以通过MyBatis向存储过程提供对象列表?我尝试使用parameterMap,但实际情况下,我的实际参数类型应为List,并且该List的元素应为自定义Java对象,因此我没有设法使用此方法找到合适的解决方案。

1 个答案:

答案 0 :(得分:0)

一个过程可以使用表类型参数,您可以编写一个自定义类型处理程序来执行转换。

使用具体对象可能更容易解释。
我将使用MY_TYPE代替S_USER_OBJ ...

create or replace type S_USER_OBJ as object (
  id integer,
  name varchar(20)
);

...一张桌子...

create table users (
  id integer,
  name varchar(20)
);

...还有一个POJO。

public class User {
  private Integer id;
  private String name;
  // setter/getter
}

这是新类型,它是S_USER_OBJ的集合。

create or replace type S_USER_OBJ_LIST as table of S_USER_OBJ;

该过程可以将表类型作为参数。例如

create or replace procedure doUpdate(
  user_list in S_USER_OBJ_LIST,
  user_out out S_USER_OBJ_LIST
) is
begin
  -- process IN param
  for i in user_list.first .. user_list.last loop
    update users
      set name = user_list(i).name)
      where id = user_list(i).id;
  end loop;
  -- set OUT param
  select * bulk collect into user_out
    from (
      select S_USER_OBJ(u.id, u.name) from users u
    );
end;

映射器如下所示:

void doUpdate(
  @Param("users") List<User> users,
  @Param("outParam") Map<String, ?> outParam);
<update id="doUpdate" statementType="CALLABLE">
  {call doUpdate(
    #{users,typeHandler=pkg.UserListTypeHandler},
    #{outParam.outUsers,jdbcType=ARRAY,jdbcTypeName=S_USER_OBJ_LIST,mode=OUT,typeHandler=pkg.UserListTypeHandler}
  )}
</update>

UserListTypeHandler是一个自定义类型处理程序,可将List<User>ARRAY的{​​{1}}之间进行转换。

STRUCT

使用该方法的代码如下所示。

import java.math.BigDecimal;
import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import oracle.jdbc.driver.OracleConnection;

public class UserListTypeHandler extends
    BaseTypeHandler<List<User>>{

  @Override
  public void setNonNullParameter(
      PreparedStatement ps, int i, List<User> parameter,
      JdbcType jdbcType) throws SQLException {
    Connection conn = ps.getConnection();
    List<Struct> structs = new ArrayList<Struct>();
    for (int idx = 0; idx < parameter.size(); idx++) {
      User user = parameter.get(idx);
      Object[] result = { user.getId(), user.getName() };
      structs.add(conn.createStruct("S_USER_OBJ", result));
    }
    Array array = ((OracleConnection) conn)
      .createOracleArray("S_USER_OBJ_LIST",
      structs.toArray());
    ps.setArray(i, array);
    array.free();
  }

  @Override
  public List<User> getNullableResult(
      CallableStatement cs,
      int columnIndex) throws SQLException {
    List<User> result = new ArrayList<>();
    Array array = cs.getArray(columnIndex);
    Object[] objs = (Object[]) array.getArray();
    for (Object obj : objs) {
      Object[] attrs = ((Struct) obj).getAttributes();
      result.add(new User(
        ((BigDecimal) attrs[0]).intValue(),
        (String) attrs[1]));
    }
    array.free();
    return result;
  }
  ...
}

对于Map<String, ?> outParam = new HashMap<>(); mapper.doUpdate(userList, outParam); List<User> outUsers = outParam.get("outUsers"); 参数,还有另一种使用refcursor和结果映射的方法。
在mapper语句中,如下指定OUT参数。

OUT

结果图非常简单。

#{outParam.outUsers,jdbcType=CURSOR,javaType=java.sql.ResultSet,mode=OUT,resultMap=userRM}

在此过程中,将OUT参数声明为<resultMap type="test.User" id="userRM"> <id property="id" column="id" /> <result property="name" column="name" /> </resultMap>

SYS_REFCURSOR

这是一个可执行的演示:
https://github.com/harawata/mybatis-issues/tree/master/so-56834806