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>
Related
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.
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.
I've created a Struts2 project in which I used XML based validation. Model class RegistrationForm is shown below
package com.projects;
import com.opensymphony.xwork2.ActionSupport;
public class RegistrationForm implements Serializable{
private static final long serialVersionUID = 1L;
private String fname;
private String lname;
private int numbr;
public int getNumbr() {
return numbr;
}
public void setNumbr(int numbr) {
this.numbr = numbr;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
}
RegistrationFormAction.Java
package com.projects;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
public class RegistrationFormAction extends ActionSupport implements ModelDriven<RegistrationForm> {
private RegistrationForm registrationForm;
public RegistrationForm getRegistrationForm() {
return registrationForm;
}
public void setRegistrationForm(RegistrationForm registrationForm) {
this.registrationForm = registrationForm;
}
public RegistrationForm getModel(){
registrationForm=new RegistrationForm();
return registrationForm;
}
public String execute(){
return "success";
}
}
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
<package name="dd" extends="struts-default">
<interceptors>
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception" />
<interceptor-ref name="alias" />
<interceptor-ref name="servletConfig" />
<interceptor-ref name="prepare" />
<interceptor-ref name="i18n" />
<interceptor-ref name="chain" />
<interceptor-ref name="debugging" />
<interceptor-ref name="profiling" />
<interceptor-ref name="scopedModelDriven" />
<interceptor-ref name="modelDriven" />
<interceptor-ref name="params"/>
<interceptor-ref name="validation"/>
<interceptor-ref name="fileUpload" />
<interceptor-ref name="checkbox" />
<interceptor-ref name="staticParams" />
<interceptor-ref name="conversionError" />
<interceptor-ref name="workflow"/>
</interceptor-stack>
</interceptors>
<action name="submitForm" class="com.projects.RegistrationFormAction">
<interceptor-ref name="defaultStack" />
<result name="success">/WelcomePage.jsp</result>
<result name="input">/RegistrationForm.jsp</result>
</action>
</package>
</struts>
RegistrationFormAction-validation.xml
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<validators>
<field name="registrationform">
<field-validator type="visitor">
<param name="appendPrefix">false</param>
<message/>
</field-validator>
</field>
</validators>
RegistrationForm-validation.xml
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<validators>
<field name="fname">
<field-validator type="requiredstring">
<message>First Name can't be blank</message>
</field-validator>
</field>
<field name="lname">
<field-validator type="requiredstring">
<message>Last Name can't be blank</message>
</field-validator>
</field>
<field name="numbr">
<field-validator type="int">
<param name="min">1</param>
<param name="max">10</param>
<message>Number between 1 to 10</message>
</field-validator>
</field>
</validators>
but validation is not working.
There are a lot of things goin' on here! I'll post them in order of appearance in the question:
Never make a POJO extends ActionSupport:
public class RegistrationForm extends ActionSupport {
must become
public class RegistrationForm implements Serializable {
Better returning SUCCESS than "success" to prevent typos (but ok this is secondary);
The intercetpor stack customization has four problems:
you are overriding the existing basicStack, this risks to violate the POLA, especially if other people will work on this project; it's better to use a custom name instead, eg. myStack;
you are using only three interceptors, and this is suspicious; while many of the default interceptors can be dropped, many others instead should be always kept, especially with validation involved, eg. ConversionError Interceptor, or Workflow Interceptor, etc. Read how the whole thing works. As a rule, you should remove an Interceptor only when you know exactly what it does and you are absolutely sure you don't (and won't) need it.
When using ModelDriven (that is usually not recommended, because basically useless and source of problems when not experts with it), you need to put the ModelDriven Interceptor before the Parameters Interceptor, otherwise when the parameters interceptor runs, the model won't be pushed yet, and the setters will be searched on the action, eg. setFname(), instead that on the model (resulting in null properties in the model, and in the warning
Unexpected Exception caught setting 'fname' on 'class RegistrationFormAction: Error setting expression 'fname' with value ['Sumit', ]
because of the missing setters in the action).
Finally, if you are really using the code you posted, then you are NOT using the wrong stack you've created, because 1) The default one is the defaultStack, not the basicStack, so overriding basicStack has no effects, and 2) you've not used <default-interceptor-ref> to change the default stack reference, nor you've used the <interceptor-ref> inside the <action> tag to specify a different interceptor stack for that action only.
You are mixing 1.0.2 and 1.0.3 in DOCTYPE of XML validation files, make everything 1.0.3 (and notice that they migrated from OpenSymphony to Apache); then change:
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
to
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
Ensure that the RegistrationFormAction-validation.xml file is in the action folder, while the RegistrationForm-validation.xml is in the RegistrationForm.java folder.
Consider avoiding ModelDriven, because as Stephen Young says,
You Must Tame Complexity to Become a Better Programmer
As pointed out by AleksandrM's comment, there is also a typo in
<field name="registrationform">
that should be
<field name="registrationForm">
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.
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.