Ich versuche, QueryDSL für eine dynamische Abfrage zu verwenden, die ich tun muss. Dies ist ein Spring Boot-Projekt mit QueryDSL 3.7.2 (com.mysema.querydsl). Ich habe das Beispiel vereinfacht, aber im Grunde habe ich eine Item-Abstract-Klasse, die mit einer Inheritance.JOINED-Strategie versehen ist. Dies ist die Artikelklasse:Spring Data JPA mit QueryDSL Problem mit JPA Inheritance.JOINED Strategie, wenn Unterklassen den gleichen Eigenschaftennamen haben
package org.porthos.concepts.domain;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
@Entity
@Table(name = "item")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "item_type")
public abstract class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "item_type", insertable = false, updatable = false)
private ItemType type;
@Column(name = "title")
private String title;
protected Item() {}
public Long getId() {
return id;
}
public ItemType getType() {
return type;
}
public String getTitle() {
return title;
}
@Override
public String toString() {
return "Item [id=" + id + ", type=" + type + "]";
}
}
Es gibt auch 2 Unterklassen, die Artikel erweitern. Das sind Buch und CD.
Buch:
package org.porthos.concepts.domain;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
@Entity
@Table(name = "book")
@DiscriminatorValue("BOOK")
public class Book extends Item {
@Column(name = "genre")
@Enumerated(EnumType.STRING)
private BookGenre genre;
@Column(name = "title")
private String title;
public static enum BookGenre {
MYSTERY, HISTORY, SCIENCE, COMPUTER;
}
protected Book() {}
public Book(BookGenre genre, String title) {
this.title = title;
this.genre = genre;
this.title = title;
}
public BookGenre getGenre() {
return genre;
}
@Override
public String toString() {
return "Book [genre=" + genre + ", title=" + title + ", getId()=" + getId() + ", getType()="
+ getType() + "]";
}
}
Und CD:
package org.porthos.concepts.domain;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
@Entity
@Table(name = "cd")
@DiscriminatorValue("CD")
public class CD extends Item {
@Column(name = "genre")
@Enumerated(EnumType.STRING)
private CDGenre genre;
@Column(name = "title")
private String title;
public static enum CDGenre {
CLASSICAL, POP, ROCK, BLUES;
}
protected CD() {}
public CD(CDGenre genre, String title) {
this.title = title;
this.genre = genre;
this.title = title;
}
public CDGenre getGenre() {
return genre;
}
@Override
public String toString() {
return "CD [genre=" + genre + ", title=" + title + ", getId()=" + getId() + ", getType()="
+ getType() + "]";
}
}
Dies ist die Test-Klasse, die ich zum Testen verwenden:
package org.porthos.concepts;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.porthos.concepts.domain.Book;
import org.porthos.concepts.domain.Book.BookGenre;
import org.porthos.concepts.domain.CD;
import org.porthos.concepts.domain.CD.CDGenre;
import org.porthos.concepts.domain.Item;
import org.porthos.concepts.domain.QBook;
import org.porthos.concepts.domain.QItem;
import org.porthos.concepts.repository.ItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.types.Predicate;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ConceptsApplication.class)
@Transactional
public class QueryDslTest {
@Autowired
private ItemRepository itemRepository;
@Before
public void setUp() throws Exception {}
@Test
public void test() {
// Persist a Book and a CD for test
Book book = new Book(BookGenre.SCIENCE, "How To Use Your Microscope");
book = itemRepository.save(book);
CD cd = new CD(CDGenre.BLUES, "The Wind Cries Mary");
cd = itemRepository.save(cd);
Predicate isScienceBook = QItem.item.as(QBook.class).genre.eq(BookGenre.SCIENCE);
BooleanBuilder builder = new BooleanBuilder();
builder.or(isScienceBook);
Page<Item> itemsPage = itemRepository.findAll(builder, new PageRequest(0, 10));
assertThat(itemsPage.getContent().size(), is(1));
}
}
Also im Grunde jede der Unterklassen Buch und CD haben eine Genre Eigenschaft, die anders getippt wird. Das Buchgenre verwendet den BookGenre-Typ und das CD-Genre den CDGenre-Typ. Das Problem tritt auf, weil die Eigenschaften exakt gleich benannt sind.
Also, wenn ich den Test, der versucht, für ein Buch zu suchen, ausführen, bekomme ich die folgende Stapel-Trace, die im Grunde angibt, dass es Genre von Type CDGenre erwartet.
Wenn ich den Test mit einer Abfrage für eine CD ausführen, funktioniert die Abfrage ohne Probleme.
Auch wenn ich die Buch- und CD-Genre-Eigenschaften umbenennen, so dass sie wie BookGenre und CDGenre einzigartig sind, dann funktioniert die Buchabfrage definitiv.
Also, um diese Art der Vererbung zu verwenden, muss ich sicherstellen, dass jede der Unterklassen unterschiedlich benannte Eigenschaften haben. Aber in diesem Beispiel sollte ein Genre für Musik-CDs nicht dasselbe wie ein Genre für Bücher sein, und ich sehe es nicht unbedingt falsch, dass sie beide genre heißen.
Ich bin mir also nicht sicher, ob es ein schlechtes Domain-Design meinerseits ist, ob es ein Problem mit Spring Data JPA und/oder QueryDSL gibt.
Danke,
Frank
Stapelüberwachung:
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [SCIENCE] did not match expected type [org.porthos.concepts.domain.CD$CDGenre (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [SCIENCE] did not match expected type [org.porthos.concepts.domain.CD$CDGenre (n/a)]
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:384)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:436)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:131)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
at com.sun.proxy.$Proxy113.findAll(Unknown Source)
at org.porthos.concepts.QueryDslTest.test(QueryDslTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.IllegalArgumentException: Parameter value [SCIENCE] did not match expected type [org.porthos.concepts.domain.CD$CDGenre (n/a)]
at org.hibernate.jpa.spi.BaseQueryImpl.validateBinding(BaseQueryImpl.java:874)
at org.hibernate.jpa.internal.QueryImpl.access$000(QueryImpl.java:80)
at org.hibernate.jpa.internal.QueryImpl$ParameterRegistrationImpl.bindValue(QueryImpl.java:248)
at org.hibernate.jpa.internal.QueryImpl$JpaPositionalParameterRegistrationImpl.bindValue(QueryImpl.java:337)
at org.hibernate.jpa.spi.BaseQueryImpl.setParameter(BaseQueryImpl.java:674)
at org.hibernate.jpa.spi.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:198)
at org.hibernate.jpa.spi.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:49)
at com.mysema.query.jpa.impl.JPAUtil.setConstants(JPAUtil.java:55)
at com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:130)
at com.mysema.query.jpa.impl.AbstractJPAQuery.count(AbstractJPAQuery.java:81)
at org.springframework.data.jpa.repository.support.QueryDslJpaRepository.findAll(QueryDslJpaRepository.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:483)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:468)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:440)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
... 38 more