首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >'Batch从update返回意外行计数-- Micronaut 3.1+更新后

'Batch从update返回意外行计数-- Micronaut 3.1+更新后
EN

Stack Overflow用户
提问于 2021-11-29 10:52:20
回答 2查看 462关注 0票数 4

我试图升级到Micronaut3.2,但从3.1开始,数据库上的一些写操作开始失败。我创建了一个示例项目来展示以下内容:https://github.com/dpozinen/optimistic-lock

此外,我在https://github.com/micronaut-projects/micronaut-data/issues/1230上制造了一个问题

简单地说,我的实体:

代码语言:javascript
运行
复制
@MappedSuperclass
public abstract class BaseEntity {

  @Id
  @GeneratedValue(generator = "system-uuid")
  @GenericGenerator(name = "system-uuid", strategy = "uuid2")
  @Column(updatable = false, nullable = false, length = 36)
  @Type(type = "optimistic.lock.extra.UuidUserType")
  private UUID id;

  @Version
  @Column(nullable = false)
  private Integer version;
}
代码语言:javascript
运行
复制
public class Game extends BaseEntity {
  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @ToString.Exclude
  @OrderColumn(name = "sort_index")
  private List<Question> questions = new ArrayList<>();
}
代码语言:javascript
运行
复制
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "question")
public abstract class Question extends BaseEntity {
  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @ToString.Exclude
  @OrderColumn(name = "sort_index")
  private List<AnswerOption> answerOptions = new ArrayList<>();
}
代码语言:javascript
运行
复制
public class ImageSingleChoiceQuestion extends Question {
  @OneToOne(cascade = CascadeType.ALL)
  private AnswerOption validAnswer;
}
代码语言:javascript
运行
复制
@Table(name = "answer_option")
public class AnswerOption extends BaseEntity {}

非常基本的设置。当我从游戏中删除问题时,就会出现异常:

代码语言:javascript
运行
复制
Game game = gameRepository.findById(gameId).orElseThrow()
Question question = questionRepository.findByGameAndId(gameId, questionId).orElseThrow()

game.getQuestions().remove(question)
gameRepository.saveAndFlush(game) // optional

预期结果:questiongame中分离并删除,将删除级联到answerOptions。直到Micronaut 3.0.3,您可以在样例项目中更改版本,测试才会成功。

实际结果:

代码语言:javascript
运行
复制
javax.persistence.OptimisticLockException: 
Batch update returned unexpected row count from update [0];
actual row count: 0; expected: 1; 
statement executed: delete from question where id=? and version=?

这里是执行的SQL,注意版本号被搞砸了。任何帮助都将不胜感激。

编辑1:

仅在将ImageSingleChoiceQuestion#setValidAnswer应用于也在Question#setAnswerOptions中使用的实例时才会出现问题。

为什么是这样,因为这在Micronaut 3.0.3中有效?

编辑2:

编辑3:

  • 确认为bug并在按下中修复
EN

回答 2

Stack Overflow用户

发布于 2021-12-05 18:45:53

正如@saw303正确指出的那样,更新是Micronaut数据版本<= 3.2.0中的一个错误。这个错误是在io.micronaut.data:micronaut-data-hibernate-jpa:3.2.1中修复的,它是在2021-12-07发布的(今天写这篇文章时)。

https://github.com/micronaut-projects/micronaut-data/releases

我用这个变化更新了我的公关。

我可以用你回购的代码来重现这个问题。Micronaut数据更改日志显示,v3.1.0中有相关的更改。https://github.com/micronaut-projects/micronaut-data/releases

只有在将ImageSingleChoiceQuestion#setValidAnswer应用于也在Question#setAnswerOptions中使用的实例时才会出现问题。

因此,当这样做时,问题就消失了:question.setValidAnswer(new AnswerOption());。说明:在一对多和一对一地使用相同的AnswerOption实例时,Hibernate尝试删除两个位置的实例。

原因似乎是CascadeType.ALL。当从课堂问题中删除它时,测试正在通过。

下面的解决方案通过了测试,但目前我无法解释为什么它是基于生成的SQL工作的。需要对这一办法进行进一步的调查。

