Mybatis2 to MyBatis3 Conversion - Results with Same Property - java

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?

Related

Is it possible to use Aggregate functions in MyBatis

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

Multiple ids in MyBatis resultMap - performance benefits?

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.

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){...}

MyBatis: Controlling Object Creation vs. Referencing Existing Objects

This is my first question here, so please let me know if there's anything I can provide to make my question clearer. Thanks!
Is there anyway to tweak MyBatis (3.0.4) so that when it's running through the nested result maps and creating objects based on the results, it doesn't create "duplicate" objects and instead references the object that has already been created? To clarify, let's say I've got an object that consists of the following (simplified) info:
Public class PrimaryObject {
Private Contact role1;
Private Contact role2;
Private List<OtherObject> otherObjects;
}
Public class OtherObject {
Private String otherProperty;
Private Contact otherRole;
}
And the result maps/SQL (assume type aliases):
<mapper namespace="PrimaryObject">
<resultMap id="primaryObject" type="primaryObject">
<association property="role1" column="ROLE_1_ID"
select="Contact.findContactByPK" />
<association property="role2" column="ROLE_2_ID"
select="Contact.findContactByPK" />
<collection property="otherObjects" column="PRIMARY_OBJECT_ID"
javaType="List" ofType="OtherObject"
select="OtherObject.findOtherObjectsByPrimaryObjectPK" />
</resultMap>
<select id="selectPrimaryObjectByPK" parameterType="..."
resultMap="primaryObject">
SELECT * FROM PRIMARY_OBJECT_TABLE
WHERE PRIMARY_OBJECT_ID = #{value}
</select>
...
<mapper namespace="OtherObject">
<resultMap id="otherObject" type="otherObject">
<result property="otherProperty" column="OTHER_PROPERTY" />
<association property="otherRole" column="OTHER_ROLE_ID"
select="Contact.findContactByPK" />
</resultMap>
<select id="findOtherObjectsByPrimaryObjectPK" parameterType="..."
resultMap="otherObject">
SELECT OTHER_OBJECT.OTHER_PROPERTY, OTHER_OBJECT.OTHER_ROLE_ID
FROM OTHER_OBJECT JOIN PRIMARY_OBJECT
ON PRIMARY_OBJECT.PRIMARY_OBJECT_ID = OTHER_OBJECT.PRIMARY_OBJECT_ID
</select>
...
<mapper namespace="Contact">
<resultMap id="contact" type="Contact">
<result property...
...
</resultMap>
<select id="findContactByPK" parameterType="..." resultMap="contact">
SELECT * FROM CONTACT...
</select>
Now let's say a contact is acting as both role1 and role2 on primaryObject. From what I've seen, MyBatis creates the contact object referenced in role1, and when it hits role2, it simply references the object it created for role1. That's great!
However, let's say that same contact is acting as the otherRole in otherObject. MyBatis now creates an identical contact object, as opposed to just referencing the original contact created/referenced when making the primaryObject. Is there any way I can prevent this from happening, and instead just store a reference in my otherObject that references that same contact that the two different fields in primaryObject are pointing to? I've been looking at a custom ResultHandler, but the example's complexity coupled with my lack of experience is preventing me from understanding if it even addresses the problem I'm trying to solve in the first place. Thanks in advance for your help and patience!
OK, I think your question might be a duplicate of How to map a myBatis result to multiple objects?. So, no MyBatis does not have native support for this problem, if I'm understanding it correctly.
You should be careful using nested selects, as they execute addition select statements on the database. I suggest checking out the MyBatis 3 User Guide, particularly around page 35 and up. MyBatis has very powerful ways to avoid the n+1 selects problem. What is SELECT N+1?

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