Calling Oracle stored procedures with MyBatis - java

I am in the process of moving our database over to Oracle from SQL Server 2008 but cannot get MyBatis to work.
Given the following example:
UserMapper.xml (example)
<resultMap type="User" id="UserResult">
<id property="userId" column="userId"/>
<result property="firstName" column="firstName"/>
<result property="lastName" column="lastName"/>
</resultMap>
<select id="getUsers" statementType="CALLABLE" resultMap="UserResult">
{CALL GetUsers()}
</select>
UserDAO.java
public interface UserDAO {
public List<User> getUsers();
}
SQL Server procedure
CREATE PROCEDURE [dbo].[GetUsers]
AS
BEGIN
SET NOCOUNT ON;
SELECT userId, firstName, lastName
FROM Users
END
...works in SQL Server 2008. Can someone please explain to me how to call the Oracle procedure (that has the same name and columns as the SQL Server procedure above) from the UserMapper.xml and populate my User class with an Oracle cursor?
This is what I tried:
<resultMap type="User" id="UserResult">
<id property="userId" column="userId"/>
<result property="firstName" column="firstName"/>
<result property="lastName" column="lastName"/>
</resultMap>
<select id="getUsers" statementType="CALLABLE" resultMap="UserResult">
{CALL GetUsers(#{resultSet,mode=OUT,jdbcType=CURSOR,resultMap=UserResult})}
</select>
and I get this error:
Caused by: org.apache.ibatis.reflection.ReflectionException:
Could not set property 'resultSet' of 'class java.lang.Class'
with value 'oracle.jdbc.driver.OracleResultSetImpl#476d05dc'
Cause: org.apache.ibatis.reflection.ReflectionException:
There is no setter for property named 'resultSet' in 'class java.lang.Class'

Result map looks like this:
<resultMap id="UserResult" type="User">
<id property="userId" column="userId"/>
<result property="firstName" column="firstName"/>
<result property="lastName" column="lastName"/>
</resultMap>
In your select statement, change the parameter type to java.util.Map.
<select id="getUsers" statementType="CALLABLE" parameterType="java.util.Map">
{call GetUsers(#{users, jdbcType=CURSOR, javaType=java.sql.ResultSet, mode=OUT, resultMap=UserResult})}
</select>
Your mapper interface looks like this, it looks like you are currently calling this the DAO. The way I've done it in the past is to make a mapper interface that gets injected into the DAO and the DAO is what calls the methods on the mapper. Here's an example mapper interface:
public interface UserMapper {
public Object getUsers(Map<String, Object> params);
}
That mapper class would then get injected into a DAO class and make the call like this:
public List<User> getUsers() {
Map<String, Object> params = new HashMap<String, Object>();
ResultSet rs = null;
params.put("users", rs);
userMapper.getUsers(params);
return ((ArrayList<User>)params.get("users"));
}

Getting a resultset from Oracle 11 using MyBatis/iBATIS 3 is a real oddball process. It makes no sense to me, but it worked. My example is different, but you'll get the idea:
create or replace
PROCEDURE SP_GET_ALL_STORED_PROC (l_cursor out SYS_REFCURSOR) IS
BEGIN
open l_cursor for select account_id, totalLegs, born, weight, mammal, animal from copybittest;
END SP_GET_ALL_STORED_PROC;
Map map = new HashMap();
session.selectList("ibatis_3_test.selectProductAllOracleStoredProc", map);
List productList = (List) map.get("key");
<resultMap id="productResultMap" type="test.Product">
</resultMap>
<select id="selectProductAllOracleStoredProc" parameterType="java.util.Map" statementType="CALLABLE">
{call SP_GET_ALL_STORED_PROC(#{key, jdbcType=CURSOR, mode=OUT, javaType=java.sql.ResultSet,resultMap=productResultMap})}
</select>

Just an addition to the clav's comment, Snoozy, you need to remove resultSet from
<select id="getUsers" statementType="CALLABLE" resultMap="UserResult">
{CALL GetUsers(# {resultSet,mode=OUT,jdbcType=CURSOR,resultMap=UserResult})}
and change that to "key" as in:
<select id="getUsers" statementType="CALLABLE" resultMap="UserResult">
{CALL GetUsers(#{key,mode=OUT,jdbcType=CURSOR,resultMap=UserResult})}
</select>
I hope that was helpful.

I also got the same error.
Caused by: org.apache.ibatis.reflection.ReflectionException: There is
no setter for property named 'columnNames' in 'class java.lang.Class'
in mapper.java getSearchResult(searchCriteriaVO vo)
in mapper.xml
#{userId, mode=IN, jdbcType=VARCHAR},
#{resultList, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=inquiryResult},
where inquiryResult is defined as
<resultMap type="java.util.LinkedHashMap" id="inquiryResult">
<result property="name" jdbcType="VARCHAR" javaType="String" column="name"/>
Struggled for a day
But when I debugged, it's a simple mistake that I made. My Value Object was passing as a null to my mapper class. The query was getting executed even my VO was null because mybatis passing values as null. But when mybatis was trying to set the result to my VO, since it is null, it was throwing the above error.
Hope this is useful info

Related

How to get Map from myBatis with a key of another table?

I have a request, which gives me one object of Advantage class:
<resultMap id="AdvantageResult" type="Advantage">
<id property="id" jdbcType="BIGINT" javaType="java.lang.Long" column="id"/>
<result property="code" column="code"/>
<result property="name" column="name"/>
<result property="description" column="description"/>
<result property="asIs" column="as_is"/>
<result property="toBe" column="to_be"/>
<result property="availableName" column="available_name"/>
<result property="availableNameShort" column="available_name_short"/>
<result property="availableDescription" column="available_description"/>
<result property="availableDescriptionShort" column="available_description_short"/>
<result property="activeName" column="active_name"/>
<result property="activeNameShort" column="active_name_short"/>
<result property="activeDescription" column="active_description"/>
<result property="activeDescriptionShort" column="active_description_short"/>
</resultMap>
Here is my request, where I use the map:
<select id="findAdvantageByLoyaltyAndConfigDetailId" resultMap="AdvantageResult">
select a.id, a.code, a.name, a.description, a.as_is, a.to_be,
a.available_name, a.available_name_short, a.available_description, a.available_description_short,
a.active_name, a.active_name_short, a.active_description, a.active_description_short
from advantage a
left join detail_advantage da on da.advantage_id = a.id
where da.config_detail_id = #{configDetailId}
</select>
I want to get Map <Long, Advantage>, where the long key will be the param #{configDetailId}
How should I rewrite the mapper?
I could think of two approaches.
Convert the returned Advantage to Map in Java code.
Defining a simple default method in the Java mapper interface should be sufficient.
Advantage internalSelect(Long configDetailId);
default Map<Long, Advantage> select(Long configDetailId) {
return Map.of(configDetailId, internalSelect(configDetailId));
}
Add a private field to Advantage that holds configDetailId and use #MapKey.
private Long configDetailId;
Then include the parameter to the column list of the select and the result map.
select ...
, #{configDetailId} as configDetailId
from ...
<result property="configDetailId" column="configDetailId" />
Then add #MapKey to your Java mapper interface.
#MapKey("configDetailId")
Map<Long, Advantage> findAdvantageByLoyaltyAndConfigDetailId(Long configDetailId);
#MapKey is handy when there is an existing property for the 'key'.
Otherwise, I would recommend the first approach.

Return list of Object inside Object with MyBatis

I encountered problems when returning a list of Objects inside another Object when using MyBatis. My main object looks like this:
private Long id;
private String symbol;
private List<TypePermission> typePermissions;
and my mapper looks like this
<resultMap type="CalendarType" id="calendarTypeMap">
<result column="id" property="id"/>
<result column="symbol" property="symbol"/>
<collection property="TypePermissions" resultMap="TypePermissions"/>
</resultMap>
<resultMap id="TypePermissions" type="TypePermission">
<result property="roleId" column="roleId"/>
<result property="permissionSymbol" column="permissionSymbol"/>
</resultMap>
My goal is to get an object like this:
content:[
"id":id,
"symbol":symbol,
"TypePermissions":{
"roleId":roleId,
"permissionSymbol":permissionSymbol
}
]
When I execute the sql query I get the following an error cannot find symbol TypePermissions, because the main SELECT tries to select rows such as TYPEPERMISSIONS, ID, SYMBOL
I searched over the internet, but failed to find anything useful. Could you help me and point out what am I doing wrong?
Please post your select snippet, I think this will ok:
<select id="selectCalendarType" parameterType="int" resultMap="calendarTypeMap">
SELECT c.id,
c.symbol
t.roleId,
t.permissionSymbol
FROM CalendarType c
LEFT JOIN TypePermission t ON c.id = t.c_id
WHERE c.id = #{id}
</select>
And I think what you will get is actully something like this:
content:{
"id":id,
"symbol":symbol,
"TypePermissions":[{
"roleId":roleId,
"permissionSymbol":permissionSymbol
}]
}
And more about this you can read this example Nested_Results_for_Collection

Is it possible to map POJO fields with names different from the table columns?

I am relatively new to ibatis. I know it is already upgraded to mybatis, but for some reason I have to use ibatis. My question is that "Is it possible to map POJO fields with names different from the table columns?"
I have a table, the mapping file, and the POJO class. I can successfully read data if my POJO class has names exactly the same as the columns in the table, but if I name the field something else, it does not work. I changed the corresponding getter and defined a resultMap in the mapping file like the following
<resultMap id="result" class="Subscriber">
<result column="AdvisorId" property="id" jdbcType="INTEGER"/>
<result column="FirstName" property="FirstName" jdbcType="VARCHAR"/>
<result column="LastName" property="LastName" jdbcType="VARCHAR"/>
<result column="EmailId" property="EmailId222" jdbcType="VARCHAR"/>
</resultMap>
<select id="getAll" resultMap="result">
SELECT AdvisorId,FirstName,LastName,EmailId FROM communication
</select>
Here as an example, I am trying to rename EmailId in my POJO field to EmailId222
In the Subscriber class you have to define getter and setter methods for the field. Be careful to use capital letters if necessary. Then you have to restart your application to make the changes work. In your case, you should implement:
getEmailId222(){...}
setEmailId222(String EmailId222){...}

Fetching a LONGBLOB as a byte array using MyBatis

I am using MyBatis in a project, where I have a query that gets data from a LONGBLOB field in a MySQL database. I wish to get the result as a byte array (byte[]), so I try this:
<select id="fetchData" resultType="_byte[]" parameterType="_long">
select blobData from Table where id = #{id}
</select>
This does not work, however. I get the following error:
java.lang.ClassCastException: [B cannot be cast to [Ljava.lang.Object;
at org.apache.ibatis.binding.MapperMethod.convertToArray(MapperMethod.java:146)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:129)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:90)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:40)
Something tells me I should specify a type handler (BlobTypeHandler?), or something? But where and how?
I should mention that I have no problem getting around this problem by creating a wrapper class for the byte array and using a result map:
<resultMap type="BlobData" id="BlobDataMap">
<constructor>
<idArg javaType="_long" column="id" />
<arg javaType="_byte[]" column="blobData" />
</constructor>
</resultMap>
Still I am wondering if there's a more elegant way that does not involve creating a wrapper class.
in my project we use blobs this way:
we define a result map for our used class:
<resultMap class="SomeClass" id="SomeClassResultMap">
<result property="classByteAttribute" column="blobData" />
<result property="classIdAttribute" column="id" />
</resultMap>
and in the select statement we use this result map
<select id="selectStatement" resultMap="SomeClassResultMap" parameterClass="Integer">
SELECT * FROM EXAMPLETABLE where id=#id#
</select>
after the execution the blob is in the byte array.
As suggested by duffy the only way to get the result as a byte array is:
Mybatis Version 3.1.+
define a resultMap
<resultMap class="MyClass" id="MyClassMap">
<result property="myByteArray" column="MYBINARY " />
</resultMap>
<select id="selectStatement" resultMap="MyClassMap">
SELECT MYBINARY FROM EXAMPLETABLE
</select>
but,
Mybatis Version 3.0.5
<select id="selectStatement" resultType="_byte[]">
SELECT MYBINARY FROM EXAMPLETABLE
</select>
it is a strange regression since mybatis is not able to apply the correct (BlobTypeHandler) TypeHandler, and is not possible to specify the TypeHandler on select tag.

Constructing resultMap to create different instances for the same collection

(Using MyBatis v3.0.4.)
I have a problem that I do not know how to solve. My object model is:
Location.java
public class Location {
// ... other content
private List addresses;
// ... other content
}
Address.java
public class Address {
public enum Type { POSTAL, POBOX, INPUT, CLEANSED }
private Type type;
private String line1;
// ... other content
}
My SQL is:
SELECT
// ... other content
postal_address_line_1,
postal_address_line_2,
postal_address_city,
cleansed_address_line_1,
cleansed_address_line_2,
cleansed_address_city,
// ... other content
How would I construct a resultMap that would plug the appropriate
columns into an address instance of the correct type and added to the
same list in Location.java? I would like to avoid having to add
another instance variable to Location.java just to hold a different
type of address.
Use a discriminator tag in your result map.
Look at the mybatis user guide. Search for "discriminator" you see more informations.
<resultMap id="vehicleResult" type="Vehicle">
<id property=”id” column="id" />
<result property="sharedPropA" column="shared_column"/>
<discriminator javaType="int" column="address_type">
<case value="1" resultMap="postalResultMap"/>
<case value="2" resultMap="inputResultMap"/>
<case value="3" resultMap="cleanResultMap"/>
<case value="4" resultMap="whatIsaCleansedAddressResultMap"/>
</discriminator>
</resultMap>
Addition 1:
You need to select the addresses as different rows.
i.e
select
postal_address_line_1 as line1,
postal_address_line_2 as line2,
postal_address_city as city,
type as 'POSTAL'
....
union
select
postal_address_line_1 as line1,
postal_address_line_2 as line2,
postal_address_city as city,
type as 'CLEANSED'
.....
then the built in enum type handler should set the type correctly.
Along the lines of Andy Pryor's suggestion, I was able to solve the problem by updating my SQL statement to something like the following:
SELECT
// ... other content
'POSTAL' as Postal_Address_Type,
postal_address_line_1,
postal_address_line_2,
postal_address_city,
'CLEANSED' as Cleansed_Address_Type,
cleansed_address_line_1,
cleansed_address_line_2,
cleansed_address_city,
// ... other content
Then update my resultMap to the following:
<resultMap ...>
//... other content
<association property="postalAddress" javaType="com.x.y.z.Address">
<result property="type" column="Postal_Address_Type"/>
<result property="line1" column="Address_Part_1_Name"/>
<result property="line2" column="Address_Part_2_Name"/>
//...other content
</association>
<association property="cleansedAddress" javaType="com.x.y.z.Address">
<result property="type" column="Cleansed_Address_Type"/>
<result property="line1" column="Address_Part_1_Name"/>
<result property="line2" column="Address_Part_2_Name"/>
//...other content
</association>
</resultMap>
Finally, within my Address class I am able to have setType(Type) and the inbuilt enumerated type handler does the magic. Within the Location class I can just have one list of instances of Address and the various setXXXAddress() methods can add to this list appropriately.
It is unfortunate that I cannot plug the columns into some sort of factory class but putting hard-coded types into the SQL statement isn't too dirty, in my opinion. The disadvantage is that I have introduced coupling between the domain model's Address.Type values and the SQL statement but this is kind of already there given that the resultMap SQL XML needs to hold the names of instance variables in the Address class anyway.

Categories