Fetching multiple embedded objects mybatis - mybatis

Class User{
private String employeeId;
private Country assignedCountry;
private Region employeeRegion;
//getter & setter
}
Class Country{
private String countryCode;
private Region countryRegion;
//getter & setter methods
}
Class Region{
private String regionCode;
}
<resultMap type="User" id="userResultMap">
<id column="employee_id" property="employeeId" />
<association property="assignedCountry" resultMap="countryResultMap"/>
<association property="employeeRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Country" id="countryResultMap">
<id column="country_cd" property="countryCode" />
<association property="countryRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Region" id="regionResultMap">
<id column="region_cd" property="regionCode" />
<id column="region_nm" property="regionName" />
</resultMap>
Employee is assigned to a country and also belongs to a region.
Country belongs to a region, which may or may not be the same as the employee's region.
The query will fetch the users assigned country and region.
select U.*, C.*, R.* from
User U left outer join Country C
on U.assigned_country_cd = C.country_cd
left outer join Region R
on U.employee_region_cd = R.region_cd
When I execute the query via mybatis and check the user object, I can see that the user region is set properly.
But I can also see that the Region object within the User's country is also set to the user's region. Which shouldn't be the case.
(I understand that I am not fetching the country region here. But if so, this object should not be set at all, rather than setting employee region to the country region)
Can some one please help me on how to map the Country's region within the country object?
I am very new to mybatis and ORM. Any help to shed some light into this would be appreciated.

In your query, you are joining the Region table, not with Country table, but with User table, which ultimately returns the region of the employee (and the countryRegion property of the Country object as well).
In your ResultMap, regionResultMap maps as follows:
region_cd -> regionCode
region_nm -> regionName
You are mapping region_cd column, both to the user.assignedCountry.countryRegion.regionCode and to the user.employeeRegion.regionCode in the same time that basically sets the same columns to same properties in different objects.
What you can do is to differentiate User's region and Country's region in your SQL and map in MyBatis accordingly:
Add another join to connect region with the country:
select U.*, C.*, R.*, CR.region_cd AS C_region_cd, CR.region_nm as C_region_nm from
User U left outer join Country C
on U.assigned_country_cd = C.country_cd
left outer join Region R
on U.employee_region_cd = R.region_cd
left outer join Region CR
on CR.belongsTo = C.country_cd
And, you need to change your ResultMap as follows in order to use the same ResultMap with a different column name. Here, belongsTo is the column in your Region table that shows which Country it belongs to :
<resultMap type="User" id="userResultMap">
<id column="employee_id" property="employeeId" />
<association property="assignedCountry" resultMap="countryResultMap"/>
<association property="employeeRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Country" id="countryResultMap">
<id column="country_cd" property="countryCode" />
<association columnPrefix="C_" property="countryRegion" resultMap="regionResultMap"/>
</resultMap>
<resultMap type="Region" id="regionResultMap">
<id column="region_cd" property="regionCode" />
<id column="region_nm" property="regionName" />
</resultMap>

Related

MyBatis mapping nest pojo of fundamental type

I have a POJO of type:
POJO {
int id;
int name;
List<Date> Dates;
}
Now my POJO is broken into two tables x and y, where y have dates and x have name, which are joined over the id as below:
select * from x
join y on x.id = y.id
Further now I want to select from mybatis mapper and map it to the given pojo. I am getting issue in mapping the Dates.
<resultMap id="pojo" type="Pojo">
<result property="id" column="id" javaType="Integer"/>
<result property="name" column="name" javaType="String"/>
<result property="Dates" column="date" javaType="ArrayList" typeHandler="org.apache.ibatis.type.DateTypeHandler"/>
</resultMap>
I tried using collection but I don't have any property within the Dates. How to populate the dates field ?
<collection property="Dates" ofType="java.util.Date">
<result column="date" />
</collection>
This worked

How to dynamically select a resultMap based on the value of a column in MyBatis

