将具有InheritanceType.JOINED的实体添加到本机查询

时间:2016-09-07 01:16:59

标签: hibernate

我正在努力让本地查询与InheritanceType.JOINED一起使用。从Hibernate documentation我发现:

  

13.1.6。处理继承

     

查询作为继承的一部分映射的实体的本机SQL查询必须包含基类及其所有子类的所有属性。

使用以下两个实体:

@Data
@Entity
@Table(name = "my_super")
@EqualsAndHashCode(of = {"id"})
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class MySuper {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

}

@Data
@Entity
@Table(name = "my_sub_a")
@EqualsAndHashCode(callSuper = true)
public class MySubA extends MySuper {

    @Column(name = "x")
    private int x;

}

当我尝试使用以下方法创建本机查询时:

Object actual = session
    .createNativeQuery("SELECT {s.*} FROM my_super {s} LEFT JOIN my_sub_a {a} USING (id)")
    .addEntity("s", MySuper.class)
    .getSingleResult();

它转换为查询:

SELECT s.id as id1_1_0_, s_1_.x as x1_0_0_, case when s_1_.id is not null then 1 when s.id is not null then 0 end as clazz_0_ FROM my_super s LEFT JOIN my_sub_a  a  USING (id) 

然后失败了:

javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not prepare statement
Caused by: org.hibernate.exception.SQLGrammarException: could not prepare statement
Caused by: org.h2.jdbc.JdbcSQLException: Column "S_1_.X" not found; SQL statement:

因此,我们观察到别名注入正在进行其工作,并确定它可能还需要x的{​​{1}}列。但是,它无法找出my_sub_a的别名。我的代码应该如何修改,以便这个别名也能正确连接?

我的代码可在https://gist.github.com/JWGmeligMeyling/51e8a305f3c268eda473511e202f76e8处找到,以便轻松复制我的问题。

(我知道这个查询也很容易用JPQL或HQL表达,甚至可以使用my_sub_aEntityManager API来实现。我这样做但是想在更复杂的查询中使用它,我简化了这个问题所需的所有细节。

1 个答案:

答案 0 :(得分:2)

该问题似乎与使用中的基础数据库方言有关,换言之,与某些“异国情调”查询部分有关。您描述的行为可以使用您提供的查询进行复制,但是使用一些微小的tweek可以正常运行 - 具体取决于您要使用的方言。

在你的例子中,你使用H2数据库,但我认为这不是你的制作方言,对吧?我也尝试使用PostgresSQL数据库(在9.5版本中)。

使用origin查询,H2和PostgreSQL的行为是相同的。但是,如果从列和别名中删除大括号(看起来像某些ODBC escape sequences)并将USING clause更改为显式ON a.id = s.id条件,则查询可以执行而没有任何异常。

为了验证行为,我使用Hibernate Session或EntityManager使用不同的查询创建了一些测试,因为在查看了链接的示例代码后,我对Hibernate Session interface的混合使用感到困惑。 EntityManager methods like createNativeQuery。如果有疑问,我试试了两者。  我使用相同的实体和或多或少相同的配置和测试代码,就像您在示例中但在Spring Boot environment内,仅为了方便起见。要在我使用Spring Boot Profiles的数据库之间切换,如果您有两个数据库的配置,只需激活/取消注释@ActiveProfiles("postgres")部分。

以下是测试,我希望确实有所帮助:

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.Session;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
//@ActiveProfiles("postgres") // activate for PostgreSQL tests
public class InheritanceDemoApplicationTests {

    private static final String QUERY = "SELECT {s.*} FROM my_super {s} LEFT JOIN my_sub_a {a} USING (id)";
    private static final String QUERY_WITHOUT_ODBC_ESCAPES = "SELECT s.* FROM my_super s LEFT JOIN my_sub_a a USING (id)";
    private static final String QUERY_WITHOUT_USING_KEYWORD = "SELECT {s.*} FROM my_super {s} LEFT JOIN my_sub_a {a} ON a.id = s.id";
    private static final String QUERY_WITHOUT_ODBC_ESCAPES_AND_WITHOUT_USING_KEYWORD = "SELECT s.* FROM my_super s LEFT JOIN my_sub_a a ON a.id = s.id";

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    public void sessionQuery() {
        validateQueryViaSession(QUERY);
    }

    @Test
    public void entityManagerQuery() {
        validateQueryViaEntityManager(QUERY);
    }

    @Test // works for PostgreSQL
    public void sessionQueryWithoutOdbc() {
        validateQueryViaSession(QUERY_WITHOUT_ODBC_ESCAPES);
    }

    @Test // works for PostgreSQL
    public void entityManagerQueryWithoutOdbc() {
        validateQueryViaEntityManager(QUERY_WITHOUT_ODBC_ESCAPES);
    }

    @Test
    public void sessionQueryWithoutUsing() {
        validateQueryViaSession(QUERY_WITHOUT_USING_KEYWORD);
    }

    @Test // works for H2
    public void entityManagerQueryWithoutUsing() {
        validateQueryViaEntityManager(QUERY_WITHOUT_USING_KEYWORD);
    }

    @Test // works for H2 & PostgreSQL
    public void sessionQueryWithoutOdbcAndWithoutUsing() {
        validateQueryViaSession(QUERY_WITHOUT_ODBC_ESCAPES_AND_WITHOUT_USING_KEYWORD);
    }

    @Test // works for H2 & PostgreSQL
    public void entityManagerQueryWithoutOdbcAndWithoutUsing() {
        validateQueryViaEntityManager(QUERY_WITHOUT_ODBC_ESCAPES_AND_WITHOUT_USING_KEYWORD);
    }

    @SuppressWarnings("rawtypes")
    private void validateQueryViaSession(final String queryString) {
        final MySubA match = persistMySubA();
        List result = entityManager.unwrap(Session.class).createSQLQuery(queryString).addEntity("s", MySuper.class)
                .list();
        assertThat(result.iterator().next()).isEqualToComparingFieldByField(match);
    }

    @SuppressWarnings("rawtypes")
    private void validateQueryViaEntityManager(final String queryString) {
        final MySubA match = persistMySubA();
        List result = entityManager.createNativeQuery(queryString, MySuper.class).getResultList();
        assertThat(result.iterator().next()).isEqualToComparingFieldByField(match);
    }

    private MySubA persistMySubA() {
        final MySubA mySubA = new MySubA();
        mySubA.setX(1);
        entityManager.persist(mySubA);
        entityManager.flush();
        return mySubA;
    }

}