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.
Related
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;
}
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.
Here i got value for catname from parameter as movie but in database its have corresponding value as 1, same for music->2, game->3....
"WHERE (\n" +
"\t\t(`Post`.`status` = 1)\n" +
"\t\tAND (`Post`.`postto_id` =\"+catname+\" \")\n" +
"\t\t)" +
"ORDER BY `Post`.`id` desc LIMIT 5", new CrelistMapper());
} catch (Exception e) {
throw e;
}
}
private class CrelistMapper implements ResultSetExtractor<List<Creativity>> {
public List<Creativity> extractData(ResultSet resultSet) throws SQLException {
List<Creativity> crelistList = new ArrayList<Creativity>();
while (resultSet.next()) {
Creativity userObject = new Creativity(resultSet.getString("**postto_id**"),
Here i got value as movie in "postto_id" how can i convert it into 1 instead of movies from parameter?
I believe you are looking for parse int from Integer class.
import java.lang.Integer;
public class test{
public static void main(String[] args){
String stringId = "1";
int intFromString = Integer.parseInt(stringId);
System.out.println(intFromString);
}
}
Look here for a version of the overloaded method that meets your purpose
Hello am trying to fetch 10 rows of data from Cassandra table. But on each request same 10 row is returning. Please see my logic here. and advise me where am doing wrong here-
public class CustomerRequestDaoImpl implements CustomerRequestDao
{
private static Cluster cluster;
#Resource
private CassandraSessionFactory cassandraSessionFactory;
/** The ProductsByTagDaoImpl session. */
private Session session;
#Override
public List<CustomerRequest> getCustomerRequestData(final String productId, final String receiptPeriod)
{
final int RESULTS_PER_PAGE = 10;
session = cassandraSessionFactory.getSession();
final List<CustomerRequest> customerRequestdata = new ArrayList<CustomerRequest>();
try
{
final PreparedStatement statement =
session.prepare("select * from customer_request where product_id = :id and receipt_period = :receiptPeriod");
final BoundStatement boundStatement = new BoundStatement(statement);
boundStatement.setFetchSize(RESULTS_PER_PAGE);
boundStatement.setString("id", productId);
boundStatement.setString("receiptPeriod", receiptPeriod);
final ResultSet resultSet = session.execute(boundStatement);
final Iterator<Row> iter = resultSet.iterator();
final PagingState nextPage = resultSet.getExecutionInfo().getPagingState();
int remaining = resultSet.getAvailableWithoutFetching();
for (final Row rowdt : resultSet)
{
customerRequestdata.add(constructCustomerReq(rowdt));
if (--remaining == 0)
{
break;
}
}
}
catch (final Exception e)
{
e.printStackTrace();
}
return customerRequestdata;
}
#PostConstruct
public void init()
{
session = cassandraSessionFactory.getSession();
cluster = session.getCluster();
}
}
My Table-
My Table structure:-
CREATE TABLE customer_request (
product_id varchar PRIMARY KEY,
id varchar,
receipt_period varchar,
delivery_method_status varchar,
first_name varchar
);
return Response-
<e>
<deliveryMethodStatus null="true"/>
<firstName null="true"/>
<id>0b0352f6b3904</id>
<lastName Adkin="true"/>
<orderId>FORMS8a04e</orderId>
<orderItemId>FORMS8a04e-1</orderItemId>
<productId>PI_NAME_CHANGE</productId>
<receiptPeriod>2016-02-06</receiptPeriod>
<receivedDate null="true"/>
<requestData null="true"/>
Several remarks:
You should not re-prepare the query each time, it's an
anti-pattern. Prepare the statement only once and re-use it for each method call
The source code you show will always return the first page of
data because you break out of the for loop once remaining
variable counts down to 0. The PagingState object is not used
anywhere ...
You question is not clear either Hello am trying to fetch 10 rows of
data from Cassandra table. But on each request same 10 row is
returning. Which 10 rows do you want ? The first 10 rows ? The
10 rows after a threshold ?
Sample code for paging:
Note: prepared the following query only once: select * from customer_request where product_id = :id and receipt_period = :receiptPeriod LIMIT :lim and pass it along with the method
#Override
public List<Tuple2<String,CustomerRequest>> getCustomerRequestData(final String productId, PreparedStatement ps, final String receiptPeriod, String pagingState)
{
final int PAGE_SIZE = 10;
session = cassandraSessionFactory.getSession();
final List<CustomerRequest> customerRequestdata = new ArrayList<CustomerRequest>();
try
{
final BoundStatement boundStatement = ps.bind(productId, receiptPeriod, PAGE_SIZE);
boundStatement.setPagingState(PagingState.fromString(pagingState));
final ResultSet resultSet = session.execute(boundStatement);
final Iterator<Row> iter = resultSet.iterator();
final PagingState nextPage = resultSet.getExecutionInfo().getPagingState();
int remaining = resultSet.getAvailableWithoutFetching();
for (final Row rowdt : resultSet)
{
customerRequestdata.add(constructCustomerReq(rowdt));
if (--remaining == 0)
{
break;
}
}
}
catch (final Exception e)
{
e.printStackTrace();
}
return new Tuple2<>(nextPage.toString(), customerRe);
Please note the use of Tuple2 class to return the list of results as well as the paging state, serialized as a String to be passed easily to the front-end
I'm currently working in migrating a project to java, keeping the data intact (database). Most data are taken via stored procedures(SP), and there's a hell lot of SP in the database.
So, while executing each SP, I have to write a class for that, which is building up a huge pile of classes.
Hence, is there any way to generalize the class, so that I could transform every SP results to this class, and then to client side(as json)?
Following scenarios are hidden n ma qn:
Dynamic number of fields.
Dynamic field names.
Type could be string
(could deal with that).
I have tried sending data as java.util.List, but that doesn't comes in a pretty format. Have to take data assuming indexes.
PS: I have searched for the same, but couldn't find any. And sorry if I'm asking for too much.
Yes, it should be possible to write such a generic class. Here is a small example class as a starting point for you. I use Firebird with the example database employee.fdb because there are already some stored procedures defined.
So to connect to the Firebird server, I use the Jaybird JDBC driver and include the jaybird-full-2.2.5.jar JAR file.
There are several different JSON libraries for JAVA. I use the JSR 353: Java API for JSON Processing - Reference Implementation here in streaming mode (like StaX for XML). So the second external JAR here is javax.json-1.0.4.jar.
My example works only for stored procedures returning result sets. For stored procedures with output parameters a CallableStatement should be used instead of a PreparedStatement.
First, a generic SQL statement is created for the specific stored procedure with its input parameters. To call the stored procedure, a PreparedStatemend is used. The parameters are set according to the individual parameter types. (Procedures createSql() and createStatement())
In procedure convertToJson() the method ResultSet.getMetaData() is used to get the result set's column information (how many columns, column name and column type).
The executeStoredProcedure() methods are public API methods to call.
The main() method connects to the èmployee.fdb database and calls three stored procedures: GET_EMP_PROJ, MAIL_LABEL and ORG_CHART.
package com.genericsptojson;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
public class GenericSpToJson {
private static final String DB_URL = "jdbc:firebirdsql:localhost/3050:/var/lib/firebird/2.5/data/employee.fdb";
private static final String DB_USER = "SYSDBA";
private static final String DB_PWD = "***";
private Connection con;
public GenericSpToJson(Connection con) {
this.con = con;
}
/**
* Creates the SQL to call the stored procedure.
*
* #param spName
* Name of stored procecdure to call
* #param paramCount
* number of input parameters
* #return SQL with placeholders for input parameters
*/
private String createSql(String spName, int paramCount) {
if(paramCount > 0) {
final StringBuilder params = new StringBuilder();
boolean isFirst = true;
for(int i = 0; i < paramCount; i++) {
if(isFirst) {
isFirst = false;
} else {
params.append(", ");
}
params.append('?');
}
return String.format("SELECT * FROM %s (%s)", spName, params.toString());
} else {
return String.format("SELECT * FROM %s", spName);
}
}
/**
* Creates a PreparedStatement to call the stored procedure. This works only
* for stored procedures creating result sets. Stored procedures with OUT
* parameters should be handled by a CallableStatement instead.
*
* #param spName
* The stored procedure name to be called.
* #param params
* The input parameters.
* #return A prepared statement. All parameters are set.
* #throws SQLException
*/
private PreparedStatement createStatement(String spName, Object... params) throws SQLException {
final PreparedStatement stmt = con.prepareStatement(createSql(spName, params.length));
for(int i = 0; i < params.length; i++) {
final Object param = params[i];
if(param instanceof String) {
stmt.setString(i + 1, (String) param);
} else if(param instanceof Integer) {
stmt.setInt(i + 1, ((Integer) param).intValue());
} else {
// Handle other param types ...
}
}
return stmt;
}
/**
* Converts the result set to JSON in streaming mode.
*
* #param spName
* The stored procedure name.
* #param rs
* The result set of the stored procedure call.
* #param out
* The output stream to write the JSON into.
* #throws SQLException
*/
private void convertToJson(String spName, ResultSet rs, OutputStream out) throws SQLException {
// Get the result set meta data to obtain column information on the fly.
final ResultSetMetaData metaData = rs.getMetaData();
// Create the JSON generator with pretty printing
final Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JsonGenerator.PRETTY_PRINTING, true);
final JsonGeneratorFactory jsonGeneratorFactory = Json.createGeneratorFactory(properties);
final JsonGenerator generator = jsonGeneratorFactory.createGenerator(out);
generator.writeStartObject(); // root object
generator.write("storedProcedureName", spName);
generator.write("columnCount", metaData.getColumnCount());
generator.writeStartArray("records"); // records array
while(rs.next()) {
generator.writeStartObject(); // record object
// Each record object contains one field for every column.
// The field name is the columns name.
for(int col = 1; col <= metaData.getColumnCount(); col++) {
final String fieldName = metaData.getColumnName(col);
switch(metaData.getColumnType(col)) {
case Types.INTEGER:
final int intValue = rs.getInt(col);
if(rs.wasNull()) {
generator.writeNull(fieldName);
} else {
generator.write(fieldName, intValue);
}
break;
case Types.VARCHAR:
case Types.CHAR:
String stringValue = rs.getString(col);
if(rs.wasNull()) {
generator.writeNull(fieldName);
} else {
if(metaData.getColumnType(col) == Types.CHAR) {
stringValue = stringValue.trim();
}
generator.write(fieldName, stringValue);
}
break;
// Handle other types here
default:
System.out.println(String.format("Unhandled SQL type: %s", metaData.getColumnTypeName(col)));
}
}
generator.writeEnd(); // record object
}
generator.writeEnd(); // records array
generator.writeEnd(); // root object
generator.flush();
generator.close();
}
/**
* Executes the stored procedures with the given input parameters and creates
* JSON in streaming mode.
*
* #param spName
* The name of the stored procedure.
* #param out
* The output stream to write the generated JSON into.
* #param params
* The stored procedure's parameters.
*/
public void executeStoredProcedure(String spName, OutputStream out, Object... params) {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = createStatement(spName, params);
rs = stmt.executeQuery();
convertToJson(spName, rs, out);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// Cleaning up ...
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* Convenience method to call the stored procedure and create a JSON string.
* This should only be called for short result sets. For longer result sets
* use {#link #executeStoredProcedure(String, OutputStream, Object...)} where
* it is not necessary to hold the entire JSON document in memory.
*
* #param spName
* The name of the stored procedure to call.
* #param params
* The stored procedure's parameters
* #return The stored procedure's call result as a JSON string.
* #throws UnsupportedEncodingException
*/
public String executeStoredProcedure(String spName, Object... params) throws UnsupportedEncodingException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
executeStoredProcedure(spName, out, params);
return out.toString("UTF-8");
}
public static void main(String[] args) {
Connection con = null;
try {
Class.forName("org.firebirdsql.jdbc.FBDriver");
con = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
final GenericSpToJson converter = new GenericSpToJson(con);
System.out.println("Executing stored procedure GET_EMP_PROJ (8):\n"
+ converter.executeStoredProcedure("GET_EMP_PROJ", 8));
System.out.println("\n\nExecuting stored procedure MAIL_LABEL (1015):\n"
+ converter.executeStoredProcedure("MAIL_LABEL", 1015));
System.out.println("\n\nExecuting stored procedure ORG_CHART:\n"
+ converter.executeStoredProcedure("ORG_CHART"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
The output is (shortened):
Executing stored procedure GET_EMP_PROJ (8):
{
"storedProcedureName":"GET_EMP_PROJ",
"columnCount":1,
"records":[
{
"PROJ_ID":"VBASE"
},
{
"PROJ_ID":"GUIDE"
},
{
"PROJ_ID":"MKTPR"
}
]
}
Executing stored procedure MAIL_LABEL (1015):
{
"storedProcedureName":"MAIL_LABEL",
"columnCount":6,
"records":[
{
"LINE1":"GeoTech Inc.",
"LINE2":"K.M. Neppelenbroek",
"LINE3":"P.0.Box 702",
"LINE4":"",
"LINE5":null,
"LINE6":"Netherlands 2514"
}
]
}
Executing stored procedure ORG_CHART:
{
"storedProcedureName":"ORG_CHART",
"columnCount":5,
"records":[
{
"HEAD_DEPT":null,
"DEPARTMENT":"Corporate Headquarters",
"MNGR_NAME":"Bender, Oliver H.",
"TITLE":"CEO",
"EMP_CNT":2
},
{
"HEAD_DEPT":"Corporate Headquarters",
"DEPARTMENT":"Sales and Marketing",
"MNGR_NAME":"MacDonald, Mary S.",
"TITLE":"VP",
"EMP_CNT":2
},
// ... SNIP ...
{
"HEAD_DEPT":"Corporate Headquarters",
"DEPARTMENT":"Finance",
"MNGR_NAME":"Steadman, Walter",
"TITLE":"CFO",
"EMP_CNT":2
}
]
}