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.
Related
i am using java, spring mvc and mybatis.
for http patch, it is used to update partial resource, while put updates the entirely resource.
my code looks like
#RestController
#RequestMapping("/test")
public class Test {
#PutMapping
public void update(MyBean myBean) {
//update MyBean
}
mybatis code is:
<update id="updateMyBean">
update My_Bean
<set>
<if test="filed1 != null>field1 = #{field1},</if>
<if test="filed2 != null>field1 = #{field2},</if>
<if test="filed3 != null>field1 = #{field3},</if>
</set>
where id = #{id}
</update>
then how to implement patch in the spring mvc? how to implement patch in mybatis?
is it add another update method like following?
#PutMapping
public void update(MyBean myBean) {
//update MyBean
}
#PatchMapping
public void updateBeanPartial(MyBean myBean) {
//update MyBean
}
//they look like the same just annotations and/or method name are different
or
#PatchMapping
public void updateBeanPartial(Map myBeanMap) {
//update MyBean
}
//use Map as parameters, but in this case, we cannot do bean validation easily and cannot show what fields need to be sent in swagger
//or use specified fields of MyBean as parameter, but it will introduce many controller methods because MyBean can have many fields
and they use same mybatis update statement?
Thus, how to implement put and patch in the code?
or their difference only in the semantic not in the code?
Lets clarify some things first:
Update: If Bean (MyBean) has multiple fields, then you create 1 SQL statement to update all fields once in your batis.
Patch: If Bean (MyBean) has multiple fields, then you create 1 SQL Statement to patch (updating only some fields).
In your Batis mapper XML, would you need to define 2 functions:
Update User ,
Patch User ,
What you define in your batis mapper is your DAO statements.
What you define your Spring MVC is your Controller/Services, and what you want to achieve.
If you want 2 endpoints (PUT, PATCH), and you want them to do different things, then you need 2 different statements in batis.
Take note of <mapper namespace="org.com.BatisUserService"> which is what you will use to later on reference the implementations from your other classes such as #Controller, or #Service in your Spring MVC app.
<?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="org.com.BatisUserService"> <---- Take note of this
<update id="updateUser"
parameterType="org.com.model.User">
UPDATE user SET
user_name=#{userName,jdbcType=VARCHAR},
user_password=#{userPassword,jdbcType=VARCHAR},
user_email=#{userEmail,jdbcType=VARCHAR}
WHERE
user_id=#{userId,jdbcType=INTEGER};
</update>
<update id="patchUser"
parameterType="org.com.model.User">
UPDATE user SET
user_name=#{userName,jdbcType=VARCHAR}
user_password=#{userPassword,jdbcType=VARCHAR},
WHERE
user_id=#{userId,jdbcType=INTEGER};
</update>
</mapper>
Then in your Controller, you would then call your Mapper org.com.BatisUserService which has the functions updateUser and patchUser defined:
#Controller
#RequestMapping(value = "/user")
public class UserController {
#Autowired
private BatisUserService batisUserService;
#RequestMapping(value = "/updateUser", method = RequestMethod.POST)
public User updateUser(User user) {
batisUserService.updateUser(user);
}
#RequestMapping(value = "/patchUser", method = RequestMethod.PATCH)
public User patchUser(User user) {
batisUserService.patchUser(user);
}
}
this is more than a simple question and my English is not as good as I want... I'll try my best.
I use java 8, with Mybatis 3.4.6 over Postgres 9.6 and I need to do a custom dynamic query.
In my mapper.java class I've created a method to use with myBatis SQL Builder class
#SelectProvider(type = PreIngestManager.class, method = "selectPreIngestsSQLBuilder")
#Results({ #Result(property = "id", column = "id"), #Result(property = "inputPath", column = "input_path"),
#Result(property = "idCategoriaDocumentale", column = "id_categoria_documentale"), #Result(property = "idCliente", column = "id_cliente"),
#Result(property = "outputSipPath", column = "output_sip_path"), #Result(property = "esito", column = "esito"),
#Result(property = "stato", column = "stato"), #Result(property = "pathRdp", column = "path_rdp"),
#Result(property = "dataInizio", column = "data_inizio"), #Result(property = "dataFine", column = "data_fine") })
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto,
#Param("dataInizioInferiore") Date dataInizioInferiore, #Param("dataInizioSuperiore") Date dataInizioSuperiore,
#Param("statiPreIngest") String statiPreIngest);
I have specified the #SelectProvider annotation, class and method to point at, which, in the example is PreIngestManager.class and selectPreIngestsSQLBuilder method.
This is the method
public String selectPreIngestsSQLBuilder(Map<String, Object> params) {
return new SQL() {
{
SELECT("*");
FROM("pre_ingest");
WHERE("id_categoria_documentale = #{idCatDoc}");
if (params.get("nomePacchetto") != null)
WHERE("input_path like '%' || #{nomePacchetto}");
if (params.get("dataInizioInferiore") != null) {
if (params.get("dataInizioSuperiore") != null) {
WHERE("data_inizio between #{dataInizioInferiore} and #{dataInizioSuperiore}");
} else {
WHERE("data_inizio >= #{dataInizioInferiore}");
}
} else {
if (params.get("dataInizioSuperiore") != null) {
WHERE("data_inizio <= #{dataInizioSuperiore}");
}
}
if (params.get("statiPreIngest") != null)
WHERE("stato in (#{statiPreIngest})");
ORDER_BY("id ASC");
}
}.toString();
}
and these are my questions:
have I to specify #Results annotation and every #Result , or can I use a java model class ? I have tried with #ResultMap(value = { "mycompany.model.PreIngest" }) but it did not work.
Most of all, as stated on documentation, with SQL builder you can access method parameters having them as final objects
// With conditionals (note the final parameters, required for the anonymous inner class to access them)
public String selectPersonLike(final String id, final String firstName,
final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID like #{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
But if I put those final in my method I can't access them. Do I need to delete the #Param from the method declaration? Do SQLBuilder need to be called without #SelectProvider ? Am I mixing solutions ?
As far as I have researched, for now I see 3 methods to do a dynamic query, or a custom where condition.
To use MyBatisGenerator library and combine where condition as search criteria to use with SelectByExample method. (I use this when the query is simple)
To Write an SQL query directly, modifying XML mapper files using if, choose, statements and others as descripted here
To use SQL Builder class with #SelectProvider annotation.
Do you know when prefer the 2° method over the 3° one ? Why in the 3° method documentation I can't find how to use it ? There is written how to create custom queries but not how to launch them.
Thank a lot for your time and your suggestions.
I don't know whether you already found the answer, I just want to share my experience. Btw please forgive my english if it wasn't good.
Note: I use MyBatis 3.4.6 and Spring Framework.
have I to specify #Results annotation and every #Result , or can I use a java model class ?
Actually you can do either one.
if you want to use #Results and #ResultMap, you just need to specify #Results annotation just once in one mapper file. The trick is you need to specify id for the Results to be used in other functions.
Using truncated version of your classes, eg:
#Results(id="myResult", value= {
#Result(property = "id", column = "id"),
#Result(property = "inputPath", column = "input_path"),
#Result(property = "idCategoriaDocumentale", ... })
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto, ...);
Then in another function you can use #ResultMap with the value refer to id from #Results mentioned before.
#ResultMap("myResult")
List<PreIngest> selectPreIngestsBySomethingElse(....);
..., or can I use a java model class ?
You can use java model class as result without using #Results and #ResultMap, but you have to make sure your java model class has the same properties/fields as the result of your query. Database tables usually have fields with snake_case. Since java is using camelCase, you have to add settings to your mybatis-config.xml file.
This is what I usually add to the mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- changes from the defaults -->
<setting name="lazyLoadingEnabled" value="false" />
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
</configuration>
The important one is mapUnderscoreToCamelCase, set this to true than you can use your java model class without the hassle of #Results and #ResultMap. You can find all the explanation of the settings in MyBatis 3 Configuration.
This is the example using your classes,
The class:
public class PreIngest {
private Long idCategoriaDocumentale;
private Long idCliente;
........ other fields
........ setter, getter, etc
}
The mapper file:
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto, ...);
Now onwards to SqlBuilder.
But if I put those final in my method I can't access them. Do I need to delete the #Param from the method declaration? Do SQLBuilder need to be called without #SelectProvider ?
I can't answer about those final in your method since I never made SqlBuilder class with final parameters.
For SqlBuilder you must use #SelectProvider, #InsertProvider, #UpdateProvider or #DeleteProvider and it depends on the query you use.
In my experience with SQLBuilder, #Param is necessary if you need more than one parameters and use Map params to access it from the SqlBuilder class. If you don't want to use #Param in the mapper file, then you need to make sure there is only one parameter in the said mapper function. You can use java model class as the parameter though if you just specify one parameter.
If using your class for example, you can have one class
public class PersonFilter {
private Long id;
private String firstName;
private String lastName;
...... setter, getter, etc
}
the mapper function
#SelectProvider(type=PersonSqlBuilder.class, method="selectPersonLike")
List<Person> selectPersonLike(PersonFilter filter);
the SqlBuilder class
public class PersonSqlBuilder {
public String selectPersonLike(PersonFilter filter) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (filter.getId() != null) {
WHERE("P.ID like #{id}");
}
if (filter.getFirstName() != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (filter.getLastName() != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
}
That's it. Hopefully my experience can help.
I don't know how to do this with the sql builder but I do have an idea how to do this with an xml mapper file:
<?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="path.to.class.PreIngestMapper">
<resultMap id="preIngestManager" type="path.to.class.PreIngestManager">
<id property="id" column="id" />
<result property="id" column="id" />
<result property="inputPath" column="input_path" />
<result property="idCategoriaDocumentale" column="id_categoria_documentale" />
...
</resultMap>
<select id="selectPreIngests" parameterType="Map" resultMap="preIngestManager">
SELECT *
FROM pre_ingest
WHERE id_categoria_documentale = #{idCatDoc}
<if test = "nomePacchetto != null">
and input_path like '%' || #{nomePacchetto}
</if>
...
;
</select>
</mapper>
I need some help here, I am trying to return a list of objects, inside a parent object using myBatis.
THE PROBLEM :
Before you begin reading the code below - My error that I get back is :
SqlSession operation; nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 4102
What is interesting here is that this somehow means I am hitting the stored procedure, seeing the amount of data there is, and erroring due to having too many results because myBatis thinks I am using selectOne() - Which is not true? I will add, that 4102 is the exact number of records in the table I am trying to pull this data from.
MY CODE :
Here are the result maps for the parent and child objects :
<resultMap id="ParentObjectMap" type="com.company.product.mybatis.model.ParentObject">
<collection property="children" resultMap="childrenMap"/>
</resultMap>
<resultMap id="childrenMap" type="com.company.product.mybatis.model.ChildObject">
<id column="ChildId" jdbcType="BIGINT" property="childId" />
<result column="Name" jdbcType="VARCHAR" property="name" />
</resultMap>
Here is the code for each of the above maps.
public class ParentObject implements Serializable {
private long id;
private List<ChildObject> childrenMap;
/* GETTERS AND SETTERS EXCLUDED FOR BREVITY. */
}
And the class for the Child object :
public class ChildObject implements Serializable {
private long childId;
private String name;
/* GETTERS AND SETTERS REMOVED FOR BREVITY. */
}
Here Is the Stored Procedure which I am calling, which aims to return the data :
ALTER PROCEDURE dbo.PR_Children_Get
AS
SET NOCOUNT ON
BEGIN
SELECT
tp.ChildId,
tp.Name
FROM dbo.Children tp WITH (NOLOCK)
END
GO
And here is how I am executing that procedure in my mapper :
<select id="getChildren" resultMap="ParentObjectMap">
exec [dbo].[PR_Children_Get]
</select>
Here is the interface through which I access my mapper :
#Override
public ParentObject getChildren() throws Exception {
ParentObject result = ParentObjectMapper.getChildren();
return result;
}
Here is the interface of that ParentObjectMapper :
/* Hiding imports for brevity */
public interface ParentObjectMapper {
// Get the list children, the list should be a property within the parent object.
ParentObject getChildren();
}
I suppose you execute through a mapper interface and the method would be: ParentObject getChildren(); this explicitly expect a single result because this is not a collection type, then the error you got.
But I'm not telling you should change to List<ParentObject> getChildren(); to avoid this error ... or maybe you should ... for a while, because it would help understanding the issue:
With your code Mybatis creates a new ParentObject for each result row because the parentId is never returned and Mybatis does not assume you have a single parent. Then the query shall return a parentId column and the ParentObjectMap start with <id column="parentId" property="id"/>, so that Mybatis knows how to group children by their parent.
I have the following stored procedure - simply returning two integers for now :
USE [DB_NAME_HERE]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create PROCEDURE [dbo].[PR_PROCEDURE_NAME]
SELECT 0 AS ValueA,0 AS ValueB
and I have the following mapper XML :
<resultMap id="BaseResultMap" type="com.companyname.service.mybatis.model.modelClass">
<result column="ValueA" jdbcType="INTEGER" property="valueA" />
<result column="ValueB" jdbcType="INTEGER" property="valueB" />
</resultMap>
<select id="getPurgedTokens" resultMap="BaseResultMap" statementType="CALLABLE">
{call PR_PROCEDURE_NAME(
#{resultContainer.valueA, mode=OUT, jdbcType=INTEGER},
#{resultContainer.valueB, mode=OUT, jdbcType=INTEGER}
)}
</select>
Then I have the following java class for the mapping :
public class modelClass implements Serializable {
private int valueA;
private int valueB;
public int getValueA() { return this.valueA; }
public void setValueA(int valueA) { this.valueA = valueA; }
public int getValueB() { return this.valueB; }
public void setValueB(int valueB) { this.valueB= valueB; }
}
But when I run up my application, and attempt to get any kind of data, I get the following error :
code=DataIntegrityViolationException,messages=[SqlSession operation; SQL []; Invalid JDBC call escape at line position 10.; nested exception is java.sql.SQLException: Invalid JDBC call escape at line position 10., Invalid JDBC call escape at line position 10.]]
All help here is greatly appreciated - I have been trying to get this working for days!
EDIT : UPDATE WITH EXACT COPIES OF LATEST CODE!
MapperXML
<?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.company.product.mybatis.mappers.MapperXML">
<!-- Commented out the base result map because now we link directly in the paramter type? -->
<!--
<resultMap id="BaseResultMap" type="com.companyname.service.mybatis.model.modelClass">
<result column="ValueA" javaType="java.lang.Integer" jdbcType="INTEGER" property="valueA"/>
<result column="ValueB" javaType="java.lang.Integer" jdbcType="INTEGER" property="valueB"/>
</resultMap>-->
<!-- -------------------------------------------- -->
<select id="getPurgedTokens" parameterType="com.companyname.service.mybatis.model.ModelClass" statementType="CALLABLE">
{call PR_TokenPurge_Clean(
#{dvalueA, mode=OUT, jdbcType=INTEGER},
#{valueB, mode=OUT, jdbcType=INTEGER}
)}
</select>
</mapper>
ModelClass
package com.companyname.service.mybatis.model;
import java.io.Serializable;
public class ModelClass implements Serializable {
private int valueA;
private int valueB;
public int getVavlueA() { return this.valueA; }
public void setValueA(int valueA) { this.valueA = valueA; }
public int getValueB() { return this.valueB; }
public void setValueB(int valueB) { this.valueB = valueB; }
}
DAO
package com.company.product.dao.mybatis;
[imports (removed for brevity and security)...]
#Repository
public class ModelClassDaoMybatis implements ModelClassDao {
private TokenPurgeModelMapper tokenPurgeModelMapper;
private SqlSessionOperations sqlSessionOperations;
#Override
public TokenPurgeModel purgeTokens()
{
if (this.sqlSessionOperations.selectOne(".getPurgedTokens") != null)
{
System.out.println("I FOUND A VALUE!!!!!!");
return (TokenPurgeModel) sqlSessionOperations.selectOne(testing);
}
else
{
System.out.println("No value found for select statement");
return null;
}
}
#Override
public List<TokenPurgeModel> getPurgedTokens() {
return tokenPurgeModelMapper.getPurgedTokens();
}
#Required
public void setTokenPurgeModelMapper(final TokenPurgeModelMapper tokenPurgeMapper) {
this.tokenPurgeModelMapper = tokenPurgeMapper;
}
#Required
public void setSqlSessionOperations(SqlSessionOperations sqlSessionOperations) {
this.sqlSessionOperations = sqlSessionOperations;
}
}
MODEL MAPPER CLASS
package com.company.product.mybatis.mappers;
[imports (removed for brevity and security)...]
public interface TokenPurgeModelMapper {
// This is the one I am using right now. (as seen in the DAO!)
TokenPurgeModel purgeTokens();
// Get the list of tokens which have been removed from the PAN table.
List<TokenPurgeModel> getPurgedTokens(); // This is actually never used - will be used in later funcationality!
}
THANKS for looking at this for me!
Indeed, first thing is to use syntax {call PR_PROCEDURE_NAME()}
Then it depends on how the procedure is written and on which SGBD is used.
There are multiple possibilities including:
Return the values as procedure OUT parameters:
<select id="getPurgedTokens" parameterType="com.companyname.service.mybatis.model.modelClass" statementType="CALLABLE">
{call PR_PROCEDURE_NAME(
#{valueA, mode=OUT, jdbcType=INTEGER},
#{valueB, mode=OUT, jdbcType=INTEGER}
)}
</select>
The modelClass parameter does not actually provide input parameters in this case and is used here just as target container for OUT parameters; and then you don't need resultMap.
If the result becomes more complex, return a cursor on the select:
{call PR_PROCEDURE_NAME(
#{resultContainer.resultList, mode=OUT, jdbcType=CURSOR, javaType=java.sql.ResultSet, resultMap=BaseResultMap}
)}
Or a function (instead of a procedure) returning the cursor fetching the Select (some SGBD may allow writing just the SELECT in the body), then merely:
SELECT function_name from DUAL
bound with the resultMap
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.