Spring не сопоставляет составной ключевой объект с базой данных

#spring #spring-boot #spring-mvc #jpa #spring-data-jpa

#Spring #spring-boot #spring-mvc #jpa #spring-data-jpa

Вопрос:

У меня есть две сущности User и Focus, для которых я должен сохранить рейтинг таким образом, чтобы у пользователя мог быть рейтинг для многих фокусов, а у фокуса может быть много пользователей. Чтобы справиться с этим, я следовал https://www.baeldung.com/jpa-many-to-many часть 3, в которой у меня есть 2 следующих класса:

 @Entity(name = "UserBaseRatings")
public class Rating {

    @EmbeddedId
    RatingKey id;

    @ManyToOne
    @MapsId("userID")
    @JoinColumn(name="userID")
    private User user;

    @ManyToOne
    @MapsId("focusID")
    @JoinColumn(name="focusID")
    private Focus focus;

    private BigDecimal baseRating;

    protected Rating(){}

    public Rating(User user, Focus focus, BigDecimal baseRating) {
        this.user = user;
        this.focus = focus;
        this.baseRating = baseRating;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Focus getFocus() {
        return focus;
    }

    public void setFocus(Focus focus) {
        this.focus = focus;
    }

    public BigDecimal getBaseRating() {
        return baseRating;
    }

    public void setBaseRating(BigDecimal baseRating) {
        this.baseRating = baseRating;
    }
}
  

и

 @Embeddable
public class RatingKey implements Serializable {

    @Column(name="userID")
    private Long userID;

    @Column(name="focusID")
    private Long focusID;

    protected RatingKey(){}

    public RatingKey(Long userID, Long focusID) {
        this.userID = userID;
        this.focusID = focusID;
    }

    public Long getUserID() {
        return userID;
    }

    public void setUserID(Long userID) {
        this.userID = userID;
    }

    public Long getFocusID() {
        return focusID;
    }

    public void setFocusID(Long focusID) {
        this.focusID = focusID;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        RatingKey ratingKey = (RatingKey) o;
        return Objects.equals(userID, ratingKey.userID) amp;amp;
                Objects.equals(focusID, ratingKey.focusID);
    }

    @Override
    public int hashCode() {
        return Objects.hash(userID, focusID);
    }
}
  

Таблица для пользовательских настроек имеет значение по умолчанию 1 для базовой настройки, так что, если у пользователя нет рейтинга в текущем фокусе, его рейтинг должен быть равен 1. Я сталкиваюсь с проблемой, когда, если пользователь не установил рейтинг, набор рейтингов пуст вместо того, чтобы иметь рейтинг 1 для каждого существующего фокуса.

Пользователь:

 public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long userID;

    @Column(name = "userHashedPassword")
    private String password;

    @Column(name = "userName")
    private String userName;
    @Column(name = "userEmail")
    private String email;

    //probably being reset
    @Transient
    private List<String> groups = new LinkedList<>();

    @ManyToMany
    @JoinTable(name = "UserRoles",
        joinColumns = @JoinColumn(
                name = "userID"),
            inverseJoinColumns = @JoinColumn(
                    name = "roleID"))
    private Set<Role> roles = new HashSet<>();

    @OneToMany(mappedBy = "user")
    private Set<Rating> ratings;

    protected User(){}

    public User(String userHashedPassword, String userName, String email, Set<Role> roles){
        this.password = userHashedPassword;
        this.userName = userName;
        this.email = email;
        this.roles = roles;
    }

    public Long getUserId() {
        return userID;
    }

    public void setId(Long userID) {
        this.userID = userID;
    }

    public String getPassword(){
        return password;
    }

    public void setPassword(String password){
        this.password = password;
    }

    public String getUsername() {
        return userName;
    }

    public void setUsername(String name) {
        this.userName = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    public Set<Rating> getRatings() {
        return ratings;
    }

    public void setRatings(Set<Rating> ratings) {
        this.ratings = ratings;
    }
}
  

Focus:

 @Entity
public class Focus {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long focusID;

    private String focusCategory;

    private String focusName;

    private String focusExplanation;

    @OneToMany(mappedBy = "focus")
    Set<Rating> ratings;

    //image

    protected Focus(){}

    public Focus(String focusCategory, String focusName, String focusExplanation, Set<Rating> ratings){
        this.focusCategory = focusCategory;
        this.focusName = focusName;
        this.focusExplanation = focusExplanation;
        this.ratings = ratings;
    }

    public Long getFocusId() {
        return focusID;
    }

