【问题标题】:How to make a field as optional in a JPA Entity?如何在 JPA 实体中将字段设为可选?
【发布时间】:2021-07-12 02:09:44
【问题描述】:

我在 SQL Server 数据库中拥有近 30 列的表用户。我还为相应的数据库表创建了一个 JPA 实体。 JPA 实体如下所示:

@Entity
@Table(name="USER")
@Setter
@Getter
public class User{

@Id
@Column(name="ID",nullable=false)
private Integer id;

@Column(name="NAME")
private String name;

@Column(name="ADDRESS")
private String address;

@Column(name="FATHER_NAME")
private String fathersName;

}

现在我正在尝试创建一个本机查询,它只会从用户表中获取 id 和 name。 Repository 类看起来像这样。

@Repository
public interface UserDao extends JpaRepository<User,Integer>{

@Query(value=" SELECT u.ID,u.NAME from USER u", nativeQuery=true)
List<User> fetchUser();

}

现在当它被执行时,它会抛出一个异常,如下所示:

com.microsoft.sqlserver.jdbc.SqlServerException: The column name address is not valid

当我将地址列添加到我的查询中时,此异常会出现,但随后它会出现在另一个字段中。

com.microsoft.sqlserver.jdbc.SqlServerException: The column name father_name is not valid

所以,现在我想修改 JPA 实体,使该字段变为可选。 我试过使用 @Transient 但是如果其他一些查询正在使用这个地址字段,那么它会在那里显示为空。 我还尝试了以下所有注释:

  • @Basic
  • @NotFound(action=NotFoundAction.IGNORE)
  • @Column(nullable="true")
  • @JsonProperty

但是这些注释都不适合我。

【问题讨论】:

  • 创建一个单独的类 UserNameView 映射到同一个表(并且不包括 @Setter)。
  • 我们已经这样做了,而且效果很好。但是假设我必须编写 20 个不同类型的查询来为同一实体获取不同的列,然后我将不得不创建 20 个不同的类。所以我正在寻找某种方式,只要修改这个实体,所有查询都可以工作。

标签: java sql-server hibernate jpa


【解决方案1】:

您可以为User 实体定义额外的构造函数:

@Entity
@Table(name="USER")
public class User {

   public User(){
   }

   public User(Integer id, String name){
      this.id = id;
      this.name = name;
   }

   // ...
}

然后编写以下查询:

@Query("select new your.package.User(u.id, u.name) from User u")
List<User> fetchUser();

注意,这不是原生的,而是 jpql 查询。

还请注意根据hibernatedocumentation

投影类在实体查询中必须是完全限定的,并且它必须定义一个匹配的构造函数。 这里的类不需要映射。它可以是 DTO 类。 如果它确实代表一个实体,结果实例以 NEW 状态返回(非托管!)。

编辑

您也可以尝试通过以下方式使用spring data jpa projections

  1. 定义接口:
interface UserIdWithName {

  Integer getId();
  String getName();
}

这里重要的是这里定义的属性与聚合实体中的属性完全匹配。

  1. 在查询方法中使用该接口:
@Query("select u from User u")
List<UserIdWithName> fetchUserIdWithName();

查询执行引擎在运行时为每个返回的元素创建该接口的代理实例,将对公开方法的调用转发到目标对象。请注意,您不能只选择在这里查询。

【讨论】:

  • 如果我必须为具有不同列的同一实体编写 10 个不同的查询以在每个查询中获取,这种方法看起来不易于维护/可读。然后我必须创建 10 个不同的构造函数。除此之外,我还尝试使用 lombok 的 @RequiredArgConstructor 。但这也无济于事。
  • 查看更新后的答案,也许 spring data jpa 预测会更适合您的需求。
【解决方案2】:

你可以使用

@Query(value=" SELECT u.ID,u.NAME, '' as address, '' as father_name from USER u", nativeQuery=true)
List<User> fetchUser();

它将这些字段设置为空字符串。

【讨论】:

  • 如果只有几列我会这样做,但表有 30 -35 列这看起来不可行。
【解决方案3】:

自从 Java 8 发布以来,我经常收到这个问题。不幸的是,Hibernate 和 JPA 2.2 不支持 Optional 作为属性类型。

但是通过一个小技巧,您仍然可以使用 Optional 作为您的 getter 方法的返回类型。如果您使用 @Id 注释来注释主键属性,则您告诉 Hibernate 使用字段访问来设置和检索实体属性值。这意味着 Hibernate 不调用 getter 或 setter 方法,您可以以任何您喜欢的方式实现它们。因此,您可以将 optional 属性包装到 Optional 中,而不是直接返回它。

但请注意,这不包括任何延迟加载,只是将数据库列的已选择值包装到 Optional 中。 Hibernate 需要字节码增强来启用基本属性的延迟加载。这远远超出了本文的范围,但我会在我的 Hibernate Performance Tuning Online Training 中进行更详细的解释。

示例 好,我们来看一个简单的例子。一本书可能已经出版,或者作者仍在努力,很快就会宣布出版日期。因此,Book 实体的 publishingDate 属性可以为空。以下代码 sn -p 显示了 Book 实体的定义。

@Entity
public class Book {
     
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
 
    @Column(nullable = true)
    private LocalDate publishingDate;
     
    ...
     
    public Optional getPublishingDate() {
        return Optional.ofNullable(publishingDate);
    }
 
    public void setPublishingDate(LocalDate publishingDate) {
        this.publishingDate = publishingDate;
    }
}

【讨论】:

  • 我试过这种方法我仍然得到列名无效。
【解决方案4】:

我认为这是Blaze-Persistence Entity Views 的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(User.class)
public interface UserDto {
    @IdMapping
    Integer getId();
    String getName();
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

UserDto a = entityViewManager.find(entityManager, UserDto.class, id);

Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<UserDto> findAll(Pageable pageable);

最好的部分是,它只会获取实际需要的状态!

【讨论】:

    猜你喜欢
    • 2020-10-24
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    • 2011-01-14
    • 1970-01-01
    • 2013-10-21
    相关资源
    最近更新 更多