使用 JPA 原生查询时是不是必须选择所有实体属性?

Posted

技术标签:

【中文标题】使用 JPA 原生查询时是不是必须选择所有实体属性?【英文标题】:Do I have to select all entity properties when use JPA native query?使用 JPA 原生查询时是否必须选择所有实体属性? 【发布时间】:2014-10-20 00:55:02 【问题描述】:

对于一些复杂的业务逻辑,我需要使用 JPA 原生查询(使用 Hibernate),我发现显然所有实体属性都需要在我的原生查询的 SELECT 子句中。否则 JPA 总是抛出

java.sql.SQLException: Column 'xxx' not found.

所以我想知道这是否是必要的,或者我在我的代码/配置中遗漏了一些东西,因为我找不到任何关于此的 JPA 规范。我的代码如下:

表架构

CREATE TABLE `casaddress` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`addressLine1` varchar(255) DEFAULT NULL,
`addressLine2` varchar(255) DEFAULT NULL,
`companyId` bigint(20) DEFAULT NULL,
`country` varchar(255) DEFAULT NULL,
`effectiveDate` date DEFAULT NULL,
`occupiedOfficer` varchar(255) DEFAULT NULL,
`postcode` varchar(255) DEFAULT NULL,
`suburb` varchar(255) DEFAULT NULL,
`type` int(11) DEFAULT NULL,
`version` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK616A8A7F1FD01BA8` (`companyId`),
CONSTRAINT `FK616A8A7F1FD01BA8` FOREIGN KEY (`companyId`) REFERENCES `cascompany` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1

JPA 实体代码(只是一个 pojo)

@Entity(name = "CASAddress")
    public class Address implements Serializable 

    private static final long serialVersionUID = -2903861598526729415L;

    public static final String TYPE = "type";

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Version
    private Long version;

    private String addressLine1;

    private String addressLine2;

    private String suburb;

    private String postcode;

    private String country;

    private String occupiedOfficer;

    @Temporal(value = TemporalType.DATE)
    private Date effectiveDate;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId", nullable = false, updatable = false, insertable = false)
    private CASCompany company;

    private Long companyId;

    private AddressType type;

    public Address() 

  

  /**
   * @return the addressLine1
   */
  public String getAddressLine1() 
    return addressLine1;
  

  /**
   * @param addressLine1 the addressLine1 to set
   */
  public void setAddressLine1(String addressLine1) 
    this.addressLine1 = addressLine1;
  

  /**
   * @return the addressLine2
   */
  public String getAddressLine2() 
    return addressLine2;
  

  /**
   * @param addressLine2 the addressLine2 to set
   */
  public void setAddressLine2(String addressLine2) 
    this.addressLine2 = addressLine2;
  

  /**
   * @return the suburb
   */
  public String getSuburb() 
    return suburb;
  

  /**
   * @param suburb the suburb to set
   */
  public void setSuburb(String suburb) 
    this.suburb = suburb;
  

  /**
   * @return the postcode
   */
  public String getPostcode() 
    return postcode;
  

  /**
   * @param postcode the postcode to set
   */
  public void setPostcode(String postcode) 
    this.postcode = postcode;
  

  /**
   * @return the country
   */
  public String getCountry() 
    return country;
  

  /**
   * @param country the country to set
   */
  public void setCountry(String country) 
    this.country = country;
  

  /**
   * @return the occupiedOfficer
   */
  public String getOccupiedOfficer() 
    return occupiedOfficer;
  

  /**
   * @param occupiedOfficer the occupiedOfficer to set
   */
  public void setOccupiedOfficer(String occupiedOfficer) 
    this.occupiedOfficer = occupiedOfficer;
  

  /**
   * @return the effectiveDate
   */
  public Date getEffectiveDate() 
    return effectiveDate;
  

  /**
   * @param effectiveDate the effectiveDate to set
   */
  public void setEffectiveDate(Date effectiveDate) 
    this.effectiveDate = effectiveDate;
  

  /**
   * @return the companyId
   */
  public Long getCompanyId() 
    return companyId;
  

  /**
   * @param companyId the companyId to set
   */
  public void setCompanyId(Long companyId) 
    this.companyId = companyId;
  

  /**
   * @return the type
   */
  public AddressType getType() 
    return type;
  

  /**
   * @param type the type to set
   */
  public void setType(AddressType type) 
    this.type = type;
  

以及查询代码(只是一个简单的显示问题的代码。真正的查询要复杂得多):

String query =
    "SELECT ad.id, ad.version, ad.addressLine1, ad.addressLine2, ad.suburb, ad.country, 
    ad.postcode, ad.type, ad.companyId, ad.effectiveDate from CASAddress ad";
    Query q = entityManager.createNativeQuery(query, Address.class);
   List<Address> addresses = q.getResultList();

请注意,地址实体的占用办公室属性未添加到 SELECT 子句中,我得到了

Caused by: org.hibernate.exception.SQLGrammarException: Column 'occupiedOfficer' not found.
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:122)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
    at org.hibernate.engine.jdbc.internal.proxy.AbstractResultSetProxyHandler.continueInvocation(AbstractResultSetProxyHandler.java:108)
    at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81)
    at com.sun.proxy.$Proxy198.getString(Unknown Source)
    at org.hibernate.type.descriptor.sql.VarcharTypeDescriptor$2.doExtract(VarcharTypeDescriptor.java:66)
    at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:65)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:261)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:257)
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:247)
    at org.hibernate.type.AbstractStandardBasicType.hydrate(AbstractStandardBasicType.java:332)
    at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2873)
    at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1668)
    at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1600)
    at org.hibernate.loader.Loader.getRow(Loader.java:1500)
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:712)
    at org.hibernate.loader.Loader.processResultSet(Loader.java:940)
    at org.hibernate.loader.Loader.doQuery(Loader.java:910)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:341)
    at org.hibernate.loader.Loader.doList(Loader.java:2516)
    at org.hibernate.loader.Loader.doList(Loader.java:2502)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2332)
    at org.hibernate.loader.Loader.list(Loader.java:2327)
    at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
    at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:1783)
    at org.hibernate.internal.AbstractSessionImpl.list(AbstractSessionImpl.java:231)
    at org.hibernate.internal.SQLQueryImpl.list(SQLQueryImpl.java:157)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:264)
    ... 79 more
Caused by: java.sql.SQLException: Column 'occupiedOfficer' not found.
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1078)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:920)
    at com.mysql.jdbc.ResultSetImpl.findColumn(ResultSetImpl.java:1163)
    at com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5729)
    at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:811)
    at com.alibaba.druid.filter.FilterAdapter.resultSet_getString(FilterAdapter.java:1754)
    at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:809)
    at com.alibaba.druid.filter.FilterAdapter.resultSet_getString(FilterAdapter.java:1754)
    at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:809)
    at com.alibaba.druid.filter.stat.StatFilter.resultSet_getString(StatFilter.java:930)
    at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:809)
    at com.alibaba.druid.proxy.jdbc.ResultSetProxyImpl.getString(ResultSetProxyImpl.java:693)
    at com.alibaba.druid.pool.DruidPooledResultSet.getString(DruidPooledResultSet.java:257)
    at sun.reflect.GeneratedMethodAccessor66.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.hibernate.engine.jdbc.internal.proxy.AbstractResultSetProxyHandler.continueInvocation(AbstractResultSetProxyHandler.java:104)
    ... 104 more

如果我添加了占用的Officer 属性(我不需要),则查询有效并返回正确的结果。对此有何想法?

【问题讨论】:

如果您不想写列名,可以使用 *。或者如果不想从数据库中获取额外的列,你可以使用@SqlResultSetMapping(我认为这对于只读数据并不危险) 如何使用@SqlResultSetMapping 来解决我的问题?据我所知,SqlResultSetMapping 只是帮助映射实体属性和列,以防它们的名称不同 ***.com/questions/25930628/… 【参考方案1】:

JPA 规范的第 3.10.16.1 节“从本机查询返回托管实体”说:

当要从本机查询返回实体时,SQL 语句应选择映射到实体对象的所有列。这应该包括相关实体的外键列。数据不足时得到的结果是不确定的。

换句话说,JPA 不支持从本机查询中填充部分实体(如果有的话)。

【讨论】:

完美答案!但为什么?实现部分查询很难吗?当前的实现(必须返回所有列)直观且低效! 在您的特定示例中,我怀疑您是否可以衡量差异

以上是关于使用 JPA 原生查询时是不是必须选择所有实体属性?的主要内容,如果未能解决你的问题,请参考以下文章

JPA EntityManager查询--使用原生sql 并且把查询结果转为实体对象

如何使用 JPA 原生查询选择多个同名的列?

Spring Data JPA 原生查询结果实体

附加实体是否必须在JPA中合并?

JPA双向实体:在查询父实体时仅选择子实体的子集

如何使用 JPA 和 Hibernate 通过自定义对象实体属性进行查询