    public void setFocusId(Long focusID) {
        this.focusID = focusID;
    }

    public String getFocusCategory() {
        return focusCategory;
    }

    public void setFocusCategory(String focusCategory) {
        this.focusCategory = focusCategory;
    }

    public String getFocusName() {
        return focusName;
    }

    public void setFocusName(String focusName) {
        this.focusName = focusName;
    }

    public String getFocusExplanation() {
        return focusExplanation;
    }

    public void setFocusExplanation(String focusExplanation) {
        this.focusExplanation = focusExplanation;
    }

    public Set<Rating> getRatings() {
        return ratings;
    }

    public void setRatings(Set<Rating> ratings) {
        this.ratings = ratings;
    }
}
  

Чего мне не хватает, что позволит заполнять оценки пользователя значением по умолчанию для каждого фокуса, существующего в базе данных?

Редактировать: добавлены @prePersist и @preUpdate к объекту оценки:

 @Entity(name = "UserBaseRatings")
public class Rating {

    @EmbeddedId
    RatingKey id;

    @ManyToOne
    @MapsId("userID")
    @JoinColumn(name="userID")
    private User user;

    @ManyToOne
    @MapsId("focusID")
    @JoinColumn(name="focusID")
    private Focus focus;

    private BigDecimal baseRating;

    protected Rating(){}

    public Rating(User user, Focus focus, BigDecimal baseRating) {
        this.user = user;
        this.focus = focus;
        this.baseRating = baseRating;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Focus getFocus() {
        return focus;
    }

    public void setFocus(Focus focus) {
        this.focus = focus;
    }

    public BigDecimal getBaseRating() {
        return baseRating;
    }

    public void setBaseRating(BigDecimal baseRating) {
        this.baseRating = baseRating;
    }

    @PrePersist
    public void prePersist() {
        if(baseRating == null) {
            baseRating = BigDecimal.ONE;
        }
    }

    @PreUpdate
    public void preUpdate() {
        if(baseRating == null) {
            baseRating = BigDecimal.ONE;
        }
    }
}
  

Изменение spring.jpa.hibernate.ddl-auto= с none на update также не имело никакого значения.

Ответ №1:

Если вы используете ORM, все значения по умолчанию должны быть явно объявлены. Это можно сделать следующим образом

 @Entity(name = "UserBaseRatings")
public class Rating {

    // ...

    private BigDecimal baseRating = BigDecimal.ONE;

    // ...
}
  

или используйте @PrePersist и @PreUpdate

 @Entity(name = "UserBaseRatings")
public class Rating {
    
    // ...
    
    private BigDecimal baseRating;
        
    // ...
    
    @PrePersist
    public void prePersist() {
        if(baseRating == null) {
           baseRating = BigDecimal.ONE;
        }
    }  
    
    @PreUpdate
    public void preUpdate() {
        if(baseRating == null) {
           baseRating = BigDecimal.ONE;
        }
    }    
}
  

Комментарии:

1. Я добавил аннотации prePersist и preUpdate, однако это не имело никакого значения. Возможно, мое понимание встроенных классов неверно — я думал, что он заполнит таблицу рейтингов, сущность и, следовательно, рейтинг пользователя, установленный при запуске на основе идентификатора пользователя и идентификатора фокуса (ранее существовавших объектов / таблиц). Что мне нужно сделать, чтобы это было так?

2. Ах, я не сохраняю его, и именно поэтому он не будет генерировать таблицы в БД. Однако в моем случае, где я должен сохранять объект? Мне нужен объект ранжирования, созданный для каждого ранее существующего пользователя и Focus, нужно ли мне вручную использовать репозиторий user и focus для поиска записей таблицы при запуске, а затем создавать и сохранять рейтинги из этого? Я думал, что этот процесс будет автоматически выполнен Spring

3. Я неправильно понял вашу проблему. Вы хотите заставить Spring генерировать столбец таблицы со значением по умолчанию?

4. Я хочу, чтобы Spring автоматически генерировал рейтинг для каждого существующего пользователя и фокусировки, например, если у меня есть User: Bob и Focus: Maths и Focus: Science, я бы хотел, чтобы Spring создал рейтинг с идентификатором пользователя Боба mathsID и рейтинг с идентификатором пользователя Боба scienceID все со значением по умолчаниюиз 1, который затем будет сохранен в базе данных и, следовательно, отражен в моем пользовательском объекте. Я думал, что встроенные классы будут делать это автоматически, но я, должно быть, ошибаюсь

5. Spring не генерирует пустые поля существующих объектов. Он управляет объектами, сохраненными из приложения