Устаревание - это отговорка от выполнения всего этого: использование массивов и структур Oracle, но оно все равно будет работать.
Короче говоря, для сопоставления типов java и java с пользовательскими типами Oracle необходимо использовать пользовательский Mybatis TypeHandler.
Код внутри обработчика типов в основном JDBC.
И когда дело доходит до манипулирования массивами и структурами, тогда оно сильно зависит от API драйвера поставщика БД (вообще не стандартного).
Существуют тонны содержимого для настройки Mybatis, так что здесь для вида конкретной части.
Следующий абстрактный класс позволяет отображать java Array / Collection в тип Oracle Array / Table.
Просто укажите имя типа на стороне Oracle в конкретной реализации: например, List <-> TYPE ARRAY_INT AS TABLE OF NUMBER.
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.util.Collection;
import java.util.Locale;
import java.util.ResourceBundle;
import oracle.jdbc.OracleConnection;
import oracle.sql.ARRAY;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.log4j.Logger;
import org.jboss.jca.adapters.jdbc.WrappedConnection; // in case when the connection is managed by the container (jboss in this case)
public abstract class AbstractArrayTypeHandler<T> extends BaseTypeHandler<Object> {
private static final Logger LOGGER = Logger.getLogger(AbstractArrayTypeHandler.class);
protected static final ResourceBundle CONFIG = ResourceBundle.getBundle("config", Locale.ENGLISH);
protected static final String SCHEMA_NAME = CONFIG.getString("schema.name");
protected static final ResourceBundle DB_STRUCTURE = ResourceBundle.getBundle("dbStructure", Locale.ENGLISH);
protected static final String TYPE_PACKAGE_NAME = DB_STRUCTURE.getString("type.package.name");
protected abstract String getSqlType();
@SuppressWarnings("rawtypes")
@Override
public void setNonNullParameter(final PreparedStatement stmt, final int index, final Object parameter,
final JdbcType jdbcType) throws SQLException {
Object[] javaArray;
if (null == parameter) {
throw new IllegalArgumentException("Parameter must not be null");
} else {
if (parameter.getClass().isArray()) {
javaArray = (Object[]) parameter;
} else if (parameter instanceof Collection) {
javaArray = ((Collection) parameter).toArray();
} else {
throw new IllegalArgumentException("Parameter must be array or collection");
}
final Connection statementConnection = stmt.getConnection();
Connection underlyingConnection = statementConnection;
if (statementConnection instanceof WrappedConnection) { // unwrap the managed connection when necessary
final WrappedConnection wrapper = (WrappedConnection) statementConnection;
LOGGER.debug("Wrapped connection type: " + wrapper.getClass().getName());
underlyingConnection = wrapper.getUnderlyingConnection();
}
LOGGER.debug("Underlying connection type: " + underlyingConnection.getClass().getName());
final OracleConnection oracleConnection = (OracleConnection) underlyingConnection;
/* java.sqlConnection.createArrayOf is not supported by Oracle Driver */
final String type = String.format("%s.%s.%s", SCHEMA_NAME, TYPE_PACKAGE_NAME, this.getSqlType());
final Array array = createArray(oracleConnection, type, javaArray);
LOGGER.debug(String.format("ARRAY type '%s' of %d elements created", type, javaArray.length));
stmt.setArray(index, array);
LOGGER.debug("statement array Set");
}
}
protected ARRAY createArray(final OracleConnection oracleConnection, final String type, final Object[] javaArray) throws SQLException {
return oracleConnection.createARRAY(type, javaArray);
}
@Override
public Object getNullableResult(final ResultSet resultSet, final String columnName) throws SQLException {
LOGGER.debug("getNullableResult - resultSet/columnName");
final Array array = resultSet.getArray(columnName);
return array.getArray();
}
@Override
public Object getNullableResult(final ResultSet resultSet, final int columnIndex) throws SQLException {
LOGGER.debug("getNullableResult - resultSet/columnIndex");
final Array array = resultSet.getArray(columnIndex);
return array.getArray();
}
@Override
public Object getNullableResult(final CallableStatement stmt, final int columnIndex) throws SQLException {
LOGGER.debug("getNullableResult - callableStatement/columnIndex");
final Array array = stmt.getArray(columnIndex);
return array.getArray();
}
}
Здесь для сопоставления массивов пользовательских типов / структур Oracle:
import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.List;
import oracle.jdbc.OracleConnection;
import oracle.sql.ARRAY;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
import org.apache.log4j.Logger;
@SuppressWarnings({"deprecation"})
public abstract class AbstractObjectArrayTypeHandler<T> extends AbstractArrayTypeHandler<T> {
private static final Logger LOGGER = Logger.getLogger(ListbeanTypeHandler.class);
public AbstractObjectArrayTypeHandler() {
super();
}
protected abstract String getOracleObjectType();
protected abstract Class<T> arrayElementClass();
protected abstract Object[] buildStructAttributes(Object object);
@Override
protected ARRAY createArray(OracleConnection oracleConnection, String oracleArrayType, Object[] javaArray) throws SQLException {
StructDescriptor itemDescriptor = createDescriptor(oracleConnection);
List<Struct> structList = new ArrayList<Struct>(javaArray.length);
Class<T> arrayElementClass = arrayElementClass();
for (Object object : javaArray) {
if (null != object && arrayElementClass.isAssignableFrom(object.getClass())) {
Object[] structAttributes = buildStructAttributes(object);
structList.add(new STRUCT(itemDescriptor, oracleConnection, structAttributes));
} else throw new IllegalArgumentException("javaArray element must be instance of " + arrayElementClass.getName() + "but is: " + (null == object ? "null" : object.getClass().getName()));
}
return super.createArray(oracleConnection, oracleArrayType, structList.toArray());
}
private StructDescriptor createDescriptor(OracleConnection oracleConnection) throws SQLException {
final String typeName = typeFullQualifiedName();
StructDescriptor descriptor = StructDescriptor.createDescriptor(typeName, oracleConnection);
LOGGER.debug(String.format("Object descriptor for type '%s' created", typeName));
return descriptor;
}
private String typeFullQualifiedName() {
return String.format("%s.%s", SCHEMA_NAME, this.getOracleObjectType());
}
@Override
public Object getNullableResult(final ResultSet resultSet, final String columnName) throws SQLException {
final Array array = resultSet.getArray(columnName);
return readOracleStructList(array);
}
@Override
public Object getNullableResult(final ResultSet resultSet, final int columnIndex) throws SQLException {
final Array array = resultSet.getArray(columnIndex);
return readOracleStructList(array);
}
@Override
public Object getNullableResult(final CallableStatement stmt, final int columnIndex) throws SQLException {
final Array array = stmt.getArray(columnIndex);
return readOracleStructList(array);
}
protected List<T> readOracleStructList(Array sqlArray) throws SQLException {
if (null == sqlArray)
return null;
Object object = sqlArray.getArray();
Object[] structObjectArray;
return null == object ? null : readNotNullStructList(object);
}
private List<T> readNotNullStructList(Object object) throws SQLException {
if (object.getClass().isArray())
return readArrayStructList((Object[]) object);
else throw new IllegalArgumentException("Returned value is not an array");
}
private List<T> readArrayStructList(Object[] structObjectArray) throws SQLException {
List<T> list = new ArrayList<T>(structObjectArray.length);
for (Object structObject : structObjectArray) {
if (structObject instanceof Struct) {
Struct struct = (Struct) structObject;
Object[] attributes = struct.getAttributes();
T javaObject = buildJavaObject(attributes);
list.add(javaObject);
} else throw new IllegalArgumentException("Expected array element of type Struct, but got: " + structObjectArray.getClass());
}
return list;
}
protected abstract T buildJavaObject(Object[] attributes);
}
Пример для работы с бетоном:
import java.math.BigDecimal;
import java.util.Collection;
import org.apache.ibatis.type.MappedTypes;
import com.example.CustomBean;
@MappedTypes({CustomBean[].class,Collection.class})
public class ListCustomBeanTypeHandler extends AbstractObjectArrayTypeHandler<CustomBean> {
@Override
protected final String getSqlType() {
return DB_STRUCTURE.getString("type.array.customBean"); // replace with full qualified name of array type declared in oracle
}
protected final String getOracleObjectType() {
return DB_STRUCTURE.getString("type.object.customBean"); // replace with full qualified name of struct/object type declared in oracle
}
protected final Class<CustomBean> arrayElementClass() {
return CustomBean.class;
}
protected final Object[] buildStructAttributes(Object object) {
CustomBean bean = (CustomBean) object;
Object[] structAttributes = new Object[] {bean.getProperty1(), bean.getProperty2(), bean.getProperty3(), null /* N/A for input */};
return structAttributes;
}
protected CustomBean buildJavaObject(Object[] attributes) {
CustomBean bean = new CustomBean();
int i = 0;
BigDecimal property1 = (BigDecimal) attributes[i++];
if (property1 != null)
bean.setProperty1(property1.intValue());
BigDecimal property2 = (BigDecimal) attributes[i++];
if (property2 != null)
bean.setProperty2(property2.longValue());
bean.setProperty3(((BigDecimal) attributes[i++]).intValue());
bean.setReturnCode(((BigDecimal) attributes[i++]).intValue());
bean.setReturnMessage((String) attributes[i++]);
return bean;
}
}
вызов Mybatis с использованием Mapper API
@Update("{ CALL ${schema.name}.theProcedure("
+ "#{beanList, mode=IN, typeHandler=com.example.ListCustomerBeanTypeHandler}, "
+ "#{resultContainer.resultList, mode=OUT, jdbcType=ARRAY, typeHandler=com.example.ListCustomBeanTypeHandler, jdbcTypeName=${schema.name}.${type.package.name}.${type.array.customBean}}, "
+ "#{resultContainer.returnCode, mode=OUT, jdbcType=INTEGER}, "
+ "#{resultContainer.returnMessage, mode=OUT, jdbcType=VARCHAR} "
+ ")}")
@Options(statementType = StatementType.CALLABLE)
void runTheProcedure(@Param("beanList") List<CustomBean> beanList, @Param("resultContainer") ResultContainer<CustomBean> resultContainer);
FYI:
public class ResultContainer<T> {
private Integer returnCode;
private List<T> resultList;
private String returnMessage;
}