I have a resultSet from DB, that returns say two records as below
TRANID Type Name Amount
1 B ABC 100.00
1 S XYZ -100.00
The above data represents a transaction where a seller and buyer are involved.
Now I need to map the above resultset to MyBatis, such that it returns me a transaction object in below structure.
Transaction :{
id : 1,
buyer:{
name : "ABC",
Amt : "100.00"
},
seller: {
name: "XYZ",
Amt: "-100.00"
}
}
If the DB had returned the data in one row, with both buyer and seller data in one dimension like
TRANID BNAME BAMOUNT SNAME SAMOUNT
1 ABC 100.00 XYZ -100.00
then I can use a resultmap something like below
<resultMap id="transactionRM" type="Transaction">
<id property="id" column="TRANID"/>
<association property="buyer" type="User">
<result property="name" column="BNAME"/>
<result propert="amt" column="BAMT"/>
</association>
<association property="seller" type="User">
<result property="name" column="SNAME"/>
<result propert="amt" column="SAMT"/>
</association>
</resultMap>
I will be able to achieve what I wanted, because I have unique aliases/column names for buyer and seller.
But how can I achieve the same results, if the results come in two row, where one is a buyer and seller, and type is a discriminator that determines if the row belongs to seller or buyer.
I tried to define a resultMap for User, like
<resultMap id ="buyerRM" type ="User">
<result property="name" column="Name"/>
<result property="amt" column="Amount"/>
</resultMap>
<resultMap id ="sellerRM" type ="User">
<result property="name" column="Name"/>
<result property="amt" column="Amount"/>
</resultMap>
<resultMap id="transacionRM" type="Transaction">
<association property="buyer" resultMap="buyerRM" type="User"/>
<association property="seller" resultMap="sellerRM" type="User">
</resultMap>
The above resultmap will not work, as same column names defined for both buyer and seller and the data will be duplicated.
Any suggestions.
Thanks in advance.
It is not possible to map this in mybatis 3.4.6 without query modification.
If you do modify query you have several options.
Self-join
The original query can be transformed to do a self join by transaction id column. This way you will a row per transaction and can map it as you described in your question.
Columns renaming
It is possible to map associations that span multiple rows. Object data may span multiple rows. Mybatis maps rows data to objects using id mapping element, that is rows that have the same id value belong to the same object so data from them is used to fill constructed object (whether it is a subordinated item belonging to collection or an association).
If you can wrap original query or modify it directly to set values into different columns based on type you can do that.
By wrapping I mean something like:
select
TRANID,
case Type when 'S' then Name else null end as seller_name,
case Type when 'S' then Amount else null end as seller_amount,
case Type when 'B' then Name else null end as buyer_name,
case type when 'B' then Amount else null end as buyer_amount
from (
-- original query goes here
)
And map it like this:
<resultMap id ="userRM" type ="User">
<result property="name" column="name"/>
<result property="amount" column="amount"/>
</resultMap>
<resultMap type="Transaction" id="twoRowMap">
<id column="TRANID" property="id"/>
<association property="buyer" resultMap="userRM" columnPrefix="BUYER_"/>
<association property="seller" resultMap="userRM" columnPrefix="SELLER_"/>
</resultMap>
Note that due to a bug in mybatis you need to specify columnPrefix in capital letters in mapping.
This would select one Transaction object with correctly set buyer and seller properties.
Associations only work for single rows. The closest thing you can get to what you want is by using collections.
<resultMap id="transactionRM" type="Transaction">
<id property="id" column="TRANID"/>
<collection property="users" type="User">
<result property="type" column="Type"/>
<result property="name" column="Name"/>
<result property="amt" column="Amount"/>
</collection>
</resultMap>
This will return a single object with a property id and a property users. The latter is a List<User>, that in this particular case will have two elements (one for each row). Not exactly what you want but close.
You'll need to transform that List<User> into two separate properties.

Mybatis collection with multiple columns

I have a table which has three foreign keys to items. These corresponding objects I want in a list property. I have the following collection mapping
<collection property="items" column="{item1Id, item2Id, item3Id}">
<association property="exampleNestedItem" column="{id, ###itemId###}" select="com.example.mapper.getItem" />
</collection>
I need the current value at ###itemId###. How can I reference the columns "item1Id", "item2Id" and "item3Id" for this Parameter?
I ended up with a very easy solution. In my case I know that there will always be 3 elements in that list. So I added a setter for each element in the model class like this
public void setElement1(Element element) {
elements.add(element);
}
...
and I added an association for each element
<association property="element1" column="element1Id" select="com.example.mapper.getItemWithId"/>
...
It will for sure not scale for many elements, but for my case it fits!

Composite keys in MyBatis <collection> mappings

I am unable to pass a composite key to a MyBatis <collection> element (using version 3.2.7). The MyBatis documentation states:
Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
However, all my attempts to implement this produce the Exception
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Error instantiating class java.lang.Integer with invalid types () or values (). Cause: java.lang.NoSuchMethodException: java.lang.Integer.<init>()
The collection (which resides in another ResultsMap) is:
<collection property="foos" ofType="FooObject"
column="{param1=user_id,param2=foo_id}" select="getFoosByUser" >
<id property="userId" column="user_id" />
<id property="foo" column="foo_id" />
<result property="fooName" column="foo_name" />
</collection>
It should return an ArrayList of Foo objects. The composite key is user_id and foo_id. The select query is:
<select id="getFoosByUser" parameterType="Integer" resultType="FooObject">
SELECT
user_id AS userId,
foo_id AS fooId,
foo_name AS fooName
FROM foo_table
WHERE user_id = #{param1}
AND foo_id = #{param2}
</select>
The query works correctly if I only use one parameter, e.g. removed foo_id=#{param2} and then use column=user_id in the collection, but I cannot work out how to structure the column attribute correctly for two keys. Any ideas?
MyBatis is confused by using parameterType when there are more than one parameter. Modify you query mapping like this:
<select id="getFoosByUser" resultType="FooObject">
SELECT
user_id AS userId,
foo_id AS fooId,
foo_name AS fooName
FROM foo_table
WHERE user_id = #{param1}
AND foo_id = #{param2}
</select>

How to make the myBatis select result(list) be set to object's property?

Generally, myBatis's select method returns single object or generic List types. for example, I want to fetch all students of a class:
<select id="fetchStudentsOfClass" parameterType="int" resultMap="resultMapStudent">
SELECT * FROM students WHERE class_id=#{id}
</select>
I can easily get a result like this: List<Student>.
Now, if I want to get the result like this rather than List<Student>:
class MyClass
{
List<Student> getStudents{return this.students;}
void setStudents(List<Student> students){this.students = students}
private List<Student> students;
}
how can I do?
This is what the <collection/> element is for. It's important that you properly mark both the container and the values with their <id/> so that MyBatis knows how to deal with collapsing multiple rows into one object.
<resultMap id="resultMapClass" type="some.package.MyClass" autoMapping="true">
<id property="classId" column="class_id" javaType="integer"/>
<collection property="students" ofType="some.package.Student" autoMapping="true">
<id property="studentId" column="student_id" javaType="integer"/>
</collection>
</resultMap>
<select id="fetchStudentsOfClass" parameterType="int" resultMap="resultMapClass">
SELECT *
FROM students
WHERE class_id = #{id}
</select>
See http://mybatis.github.io/mybatis-3/sqlmap-xml.html#Result_Maps for more details.