I want to call a oracle stored procedure with custom objects using Mybatis 3 in a Spring-boot application. I didn't any example of how to do it.
I already have a method that calls the procedure using standard JDBC, I want to convert it to MyBatis.
public void perform() throws DialectException {
PreparedStatement ps=null;
ResultSet rs=null;
UnitBean unitBean;
unitList = new ArrayList();
CallableStatement cs=null;
Connection oraConn;
try {
oraConn = ((PooledConnection)conn).getPhysicalConnection();
cs = oraConn.prepareCall(sqlSvc.getSqlStatement("GIB_INTERFACE.list"));
StructDescriptor structDescStdUntTyp = StructDescriptor.createDescriptor("STD_UNT_TYP", oraConn);
StructDescriptor structDescAdvUntTyp = StructDescriptor.createDescriptor("ADV_UNT_TYP", oraConn);
ArrayDescriptor descriptorVarcharVarrayType = ArrayDescriptor.createDescriptor("VARCHAR_VARRAY_TYPE", oraConn);
Object[] attributesStdUntTyp = new Object[9];
Object[] attributesAdvUntTyp = new Object[15];
ARRAY tecArray = null;
ARRAY geSerialNumberArray = null;
ARRAY oemSerialNumberArray = null;
ARRAY jobNumberArray = null;
ARRAY unitStatusArray = null;
ARRAY equipmentArray = null;
ARRAY contractualStatusArray = null;
ARRAY trainServiceTypeArray = null;
ARRAY fuelTypeArray = null;
ARRAY combustionSystemArray = null;
ARRAY equipmentLocationArray = null;
tecArray = populateUnitDataSet(filterUnitBean, "getTechnologyInput", "getTechnologyInput", oraConn, descriptorVarcharVarrayType);
geSerialNumberArray = populateUnitDataSet(filterUnitBean, "getGeSerialInput", "getGeSerialOutput", oraConn, descriptorVarcharVarrayType);
oemSerialNumberArray = populateUnitDataSet(filterUnitBean, "getOemSerialNumberInput", "getOemSerialNumberOutput", oraConn, descriptorVarcharVarrayType);
jobNumberArray = populateUnitDataSet(filterUnitBean, "getJobNumberInput", "getJobNumberOutput", oraConn, descriptorVarcharVarrayType);
unitStatusArray = populateUnitDataSet(filterUnitBean, "getUnitStatusInput", "getUnitStatusInput", oraConn, descriptorVarcharVarrayType);
equipmentArray = populateUnitDataSet(filterUnitBean, "getEquipmentInput", "getEquipmentInput", oraConn, descriptorVarcharVarrayType);
contractualStatusArray = populateUnitDataSet(filterUnitBean, "getContractualStatusInput", "getContractualStatusInput", oraConn, descriptorVarcharVarrayType);
trainServiceTypeArray = populateUnitDataSet(filterUnitBean, "getTrainServiceTypeInput", "getTrainServiceTypeInput", oraConn, descriptorVarcharVarrayType);
fuelTypeArray = populateUnitDataSet(filterUnitBean, "getFuelTypeInput", "getFuelTypeInput", oraConn, descriptorVarcharVarrayType);
combustionSystemArray = populateUnitDataSet(filterUnitBean, "getCombustionSystemInput", "getCombustionSystemInput", oraConn, descriptorVarcharVarrayType);
equipmentLocationArray = populateUnitDataSet(filterUnitBean, "getEquipmentLocationInput", "getEquipmentLocationInput", oraConn, descriptorVarcharVarrayType);
STRUCT standardUnit;
attributesStdUntTyp[0] = geSerialNumberArray;
attributesStdUntTyp[1] = oemSerialNumberArray;
attributesStdUntTyp[2] = Utility.resolveNull(filterUnitBean.getCustomer());
attributesStdUntTyp[3] = Utility.resolveNull(filterUnitBean.getSiteName());
attributesStdUntTyp[4] = jobNumberArray;
attributesStdUntTyp[5] = unitStatusArray;
attributesStdUntTyp[6] = equipmentArray;
attributesStdUntTyp[7] = tecArray;
attributesStdUntTyp[8] = Utility.resolveNull(filterUnitBean.getEquipmentName());
standardUnit = new STRUCT(structDescStdUntTyp,oraConn,attributesStdUntTyp);
STRUCT advancedUnit;
attributesAdvUntTyp[0] = Utility.resolveNull(filterUnitBean.getRelatedMachines());
attributesAdvUntTyp[1] = Utility.resolveNull(filterUnitBean.getGlobalCustomer());
attributesAdvUntTyp[2] = contractualStatusArray;
attributesAdvUntTyp[3] = Utility.resolveNull(filterUnitBean.getWarranty());
attributesAdvUntTyp[4] = Utility.resolveNull(filterUnitBean.getWhru());
attributesAdvUntTyp[5] = Utility.resolveNull(filterUnitBean.getRmdAvailable());
attributesAdvUntTyp[6] = Utility.resolveNull(null);
attributesAdvUntTyp[7] = Utility.resolveNull(filterUnitBean.getPilotAvailable());
attributesAdvUntTyp[8] = Utility.resolveNull(filterUnitBean.getExtendorKit());
attributesAdvUntTyp[9] = null;
attributesAdvUntTyp[10] = trainServiceTypeArray;
attributesAdvUntTyp[11] = fuelTypeArray;
attributesAdvUntTyp[12] = combustionSystemArray;
attributesAdvUntTyp[13] = equipmentLocationArray;
attributesAdvUntTyp[14] = Utility.resolveNull(filterUnitBean.getRelatedOem());
advancedUnit = new STRUCT(structDescAdvUntTyp,oraConn,attributesAdvUntTyp);
cs.registerOutParameter(1,OracleTypes.CURSOR);
cs.setObject(2,standardUnit);
cs.setObject(3,advancedUnit);
cs.setInt(4,Integer.parseInt((lowerBound!=null)?lowerBound:"0")+Integer.parseInt(maxPageItems));
cs.setInt(5,Integer.parseInt((lowerBound!=null)?lowerBound:"0"));
cs.registerOutParameter(6,OracleTypes.NUMBER);
cs.execute();
rs = (ResultSet) cs.getObject(1);
int count = cs.getInt(6);
itemsCount = String.valueOf(count);
while(rs.next()){
unitBean = new UnitBean();
unitBean.setGibSerialNumber(Utility.resolveNull(rs.getString("GIB_SERIAL_NUMBER")));
unitBean.setOemSerialNumber(Utility.resolveNull(rs.getString("OEM_SERIAL_NUMBER")));
unitBean.setSiteCustomerDuns(Utility.resolveNull(rs.getString("SITE_CUSTOMER_DUNS")));
unitBean.setSiteCustomerName(Utility.resolveNull(rs.getString("SITE_CUSTOMER_NAME")));
unitBean.setSiteCustomerCountry(Utility.resolveNull(rs.getString("SITE_CUSTOMER_COUNTRY")));
unitBean.setSiteNameAlias(Utility.resolveNull(rs.getString("SITE_NAME_ALIAS")));
unitBean.setGloCustomerDuns(Utility.resolveNull(rs.getString("GLO_CUSTOMER_DUNS")));
unitBean.setGloCustomerName(Utility.resolveNull(rs.getString("GLO_CUSTOMER_NAME")));
unitBean.setGloCustomerCountry(Utility.resolveNull(rs.getString("GLO_CUSTOMER_COUNTRY")));
unitBean.setTechnologyCode(rs.getString("TECHNOLOGY_CODE_OG")); //GIB Remediation Changes
unitBean.setTechnologyDesc(Utility.resolveNull(rs.getString("TECHNOLOGY_DESC")));
unitBean.setTechnologyDescOg(Utility.resolveNull(rs.getString("TECHNOLOGY_DESC_OG")));
unitBean.setEquipmentCode(Utility.resolveNull(rs.getString("EQUIPMENT_CODE")));
unitBean.setEquipmentEngDesc(Utility.resolveNull(rs.getString("EQUIPMENT_ENG_DESC")));
unitBean.setUnitCustomerName(Utility.resolveNull(rs.getString("UNIT_CUSTOMER_NAME")));
unitBean.setEngProjectRef(Utility.resolveNull(rs.getString("ENG_PROJECT_REF")));
unitBean.setOemLocationDesc(Utility.resolveNull(rs.getString("OEM_LOCATION_DESC")));
unitBean.setUnitStatusDesc(Utility.resolveNull(rs.getString("UNIT_STATUS_DESC")));
unitBean.setUnitShipDate(Utility.dateToString(rs.getDate("UNIT_SHIP_DATE")));
unitBean.setUnitCodDate(Utility.dateToString(rs.getDate("UNIT_COD_DATE")));
unitBean.setUnitRetireDate(Utility.dateToString(rs.getDate("UNIT_RETIRE_DATE")));
unitBean.setServiceRelationCode(Utility.resolveNull(rs.getString("SERVICE_RELATION_CODE")));
unitBean.setServiceRelationDesc(Utility.resolveNull(rs.getString("SERVICE_RELATION_DESC")));
unitBean.setMainWarrantyActive(Utility.resolveNull(rs.getString("MAIN_WARRANTY_ACTIVE")));
unitBean.setServiceWarrantyActive(Utility.resolveNull(rs.getString("SERVICE_WARRANTY_ACTIVE")));
unitBean.setCsaEndDate(Utility.dateToString(rs.getDate("CSA_END_DATE")));
unitBean.setOgSalesRegion(Utility.resolveNull(rs.getString("OG_SALES_REGION")));
unitBean.setSanctionedUnitFlag(Utility.resolveNull(rs.getString("SANCTIONED_UNIT_FLAG")));
unitBean.setUnitRating(Utility.resolveNull(rs.getString("UNIT_RATING")));
unitBean.setUnitRatingUom(Utility.resolveNull(rs.getString("UNIT_RATING_UOM")));
unitBean.setControlSystemDesc(Utility.resolveNull(rs.getString("CONTROL_SYSTEM_DESC")));
unitBean.setServiceTypeDesc(Utility.resolveNull(rs.getString("SERVICE_TYPE_DESC")));
unitBean.setDrivenEquipmentDesc(Utility.resolveNull(rs.getString("DRIVEN_EQUIPMENT_DESC")));
unitBean.setCombustionSystemDesc(Utility.resolveNull(rs.getString("COMBUSTION_SYSTEM_DESC")));
unitBean.setPrimaryFuelTypeDesc(Utility.resolveNull(rs.getString("PRIMARY_FUEL_TYPE_DESC")));
unitBean.setExtendorKitInstalled(Utility.resolveNull(rs.getString("EXTENDOR_KIT_INSTALLED")));
unitBean.setWhruFlag(Utility.resolveNull(rs.getString("WHRU_FLAG")));
unitBean.setRmdServiceFlag(Utility.resolveNull(rs.getString("RMD_SERVICE_FLAG")));
unitBean.setPilotServiceFlag(Utility.resolveNull(rs.getString("PILOT_SERVICE_FLAG")));
unitBean.setLineupServiceDescription(Utility.resolveNull(rs.getString("LINEUP_SERVICE_DESC")));
unitBean.setEquipmentLocationDescription(Utility.resolveNull(rs.getString("EQUIP_LOCATION_DESC")));
unitBean.setLastUpdateDate(Utility.dateToString(rs.getDate("LAST_UPDATE_DATE")));
unitBean.setComments(Utility.resolveNull(rs.getString("COMMENTS")));
unitList.add(unitBean);
}
} catch (SQLException e) {
throw new DialectException(e.getMessage());
}finally{
DBUtility.close(ps, rs);
DBUtility.close(cs);
}
}
Also, when I use ojdbc7.jar, StructDescriptor class and ARRAY class is shown as deprecated. Is there any other better way to achieve this?
Any help will be appreciated. Thanks in advance.
Deprecation is to discourage from doing the whole thing: using Oracle Arrays and Structs, but it will work anyway.
In short, mapping from and to java type to Oracle custom types requires using custom Mybatis TypeHandler.
The code inside the type handler isbasically JDBC.
And when it comes to manipulate Arrays and Structures, then it strongly relies on DB vendor's driver API (not standard at all).
There are tons of contents to set up Mybatis, so here for the kind of specific part.
Following abstract class allows mapping java Array/Collection to Oracle Array/Table type.
Just specify the type name on Oracle side in the concrete implementation: e.g: 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();
}
}
Here to map arrays of custom Oracle type/struct:
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);
}
An example for concrete type handling:
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;
}
}
The Mybatis call using 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;
}
Related
The current question is the second part of this ODCI related question.
I have implemented a collection type in Oracle SQL which is practically defined as a type and a table of that type.
CREATE TYPE row_type AS OBJECT
(
C1 VARCHAR2(50),
C2 VARCHAR2(50),
C3 VARCHAR2(50)
);
/
CREATE TYPE row_type_set AS TABLE OF row_type;
Also, I have defined an ODCI type with its implementation as a Java Stored Procedure within database:
SQL:
CREATE OR REPLACE TYPE ODCIImpl AS OBJECT (
key INTEGER,
STATIC FUNCTION ODCITableStart(sctx OUT ODCIImpl, cur SYS_REFCURSOR)
RETURN NUMBER
AS LANGUAGE JAVA
NAME 'ODCIImpl.ODCITableStart(oracle.sql.STRUCT[], java.sql.ResultSet) return java.math.BigDecimal',
MEMBER FUNCTION ODCITableFetch(self IN OUT ODCIImpl, nrows IN NUMBER,
outSet OUT row_type_set) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'ODCIImpl.ODCITableFetch(java.math.BigDecimal, oracle.sql.ARRAY[]) return java.math.BigDecimal',
MEMBER FUNCTION ODCITableClose(self IN ODCIImpl) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'ODCIImpl.ODCITableClose() return java.math.BigDecimal'
);
/
Java Stored Procedure:
import java.io.*;
import java.util.*;
import oracle.sql.*;
import java.sql.*;
import java.math.BigDecimal;
import oracle.CartridgeServices.*;
// stored context type
public class StoredCtx
{
ResultSet rset;
public StoredCtx(ResultSet rs) { rset=rs; }
}
// implementation type
public class ODCIImpl implements SQLData
{
private BigDecimal key;
final static BigDecimal SUCCESS = new BigDecimal(0);
final static BigDecimal ERROR = new BigDecimal(1);
final static int MAX_COLUMNS = 3;
// Implement SQLData interface.
String sql_type;
public String getSQLTypeName() throws SQLException
{
return sql_type;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
sql_type = typeName;
key = stream.readBigDecimal();
}
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeBigDecimal(key);
}
// type methods implementing ODCITable interface
static public BigDecimal ODCITableStart(STRUCT[] sctx,ResultSet rset)
throws SQLException
{
Connection conn = DriverManager.getConnection("jdbc:default:connection:");
// create a stored context and store the result set in it
StoredCtx ctx=new StoredCtx(rset);
// register stored context with cartridge services
int key;
try {
key = ContextManager.setContext(ctx);
} catch (CountException ce) {
return ERROR;
}
// create a ODCIImpl instance and store the key in it
Object[] impAttr = new Object[1];
impAttr[0] = new BigDecimal(key);
StructDescriptor sd = new StructDescriptor("ODCIIMPL",conn);
sctx[0] = new STRUCT(sd,conn,impAttr);
return SUCCESS;
}
public BigDecimal ODCITableFetch(BigDecimal nrows, ARRAY[] outSet)
throws SQLException
{
Connection conn = DriverManager.getConnection("jdbc:default:connection:");
// retrieve stored context using the key
StoredCtx ctx;
try {
ctx=(StoredCtx)ContextManager.getContext(key.intValue());
} catch (InvalidKeyException ik ) {
return ERROR;
}
// get the nrows parameter, but return up to 10 rows
int nrowsval = nrows.intValue();
// create a vector for the fetched rows
Vector v = new Vector(nrowsval);
int i=0;
StructDescriptor outDesc =
StructDescriptor.createDescriptor("ROW_TYPE", conn);
Object[] out_attr = new Object[MAX_COLUMNS];
ResultSetMetaData rsmd = ctx.rset.getMetaData();
int columnsNumber = rsmd.getColumnCount();
while(nrowsval>0 && ctx.rset.next()){
for(int j = 0; j < columnsNumber; j++) {
if(j == MAX_COLUMNS)
break;
out_attr[j] = (Object)ctx.rset.getString(j+1);
}
v.add((Object)new STRUCT(outDesc, conn, out_attr));
i+=1;
nrowsval-=1;
}
// return if no rows found
if(i==0) return SUCCESS;
// create the output ARRAY using the vector
Object out_arr[] = v.toArray();
ArrayDescriptor ad = new ArrayDescriptor("ROW_TYPE_SET",conn);
outSet[0] = new ARRAY(ad,conn,out_arr);
return SUCCESS;
}
public BigDecimal ODCITableClose() throws SQLException {
// retrieve stored context using the key, and remove from ContextManager
StoredCtx ctx;
try {
ctx=(StoredCtx)ContextManager.clearContext(key.intValue());
} catch (InvalidKeyException ik ) {
return ERROR;
}
// close the result set
Statement stmt = ctx.rset.getStatement();
ctx.rset.close();
if(stmt!=null) stmt.close();
return SUCCESS;
}
}
After all of this, I've implemented a pipelined function that can be called using a cursor.
CREATE OR REPLACE FUNCTION Exec_Remote_SQL_JSP(p SYS_REFCURSOR) RETURN row_type_set
PIPELINED USING ODCIImpl;
/
My question now is how can we implement an ODCITableDescribe method in a Java Stored Procedure in order to output any data type in the emulated table? First of all, is it possible at all? I didn't seem to find any relevant information about this on the Oracle documentation from here and here
If it is possible to do so, it is self-explainable that we do not need anymore the collection types mentioned at the beginning. The emulated table should have the same size and data types as the table from which we intend to select information.
I have written java code for the below plsql code to execute stored procedure. I am not getting result when I run the procedure from Java code.
PLSQL
declare
CAMFORMLIST_ID_TY CAMFORMLIST_ID_TYPES;
CAMFORMLIST_ID_T CAMFORMLIST_ID_TYPE;
OFEERS_INFO_TY OFEERS_INFO_TYPE;
begin
CAMFORMLIST_ID_T:=CAMFORMLIST_ID_TYPE('HH','K');
CAMFORMLIST_ID_TY:=CAMFORMLIST_ID_TYPES(CAMFORMLIST_ID_T);
GETOFEERSFROMCAMFORM(CAMFORMLIST_ID_TY,OFEERS_INFO_TY);
for i in OFEERS_INFO_TY.FIRST..OFEERS_INFO_TY.LAST loop
DBMS_OUTPUT.PUT_LINE('Name: '|| OFEERS_INFO_TY(i).OFFER_NAME);
end loop;
end;
My Java Stored Procedure class.
#Component
public class GetOffersFromCAMFORM extends StoredProcedure {
private static final String OFEERS_LIST = "Offers";
#Autowired
private String MySchema;
#Autowired
#Qualifier("MyDataSource")
private DataSource dataSourceMy;
#PostConstruct
public void postConstruct() {
this.setDataSource(dataSourceMy);
this.setSql(MySchema + ".getOffersFromCAMFORM");
declareParameter(new SqlParameter(SUB_FORM, Types.ARRAY, MySchema + ".CAMFORMLIST_ID_TYPES"));
declareParameter(
new SqlOutParameter(OFEERS_LIST, OracleTypes.ARRAY,
MySchema + ".OFEERS_INFO_TYPE", new ReturnType()));
}
#SuppressWarnings("unchecked")
public List<MyOffer> execute(final String subscriptionForm, final String subscriptionClass)
throws Exception {
Map<String, Object> objects = new HashMap<String, Object>();
objects.put(SUB_FORM, new AbstractSqlTypeValue() {
#Override
protected Object createTypeValue(Connection con, int SqlType, String typeName)
throws SQLException {
OracleConnection oracle =
(OracleConnection) (new DelegatingConnection(con)).getInnermostDelegate();
if (oracle != null) {
con = oracle;
}
ArrayList<Struct> CAMFORMListIdType = new ArrayList<Struct>();
CAMFORMListIdType
.add(((OracleConnection) con).createStruct(MySchema + ".CAMFORMLIST_ID_TYPE",
new String[] {subscriptionForm, subscriptionClass}));
return ((OracleConnection) con).createARRAY(MySchema + ".CAMFORMLIST_ID_TYPES",
CAMFORMListIdType.toArray(new Struct[] {}));
}
});
Map<?, ?> result = execute(objects);
List<MyOffer> items = (List<MyOffer>) result.get(OFEERS_LIST);
return items;
}
}
Return type
public class ReturnType implements SqlReturnType {
#Override
public List<PCOffer> getTypeValue(CallableStatement cs, int colIndx, int sqlType,
String typeName) throws SQLException {
Array dba = (Array) cs.getObject(colIndx);
if (dba == null || ((Object[]) dba.getArray()).length == 0) {
return null;
}
...
}
Ojdbc 7 driver is used.
I am not getting the output. When I execute the procueudre, I get empty array in ReturnType class.
Am I doing something wrong.
I have the following use case where I have a table type as OUT param to my function which invokes a java stored procedure. I am struggling to get this working. Can you plz help me in this regard ?
SQL Record Type
TYPE tmsint_xfer_html_ws_objr AS OBJECT
(file_name VARCHAR2(200),
html_text VARCHAR2(2000),
job_id NUMBER(10));
SQL Table Type
TYPE tmsint_xfer_html_ws_objt
AS TABLE OF tmsint_xfer_html_ws_objr;
Java POJO to map the SQL Record Type :
import java.math.BigDecimal;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class ExtractXmlLine implements SQLData {
public ExtractXmlLine(String file_name, String html_text,BigDecimal job_id) {
super();
this.file_name = file_name;
this.html_text = html_text;
this.job_id = job_id;
}
private String sql_type;
private String file_name;
private String html_text;
private BigDecimal job_id;
#Override
public String getSQLTypeName() throws SQLException {
return sql_type;
}
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
sql_type = typeName;
job_id = stream.readBigDecimal();
file_name = stream.readString();
html_text = stream.readString();
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeBigDecimal(job_id);
stream.writeString(file_name);
stream.writeString(html_text);
}
}
Java class that is registered as java stored procedure
public class ExtractData {
public ExtractData() {
super();
}
public static void getDataFromURL(String jobId, String sourceUrl, String userName, String password,
ExtractXmlLine[] xmlResponse, String[] errorMessage) {
try {
conn = JDBCUtil.getConnection();
java.util.Map map = conn.getTypeMap();
map.put("TMSINT_XFER_HTML_WS_OBJT", Class.forName("ExtractXmlLine"));
conn.setTypeMap(map);
response = HttpUtil.getHttpResponse(sourceUrl, userName, password);
if (response.getStatus().equals(ReturnStatus.SUCCESS)) {
String xmlBody = response.getResponseBody();
if (xmlBody != null) {
xmlBody = xmlBody.replaceAll("[^\\x20-\\x7e]", "");
xmlBody = XMLUtil.format(xmlBody);
textLines.addAll(Arrays.asList(xmlBody.split("\\r\\n|\\n|\\r")));
}
if (!textLines.isEmpty()) {
dataLines = new ExtractXmlLine[textLines.size()];
for (int i = 0; i < textLines.size(); i++) {
// ignore xml declaration lines
if (!textLines.get(i).startsWith("<?xml")) {
xmlResponse[i] = new ExtractXmlLine(sourceUrl, textLines.get(i), new BigDecimal(jobId));
}
}
}
} else
errorMessage[0] = response.getErrorMessage() + response.getResponseBody();
} catch (Exception e) {
errorMessage[0] = ExceptionUtils.getStackTrace(e);
}
}
}
SQL Procedure to wrap the java call
create or replace PROCEDURE getDataFromURL
(pJobID IN VARCHAR2,
pSourceURL IN VARCHAR2,
pUserName IN VARCHAR2,
pPassword IN VARCHAR2,
pXMLResponse OUT tmsint_xfer_html_ws_objt,
pErrorMessage OUT VARCHAR2)
IS LANGUAGE JAVA
NAME 'ExtractData.getDataFromURL
(java.lang.String,
java.lang.String,
java.lang.String,
java.lang.String,
ExtractXmlLine[],
java.lang.String[])';
Is this the correct approach ? Am I missing something here ?
I am trying use a from a multi-dimensional array that I create in another classes method. Below is my main method:
public class main {
public static void main(String[] args) throws Exception {
sql test = new sql();
String[][] test2 = test.getDb();
System.out.print(test2[0][0]);
}
Now here is the class that returns an multi-dimensional array.
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import javax.swing.JLabel;
import javax.swing.JTextField;
import com.mysql.jdbc.Statement;
public class sql {
java.sql.Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
String url = "jdbc:mysql://localhost:8889/deliveryEarn";
String user = "root";
String password = "root";
ArrayList<String> sqlCol1 = new ArrayList<String>();
ArrayList<String> sqlCol2 = new ArrayList<String>();
ArrayList<String> sqlCol3 = new ArrayList<String>();
ArrayList<String> sqlCol4 = new ArrayList<String>();
ArrayList<String> sqlCol5 = new ArrayList<String>();
ArrayList<String> sqlCol6 = new ArrayList<String>();
ArrayList<String> sqlCol7 = new ArrayList<String>();
String sqlArray[][] = new String[7][7];
public sql() {
}
public String[][] getDb() {
try {
con = DriverManager.getConnection(url, user, password);
pst = con.prepareStatement("select * from incomeCalc");
rs = pst.executeQuery();
while (rs.next()) {
sqlCol1.add(rs.getString(1));
int i1=0;
for(String s: sqlCol1){
sqlArray[i1++][0] = s;
}
sqlCol2.add(rs.getString(2));
int i2=0;
for(String s: sqlCol2){
sqlArray[i2++][1] = s;
}
sqlCol3.add(rs.getString(3));
int i3=0;
for(String s: sqlCol3){
sqlArray[i3++][2] = s;
}
sqlCol4.add(rs.getString(4));
int i4=0;
for(String s: sqlCol4){
sqlArray[i4++][3] = s;
}
sqlCol5.add(rs.getString(5));
int i5=0;
for(String s: sqlCol5){
sqlArray[i5++][4] = s;
}
sqlCol6.add(rs.getString(6));
int i6=0;
for(String s: sqlCol6){
sqlArray[i6++][5] = s;
}
sqlCol7.add(rs.getString(7));
int i7=0;
for(String s: sqlCol7){
sqlArray[i7++][6] = s;
}
}
}
catch( Exception E ) {
System.out.println( E.getMessage() );
}
return sqlArray;
}
}
Here is the screenshot of the MySQL database.
Edit: It appears I wasn't clear with my question. I apologize. I am getting a runtime error at this line:
System.out.print(test2[0][0]);
What am I doing wrong? Also, for correct OOP, is it better to use a constructor or a method to pull from or input to a database? THis is my first program so sorry if it seems trivial.
Edit2: Here is the error:
Exception in thread "main" java.lang.NullPointerException
at main.main(main.java:17)
As to why you've got an error, it would be nicer to know the error, however...
Personally, I'd drop the contents of the result set into a "Data Object"...
public class Income {
// Column decelerations...
private long id;
private int tips;
private int hours;
private int gas;
private double hourly;
private double other;
private double other2;
public int getGas() {
return gas;
}
public double getHourly() {
return hourly;
}
public int getHours() {
return hours;
}
public long getId() {
return id;
}
public double getOther() {
return other;
}
public double getOther2() {
return other2;
}
public int getTips() {
return tips;
}
public void setGas(int gas) {
this.gas = gas;
}
public void setHourly(double hourly) {
this.hourly = hourly;
}
public void setHours(int hours) {
this.hours = hours;
}
public void setId(long id) {
this.id = id;
}
public void setOther(double other) {
this.other = other;
}
public void setOther2(double other2) {
this.other2 = other2;
}
public void setTips(int tips) {
this.tips = tips;
}
}
Then when you load it you could do something like...
public Income[] getIncome() {
// Call database...
List<Income> data = new ArrayList<Income>(25);
while (rs.next()) {
Income income = new Income();
income.setID(rs.getInt(1)));
income.setTips(rs.getInt(2)));
income.setHours(rs.getInt(3)));
income.setGas(rs.getInt(4)));
income.setHourly(rs.getDouble(5)));
income.setOther(rs.getDouble(6)));
income.setOther2(rs.getDouble(7)));
data.add(income);
}
return data.toArray(new Income[data.size()]);
}
The you could do things like this...
sql test = new sql();
Income[] incomes = test.getIncome();
System.out.println(incomes[0].getID());
Isn't that easier to read :P
Your attempt to use a Factory is probably the best idea. It comes down to a matter of management as to weather you maintain a single instance (Singlton) or allow multiple instances of this Factory to be created. Personally, I prefer to use Singltons in this case where I can, it allows a centralised place to perform operations (saving changes, creating new objects, listing, deleting) and helps manage the resources involved. IMHO
i want to know if we can create a common database class same like we create a connection class and just call getConnection when we need connection to be established.
Basically, i want a database manager class which can handle database operation irrespective of tablename, columncount,etc.
tablename, columnname, values to be inserted would be passed as parameters from servlet.
that way, i can reduce duplication of code. m tryin to make a simple mvc application using jsp-servlets. my database is mysql. i dont know struts, spring, hibernate.
For Example, servlet code will call(databaseManager is the class name.) :
int count=databaseManager.getCount("tableName", "columnName", "value");
and in databaseManager, there will be a function -
public static int getCount(String tableName, String[] arrC, objectArray[] arrV)
{}
similarly, for other functions.
i googled and found out that it could be done using metadata.
but i dont know how to use it.
it would be helpful if u could post code of one function for similar approach.
Check DbUtils component of Apache Commons. Also there are examples provided.
Yes, sure you can. I have done something similar (but not the same) and there can be many approaches. I think you should google more, I'm sure, that there are lot of open source applications for database management/database clients. Try to get inspiration there.
Okay, here is some code for inspiration. It is not totally generic, or what are you looking for, but I think, this could lead you somewhere. If not, throw the stone. :-)
Database provider class:
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.DynaProperty;
public class DatabaseProvider<T extends DatabaseObject> {
private static DatabaseProvider databaseProvider;
private static String connectionString = "";
private static String password = "";
private static String username = "";
private static boolean initialized = true;
public DatabaseProvider(){ }
public static void initDatabaseProvider() {
try {
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
}
catch(SQLException e){
initialized = false;
e.printStackTrace();
}
connectionString = "XXX";
username = "XXX";
password = "XXX";
}
public List<T> performSimpleSelectQuery(String table, String columns, String where, Class targetObj) throws SQLException {
if(!initialized) return null;
List<T> results = new ArrayList<T>();
Constructor ct;
DatabaseObject dbo;
try {
ct = targetObj.getConstructor(null);
dbo = (DatabaseObject)ct.newInstance(null);
}
catch(Exception e){
e.printStackTrace();
return null;
}
String[] cols = columns.split(",");
String[] properties = new String[cols.length];
for(int i = 0; i < cols.length; i++){
cols[i] = cols[i].trim();
properties[i] = dbo.getMappingFromColumnName(cols[i]);
}
Connection conn = DriverManager.getConnection(connectionString, username, password);
PreparedStatement pst = conn.prepareStatement("SELECT " + columns + " FROM " + table + (where.equals("") ? "" : " WHERE " + where));
pst.execute();
ResultSet rs = pst.getResultSet();
while(rs.next()){
try {
dbo = (DatabaseObject)ct.newInstance(null);
for(int i = 0; i < cols.length; i++){
BeanUtils.setProperty(dbo, properties[i], rs.getObject(cols[i]));
}
results.add((T)dbo);
}
catch(Exception e){
e.printStackTrace();
rs.close();
pst.close();
conn.close();
return null;
}
}
rs.close();
pst.close();
conn.close();
return results;
}
public int performInsert(String columns, String values, String table) throws SQLException {
String sqlInsert = "INSERT INTO " + table + " (" + columns + ") VALUES (" + values + ")";
Connection conn = DriverManager.getConnection(connectionString, username, password);
PreparedStatement pst = conn.prepareStatement(sqlInsert);
int toReturn = 0;
try {
toReturn = pst.executeUpdate();
}
catch(Exception e){
e.printStackTrace();
pst.close();
conn.close();
return toReturn;
}
pst.close();
conn.close();
return toReturn;
}
}
Database object class:
import java.util.HashMap;
public abstract class DatabaseObject {
protected HashMap<String, String> dbToBeanMapping = new HashMap<String, String>();
public DatabaseObject() {
initialize();
}
protected abstract void initialize();
public String getMappingFromColumnName(String columnName) {
return dbToBeanMapping.get(columnName);
}
}
Example class:
public class CounterParty extends DatabaseObject {
private String name;
private int instrument;
private int partyId;
public int getPartyId() {
return partyId;
}
public void setPartyId(int partyId) {
this.partyId = partyId;
}
public CounterParty(){}
public int getInstrument() {
return instrument;
}
public void setInstrument(int instrument) {
this.instrument = instrument;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected void initialize() {
this.dbToBeanMapping.put("company_name", "name");
this.dbToBeanMapping.put("party_id", "partyId");
this.dbToBeanMapping.put("inst_id", "instrument");
}
}