Mybatis collection with multiple columns - mybatis

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!

Related

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.

In CrafterCMS, how can I query a model to get an array with all the fields in a repeated group?

In CrafterCMS, I have a component Team with a itemSelector field where I'm assigning some instances of another component TeamMember.
In the template of Team I'm using siteItemService.getSiteItem to get the model information of child components:
<#assign memberModel = siteItemService.getSiteItem(memberItem.storeUrl) />
Between the fields of type TeamMember I have:
Some fields of TeamMember
I'm able to get the value of skillTitle like this:
<#assign skillsTitle = memberModel.queryValue("//skillsTitle")!"" />
But I'm not able to get the value of the values in the repeating group.
I tried with:
<#assign skills = memberModel.queryValues("//skills")![] />
It returns an array of just one element, I think is an empty string
<#assign skills = memberModel.queryValues("//skills/item")![] />
It returns an array with the right number of elements, but I think all of them are empty strings
If I use:
<#assign skills = memberModel.queryValues("//skills/item/skillName")![] />
I get a correct array with all the skill names, but I need iterate over both values (skillName and skillLevel)
How can I query the model in order to get an array which elements have all the values in the repeated group?
Once you get the SiteItem with
<#assign memberModel = siteItemService.getSiteItem(memberItem.storeUrl) />
it works just like any other contentModel variable within a FreeMarker template. So, you can iterate it with
<#list memberModel.skills.item as skill>
${skill.skillName} = ${skill.skillLevel}
</#list>

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.

MyBatis - lists of primitive types

This seems to have eluded me. I know I can use a map to return a vanilla set of rows from a myBatis query but how to do it with a list of primitive types?
e.g. If I had SQL like:
select product_price from products
Does this require a resultMap? I've tried to use java.util.ArrayList as the result type but get class not found errors.
In a similar vein, how do I pass a list of items as an argument to a query.
Any input, pointers to docs appreciated.
Just declare the resultType as the primitive type that you want, which in your case is a Long. It will be returned as a list.
<select id="getPrice" resultType="java.lang.Long">
select product_price from products
</select>
In the mapper interface you should expect to get back a list of Long.
List<Long> getPrice();
Try using below code snippet inside your resultmap for product_price column mapping -
<collection property="price" ofType="java.lang.Long">
<result property="price" column="product_price"/>
</collection>
try using resultMap
<resultMap type="java.lang.Long" id="domainsResult">
<result property="" column="product_price"/>
</resultMap>
<select id="getPrice" resultMap="domainsResult">
select product_price from products
</select>
It will give you a List priceList.
Trey this :
You will get it as a list of map if you write like this:
<select id="getPrice" resultType="Hashmap">
select product_price from products
</select>
Where the key will be column name. Each map will contain a single entry. If You want an ArrayList then you can write a function to convert this map to an ArrayList:
public List listFromListOfMap (List<Map> listOfMap ) {
List<Integer> prices = new ArrayLisyt<Integer>();
for(int i = 0; i < listOfMap.size(); i++) {
Integer value = (Integer)listOfMap.get(i).get("product_price");
prices.add(value);
}
return prices;
}
Hope this helps :)