Ошибка SQL: 1364, SQLState: HY000 — поле ‘rating_id’ не имеет значения по умолчанию / При сохранении дочернего объекта с отношением один к одному

#java #mysql #hibernate #jpa

#java #mysql #спящий режим #jpa

Вопрос:

У меня есть два объекта с двунаправленным отношением «один к одному».

Родительский объект:

 package com.pierre.inventorymanager.model;

import lombok.*;

import javax.persistence.*;
import java.util.Objects;

@Entity
@Getter
@NoArgsConstructor
public class Rating {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id", referencedColumnName = "id")
    private Product product;
    private Float rate;
    @Column(name = "review_left")
    private boolean reviewLeft;
    @OneToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "review_id", referencedColumnName = "id")
    private Review review;

    public void setProduct(Product product) {

        Product oldProduct = this.product;

        if (Objects.equals(product, oldProduct))
            return;

        this.product = product;

        if (oldProduct != null)
            oldProduct.removeRating(this);

        if (product != null)
            product.addRating(this);
    }

    public void setRate(Float rate) {

        this.rate = rate;
    }

    public void setReviewLeft(boolean reviewLeft) {

        this.reviewLeft = reviewLeft;
    }

    public void setReview(Review review) {

        Review oldReview = this.review;

        if (Objects.equals(review, oldReview))
            return;

        this.review = review;

        if (oldReview != null)
            oldReview.setRating(null);

        if (review != null)
            review.setRating(this);
    }

    @Override
    public String toString() {

        return "Rating "   id   " : [productID = "   product.getId()   ", rate = "   rate  
               ", reviewLeft = "   reviewLeft   ", review = "   review   "]";
    }
}

 

Соответствующая таблица MySQL:

 CREATE TABLE IF NOT EXISTS inventory.rating
(
    `id`          INT     NOT NULL AUTO_INCREMENT,
    `product_id`  INT     NOT NULL,
    `review_id`   INT,
    `rate`        INT,
    `review_left` BOOLEAN NOT NULL,
    PRIMARY KEY (id)
);
 

Дочерний объект:

 package com.pierre.inventorymanager.model;

import lombok.*;

import javax.persistence.*;
import java.util.Objects;

@Entity
@Getter
@NoArgsConstructor
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @OneToOne(mappedBy = "review", cascade = CascadeType.PERSIST)
    private Rating rating;
    private String title;
    private String body;

    public void setRating(Rating rating) {

        Rating oldRating = this.rating;

        if (Objects.equals(rating, oldRating))
            return;

        this.rating = rating;

        if (oldRating != null)
            oldRating.setReview(null);

        if (rating != null)
            rating.setReview(this);
    }

    public void setTitle(String title) {

        this.title = title;
    }

    public void setBody(String body) {

        this.body = body;
    }

    @Override
    public String toString() {

        return "Review "   id   " : [ratingID = "   rating.getId()   ", title = "   title  
               ", body = "   body.substring(0, Math.min(body.length(), 10))   "]";
    }
}
 

Его таблица:

 CREATE TABLE IF NOT EXISTS inventory.review
(
    `id`        INT         NOT NULL AUTO_INCREMENT,
    `rating_id` INT         NOT NULL,
    `title`     VARCHAR(45) NOT NULL,
    `body`      TEXT        NOT NULL,
    PRIMARY KEY (id)
);
 

I’m retrieving the values of both entities through this form using Thymeleaf as template:

 <form class="user" action="#" th:action="@{/product/{id}/product_rated(id = ${product.id})}"
      th:object="${rating}" method="post">
    <div class="form-group row">
        <div class="col-sm-3">
            <p>Rate Product:</p>
        </div>
        <div class="slider-container col-sm-3">
            <input th:field="*{rate}" type="range" min="1" max="5" step="1" value="3"
                   class="slider" id="myRange"/>
        </div>
    </div>
    <div class="form-group row">
        <div class="custom-control custom-checkbox small">
            <input th:field="*{reviewLeft}" type="checkbox"
                   class="custom-control-input" id="customCheck"
                   onchange="document.getElementById('inputTitle').disabled = !this.checked;
                   document.getElementById('inputBody').disabled = !this.checked;"/>
            <label class="custom-control-label" for="customCheck">Leave Review</label>
        </div>
    </div>
    <div class="form-group row">
        <div class="col-sm-3">
            <input th:field="*{review.title}" type="text" disabled
                   class="form-control form-control-user"
                   id="inputTitle" placeholder="Title for your review"/>
        </div>
    </div>
    <div class="form-group row">
        <div class="col-sm-3">
            <textarea th:field="*{review.body}" class="form-control rounded-0" rows="10"
                      id="inputBody" placeholder="Tell us what you think.." disabled>
            </textarea>
        </div>
    </div>
    <div class="form-group row">
        <button type="submit" class="btn btn-primary btn-user btn-block col-sm-3">
            Submit
        </button>
    </div>
