How to Pass Java List of Objects to Oracle Stored Procedure Using MyBatis? - oracle

I have been googling this for a while and cannot seem to find any real answers.
I have an Oracle stored procedure that has a number of in parameters that have a type that is table of the table rowtype. So for example:
Declared in the pacakge:
TYPE param1_type_t IS TABLE OF table1%ROWTYPE;
TYPE param2_type_t IS TABLE OF table2%ROWTYPE;
TYPE param3_type_t IS TABLE OF table3%ROWTYPE;
Oracle Procedure:
PROCEDURE my_proc
(
parameter1 IN param1_type_t,
parameter2 IN param2_type_t,
parameter3 IN param3_type_t
)
On the java side, I have 3 corresponding Lists of objects representing each of the parameters that are populated in Java. Is it possible to call the Oracle procedure using MyBatis in this scenario?
<update id="callOracleSP" statementType="CALLABLE">
{CALL my_proc( #{param1, mode=IN},
#{param2, mode=IN},
#{param3, mode=IN}
)
}
</update>
The objects themselves are simple VOs with String and Integer properties and their respective getters and setters.
I am not really sure how to proceed. Do I need to somehow map the Java object lists to the Oracle types?

I can't tell if you do already or not, but you'll need Oracle objects defined.
CREATE OR REPLACE TYPE SCHEMA."YOUR_OBJECT" AS OBJECT
(
field_one varchar2(50),
field_two varchar2(100)
);
/
CREATE OR REPLACE TYPE SCHEMA."YOUR_OBJECT_ARRAY" AS TABLE OF YOUR_OBJECT;
/
Then you can write type handlers to map the Java objects to the Oracle objects.
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
....
public class YourTypeHandler implements TypeHandler
{
....
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException
{
List<YourObject> objects = (List<YourObject>) parameter;
StructDescriptor structDescriptor = StructDescriptor.createDescriptor("YOUR_OBJECT", ps.getConnection());
STRUCT[] structs = new STRUCT[objects.size()];
for (int index = 0; index < objects.size(); index++)
{
YourObject pack = packs.get(index);
Object[] params = new Object[2];
params[0] = pack.getFieldOne();
params[1] = pack.getFieldTwo();
STRUCT struct = new STRUCT(structDescriptor, ps.getConnection(), params);
structs[index] = struct;
}
ArrayDescriptor desc = ArrayDescriptor.createDescriptor("YOUR_OBJECT_ARRAY", ps.getConnection());
ARRAY oracleArray = new ARRAY(desc, ps.getConnection(), structs);
ps.setArray(i, oracleArray);
}
}
Then invoke the procedure,
call your_proc
(
#{yourObjects, javaType=Object, jdbcType=ARRAY, jdbcTypeName=YOUR_OBJECT_ARRAY, mode=IN, typeHandler=YourObjectArrayTypeHandler}
)

