Currently working on upgrading from iBatis to myBatis. In ibatis we would have a sql map like so
<resultMap id="PCRV_HIERARCHY_LVL_LIST_MAP" class="com.fmrco.sai.aadpm.domain.ConstraintHierarchyLevel">
<result property="levelId" column="LEVEL_ID"/>
<result property="levelDescription" column="LEVEL_DESCRIPTION"/>
<result property="levelRank" column="LEVEL_RANK"/>
<result property="levelCode" column="LEVEL_CODE"/>
</resultMap>
<parameterMap id="GET_HIERARCHY_LVL_LIST_MAP" class="java.util.Map">
<parameter property="PCRV_HIERARCHY_LVL_LIST" jdbcType="ORACLECURSOR" javaType="java.sql.ResultSet" mode="OUT" resultMap="PCRV_HIERARCHY_LVL_LIST_MAP"/>
</parameterMap>
<procedure id="GET_HIERARCHY_LVL_LIST" parameterMap="GET_HIERARCHY_LVL_LIST_MAP">
{ call GET_HIERARCHY_LVL_LIST ( ? ) }
</procedure>
I'd like to fully utilize the functionality provided by myBatis (such as not needing to implement an implementation of a mapper and avoiding using deprecated features such as parameterMap) but I'm having some issues. I kept running into errors trying to set the return properties so I had to wrap my resultSet object in a wrapper object which is something I'd like to avoid
Mapper.java
public void getHierarchyLevels(ListConstraintHierarchyLevel constraintHierarchyLevels);
ConstraintHierarchyLevel class
public class ListConstraintHierarchyLevel {
private List<ConstraintHierarchyLevel> constraintHierarchyLevels ;
public List<ConstraintHierarchyLevel> getConstraintHierarchyLevels() {
return constraintHierarchyLevels;
}
public void setConstraintHierarchyLevels(List<ConstraintHierarchyLevel> constraintHierarchyLevels) {
this.constraintHierarchyLevels = constraintHierarchyLevels;
}
}
mapper.xml
<resultMap id="HierarchyLvlMap" type="com.fmrco.sai.aadpm.domain.ConstraintHierarchyLevel">
<result property="levelId" column="LEVEL_ID"/>
<result property="levelDescription" column="LEVEL_DESCRIPTION"/>
<result property="levelRank" column="LEVEL_RANK"/>
<result property="levelCode" column="LEVEL_CODE"/>
</resultMap>
<select statementType="CALLABLE"
id="getHierarchyLevels"
parameterType="com.fmrco.sai.aadpm.domain.ListConstraintHierarchyLevel"
resultMap="HierarchyLvlMap">
{ call GET_HIERARCHY_LVL_LIST (
#{constraintHierarchyLevels,
jdbcType=CURSOR,
mode=OUT,
javaType=java.sql.ResultSet,
resultMap=HierarchyLvlMap}
) }
</select>
I have attempted another solution unsuccessfully. In this solution I make use of the param annotation
mapper.java
public void getHierarchyLevelsWithParam(#Param("constraintHierarchyLevels") List<ConstraintHierarchyLevel> constraintHierarchyLevels);
I use the same resultMap as above but with a different select block
mapper.xml
<select statementType="CALLABLE"
id="getHierarchyLevelsWithParam"
parameterType="list"
resultMap="HierarchyLvlMap">
{ call GET_HIERARCHY_LVL_LIST (
#{constraintHierarchyLevels,
jdbcType=CURSOR,
mode=OUT,
javaType=java.sql.ResultSet,
resultMap=HierarchyLvlMap}
) }
When running this I have debugged into the MapperMethod class to the execute method and the Object param gets the correct data from the result set however as this does not get placed into the argument sent down (List) these values do not get returned. When running the first method with the object wrapper the objects are placed in the argument sent down and thus are retrievable.
Thanks
I don't know any other way than wrapping result object.
Indeed, the OUT variable has to be bound on something, this is a variable scope issue, then indirection is mandatory. But it can be generic:
class Wrapper<T> {
private List<T> member;
}
The #Param annotation is actually used to give parameters a name to uses as reference in the SQL (especially if the mapper method has multiple parameters)
Related
I want to refer to a property in an object in my mapper file which is an array of strings in a SQL IN criteria. The query does a count, so all it needs to return is a numeric value. The query needs to adjust its count based on a flexible set of criteria defined in a filter object. Some filters will be present (ie. not null), and others will be absent.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="TotalUniqueUsers">
<select id="getTotalUniqueUsers"
resultType="int"
parameterType="RequestFilter">
SELECT *
FROM MY_TABLE
WHERE
<if test="quarterList!=null and quarterList.length>0">
AND trim(FISCAL_QUARTER_NAME) IN #{quarterList javaType=list}
</if>
</select>
</mapper>
public class RequestFilter {
private String[] quarterList;
public String[] getQuarterList(){
return this.quarterList;
}
public void setQuarterList(String[] quarterList){
this.quarterList=quarterList;
}
}
Note, there is no type handler for RequestFilter. I did not think I needed one. I'm not trying to take an object and condense it into say one field in some weird way. All I want to do is have an input parameter to the
With the above, I get
org.apache.ibatis.exceptions.PersistenceException:
...
Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'quarterList javaType=list' in 'class RequestFilter'
I tried javaType=Array also, but get the same result. If I change the
#{quarterList javaType=list}
to
#{quarterList}
it says the typeHandler is null for the RequestFilter.
There is no portable way to set the list or array to IN prepared statement parameter in JDBC and therefore in mybatis (there are ways to do that if you are using postgres).
So in the general case you need to dynamically generate the query with a parameter per element in the list:
<select id="getTotalUniqueUsers"
resultType="int"
parameterType="RequestFilter">
SELECT *
FROM MY_TABLE
WHERE
<if test="quarterList!=null and quarterList.length>0">
trim(FISCAL_QUARTER_NAME) IN (
<foreach item='quarter' collection='quarterList' separator=','>
#{quarter}
</foreach>
)
</if>
</select>
I have a result map that looks like this:
<resultMap id="myMap" type="myEntity">
<id property="id" column="ID" />
<result property="name" column="NAME" />
<collection property="places" ofType="MyPlace" >
<result property="placeName" column="PLACE_NAME" />
</collection>
<resultMap>
<select id="mySelectStatement" parameterType="MyQuery" resultMap="myMap">
....
</select>
In the incoming parameter (MyQuery) of the select statement I have a flag that indicates whether the places should by joined and resolved or left out. Using the <if test="myFlag" />
construct this all works well.
Now, the only problem that I have is the following: When the flag indicates that the places should be resolved but there are no places connected with the entity then the resulting collection is empty (so far so good). However, when the flag indicates that the places should not be resolved, the resulting collection is also empty.
It is no longer decidable whether the field "places" is empty because there are simply no places or because they weren't being resolved at all. What I would like to have is some mechanism that sets the field "places" to ´null´ instead of returning an empty collection in the case that the flag that decides whether the places should be resolved is set to false.
EDIT:
Some more code to better understand the example
// MyEntity.java
public class MyEntity {
private int id;
private String name;
private List<MyPlace> places;
}
// MyQuery.java
public class MyQuery {
private boolean myFlag;
// getter & setter
}
// MyComponent.java
public class MyComponent {
private MyMapper myMapper;
public void findByQuery(MyQuery myQuery) {
List<MyEntity> myEntities = myMapper.mySelectStatement();
MyEntity firstEntity = myEntities.get(0);
List<Place> places = firstEntity.getPlaces();
if(places.isEmpty()) {
System.out.println("Hm I wonder why they are empty");
}
}
}
// MyMapper.java
public interface MyMapper {
List<MyEntity> mySelectStatement(MyQuery myQuery);
}
// MyMapper.xml
// result map from above
<select id="mySelectStatement" parameterType="MyQuery" resultMap="myMap">
SELECT * FROM MY_ENTITY
<if test="myFlag">
LEFT OUTER JOIN PLACES ON .....
</if>
</select>
And some clarification: This all works in principle. The only problem that I have is that I can not distinguish between an empty collection "places" that is empty because there were no entries in the table AND an empty collection places that is empty because they were not supposed to be resolved in the first place.
My current solution is to check in MyComponent after the method call whether the query that was passed in has the flag set to false. If that is the case, the "places" variable is manually set to null.
I want the returned result of the select statement below to be Map<String, Profile>:
<select id="getLatestProfiles" parameterType="string" resultMap="descProfileMap">
select ml.layerdescription, p1.*
from ( select max(profile_id) as profile_id
from SyncProfiles
group by map_layer_id) p2
inner join SyncProfiles p1 on p1.profile_id = p2.profile_id
inner join maplayers ml on ml.LAYERID = p1.MAP_LAYER_ID
where ml.maxsite = #{site}
</select>
I have seen this post which maps a String to a custom class, but the key was part of the custom class. In my query above, the layerdescription field is not part of the Profile class since I'm aiming to have the Profile class strictly represent the syncprofiles table and the layerdescription field is in another table.
My interface looks like:
public Map<String, Profile> getLatestProfiles(final String site);
How should descProfileMap be defined? I want to do something like:
<resultMap id="descProfileMap" type="java.util.HashMap">
<id property="key" column="layerdescription" />
<result property="value" javaType="Profile"/>
</resultMap>
But this is clearly wrong. Thanks for your help!
Achieving this requires 2 steps:
-Use association and nested resultMap:
<resultMap type="Profile" id="profileResultMap">
<!-- columns to properties mapping -->
</resultMap
<resultMap type="map" id="descProfileMap">
<id property="key" column="layerdescription" />
<association property="value" resultMap="profileResultMap" />
</resultMap>
-Add every record to a Map with expected structure using ResultHandler:
final Map<String, Profile> finalMap = new HashMap<String, Profile>();
ResultHandler handler = new ResultHandler() {
#Override
public void handleResult(ResultContext resultContext) {
Map<String, Object> map = (Map) resultContext.getResultObject();
finalMap.put(map.get("key").toString()), (Profile)map.get("value"));
}
};
session.select("getLatestProfiles", handler);
If you run that as is, expect this exception will likely be raised:
org.apache.ibatis.executor.ExecutorException: Mapped Statements with
nested result mappings cannot be safely used with a custom
ResultHandler. Use safeResultHandlerEnabled=false setting to bypass
this check or ensure your statement returns ordered data and set
resultOrdered=true on it.
Then following the suggestion, you can either disable the check globally in Mybatis config:
According to the documentation:
safeResultHandlerEnabled: Allows using ResultHandler on nested statements. If allow, set the
false. Default: true.
<settings>
<setting name="safeResultHandlerEnabled" value="false"/>
</settings>
or specify your result is ordered in the statement:
The documentation states:
resultOrdered This is only applicable for nested result select
statements: If this is true, it is assumed that nested results are
contained or grouped together such that when a new main result row is
returned, no references to a previous result row will occur anymore.
This allows nested results to be filled much more memory friendly.
Default: false.
<select id="getLatestProfiles" parameterType="string" resultMap="descProfileMap" resultOrdered="true">
But I have not found anyway to specify this statement option when using annotations.
My Problem
I'm getting the error Type class myPackage.MyClass is not known to the MapperRegistry.
I successfully acquired a session, and upon debugging I can see that it otherwise appears to be configured correctly so the interface association seems to be working; therefor I'm confident this error is distinct from the stack-overflow-suggested Type interface is not known... question.
I'm new to myBatis but from the documentation I understood that the following was all that was required to get resultType auto-mapping to work.
Update: This also happens when mapping the mapper resources by xml file instead of by class.
My Mapper
public interface MyClassMapper{
MyClass getMyClass(Integer id);
}
My Model
public class MyClass{
private String itemValue;
public String getItemValue() {
return itemValue;
}
public void setItemValue(String itemValue) {
this.itemValue = itemValue;
}
}
My Sql Map
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="myPackage.orm.sqlMap.MyClassMapper" >
<select id="getMyClass" resultType="myPackage.MyClass" >
select itemValue
from SOME_TABLE
WHERE id = #{id}
</select>
</mapper>
My mybatis-config.xml
...
<mappers>
<mapper class="myPackage.MyClass" />
</mappers>
...
Fixed:
public MyClass getMyClassValue(Integer id) throws Exception{
SqlSession session = MyBatisSessionFactory.openSession();
MyClassMapper mapper = (MyClassMapper) session.getMapper(MyClass.class);
return mapper.getMyClass(id);
}
Here is the code I was using to execute the query, discovered that I was looking up the mapper in the mapper registry by the model class name, rather than the mapper interface name. Works just fine now.
In your mapper.xml file mapper's namespace should be the path to the mapper interface.
for example:
<mapper namespace="com.mapper.LineMapper">
<select id="selectLine" resultType="com.jiaotong114.jiaotong.beans.Line">
select * from bus_line where id = #{id}
</select>
</mapper>
your mapper interface should be in com.mapper package and the name of it is LineMapper.
hope help.
I solved this issue by adding the mapper XML to the mybatis xml configuration file
<mappers>
<mapper resource="com/java/Mapper.xml"/>
</mappers>
I have scalar function:
CREATE FUNCTION [dbo].[CheckLocation]
(
#locationId Int
)
RETURNS bit
AS
BEGIN
//code
END
I want to use it in Entity Framework context.
I have added this in the *.edmx file:
<Function Name="CheckLocation" ReturnType="bit" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" >
<Parameter Name="locationId" Type="int" Mode="In" />
</Function>
I have also created a partial class with method decorated with EdmFunctionAttribute:
public partial class MainModelContainer
{
[EdmFunction("MainModel.Store", "CheckLocation")]
public bool CheckLocation(int locationId)
{
throw new NotSupportedException("Direct calls not supported");
}
}
I try to use this function like this:
Context.CheckLocation(locationId);
And get NotSupportedException("Direct calls not supported").
It works within Select method, but it does not suit me.
Help please!
How can I call this function without select method?
you need to access it as a select
var students = context.Locations
.Select ( new { location= CheckLocation(locationId)}):