</form>
 

Controller methods:

 @GetMapping(value = "/product/{id}/rate_product")
public String rateProduct(@PathVariable("id") long id, Model model) {

    model.addAttribute("rating", new Rating());
    model.addAttribute("product", service.getProductByID(id));
    return "rate_product";
}

@PostMapping(value = "/product/{id}/product_rated")
public String saveProductRating(@PathVariable("id") long id, @ModelAttribute("rating") Rating rating) {

    service.saveRating(rating, id);
    return "redirect:/";
}
 

When trying to call this method in my Service, neither parent nor child get saved and the exception is thrown:

 public void saveRating(Rating rating, long id) {

    Product product = productRepository.getById(id);
    rating.setProduct(product);
    ratingRepository.save(rating);
}
 
 2021-03-21 12:33:47.507  WARN 5448 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1364, SQLState: HY000
2021-03-21 12:33:47.507 ERROR 5448 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper   : Field 'rating_id' doesn't have a default value
2021-03-21 12:33:47.531 ERROR 5448 --- [io-8080-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement] with root cause

java.sql.SQLException: Field 'rating_id' doesn't have a default value
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1347) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025) ~[mysql-connector-java-8.0.23.jar:8.0.23]
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-4.0.2.jar:na]
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-4.0.2.jar:na]
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:43) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3200) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3806) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:84) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:330) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:287) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:123) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:93) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:720) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:706) ~[hibernate-core-5.4.28.Final.jar:5.4.28.Final]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~
.
.
.
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.5-20210315.181825-80.jar:5.3.5-SNAPSHOT]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.5-20210315.181825-80.jar:5.3.5-SNAPSHOT]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
    at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
 

Сначала я подумал, что проблема в том, что я пытаюсь сохранить оба объекта одновременно, когда ни у одного из них не было идентификатора в таблицах, поэтому я изменил код на:

 public void saveRating(Rating rating, long id) {

    Product product = productRepository.getById(id);
    rating.setProduct(product);
    Review review = rating.getReview();

    rating.setReview(null);
    ratingRepository.save(rating);

    review.setRating(rating);
    reviewRepository.save(review);
}
 

С помощью приведенного выше кода сохранение родительского Rating элемента работает нормально, проблема возникает, когда hibernate пытается сохранить дочерний Review элемент. Я перепробовал все CascadeType s для обоих объектов и обоих FetchType объектов, и, похоже, ничего не работает. Я также пробовал GenerationType.AUTO и тоже не работал. Удаление базы данных также не сработало.

application.properties :

 spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.datasource.url=jdbc:mysql://localhost:3306/inventory?allowMultiQueries=trueamp;createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=pass
spring.datasource.initialization-mode=always
spring.datasource.separator=^;
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
spring.jpa.properties.hibernate.format_sql=true
 

Изменение режима ddl на create и update не дало никаких результатов.

Примечание

A Rating должен иметь возможность существовать без a Review (пользователь оценивает продукт, не оставляя отзыв), но у a Review всегда должен быть родитель Rating . Это предполагаемое поведение, которого я пытаюсь достичь.

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

1. Вы проверяли, имеет ли rating_id значение в Rating таблице какое-либо значение по умолчанию?

2. Да, значение сохраняется в Rating таблице, но без ссылки на Review (соответствующее review_id значение равно null).

3. В saveRating () у вас есть setReview (null)

4. Да, это был эксперимент, чтобы проверить, была ли проблема в сохранении родительского или дочернего элемента. Кажется, это из-за дочернего элемента, родительский элемент был сохранен таким образом.

Ответ №1:

Извините, я был неправ.

Вам не нужен rating_id, потому что вы уже используете review_id в таблице рейтингов для этой цели.

Таким образом, вы можете удалить rating_id в таблице обзора.

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

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

2. Да, это решило проблему, спасибо за ответ.