Getting Map<Long, List<Object>> using MyBatis - java

Here is my method, I need to get a Map<Long, List<CMBill>>:
#Mapper
public interface CMBillMapper {
#MapKey("groupId")
Map<Long, List<CMBill>> getCMInvoicesByIdListSQL(#Param("invoiceIds") Set<Long> invoiceIds);
}
Here is MyBatis xml config, which configures this 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="ru.example.CMBillMapper">
<resultMap id="CMBillResult" type="CMBill">
<result property="id" column="id"/>
<result property="groupId" column="group_id"/>
<result property="clientId" column="client_id"/>
<result property="paidAmount" column="paid_amount"/>
<result property="amount" column="amount"/>
<result property="currency" column="currency"/>
<result property="status" column="status"/>
</resultMap>
<select id="getCMInvoicesByIdListSQL" resultMap="CMBillResult">
select it.group_id as group_id,
it.id as id,
it.client_id as receiver_id,
it.status as invoice_status,
it.amount as amount,
it.currency as currency,
it.paid_amount as paid_amount,
if.type as if_type,
if.code as if_code,
if.val as if_val
from cm_invoice_tab it
left join cm_inv_group_field_tab if
on if.group_id = it.group_id
where it.group_id in (#{invoiceIds})
</select>
</mapper>
But the result of mapper is Map<Long, CMBill>, and I need another one. How to rewrite my mapper for getting result which I need?

In this case, it is recommended that you directly query and obtain List, and then group the group_id after obtaining the result set.

Related

How to solve the MyBatis Pagination PageHelper query return data has duplicates?

When I use MyBatis Pagination PageHelper, the query returns are duplicates. I think should not be!, because will affect the duplicate problem when I query two tables. For example what I wrote the sql mapping file,the following code:
SELECT
o.id,
o.uid,
u.nickname,
o.order_num,
o.order_type,
o.order_price,
o.pay_type,
o.order_status,
o.name,
o.phone,
o.delivery_time,
o.createtime
FROM
orders AS o,
water_member AS u
WHERE
o.order_status = 1
ORDER BY
o.id
DESC
But is has repetitions:
UnpaidOrderController.java
#RestController
public class UnpaidOrderController {
#Autowired
private UnpaidOrderService unpaidOrderService;
#RequestMapping(value = "unpaidorder",method = RequestMethod.GET)
public Object getByPage(#RequestParam(value = "pageNo", defaultValue = "1") int pageNo,
#RequestParam(value = "pageSize", defaultValue = "3") int pageSize) {
Page<UnpaidOrder> list = unpaidOrderService.findByPage(pageNo, pageSize);
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg","查询成功");
jsonObject.put("data",list);
return jsonObject;
}}
UnpaidOrderMapper.java
#Mapper
public interface UnpaidOrderMapper {
Page<UnpaidOrder> findByPage();
}
UnpaidOrderService.java
public interface UnpaidOrderService {
Page<UnpaidOrder> findByPage(int pageNo, int pageSize);}
UnpaidOrderServiceImpl.java
#Service
public class UnpaidOrderServiceImpl implements UnpaidOrderService {
#Autowired
private UnpaidOrderMapper unpaidOrderMapper;
public Page<UnpaidOrder> findByPage(int pageNo, int pageSize) {
PageHelper.startPage(pageNo,pageSize);
return unpaidOrderMapper.findByPage();
}
}
UnpaidOrderMapper.xml
<mapper namespace="com.zyl.water.mapper.UnpaidOrderMapper">
<!-- 映射订单对象的resultMap -->
<resultMap id="BaseResultMap" type="UnpaidOrder">
<result column="id" property="id" />
<result column="uid" property="uid" />
<result column="nickname" property="nickname" />
<result column="order_num" property="order_num" />
<result column="order_type" property="order_type" />
<result column="pay_type" property="pay_type" />
<result column="order_status" property="order_status" />
<result column="name" property="name" />
<result column="phone" property="phone" />
<result column="delivery_time" property="delivery_time" />
<result column="createtime" property="createtime" />
</resultMap>
<!-- 查询未支付订单 -->
<select id="findByPage" resultMap="BaseResultMap">
SELECT
o.id,
o.uid,
u.nickname,
o.order_num,
o.order_type,
o.order_price,
o.pay_type,
o.order_status,
o.name,
o.phone,
o.delivery_time,
o.createtime
FROM
orders AS o,
water_member AS u
WHERE
o.order_status = 1
ORDER BY
o.id
DESC
</select>
I wanted to comment on your strategy to paginate.
This is not an issue of MyBatis, but of your query. When paginating you need to specify a UNIQUE ordering set of columns.
The reason of this is that in SQL, rows do NOT have a specific ordering by default. Therefore, the database will return them in any order when the ORDER BY clause is not specific enough. This way "Page 1" and "Page 2" may end up showing a few/lot identical rows.
Every time the query is executed (every time you click "Next Page") the row order must be clearly specified, but it's not in your case.

Lazy fetching in MyBatis

I couldn't able to find how to achieve lazy loading(even in MyBatis docs).
My mapper xml is shown below:
<?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="com.example.FooMyBatisLazyFetch">
<select id="callFooProc"
parameterType="com.example.FooProcBundle"
statementType="CALLABLE">
{call FooProc(
#{arg1, jdbcType=VARCHAR, mode=IN},
#{arg2, jdbcType=VARCHAR, mode=IN},
#{arg3, jdbcType=VARCHAR, mode=IN},
#{error, jdbcType=NUMERIC, mode=OUT},
#{res2, jdbcType=CURSOR, mode=OUT, resultMap=FooProcResult}
)
}
</select>
<resultMap id="FooProcResult" type="com.example.FooProcResult">
<result property="bar1" column="barcol1"/>
<result property="bar2" column="barcol2"/>
<result property="bar3" column="barcol3"/>
<result property="bar4" column="barcol4"/>
<result property="bar5" column="barcol5"/>
</resultMap>
</mapper>
Pojo Class:
public class FooProcResult {
private String bar1;
private String bar2;
private String bar3;
private String bar4;
private String bar5;
}
public class FooProcBoondle {
private String arg1;
private String arg2;
private String arg3;
private Integer error;
private List<FooProcResult> res2;
//getters,setters, etc
}
And usage code;
FooProcBundle bundle = new FooProcBundle();
bundle.setArg1("foo");
bundle.setArg2("bar");
bundle.setArg3("baz");
fooMyBatisLazyFetch.callFooProc(bundle);
Integer error = bundle.getError();
if(error == 123) /*some condition*/ {
List<FooProcResult> res2 = bundle.getRes2();
// iterate res2
--->// Only here CURSOR may be opened and executed
}
i.e. I don't want to fetch res2 unless my code explicitly request for it. That particular cursor is quite heavy, and I don't want to execute it when it's not required(but mybatis does it).
Also I want to apply this to generator-like procedures (Oracle call them "Pipelined Table Functions" they yield result, sleep and wait until caller fetches next row - wakeup and calculate next. Usually they called this way: SELECT * FROM TABLE(GenProc(arg1,arg2)).
Any ideas about the configuration required to achieve this?
Procedure output cursor parameters are processed in class org.apache.ibatis.executor.resultset.DefaultResultSetHandler
in method handleOutputParameters and then in method handleRefCursorOutputParameter. You will note that the code in its current state, does not allow to do want you seek, the only "custom option" used is the resultMap that must be provided. I also would have appreciated some more options just like lazy loading, custom result handler, and some logs to be able to monitor the actual execution time and the fetch time.
This could be achieved in JDBC and would require configuration that is not implemented in the framework.

nested collections with POJO

I am trying to follow the same methods listed in this blog here to avoid the N+1 problem.
I have created 5 classes with a many to 1 relationship as follows:
Trunk -> Branch 1 -> Branch 2 -> Branch 3 ->Leaf
I am trying to build my XML mapper which complies but fails at runtime with an error that there is no property 'Branch2' in 'class com.mytest.branch1'
I have defined my XML mapper as
<mapper namespace="com.mytest.test">
<resultMap id="resultTest" type="com.mytest.test.trunk">
<id property="TrunkID" column="TRUNK_ID" />
<collection property="Branch1" column="BRANCH_1_ID" javaType="ArrayList" ofType="com.mytest.test.branch1">
<id property="branch1ID" column="BRANCH_1_ID">
<collection property="Branch2" column="BRANCH_2" javaType="ArrayList" ofType="com.mytest.test.branch2">
<id property="branch2ID" column="BRANCH_2_ID">
<collection property="Branch3" column="BRANCH_3" javaType="ArrayList" ofType="com.mytest.test.branch3">
<id property="branch3ID" column="BRANCH_3_ID">
<result property="Value" column="Value">
</collection>
</collection>
</collection>
</resultMap>
<select id="getAllNodes" resultMap="resultTest">
select n.TRUNK_ID,b.BRANCH_1_ID,b.BRANCH_2_ID,b.BRANCH_3_ID,b.VALUE FROM node n join branches b on n.node_id=b.node_id
</select>
</mapper>
Class Trunk:
public class Trunk {
private int TrunkID;
private List<Branch1> Branch1;
//getters and setters
}
Class Branch1:
public class Branch1{
private int Branch1ID;
private List<Branch2> Branch2;
//getters and setters ...
}
Class Branch2:
public class Branch2{
private int Branch2ID;
private List<Branch3> Branch3;
//getters and setters ...
}
Class Branch3:
public class Branch3{
private int Branch3ID;
//getters and setters ...
}
There are two things here.
First, did you even try to compile your code?
BranchX classes (and Trunk, too) don't have names for List fields.
It should look like this:
public class Trunk {
private int TrunkID;
private List<Branch1> listOfBranches; // name added
}
So please fix Trunk, Branch1 and Branch2 to follow the abouve example.
The second thing is that nested <collection>s don't work this way.
Please refer to the docs.
They say:
property: The field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name.
So if you write
<collection property="Branch1" column="BRANCH_1_ID" javaType="ArrayList" ofType="com.mytest.test.branch1">
<id property="branch1ID" column="BRANCH_1_ID">
<collection property="Branch2" column="BRANCH_2" javaType="ArrayList" ofType="com.mytest.test.branch2">
...
</collection>
</collection>
the outer <collection> element is ArrayList of Branch1.
It has two inner elements:
<id> which is correctly mapped to branch1ID field in Branch1 class
<collection> which is supposed to be mapped to Branch2 field in Branch1 class - which is not existing.
Suggested fix for this is to name all List fields in your classes adequately, i.e:
public class Trunk {
private int TrunkID;
private List<Branch1> branch1;
}
public class Branch1{
private int Branch1ID;
private List<Branch2> branch2;
}
etc.
Hope that helps to understand what the problem was.

Using struts2 json plugin

I have an abstract class that looks like the following with appropriate getters and setters for private properties (left out to keep the post sort):
public abstract class CityDistanceAwareAction extends BaseAction implements Preparable {
private CityDistanceRepository cityDistanceRepository;
private CityRepository cityRepository;
private String cityA;
private String cityB;
private CityDistance cityDistance;
public void prepare() {
if (StringUtils.isNotBlank(cityA) && StringUtils.isNotBlank(cityB)) {
CityPair cityPair = new CityPair(getCityRepository().findByName(cityA), getCityRepository().findByName(cityB));
setCityDistance(getCityDistanceRepository().load(cityPair));
}
}
}
And then I have a very simple Class that looks like the following:
public class SearchCityDistanceAjaxAction extends CityDistanceAwareAction {
#Override
public String perform() {
if (getCityDistance() == null) {
addActionError("No city distance found for that pair");
return ERROR;
}
return SUCCESS;
}
}
The idea is that I can send in a String cityA and String cityB. Do my look-ups and simply return the cityDistance object via AJAX.
When I set my struts.xml action up as follows:
<action name="searchCityDistance" class="searchCityDistanceAjaxAction">
<result type="json">
<param name="includeProperties">
cityDistance.*
</param>
</result>
<result name="error" type="json">
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">
actionErrors.*
</param>
</result>
</action>
I get the following return:
(CommonsLogger.java:68) - Adding include property expression:
cityDistance.* (CommonsLogger.java:68) - [JSON]{}
But I expected to get the cityDistance.* object back provided by the getter in the abstract CityDistanceAwareAction.
Interestingly enough if I add a special getter on the SearchCityDistanceAjaxAction object like the following:
public CityDistance getAjaxCityDistance() {
return getCityDistance();
}
and change the struts file to:
<result type="json">
<param name="includeProperties">
ajaxCityDistance.*
</param>
</result>
It gives me what I expected from my first attempt.
(CommonsLogger.java:68) - Adding include property expression:
ajaxCityDistance.* (CommonsLogger.java:68) -
[JSON]{"ajaxCityDistance":{"cityPair":{"cityA":{"cityName":"American
Falls","id":68},"cityB":{"cityName":"Ririe","id":119}},"distance":85}}
It would be ideal to remove the getter from my SearchCityDistanceAjaxAction as a getter is already provided in the parent. Is there something I am missing here or is this the way it should function?
Thanks in advance.
I was able to change my struts.xml file to the following:
<action name="searchCityDistance" class="searchCityDistanceActionAjaxAction">
<result type="json">
<param name="ignoreHierarchy">false</param>
<param name="excludeProperties">
cityDistanceRepository
</param>
<param name="includeProperties">
cityDistance.*
</param>
</result>
<result name="error" type="json">
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">
actionErrors.*
</param>
</result>
</action>
But it still smells a little fishy to use the excludeProperties to leave out my cityDistanceRepository. If this is how has to be I guess that is what I will do but hopefully there is a better solution.

Mybatis - Inherited properties not being mapped

I have two classes where one inherits the other. I'm trying to map my resultSet to the subclass and Mybatis is ignoring the properties on the superclass (Setters also on the superclass)
Code is as below:
public class CocTreeNode extends CocBean implements TreeNode<CocTreeNode> {
private String level1, level2;
public void setLevel1(String level1){...}
public void setLevel2(String level2){...}
public String getLevel1(){...}
public String getLevel1(){...}
}
public class CocBean {
protected String name;
protected Double volume;
public void setName(String name){...}
public void setVolume(Double volume){...}
public String getName(){...}
public Double getVolume(){...}
}
My resultMap is -
<resultMap id="simpleRow" type="CocTreeNode">
<id property="level1" column="LEVEL1"/>
<id property="level2" column="LEVEL2"/>
<result property="name" column="NAME"/>
<result property="volume" column="VOLUME"/>
</resultMap>
The resulting CocTreeNode objects are populated with 'level1' and 'level2' attributes but not 'name' and 'volume'.
I have tried using extends but that didn't make any difference.
Any ideas will be appreciated.
You have to use extends in your simpleRow resultmap to extend properties from CocBean's resultmap:
<resultMap id="CocBeanResult" type="CocBean">
<result property="name" column="NAME"/>
<result property="volume" column="VOLUME"/>
</resultMap>
<resultMap id="simpleRow" type="CocTreeNode" extends="CocBeanResult">
<result property="level1" column="LEVEL1"/>
<result property="level2" column="LEVEL2"/>
</resultMap>

Categories

Resources