代码语言:javascript
运行
复制
@Getter
@Setter
@ToString
@Entity(name = "ImageSingleChoiceQuestion")
@Table(name = "image_single_choice_question")
public class ImageSingleChoiceQuestion extends Question {

    @OneToOne
    @PrimaryKeyJoinColumn
    private AnswerOption validAnswer;
}

到目前为止,通过测试的最佳解释解决方案是将@OneToOne替换为@OneToMany,如下所示。Hibernate将使用这种方法创建连接表image_single_choice_question_valid_answers。这当然不是最理想的。

代码语言:javascript
运行
复制
@Getter
@Setter
@ToString
@Entity(name = "ImageSingleChoiceQuestion")
@Table(name = "image_single_choice_question")
public class ImageSingleChoiceQuestion extends Question {

    @OneToMany
    private Set<AnswerOption> validAnswers = new HashSet<>();

    public AnswerOption getValidAnswer() {
        return validAnswers.stream().findFirst().orElse(null);
    }

    public void setValidAnswer(final AnswerOption answerOption) {
        validAnswers.clear();
        validAnswers.add(answerOption);
    }
}

PR在这里:https://github.com/dpozinen/optimistic-lock/pull/1

我向您的项目添加了测试容器,测试类现在如下所示:

代码语言:javascript
运行
复制
package optimistic.lock

import optimistic.lock.answeroption.AnswerOption
import optimistic.lock.image.ImageSingleChoiceQuestion
import optimistic.lock.testframework.ApplicationContextSpecification
import optimistic.lock.testframework.testcontainers.MariaDbFixture

import javax.persistence.EntityManager
import javax.persistence.EntityManagerFactory

class NewOptimisticLockSpec extends ApplicationContextSpecification
        implements MariaDbFixture {

    @Override
    Map<String, Object> getCustomConfiguration() {
        [
                "datasources.default.url"     : "jdbc:mariadb://localhost:${getMariaDbConfiguration().get("port")}/opti",
                "datasources.default.username": getMariaDbConfiguration().get("username"),
                "datasources.default.password": getMariaDbConfiguration().get("password"),
        ]
    }

    GameRepository gameRepository
    QuestionRepository questionRepository

    TestDataProvider testDataProvider
    GameTestSvc gameTestSvc

    EntityManagerFactory entityManagerFactory
    EntityManager entityManager

    @SuppressWarnings('unused')
    void setup() {
        gameRepository = applicationContext.getBean(GameRepository)
        questionRepository = applicationContext.getBean(QuestionRepository)

        testDataProvider = applicationContext.getBean(TestDataProvider)
        gameTestSvc = applicationContext.getBean(GameTestSvc)

        entityManagerFactory = applicationContext.getBean(EntityManagerFactory)
        entityManager = entityManagerFactory.createEntityManager()
    }

    void "when removing question from game, game has no longer any questions"() {
        given:
        def testGame = testDataProvider.saveTestGame()
        and:
        Game game = gameRepository.findById(testGame.getId()).orElseThrow()
        and:
        Question question = testGame.questions.first()
        assert question != null
        and:
        def validAnswer = ((ImageSingleChoiceQuestion)question).getValidAnswer()
        assert validAnswer != null

        when:
        gameTestSvc.removeQuestionFromGame(game.getId(), question.getId())

        then:
        noExceptionThrown()
        and:
        0 == entityManager.find(Game.class, game.getId()).getQuestions().size()
        and:
        null == entityManager.find(AnswerOption.class, validAnswer.getId())
    }
}
票数 2
EN

Stack Overflow用户

发布于 2021-12-07 07:37:43

您正在遭受一个在MicronautData3.1中引入的bug。在您的示例中,version属性被错误地递增。这个错误已经被Micronaut数据小组确认了,并且有一个挂起的拉请求(请参阅按下)来解决这个问题。

是关于io.micronaut.data.runtime.event.listeners.VersionGeneratingEntityEventListener错误地增加了JP A实体的version属性(参见代码指针)。

我认为您可以期待在即将发布的Micronaut 3.2.2版本中得到修复。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70153405

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档