如何使用Spring Data JPA查询Map值?

时间:2014-10-07 21:10:01

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

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

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

你可以看到它是<Locale, LocalizedValue>的地图,其中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 - 仍然没有运气。

所以我的问题是:有没有办法查询具有英文名称的商店&#39; Some Value&#39;?

3 个答案:

答案 0 :(得分:7)

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

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);
}

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

答案 1 :(得分:2)

你还没有发布你的映射,但在我看来,嵌入式密钥是一个根本问题。

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

@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

因此虽然可以编写查询方法:

public interface 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的属性。

答案 2 :(得分:0)

我今天遇到了一个类似的挑战,但我想找到所有实体的名称至少是给定名称的 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);
}

但我看到,如果多个键的值相同,那么结果列表中将多次出现相同的实体。所以我不得不使用不同的关键字

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