so my database model goes like this: I have Store
s and every Store
has a localized name. So I have chosen to represent the localized name as a Map
like this:
public class Store {
private Map<Locale,LocalizedValue> name;
}
as you can see it's a Map of <Locale, LocalizedValue>
where the LocalizedValue
is a class like this:
@Embeddable
public class LocalizedValue {
@Column(name = "value")
private String value;
}
and it all works great. However I get to a problem where I want to query my Spring Data JPA repository and find all the stores with a given english name. So my repository method looks like this:
Store findByName(Map.Entry<Locale, LocalizedValue> name);
but it throws this exception:
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)
So then I changed my repository method to be like this:
Store findByName(LocalizedValue name);
but then I got this exception:
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)
I also tried using Contains in the query method - still no luck.
So my question is: Is there a way to query for the stores with English name 'Some Value'?
If a given JPA GROUP BY query returns only two columns where one is unique, it's very suitable to return the result as a Java Map. For this, you can use either the Java Stream functionality or the Hibernate-specific ResultTransformer .
The easiest way to map a query result to an entity is to provide the entity class as a parameter to the createNativeQuery(String sqlString, Class resultClass) method of the EntityManager and use the default mapping.
Interface-based DTO projections You first need to define an interface that defines a getter method for each attribute your projection shall contain. At runtime, Spring Data JPA then generates a class that implements that interface. you can then use that interface as the return type of a repository method.
Its findById method retrieves an entity by its id. The return value is Optional<T> . Optional<T> is a container object which may or may not contain a non-null value. If a value is present, isPresent returns true and get returns the value.
This requires a manually defined query something like this:
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);
}
It basically tells the persistence provider to expand the map values and check whether the given parameter is in the list of expanded values.
You have not posted your mappings but there seems to me to be a fundamental issue with way that Embeddables are keyed.
This is the only obvious way I can see to map the association:
@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;
}
}
The following test passes:
@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));
}
The issue with this is however that 'Locale' can never be a property of LocalisedName otherwise Hibernate complains of a duplicate column mapping. See the bug report:
https://hibernate.atlassian.net/browse/HHH-5393
So while it is possible to write a query method:
public interface StoreRepository extends JpaRepository {
@Query(value = "from Store s join s.names n where n.name = ?1")
Store findByName(String name);
}
for which the following test passes
@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));
}
as far as I can see this can never take Locale into account as it is not a property of LocalizedName.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With