我正在努力让本地查询与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_a
和EntityManager
API来实现。我这样做但是想在更复杂的查询中使用它,我简化了这个问题所需的所有细节。
答案 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;
}
}