当子类具有相同的属性名时,带有QueryDSL的Spring Data JPA与JPA Inheritance.JOINED策略一起出现问题

时间:2016-05-02 18:03:29

标签: jpa

我正在尝试使用QueryDSL进行我需要执行的动态查询。这是一个使用QueryDSL 3.7.2(com.mysema.querydsl)的Spring Boot项目。我已经简化了示例,但基本上我有一个使用Inheritance.JOINED策略注释的Item抽象类。这是Item类:

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 + "]";
  }

}

还有2个扩展Item的子类。那些是Book和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 = "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() + "]";
  }

}

和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() + "]";
  }

}

这是我用于测试的Test类:

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

}

基本上,每个子类Book和CD都有一个流派属性,其输入方式不同。 Book类型使用BookGenre类型,CD类型具有CDGenre类型。出现问题是因为属性的名称完全相同。

因此,当我运行试图查询Book的测试时,我得到以下堆栈跟踪,这基本上表明它希望流派属于Type CDGenre。

如果我使用查询CD运行测试,则查询无任何问题。

此外,如果我重命名Book和CD类型属性以使它们是唯一的,例如bookGenre和cdGenre,那么Book查询肯定有效。

因此,为了使用这种类型的继承,我必须确保每个子类具有不同的命名属性。但是在这个例子中,音乐CD的类型不应该与书籍的类型相同,我认为它们都被命名为流派并不一定是错的。

所以我不确定我的域设计是否糟糕,如果Spring Data JPA和QueryDSL存在问题。

谢谢,

堆栈追踪:

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

1 个答案:

答案 0 :(得分:0)

在Spring Boot JPA / Web框架中没有QueryDSL,一切正常,但我必须摆脱ItemType中的Item字段,因为JPA会为您提供。{ / p>

@Override
public void createBook() {
    Book book = new Book(BookGenre.SCIENCE, "How To Use Your Microscope");
    bookRepository.save(book);
    CD cd = new CD(CDGenre.BLUES, "The Wind Cries Mary");
    cdRepository.save(cd);        
}

@Override
public void getBook() {
    Page<Book> books = bookRepository.findByGenre(BookGenre.SCIENCE, new PageRequest(0, 20) );
    System.out.println( books.getContent().get(0) );
}

给出

Hibernate: select count(book0_.id) as col_0_0_ from book book0_ inner join item book0_1_ on book0_.id=book0_1_.id where book0_.genre=?
Hibernate: select book0_.id as id2_2_, book0_1_.title as title3_2_, book0_.genre as genre1_0_, book0_.title as title2_0_ from book book0_ inner join item book0_1_ on book0_.id=book0_1_.id where book0_.genre=? limit ?
Book [genre=SCIENCE, title=How To Use Your Microscope, getId()=1]