【问题标题】:Existing entity cannot be updated with Spring Boot无法使用 Spring Boot 更新现有实体
【发布时间】:2016-06-08 09:07:49
【问题描述】:

我的 Spring Boot 应用有以下类:

董事会(JPA 实体)

@Entity
@Table(name = "board")
public class Board {
  public static final int IN_PROGRESS = 1;
  public static final int AFK         = 2;
  public static final int COMPLETED   = 3;

  @Column(name = "id")
  @Generated(GenerationTime.INSERT)
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Id
  private Long id;

  @Column(name = "status", nullable = false)
  private int status = IN_PROGRESS;
}

BoardRepository(JPA 存储库)

public interface BoardRepository extends JpaRepository<Board, Long> {}

CommonBoardService(基础服务)

public interface CommonBoardService {
  Board save(Board board);
  Board update(Board board, int status);
}

CommonBoardServiceImpl(基础服务实现)

@Service
@Transactional
public class CommonBoardServiceImpl implements CommonBoardService {
  @Autowired
  private BoardRepository boardRepository;

  public Board save(final Board board) {
    return boardRepository.save(board);
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public Board update(final Board board, final int status) {
    board.setStatus(status);

    return save(board);
  }
}

BoardService(具体服务接口)

public interface BoardService {
  Board startBoard();
  void synchronizeBoardState(Board board);
}

BoardServiceImpl(具体服务实现)

@Service
@Transactional
public class BoardServiceImpl implements BoardService {
  @Autowired
  private CommonBoardService commonBoardService;

  public Board startBoard() { return new Board(); }

  public void synchronizeBoardState(final Board board) {
    if (board != null && inProgress(board)) {
      if (!canPlayWithCurrentBoard(board)) {
        commonBoardService.update(board, Board.AFK);
      }
      else {
        commonBoardService.update(board, Board.COMPLETED);
      }
    }
  }

  private boolean canPlayWithCurrentBoard(final Board board) {
    return !inProgress(board);
  }

  private boolean inProgress(final Board board) {
    return board != null && board.getStatus() == Board.IN_PROGRESS;
  }
}

BoardServiceTest(单元测试)

1.  @RunWith(SpringJUnit4ClassRunner.class)
2.  @Transactional
3.  public class BoardServiceTest {
4.    @Autowired
5.    private BoardRepository boardRepository;
6.
7.    @Autowired
8.    private BoardService       boardService;
9.    @Autowired
10.   private CommonBoardService commonBoardService;
11.
12.   @Test
13.   public void testSynchronizeBoardStatus() {
14.     Board board = boardService.startBoard();
15.
16.     board = commonBoardService.save(board);
17.
18.     assertEquals(1, boardRepository.count());
19.
20.     boardService.synchronizeBoardState(board);
21.
22.     assertEquals(1, boardRepository.count());
23.   }
24. }

此测试在第 22 行失败,错误为 java.lang.AssertionError: Expected :1 Actual:2。 Hibernate SQL 日志显示在第 20 行触发了 INSERT,而不是 UPDATE。由于我在整个测试过程中使用相同的 Board 对象,因此我希望第 20 行会触发 UPDATE 而不是 INSERT

谁能解释为什么会发生这种情况以及如何获得预期的行为(第 20 行的UPDATE)?

【问题讨论】:

  • 您的 TestDataSourceConfiguration.class 指向哪个数据库?它是否有您要更新的董事会实体?
  • BoardServiceTest 中的boardServicecommonBoardService 是什么? boardService.startBoard(admin)commonBoardService.save(board) 看起来像什么?大概,第一个是创建一个新的board,第二个是保存它?另外,BoardUser 类的代码是什么样的?如果您可以创建一个演示问题的小示例可能会更好,因为要涵盖的代码非常多。
  • @koder23,感谢您的回复。我正在使用postgres。是的,它应该有 Board board = boardService.startBoard(admin); - 创建新的板,实体的 ID 等于 1。
  • 你能贴出完整的代码来测试一下吗?仍有许多部分缺失,难以重现问题。可以在 Github 上发布示例应用吗?
  • @manish,你是对的,boardService.startBoard(admin) - 为用户创建新版块。如您所见,boardService.synchronizeBoardState(board, admin) 有不同的检查。所以,我想在我们进入if (!canPlayWithCurrentBoard(board)) 代码块时触发这个案例。为此,我需要更新board 实体以满足这些条件。这就是我在测试中使用commonBoardService.save(board) 的原因。

标签: spring-data-jpa jpa-2.1


【解决方案1】:

罪魁祸首是这一行:@Transactional(propagation = Propagation.REQUIRES_NEW)。让我们看看执行测试用例时会发生什么。

  • 因为BoardServiceTest@Transactional 注释,所以当BoardServiceTest.testSynchronizeBoardStatus 开始执行时会启动一个新事务。
  • 第 14 行创建一个新的 Board 实例。
  • 第 16 行尝试保存在第 14 行创建的Board 实例并触发数据库INSERT
  • 第 20 行间接调用 CommonBoardServiceImpl.update,并用 @Transactional(propagation = Propagation.REQUIRES_NEW) 注释。这会暂停正在进行的事务(请参阅JavaDocs for Propagation),该事务目前既未提交也未回滚。
  • CommonBoardServiceImpl.update 反过来尝试保存传递给它的 Board 实例。
  • 给定实例未被识别为现有实例,因为将其保存到数据库的事务当前处于挂起状态。因此,它被假定为一个新实例并产生第二个INSERT
  • 第 20 行现在完成,提交为 CommonBoardServiceImpl.update 启动的内部事务。外部事务恢复。
  • 第 22 行找到脏会话并在触发 SELECT 查询之前刷新它。这意味着数据库中现在有两个实例,因此测试失败。

删除@Transactional(propagation = Propagation.REQUIRES_NEW) 确保整个测试在同一个事务中执行并因此通过。

【讨论】:

    猜你喜欢
    • 2020-05-20
    • 2019-05-15
    • 1970-01-01
    • 1970-01-01
    • 2018-01-13
    • 2022-08-21
    • 1970-01-01
    • 2019-02-09
    • 1970-01-01
    相关资源
    最近更新 更多