为什么这个 Spring Data JPA query by name 方法给我这个错误,而 Eclipse 强制我 return 一个 Optional<User> 而不是一个简单的 User 对象?

Why this Spring Data JPA query by name method gives me this error and Eclipse forces me to return an Optional<User> instead a simple User object?

我正在使用 Spring Data JPA 和 Hibernate 映射开发 Spring 引导项目。在我的存储库 classes 中,我正在使用 通过方法名称查询 并且我有以下问题。我有这个 User 实体 class:

@Entity
@Table(name = "portal_user")
@Getter
@Setter
public class User implements Serializable {
     
    private static final long serialVersionUID = 5062673109048808267L;
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    
    @Column(name = "first_name")
    @NotNull(message = "{NotNull.User.firstName.Validation}")
    private String firstName;
    
    @Column(name = "middle_name")
    private String middleName;
    
    @Column(name = "surname")
    @NotNull(message = "{NotNull.User.surname.Validation}")
    private String surname;
    
    @Column(name = "sex")
    @NotNull(message = "{NotNull.User.sex.Validation}")
    private char sex;
    
    @Column(name = "birthdate")
    @NotNull(message = "{NotNull.User.birthdate.Validation}")
    private Date birthdate;
    
    @Column(name = "tax_code")
    @NotNull(message = "{NotNull.User.taxCode.Validation}")
    private String taxCode;
    
    @Column(name = "e_mail")
    @NotNull(message = "{NotNull.User.email.Validation}")
    private String email;
    
    @Column(name = "pswd")
    @NotNull(message = "{NotNull.User.pswd.Validation}")
    private String pswd;
    
    @Column(name = "contact_number")
    @NotNull(message = "{NotNull.User.contactNumber.Validation}")
    private String contactNumber;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "created_at")
    private Date createdAt;
    
    @Column(name = "is_active")
    private boolean is_active;
    
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
    @JsonManagedReference
    private Set<Address> addressesList = new HashSet<>();
    
    @ManyToMany(cascade = { CascadeType.MERGE })
    @JoinTable(
        name = "portal_user_user_type", 
        joinColumns = { @JoinColumn(name = "portal_user_id_fk") }, 
        inverseJoinColumns = { @JoinColumn(name = "user_type_id_fk") }
    )
    Set<UserType> userTypes;


    @ManyToOne(fetch = FetchType.EAGER)
    @JsonProperty("subagent")
    private User parent;

    public User() {
        super();
        // TODO Auto-generated constructor stub
    }


    public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
            String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
        super();
        this.firstName = firstName;
        this.middleName = middleName;
        this.surname = surname;
        this.sex = sex;
        this.birthdate = birthdate;
        this.taxCode = taxCode;
        this.email = email;
        this.pswd = pswd;
        this.contactNumber = contactNumber;
        this.createdAt = createdAt;
        this.is_active = is_active;
    }
    

}

然后我有这个存储库界面:

public interface UsersRepository extends JpaRepository<User, Integer> {
    
    /**
     *  Retrieve an user by its e-mail address:
     * @param email is the e-mail of the user
     * @return the user related to the specified e-mail
     */
    User findByemail(String email);
    
    /**
     * Retrieve the list of users belonging to a specific user type
     * @param typeName is the name of the user type
     * @return the list of users belonging to a specific user type
     */
    List<User> findByUserTypes_TypeName(String typeName);
    
    /**
     *  Retrieve the list of users children of an user. 
     *  Generally used to retrieve the client users of a specific sub-agent user
     * @param id is the id of the parent user
     * @return the list of children of a specific user (generally used to retrieve the client users of a specific sub-agent user)
     */
    List<User> findByParent_Id(Integer id);
    
    
    /**
     * Retrieve an user starting from is id
     * @param id of the user which you want to retrieve
     * @return the retrieved object
     */
    Optional<User> findById(Integer id);

}

我怀疑最后一个方法(旨在通过其自己的 id 字段值检索 User 对象,这个:

Optional<User> findById(Integer id);

最初我试图将其简单定义为:

User findById(Integer id);

但是这样做 Eclipse\STS 给我以下错误:

Multiple markers at this line
    - implements org.springframework.data.repository.CrudRepository<com.easydefi.users.entity.User,java.lang.Integer>.
     findById
    - The return type is incompatible with CrudRepository<User,Integer>.findById(Integer)
    - The return type is incompatible with CrudRepository<User,Integer>.findById(Integer)

为什么会出现这个错误?以及为什么 Eclipse 强制我将 return Optional<User> 作为 returned 类型?这是什么可选?为什么在 findByemail() 中允许我 return 一个简单的 User 类型?

正如您在 JpaRepository 扩展的 CrudRepository 接口的 JavaDoc 中看到的那样,已经有一个具有相同签名的 findById 方法。

Java 重写规则不允许您使用具有相同名称和参数的方法以及与父 class 定义不一致的 return 类型(在此例,Optional<User>).

如果您以某种方式更改方法名称,Spring Data JPA 将能够使用普通引用。在这种情况下,如果未找到实体,null 将被 returned。但是,对于您的情况,您应该只删除 findById 方法。

您的存储库扩展了 JpaRepository。 JpaRepository 扩展了 PagingAndSortingRepository,后者扩展了 CrudRepository。 CrudRepository 有方法:Optional<T> findById(ID var1);https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html

当您使用此签名定义方法时,您将覆盖 CrudRepository 方法。但是,如果您为存储库中创建的此方法定义不同的 return 类型,您的代码将无法编译。

Optional创建于Java 8、resume中,用于表示method的return数据可能为null。在这些情况下使用 Optional 是一种很好的做法。 https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

关于您提到的其他方法,它们只是您可以根据需要创建的 UsersRepository 的方法。

Alexy 和 Raul 已经很好地解释了为什么这不起作用。 但是为避免该问题提供的选项相当有限。 以下是您拥有的最相关的选项:

  1. 省略您的 findById 方法,只使用 CrudRepository 提供的方法。可行,但您似乎不想要 Optional,所以这并不令人满意。

  2. 您可以使用 find 的替代方法来修改方法名称,例如searchquery。请参阅 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords 以获取可能使用的单词列表。

  3. 您还可以在 findBy 之间添加任何内容。 findUserById 也可以。

  4. 您可以决定不继承 CrudRepository。这当然也会删除 CrudRepository 的所有其他方法。但是如果你将它们的声明复制到你的存储库接口中,它们将得到正确的实现。您也可以使用此变体来删除您可能不想在存储库中包含的方法。