【问题标题】:How to query for Map values with Spring Data JPA?如何使用 Spring Data JPA 查询 Map 值?
【发布时间】:2014-12-02 10:21:38
【问题描述】:

所以我的数据库模型是这样的:我有Stores 并且每个Store 都有一个本地化名称。所以我选择将本地化名称表示为Map,如下所示:

public class Store {
   private Map<Locale,LocalizedValue> name;
}

如您所见,它是&lt;Locale, LocalizedValue&gt; 的地图,其中LocalizedValue 是这样的类:

@Embeddable
public class LocalizedValue {

   @Column(name = "value")
   private String value;
}

这一切都很好。但是我遇到了一个问题,我想查询我的 Spring Data JPA 存储库并找到所有具有给定英文名称的商店。所以我的存储库方法如下所示:

Store findByName(Map.Entry<Locale, LocalizedValue> name);

但它会抛出这个异常:

 2014-10-07 23:49:55,862 [qtp354231028-165] ERROR: Parameter value [en=Some Value] did not match expected type [com.test.LocalizedValue(n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [en=Some Value] did not match expected type [com.test.LocalizedValue (n/a)]
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [en=Some Value] did not match expected type [com.test.LocalizedValue (n/a)]; 
nested exception is java.lang.IllegalArgumentException: Parameter value [en=Some Value] did not match expected type [com.test.LocalizedValue (n/a)]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:384)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:216)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)

然后我将存储库方法更改为:

Store findByName(LocalizedValue name);

但后来我得到了这个异常:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'name1_.pk' in 'where clause'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)

我也尝试在查询方法中使用 Contains - 仍然没有运气。

所以我的问题是:有没有办法查询英文名称为“Some Value”的商店?

【问题讨论】:

    标签: java spring jpa spring-data spring-data-jpa


    【解决方案1】:

    这需要手动定义的查询,如下所示:

    interface StoreRepository extends Repository<Store, Long> {
    
      @Query("select s from Store s join s.map m where ?1 in (VALUE(m))"
      Optional<Store> findByName(String name);
    }
    

    它基本上告诉持久化提供者扩展映射值并检查给定参数是否在扩展值列表中。

    【讨论】:

    • 感谢您的回答。这是否记录在 Spring JPA 中?我在哪里可以获得有关 Spring JPA 如何扩展地图值的更多信息。
    【解决方案2】:

    您尚未发布您的映射,但在我看来,Embeddable 的键控方式存在根本问题。

    这是我能看到的唯一明显的关联映射方式:

    @Entity
    @Table(name = "stores")
    public class Store {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "id")
        private Long id;
    
        @ElementCollection
        @CollectionTable(name = "store_names", joinColumns = @JoinColumn(name = "store_id"))
        @MapKeyColumn(name = "locale")
        private Map<Locale, LocalizedName> names;
    
        public Map<Locale, LocalizedName> getNames() {
            return names;
        }
    
        public String getName(Locale locale) {
            return names.get(locale).getName();
        }
    }
    
    
    @Embeddable
    public class LocalizedName {
    
        @Column(name = "name")
        private String name;
    
        @SuppressWarnings("unused")
        private LocalizedName() {
    
        }
    
        public LocalizedName(String name) {
            this.name = name;
        }
    
        public String getName(){
            return name;
        }
    }
    

    以下测试通过:

    @Test
    public void testLoadStore() {
        Store store = repository.findOne(1l);
        Assert.assertNotNull(store);
        Assert.assertEquals("EN Name", store.getName(Locale.ENGLISH));
        Assert.assertEquals("DE Name", store.getName(Locale.GERMAN));
    }
    

    然而,这个问题是“Locale”永远不能成为 LocalisedName 的属性,否则 Hibernate 会抱怨重复的列映射。查看错误报告:

    https://hibernate.atlassian.net/browse/HHH-5393

    所以虽然可以写一个查询方法:

    公共接口 StoreRepository 扩展 JpaRepository {

    @Query(value = "from Store s join s.names n where n.name = ?1")
    Store findByName(String name);
    

    }

    以下测试通过

    @Test
    public void testLoadByName() {
        Store store = repository.findByName("EN Name");
        Assert.assertNotNull(store);
        Assert.assertEquals("EN Name", store.getName(Locale.ENGLISH));
        Assert.assertEquals("DE Name", store.getName(Locale.GERMAN));
    }
    

    据我所知,这永远不会考虑 Locale,因为它不是 LocalizedName 的属性。

    【讨论】:

    • 不,这当然行不通——你从不指定语言环境。我试了一下,我得到:“参数值 [Some Value] 与预期类型 [com.test.LocalizedValue (n/a)] 不匹配;”
    【解决方案3】:

    我今天遇到了一个类似的挑战,但我想找到所有实体的名称至少是给定名称的 1 倍。为我使用 spring data jpa 2.3.7 可以使用以下更短的语法:

    interface StoreRepository extends Repository<Store, Long> {
    
      @Query("select s from Store s where :name in (VALUE(s.name))"
      List<Store> findByName(String name);
    }
    

    但是我看到,如果您对多个键具有相同的值,那么您将在结果列表中多次使用相同的实体。所以我不得不使用 distinct 关键字

      @Query("select distinct s from Store s where :name in (VALUE(s.name))"
    

    【讨论】:

      猜你喜欢
      • 2017-10-09
      • 1970-01-01
      • 1970-01-01
      • 2017-04-25
      • 1970-01-01
      • 2015-08-04
      • 2018-09-23
      • 1970-01-01
      相关资源
      最近更新 更多