Andy Pryor's answer is very good I tested it and it really works. But it has an error at typeHandler:
call your_proc
(
#{yourObjects, javaType=Object, jdbcType=ARRAY, jdbcTypeName=YOUR_OBJECT_ARRAY, mode=IN, typeHandler=YourObjectArrayTypeHandler}
)
should be:
call your_proc
(
#{yourObjects, javaType=Object, jdbcType=ARRAY, jdbcTypeName=YOUR_OBJECT_ARRAY, mode=IN, typeHandler=YourTypeHandler}
)
The TypeHandler has an error as well: (there is no "packs" and there is some difference in the method parameters in my version)
#Override
public void setParameter(PreparedStatement ps, int i, Object parameter, String arg3) throws SQLException {
List<YourObject> objects = (List<YourObject>) parameter;
StructDescriptor structDescriptor = StructDescriptor.createDescriptor("YOUR_OBJECT", ps.getConnection());
STRUCT[] structs = new STRUCT[objects.size()];
for (int index = 0; index < objects.size(); index++)
{
YourObject pack = objects.get(index);
Object[] params = new Object[2];
params[0] = pack.getFieldOne();
params[1] = pack.getFieldTwo();
STRUCT struct = new STRUCT(structDescriptor, ps.getConnection(), params);
structs[index] = struct;
}
ArrayDescriptor desc = ArrayDescriptor.createDescriptor("YOUR_OBJECT_ARRAY", ps.getConnection());
ARRAY oracleArray = new ARRAY(desc, ps.getConnection(), structs);
ps.setArray(i, oracleArray);
}
And here is an example for xml mapping:
<parameterMap id="updateHierPersonAssignMap" class="java.util.Map" >
<parameter property="p_array" jdbcType="ARRAY" javaType="Object" mode="IN" typeHandler="com.aamtech.ria.model.domain.typehandler.YourTypeHandler"/>
</parameterMap>
<procedure id="updateHierPersonAssign" parameterMap="updateHierPersonAssignMap" >
<![CDATA[
{ call ria_am_util_pkg.j_update_hier_person_assign( ? ) }
]]>
</procedure>
And here is how you can call it from the DAO:
public void update(List array) {
Map<String, Object> queryParams = new HashMap<String, Object>();
queryParams.put("p_array", array);
try {
client.update("HashMapResult.updateHierPersonAssign", queryParams);
} catch (SQLException e) {
}
}
And my procedure looks like this (it just inserts a row into a test table):
Procedure j_update_hier_person_assign (p_array IN YOUR_OBJECT_ARRAY) is
begin
FOR i IN 1..p_array.count LOOP
--dbms_output.put_line();
insert into test (a) values (p_array(i).field_one);
END LOOP;
end;

Related

Custom Result handling by calling store procedure in Spring Data JPA

I have requirement to call store procedures which takes input parameters. This store procedure returns custom result set, that result set i need to read and process further before return to UI. How we can achieve this?
EG:
#Query("CALL SP_EMPLOYEE_REPORT(:year)",nativeQuery = true)
List<EmpolypeeCustomReportBean> getEmployeeReport(#param("year") Integer year);
Given the following stored procedure.
CREATE PROCEDURE NAME_OF_THE_PROCEDURE(IN param VARCHAR(255), OUT retval INT)
You can call it from interface query:
#Procedure(value = "NAME_OF_THE_PROCEDURE")
int getFromStoredProcedure(String param);
Also by #Query annotation:
#Query(value = "CALL NAME_OF_THE_PROCEDURE(:input_value);", nativeQuery = true)
Integer findSomeThing(#Param("input_value") Integer name);
Or you can use named stored procedure query too.
#Entity
#NamedStoredProcedureQuery(name = "MyObj.getSomethingFromProc",
procedureName = "NAME_OF_THE_PROCEDURE", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "param", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "retval", type = Integer.class)})
public class MyObj{
// class definition
}
Then call it.
#Procedure(name = "MyObj.getSomethingFromProc")
Integer getSomethingFromStoredProc(#Param("param") String model);
Also you can use resultClasses and resultSetMapping properties in #NamedStoredProcedureQuery for complex return types.
Complex example provided by Eclipselink:
#NamedStoredProcedureQuery(
name="ReadUsingMultipleResultSetMappings",
procedureName="Read_Multiple_Result_Sets",
resultSetMappings={"EmployeeResultSetMapping", "AddressResultSetMapping", "ProjectResultSetMapping", "EmployeeConstructorResultSetMapping"}
)
#SqlResultSetMappings({
#SqlResultSetMapping(
name = "EmployeeResultSetMapping",
entities = {
#EntityResult(entityClass = Employee.class)
}
),
#SqlResultSetMapping(
name = "EmployeeConstructorResultSetMapping",
classes = {
#ConstructorResult(
targetClass = EmployeeDetails.class,
columns = {
#ColumnResult(name="EMP_ID", type=Integer.class),
#ColumnResult(name="F_NAME", type=String.class),
#ColumnResult(name="L_NAME", type=String.class),
#ColumnResult(name="R_COUNT", type=Integer.class)
}
)
}
)
})
public Employee(){
....
}

How to return the new object created by linq in a generic func

