Spring MVC结合JPA的@ModelAttribute方法呈异步行为 如何实现同步执行
核心根因定位
你遇到的不同步问题完全不是代码异步执行导致的,就算所有代码按顺序同步执行,也会出现首次访问只展示创世块的现象,核心矛盾来自两个设计问题:
- 返回给页面渲染的区块链内存实例,和后续写入区块时操作的实体是完全独立的两个引用
触发NoResultException走新建分支时,blockchainApp.newBlockchainService("beancoin")返回的是仅包含创世块的内存快照,这个快照会被直接作为@ModelAttribute的值返回给页面。后续不管是调用Initializer.loadBC还是控制器内循环调addBlockService,底层的JPA逻辑都是每次新建数据库连接、从数据库重新查询对应名称的Blockchain实体,给实体追加新块后写回数据库,整个过程没有任何逻辑会修改你已经返回给页面的那个旧快照的块列表属性。
这也是为什么你加15秒等待也无效:等待期间数据库已经把全量区块写入完成,但return出去的旧内存对象从来没被更新过,页面拿的永远是只有创世块的初始快照,不存在所谓的异步延迟问题。 - JPA一级缓存的隔离特性放大了不同步表现
最初拿到的Blockchain实体,在所属的持久化上下文没有被主动刷新、驱逐的情况下,哪怕数据库的实体数据已经发生变更,内存里的字段值也不会自动和数据库对齐。重启服务后所有持久化上下文重建,getBlockchainService会从数据库全量查询所有区块重新组装Blockchain实例,这时候拿到的对象就是包含全部5个新区块的状态,页面展示自然恢复正常,这也是为什么必须重启才能看到全量数据的原因。
同步逻辑修正方案
不需要额外加线程等待、分布式锁或者并发同步逻辑,只要调整返回的实例引用逻辑即可彻底解决问题:
- 所有初始区块写入完成后,重新从数据库加载全量区块链实例再返回。不要直接把
newBlockchainService返回的初始快照直接return,等所有初始块写入完成后,重新调用blockchainApp.getBlockchainService("beancoin")拿到和数据库状态完全一致的实例,再把这个实例返回给页面。
修正后的catch块核心参考逻辑:System.err.println("正在创建新的beancoin实例"); blockchainApp.newBlockchainService("beancoin"); // 按原有逻辑写完所有初始区块 Initializer.loadBC("beancoin"); // 关键:重新从数据库加载全量链实例,替换掉只有创世块的初始快照 Blockchain blockchain = blockchainApp.getBlockchainService("beancoin"); pnapp = new PubNubApp(blockchain); return blockchain; - 统一实体操作的持久化上下文。把所有初始区块写入的逻辑放到同一个JPA连接里完成,写完直接返回该上下文中持有完整块列表的Blockchain对象,不要跨数据库连接传递实体引用。
- 避免跨实例操作实体。不要在
Initializer.loadBC里new一个全新的BlockchainService实例做写入,直接复用容器注入的单例BlockchainService完成所有操作,确保内存中的实体引用是同一个。
内容的提问来源于stack exchange,提问作者gcr
相关产品推荐
相关产品推荐

