【问题标题】:Returning PostgreSQL aggregations to a HashMap using MyBatis使用 MyBatis 将 PostgreSQL 聚合返回到 HashMap
【发布时间】:2017-01-20 11:38:16
【问题描述】:

我有一张超级简单的桌子test 例如

create table test (
    id serial primary key,
    status varchar (10)
);

insert into test (status) 
     values ('ready'), ('ready'), 
            ('steady'), 
            ('go'), ('go'), ('go'), 
            ('new');

要获得我可以运行的汇总计数:-

1) 使用group by 的简单多行结果

select status, 
       count(id) as count        
  from test
 group by status

...返回...

-------+-------
status | counts
-------+-------
go     |      3
ready  |      2
new    |      1
steady |      1
-------+-------

2) 使用jsonb_object_agg 的单行结果

    with stats as (
       select status, 
              count(id) as count        
         from test
     group by status
    )
    select jsonb_object_agg (status, count) as status_counts from stats

...返回...

--------------------------------------------------
status_counts
--------------------------------------------------
{ "go" : 3, "new" : 1, "ready" : 2, "steady" : 1 }
--------------------------------------------------

Mybatis 接口方法。

在我的 Java 代码中(通过 MyBatis)我有一个方法:-

public Map<String, Integer> selectStatusCounts();

我很想知道如何通过 MyBatis 将任一查询映射到 Map&lt;String, Integer&gt; Java 对象?


更新 (1)

a_horse_with_no_name 建议和this stackover article 上,我想出了这个:-

3) 使用hstore 的单行结果

select hstore(array_agg(hs_key), array_agg(hs_value::text))
from (
    select 
        status, 
        count(id) as count        
    from test
    group by status    
) x(hs_key,hs_value)

...返回...

--------------------------------------------------
status_counts
--------------------------------------------------
"go"=>"3", "new"=>"1", "ready"=>"2", "steady"=>"1"
--------------------------------------------------

使用这样的东西可能会起作用:-

https://github.com/gbif/checklistbank/blob/master/checklistbank-mybatis-service/src/main/java/org/gbif/checklistbank/service/mybatis/postgres/HstoreCountTypeHandler.java

现在将进行测试! :-)


更新(2)

再次感谢 a_horse_with_no_name 的贡献 - 我现在非常接近,但对 MyBatis 仍然很奇怪。这是我创建的类型处理程序(因此我可以在其他地方重用聚合):-

@MappedTypes(LinkedHashMap.class)
@MappedJdbcTypes(JdbcType.OTHER)
public class MyBatisMapHstoreToStringIntegerMap implements TypeHandler<Map<String, Integer>> {

    public MyBatisMapHstoreToStringIntegerMap() {}

    public void setParameter(PreparedStatement ps, int i, Map<String, Integer> map, JdbcType jdbcType) throws SQLException {
        ps.setString(i, HStoreConverter.toString(map));
    }

    public Map<String, Integer> getResult(ResultSet rs, String columnName) throws SQLException {
        return readMap(rs.getString(columnName));
    }

    public Map<String, Integer> getResult(ResultSet rs, int columnIndex) throws SQLException {
        return readMap(rs.getString(columnIndex));
    }

    public Map<String, Integer> getResult(CallableStatement cs, int columnIndex) throws SQLException {
        return readMap(cs.getString(columnIndex));
    }

    private Map<String, Integer> readMap(String hstring) throws SQLException {
        if (hstring != null) {
            Map<String, Integer> map = new LinkedHashMap<String, Integer>();
            Map<String, String> rawMap = HStoreConverter.fromString(hstring);
            for (Map.Entry<String, String> entry : rawMap.entrySet()) {
                map.put(entry.getKey(), Integer.parseInt(entry.getValue())); // convert from <String, String> to <String,Integer>
            }

            return map;
        }
        return null;
    }

}

...这是映射器界面...

public interface TestMapper {

    public Map<String, Integer> selectStatusCounts();

}

...这里是 XML 映射器文件中的 &lt;select&gt;...

<select id="selectStatusCounts" resultType="java.util.LinkedHashMap">
    select hstore(array_agg(hs_key), array_agg(hs_value::text)) as status_counts
    from (
        select 
            status, 
            count(id) as count        
        from test
        group by status    
    ) x(hs_key,hs_value)
</select>

但是,它返回一个Map,其中有一个名为status_counts 的条目,其值是我想要的实际地图,即{status_counts={new=1, ready=2, go=3, steady=1}}

以下是我对 PostgreSQL / MyBatis 的 maven 依赖项:-

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.2.2</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4-1201-jdbc41</version>
    </dependency>

【问题讨论】:

  • 为什么不使用hstore?从 JDBC 驱动程序以 Map 的形式返回
  • 从来没有使用过hstore——我可以跳过它直接使用 JSONB :-)。聚合查询会是什么样子?
  • 我采用了hstore 解决方案,它返回了{status_counts={new=1, ready=2, go=3, steady=1}},即包含1 个条目的地图,即status_counts,而该地图又包含&lt;String,Integer&gt; 的另一张地图——我真正想要的是Map&lt;String,Integer&gt; 与列返回的内容保持一致。

标签: postgresql mybatis


【解决方案1】:

最简单的方法是定义一个hstore_agg()函数:

CREATE AGGREGATE hstore_agg(hstore) 
(
    SFUNC = hs_concat(hstore, hstore),
    STYPE = hstore
);

那么你可以这样做:

select hstore_agg(hstore(status, cnt::text))
from (
  select status, count(*) cnt
  from test
  group by status
) t;

使用当前的 JDBC 驱动程序Statement.getObject() 将返回一个Map&lt;String, String&gt;

由于hstore只存储字符串,它不能返回Map&lt;String, Integer&gt;

【讨论】:

  • 我已经测试过了,效果很好。然而,问题在于我们如何连接 MyBatis。将更新您的答案。
  • @bobmarksie:请不要在答案中添加新问题。扩展您的问题或提出新问题。
【解决方案2】:

发布一个答案(这并不完美),但很想看看其他人是否有更好的解决方案。

我的答案基于这个 stackoverflow 答案:-

Return HashMap in mybatis and use it as ModelAttribute in spring MVC

我创建了一个名为 KeyValue 的 POJO 类:-

public class KeyValue<K, V> {

    private K key;
    private V value;

    public KeyValue() {
    }

    public KeyValue(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

}

...并将测试映射器方法更改为...

@MapKey("key")
public Map<String, KeyValue<String, Integer>> selectStatusCounts();

注意@MapKey参数的使用

我正在使用 "1) Simple multi-row result using group by" SQL 从原始问题并将结果列更改为key + value(因此它映射到新的KeyValue 对象)如下:-

<select id="selectStatusCounts" resultType="test.KeyValue">
   select status    as key, 
          count(id) as value        
     from bulk_upload
 group by status
</select>

在Java中访问this的实现方式如下:-

Map<String, KeyValue<String, Integer>> statusCounts = mapper.selectStatusCounts();

并检索例如我们简单做的new 项目的映射值:-

int numberOfstatusCounts = statusCounts.get("new").getValue();

我对这个解决方案相当满意,但我仍然更喜欢 Map&lt;String, Integer&gt; 而不是 Map&lt;String, KeyValue&lt;String, Integer&gt;&gt;,所以我不会接受我的解决方案 - 它纯粹是为了展示我是如何工作的(对于现在)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-31
    • 1970-01-01
    • 2022-07-27
    • 2012-12-08
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    相关资源
    最近更新 更多