I have a generic func that takes a string of fields and selects them from an object using linq. However the output is not type of object but a subset of. So to convert this new List<subsetOfOriginal> to a DataTable, I cannot use list.GetType() because it will get me Original.
I have converted
Func<T, T> CreateSelectStatement<T>(string fields)
to
Func<T, S> CreateSelectStatement<T, S>(string fields)
because I don't want TResult to be of type T. Not sure if this is the correct way. This is the code I have at the moment and how it is invoked.
Func<T, S> CreateSelectStatement<T, S>(string fields)
{
var xParameter = Expression.Parameter(typeof(T), "o");
var xNew = Expression.New(typeof(T));
var bindings = fields.Split(',').Select(o => o.Trim())
.Select(o => {
var mi = typeof(T).GetProperty(o);
var xOriginal = Expression.Property(xParameter, mi);
return Expression.Bind(mi, xOriginal);
}
);
var xInit = Expression.MemberInit(xNew, bindings);
var lambda = Expression.Lambda<Func<T, S>>(xInit, xParameter);
return lambda.Compile();
}
and then to invoke it I'm using
CreateSelectStatement<SP_ProjectAnalysis_NP, dynamic>(columns)
Expected outcome should be the fields that are loading correctly but the type of the object is as if I used p => new { }. Currently I am getting object as the return type and trying to perform the following gets me 0 properties:
private static DataTable ToDataTable<T>(IList<T> list, string tableName = "table")
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));

how to get alias name in Spring boot

how to get alias name using spring-data-jpa in Spring boot?
I want alias name for stored procedure
StoredProcedureQuery storedProcedure = entitymanager.createStoredProcedureQuery(Proc);
if (params != null && params.length > 0) { // If no parameters supplied then no need to add it
int paramCount = 0;
for (String param : params) {
paramCount++;
storedProcedure.registerStoredProcedureParameter(paramCount, String.class, ParameterMode.IN);
storedProcedure.setParameter(paramCount, param);
// System.out.println("param" +param);
}
}
List<Object[]> storedProcedureResults = storedProcedure.getResultList();
this will return me only result but i also want column name which i have given in stored procedure

Ibatis TypeHandler and empty Varchar parameters in types in stored procedure

In a mybatis, spring application I have a TypeHandler which fills data for an ARRAY of STRUCTS required to call Oracle's stored procedure. A blob entry is properly filled and visible in the stored procedure; String entries are not, no string data is sent. No error or warning printed in logs. Data is not null and valid in application. The data simply disappears between application and oracle.
My handler's setParameter implementation looks like this:
public void setParameter(PreparedStatement ps, int i, List<MailAttachment> parameter,
JdbcType jdbcType) throws SQLException
{
List<MailAttachment> attachmentList = parameter;
OracleConnection oracleConnection = ps.getConnection().unwrap(OracleConnection.class);
StructDescriptor structDescriptor = StructDescriptor.createDescriptor(ATTACHMENT, oracleConnection);
Object[] structs = null;
structs = new Object[attachmentList == null ? 0 :attachmentList.size()];
if (attachmentList != null) {
//CharacterSet chs = CharacterSet.make(CharacterSet.UTF8_CHARSET);
for (int index = 0; index < attachmentList.size(); index++) {
MailAttachment mailAttachment = attachmentList.get(index);
BLOB blob = null;
if (mailAttachment.getData() != null){
blob = BLOB.createTemporary(oracleConnection,false,BLOB.DURATION_SESSION);
// filling blob works
}
CHAR attachName = new CHAR(mailAttachment.getFilename(), CharacterSet.make(CharacterSet.UTF8_CHARSET) );
CHAR contentType = new CHAR(mailAttachment.getContentType(), CharacterSet.make(CharacterSet.UTF8_CHARSET) );
STRUCT struct = new STRUCT(structDescriptor, oracleConnection,
new Object[] {blob, attachName, contentType, null}
);
structs[index] = struct;
}
}
ArrayDescriptor arrayDesc = ArrayDescriptor.createDescriptor(ATTACHMENT_LIST, oracleConnection);
ARRAY oracleArray = new ARRAY(arrayDesc, oracleConnection, structs);
ps.setObject(i, oracleArray);
}
This issue is connected with Oracle JDBC driver and it's support for internationalization.
Remember to include orai18n.jar in classpath/pom file in correct version for your ojdbc jar file.
If orai18n.jar is missing:
setParameters: varchar2 parameters in Oracle type will be set to null
getResult/getNonNullParameter: varchar2 parameters will be loaded to java class as "???" string.

