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.
Related
I am a newbie to MyBatis. As per my knowledge every column is mapped to a property value while retrieving data from the database. So is it possible to use aggregate functions using MyBatis. To what property we will map the results? I have searched it everywhere But can't find any details about the usage of aggregate functions with MyBatis. If anyone can help, please.
Every column of the result set is mapped to a property. Therefore, it's just a matter of producing a SQL statement that does that.
For example:
<resultMap id="regionProfit" type="app.RegionProfitVO">
<result property="region" column="region" />
<result property="cnt" column="cnt" />
<result property="profit" column="profit" />
</resultMap>
<select id="getRegionProfit" resultMap="regionProfit">
select
region,
count(sales) as cnt,
sum(revenue) - sum(expenses) as profit
from sales
group by region
</select>
Now, in the Java code you could do something like:
List<RegionProfitVO> rp = sqlSession.selectList("getRegionProfit");
If you don't want to create another POJO, you can always retrieve the result as a list of java.util.Map<String, Object> but use resultType="hashmap" in mapper.xml file for that select query
I am working on converting a large MyBatis2 map to a MyBatis3 map, and have run into a bit of an issue where I have a resultMap with multiple result elements using the same property attribute (and the class is generated from a WSDL outside of my control):
<resultMap id="blah" class="someWsdlGeneratedClass">
<result property="addressLine"
resultMap="addressLineOneListMap"
javaType="java.util.List" />
<result property="addressLine"
resultMap="addressLineTwoListMap"
javaType="java.util.List" />
</resultMap>
<resultMap id="addressLineXListMap" class="string">
<!-- Result maps are the same except for column -->
<result property="addressLine" column="COLUMN_X" />
</resultMap>
Notice that both properties are "addressLine".
This works fine for Mybatis2. However, if I try to use the same pattern for MyBatis3, I get an IllegalArgumentException: Result Maps collection already contains value for Mapper.mapper_resultMap[blah]_collection[addressLine]
<resultMap id="blah" type="someWsdlGeneratedClass">
<collection property="addressLine"
resultMap="addressLineOneListMap"
javaType="java.util.List" />
<collection property="addressLine"
resultMap="addressLineTwoListMap"
javaType="java.util.List" />
</resultMap>
I'd like to avoid writing a wrapper around the generated class in a Dto object, if possible, as that would result in a major refactoring effort in the project. Is there something I can add in the map itself?
You can add a 2nd setter (setAddressLine2) to your generated dto.
In which your code for it can just add to addressLine. ex:
void setAddressLine2(final List<Address> addressLine2) {
address.addAll(addressLine2);
}
If that's not possible you can try changing your query to return a union of the 2 columns
Without knowing your exact query it would look something like:
SELECT foo, addressLine1 as Address
FROM bar
UNION
SELECT foo, addressLine2 as Address
FROM bar
If that isn't possible then you need to create a test project and create an issue on https://github.com/mybatis/mybatis-3 and request a feature.
That option is probably best anyways as I'm not sure you are using it correctly. It seems that your 2nd example (using collection) is correct (conceptually at least. you still can't map to the same property using it) but the first won't behave as you explained it?
I am using MyBatis with Java. Can I use multiple ids in the result map?
Code
My Result Java class looks like this:
public class MyClass {
private int id1;
private int id2;
private int total;
// getters & setters
}
Currently, my MyBatis result map looks like this:
<resultMap id="MyResult" type="MyClass">
<result property="id1" column="id1" />
<result property="id2" column="id2" />
<result property="total" column="total" />
</resultMap>
This result map is used in this query:
<select id="mySelect" resultMap="MyResult" parameterType="Map">
select
id1,
id2,
sum(total) as total
from
myTable
group by
id1,id2;
</select>
Everything works fine, but according to MyBatis documentation I should use id to gain some efficiency. I want to change the MyBatis result map to:
<resultMap id="MyResult" type="MyClass">
<id property="id1" column="id1" />
<id property="id2" column="id2" />
<result property="total" column="total" />
</resultMap>
Questions
Will it work the same as before?
[Later Edit]: tested, it seems that it does not screw up the groupings and the rows, so I would say that it works as before and as expected.
Will it bring the MyBatis performance benefits by using id?
Where did I look, but could not find an answer
MyBatis documentation - they do not say or give example of a similar situation.
Composite Key - however, I do not have a composite key, and I would rather not modify MyClass to create a pseudoclass ID that has id1, id2.
Yes, it will work the same as before, and according to documentation, it will gain some efficiency, that you will appreciate when you work with massive rows. Anyway, my recommendation is to use the second result map, in order to be the most accurate in the definition of your resultmap according to your database estructure.
there is simple select
<select id = "getWidgetConfig" resultType="com.comp.entity.widgets.cnfg.Widget">
SELECT `json_config`
FROM `widget_config`
WHERE `widget_id` = #{widgetId}
</select>
where json_config is string value.
Can i bind type/result handler(inline) to convert json_config value into my entity Widget using my handler?
I can do this inline for update/insert statements
INSERT INTO `widget_config`
(
`json_config`
)
VALUES
(
#{widget,typeHandler=com.comp.mybatis.handlers.widget.WidgetTypeHandler}
)
How can i do similar for select statements?
Thanks
I don't think it is possible to use type handlers on the top level in select. But you can approach this a bit by using result map with constructor:
<resultMap id="widgetMap" type="Widget">
<constructor>
<arg column="json_config" javaType="string"/>
</constructor>
</resultMap>
<select id="getWidgetConfig" resultMap="widgetMap">
SELECT `json_config`
FROM `widget_config`
WHERE `widget_id` = #{widgetId}
</select>
The potential downside is that you need to have parsing logic in constructor of Widget. If this not an option one way to overcome this is to create copy constructor for Widget and change mapping like this:
<resultMap id="widgetMap" type="Widget">
<constructor>
<arg column="json_config" javaHandler="com.comp.mybatis.handlers.widget.WidgetTypeHandler"/>
</constructor>
</resultMap>
You need make sure Widget has appropriate copy constructor that takes value of type Widget and creates a copy (it may probably reuse internals of the passed Widget if they are immutable).
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