0

I'm rather new to MyBatis. There's theFoo which could have many Bars, and the Bar has fooId as FK. I want to query for all Foos with its associated Bars.

Here are my domain objects:

public class Foo {
    private int id;
    private String name;
    // key: Bar.id, value: Bar instance
    private Map<Integer, Bar> bars;

    // getter, setter etc
}
public class Bar {
    private int id;
    private int fooId;
    private String model;

    // getter, setter etc
}

The schema:

Foo
id int PK
name varchar
Bar
id int PK
fooId varchar FK
model varchar

The result maps:

<resultMap id="selectBar" type="Bar">
    <id property="id" column="id"/>
    <result property="fooId" column="fooid"/>
    <result property="model" column="model"/>
</resultMap>
<resultMap id="fooWithBars" type="Foo">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="bars" ofType="Bar" resultMap="Bar.selectBar" javaType="java.util.Map"/>
</resultMap>

The query:

<select id="selectFoos" resultMap="fooWithBars">
    select F.name, F.id, B.id, B.fooId, B.model
    from Foo P join Bar B on F.id = B.fooId
</select>

My question is how to map the collection to Map<Integer, Bar>? without using ResultHandler The resultMap above apparently doesn't work. I also tried:

  • nested resultMap which end up with collection of HashMaps instead of a collection in Hashmap, and there's no way of specifying the key to be the value of Bar.id
  • constructor injection which failed since Mybatis does not support use collection as an argumenthttps://github.com/mybatis/mybatis-3/issues/101

Thanks!

aaronzhaocr
  • 53
  • 1
  • 6
  • You may have to use `ResultHandler` for that mapping. – ave Apr 18 '22 at 20:33
  • You may try something with @MapKey ... It looks like it doesn't work directly inside of XML mapper, but nevertheless it could lead you on the right track. Check https://stackoverflow.com/questions/11913013/return-hashmap-in-mybatis-and-use-it-as-modelattribute-in-spring-mvc and https://mybatis.org/mybatis-3/apidocs/org/apache/ibatis/annotations/MapKey.html – BrunoT Apr 22 '22 at 14:36

1 Answers1

0

While MyBatis doesn't natively support maps, it does support sets, and it's relatively straightforward to create an adapter that makes a map's entrySet writeable, allowing MyBatis to add elements to it.

AddEntrySet.java

import java.util.*;

public class AddEntrySet<K,V> extends AbstractSet<Map.Entry<K, V>> {
    private final Map<K, V> map;

    public AddEntrySet(Map<K, V> map) {
        this.map = map;
    }

    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
        return map.entrySet().iterator();
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean add(Map.Entry<K, V> kvEntry) {
        V value = kvEntry.getValue();
        return !Objects.equals(value, map.put(kvEntry.getKey(), value));
    }
}

Foo.java

    // For MyBatis to add through
    private Set<Map.Entry<Integer, Bar>> getBarEntries() {
        return new AddEntrySet<>(bars);
    }

Result maps

  <resultMap id="selectBar" type="Bar">
    <id property="id" column="id"/>
    <result property="fooId" column="fooid"/>
    <result property="model" column="model"/>
  </resultMap>

  <resultMap id="barEntry" type="java.util.AbstractMap$SimpleImmutableEntry">
    <constructor>
      <idArg javaType="java.lang.Object" column="id"/>
      <arg javaType="java.lang.Object" resultMap="selectBar"/>
    </constructor>
  </resultMap>

  <resultMap id="fooWithBars" type="Foo">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="barEntries" columnPrefix="bar_" ofType="java.util.AbstractMap$SimpleImmutableEntry" resultMap="barEntry"/>
  </resultMap>

Query

select F.name, F.id, B.id AS bar_id, B.fooId AS bar_fooId, B.model AS bar_model
from Foo F left join Bar B on F.id = B.fooId
Clement Cherlin
  • 387
  • 6
  • 13