Filtering Comma Separated Data

My site has a bunch of widgets and i'm trying to filter them based on the url which is passed in. Say a Widget has the following structure:
public class Widget {
public int Id { get; set; }
public string Name { get; set; }
public string Urls { get; set; }
}
Where Urls is a comma separated list for the urls where the widget should be displayed, e.g.:
/, /Blog/, /Blog/123, /News/*
The asterisk after News indicates the Widget will be selected whenever the passed in url starts with /News/.
How could i modify the following method to filter the widgets based on my conditions above?
public IList<Widget> GetWidgets(string url) {
return _session
.Where(w => w.Urls.Contains(url))
.ToList();
}
Ideally i'd like to use a linq query and it must only hit the database once. I'd appreciate the help. Thanks
I managed to solve this by adding my own wild card match generator. See http://sentinel101.wordpress.com/2010/12/30/extend-nhibernate-linq-for-regex-matching/ for example of how to register the generator. Here's the generator incase anyone is interested:
public class WildCardMatchGenerator : BaseHqlGeneratorForMethod {
public WildCardMatchGenerator() {
var methodDefinition = ReflectionHelper.GetMethodDefinition(() => WildCardMatchExtensions.WildCardMatch(null, null, ','));
SupportedMethods = new[] { methodDefinition };
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) {
return treeBuilder.Equality(treeBuilder.MethodCall("[dbo].[WildCardMatch]", new[] {
visitor.Visit(arguments[0]).AsExpression(),
visitor.Visit(arguments[1]).AsExpression(),
visitor.Visit(arguments[2]).AsExpression()
}), treeBuilder.Constant(1));
}
}
And here is the WildCardMatch UDF:
CREATE FUNCTION [dbo].[WildCardMatch] (
#Pattern NVARCHAR(MAX),
#Input NVARCHAR(MAX),
#Separator NVARCHAR(5)
)
RETURNS BIT
AS
BEGIN
SET #Pattern = REPLACE(#Pattern, '*', '%')
DECLARE #RtnValue BIT
SELECT #RtnValue = CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END FROM [dbo].[Split](#Pattern, #Separator) WHERE #Input LIKE [Data]
RETURN #RtnValue
END
And the Split function it calls (from http://blogs.microsoft.co.il/blogs/itai/archive/2009/02/01/t-sql-split-function.aspx):
CREATE FUNCTION [dbo].[Split]
(
#RowData NVARCHAR(MAX),
#Separator NVARCHAR(MAX)
)
RETURNS #RtnValue TABLE
(
[Id] INT IDENTITY(1,1),
[Data] NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #Iterator INT
SET #Iterator = 1
DECLARE #FoundIndex INT
SET #FoundIndex = CHARINDEX(#Separator, #RowData)
WHILE (#FoundIndex > 0)
BEGIN
INSERT INTO #RtnValue ([Data])
SELECT Data = LTRIM(RTRIM(SUBSTRING(#RowData, 1, #FoundIndex - 1)))
SET #RowData = SUBSTRING(#RowData, #FoundIndex + DATALENGTH(#Separator) / 2, LEN(#RowData))
SET #Iterator = #Iterator + 1
SET #FoundIndex = CHARINDEX(#Separator, #RowData)
END
INSERT INTO #RtnValue ([Data])
SELECT Data = LTRIM(RTRIM(#RowData))
RETURN
END
Lastly you'll need the C# implementation of the above UDF:
public static class WildCardMatchExtensions {
public static bool WildCardMatch(this string pattern, string input, char separator = ',') {
foreach (var str in pattern.Split(new char[] { separator }, StringSplitOptions.RemoveEmptyEntries)) {
if (Regex.IsMatch(input, Regex.Escape(str.Trim()).Replace("\\*", ".*")))
return true;
}
return false;
}
}
Hope this helps.
I don't think SQL server allows you to use IN for comma delimited field values. You need LIKE (SQL example below):
Urls LIKE '%_your_url_%'
Linq, Expressions, NHibernate and Like comparison may help you with translation LIKE to Linq to NHibernate

Resources