I try to map the inverse relation of a collection with MyBatis 3.2.8. It seems like MyBatis duplicates objects even when they have the same id.
public class ObjA {
private String id;
private ArrayList<ObjB> objBs;
// Getters, setters ...
// Equals based on the id field
}
public class ObjB {
private String id;
private ObjA objA;
// Getters, setters ...
// Equals based on the id field
}
The mapping xml file
<resultMap id="xx" type="ObjA">
<id column="idA" property="id" />
<collection property="objBs" javaType="ArrayList" ofType="ObjB">
<id column="idB" property="id"/>
<association property="objA" type="ObjA">
<id column="idA" property="id" />
</association>
</collection>
</resultMap>
The JUnit test
ArrayList<ObjA> result = service.getAllObjA();
for(ObjA objA : result) {
for(ObjB objB : objA.getObjBs()) {
assertEquals(objB.getObjA(), objA); // Pass
assertTrue(objB.getObjA() == objA); // Does not pass
}
}
I would like objB.getObjA() and objA to be the same instance (same reference) of ObjA.
How could I configure my result map to get this working.
The POJO's ObjA
and ObjB
which you have described are in circular dependency
.
Consider the following example: Let's have an Artist
class and a Recording
class. They do not need to implement the Comparable interface, but we're doing that because we want to sort these objects. Alternately, the SQL queries could be written to specify the desired sort order of the rows that are returned.
Here's the Artist class. Note that an Artist holds a collection of its Recordings.
import java.util.List;
public class Artist implements Comparable {
private int id;
private List<Recording> recordings;
private String name;
public Artist() {}
public Artist(String name) { this.name = name; }
public int compareTo(Object obj) {
Artist r = (Artist) obj;
return name.compareTo(r.name);
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
public List<Recording> getRecordings() { return recordings; }
public void setRecordings(List<Recording> recordings) {
this.recordings = recordings;
}
}
Here's the Recording class. Note that a Recording holds a reference to its Artist.
public class Recording implements Comparable {
private int id;
private int year;
private Artist artist;
private String name;
public Recording() {}
public Recording(Artist artist, String name, int year) {
this.artist = artist;
this.name = name;
this.year = year;
}
public int compareTo(Object obj) {
Recording r = (Recording) obj;
return name.compareTo(r.name);
}
public Artist getArtist() { return artist; }
public void setArtist(Artist artist) { this.artist = artist; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getYear() { return year; }
public void setYear(int year) { this.year = year; }
}
Configuration :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings useStatementNamespaces="true"/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="com.mysql.jdbc.Driver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost:3306/music"/>
<property name="JDBC.Username" value="root"/>
<property name="JDBC.Password" value=""/>
</dataSource>
</transactionManager>
<sqlMap resource="Artist.xml"/>
<sqlMap resource="Recording.xml"/>
</sqlMapConfig>
Mapped statements are defined in XML files that are referenced from the SqlMapConfig.xml file.
Here's the file that specifies statements related to our artists table.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Artist">
<delete id="deleteAll">
delete from artists
</delete>
<insert id="insert" parameterClass="com.ociweb.music.Artist">
insert into artists (name) values (#name#)
<selectKey resultClass="int" keyProperty="id">
select last_insert_id() as id
</selectKey>
</insert>
<resultMap id="result" class="com.ociweb.music.Artist">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- This results in N+1 selects. To avoid this, see page 40
in the iBATIS Developer Guide. -->
<result property="recordings" column="id"
select="Recording.getByArtist"/>
</resultMap>
<select id="getById"
parameterClass="java.lang.Integer" resultMap="result">
select * from artists where id=#id#
</select>
<select id="getAll" resultClass="com.ociweb.music.Artist">
select * from artists
</select>
</sqlMap>
Here's the file that specifies statements related to our recordings table.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="Recording">
<delete id="deleteAll">
delete from recordings
</delete>
<insert id="insert" parameterClass="com.ociweb.music.Recording">
insert into recordings (name, year, artist_id)
values (#name#, #year#, #artist.id#)
<selectKey resultClass="int" keyProperty="id">
select last_insert_id() as id
</selectKey>
</insert>
<resultMap id="resultWithoutArtist" class="com.ociweb.music.Recording">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="year" column="year"/>
</resultMap>
<resultMap id="result"
class="com.ociweb.music.Recording" extends="resultWithoutArtist">
<result property="artist" column="artist_id" select="Artist.getById"/>
</resultMap>
<select id="getAll" resultClass="com.ociweb.music.Recording">
select * from recordings
</select>
<!-- resultWithoutArtist is used here to avoid a circular
dependency when Artist.result (in Artist.xml) is used. -->
<select id="getByArtist"
parameterClass="java.lang.Integer" resultMap="resultWithoutArtist">
select * from recordings where artist_id=#artistId#
</select>
<select id="getByYear"
parameterClass="java.lang.Integer" resultMap="result">
select * from recordings where year=#year#
</select>
</sqlMap>