微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Spring / Hibernate数据访问并发问题

如何解决Spring / Hibernate数据访问并发问题

我已经承担了很多我自己没有写的大型项目的全部责任,我希望在这里寻求一些建议,因为这些项目存在使我无法入睡的问题,而其中很多是新的。我。

我注意到,通过ORM进行的数据库查询有时将找不到已经请求保存的实体,从而导致无效状态并崩溃。通过并发线程通过ORM访问数据库的问题变得更加明显。

在故障排除方面获得的所有帮助将不胜感激。

相关依赖项

  • Web框架:Spring MVC
  • ORM:休眠
  • DBMS:MysqL 5.6
  • ehcache:2.10.6
  • 休眠:5.1.17.Final
  • 休眠验证器:5.4.3。最终
  • MysqL连接器:5.1.48
  • 春季:4.3.29。发布
  • 春季集成:4.3.23.RELEASE
  • 春季安全性:4.2.19。释放

该Web应用程序在具有Java 8运行时的Tomcat 7下运行。

我们在几个依赖项上落后了,我想改天解决

问题排查

我读到EntityManager本身不是线程安全的,并且@PersistenceContext将其变成一个代理,该代理允许线程安全访问基础的EntityManager。我还可以看到它是调试器中的某种代理。现在我不知道它是如何工作的,以及我们使用它的方式是否真的安全。

尽管如此,我注意到以下内容

  1. 传入的HTTP请求导致实体通过ORM保存到数据库中。
  2. 此后不久,另一个传入的HTTP请求希望通过ORM查找相同的实体。
  3. 大多数情况下,实体是按预期的方式找到的,但有时即使已将其保存也没有找到。
  4. 如果我尝试在保存后立即在同一线程上找到该实体,则始终会找到它,但是如果我在保存后立即启动新线程,则除非在我{{1} }。

我在调试器中看到,下面的示例代码中的Thread.sleep()SomeServiceSomeDaoImpl代理的相同实例被重用于多个传入HTTP请求并由不同线程处理

即使我们自己的类是无状态的,通过ORM进行数据访问也显然存在问题。

大大简化的伪代码

EntityManager

我相信这是持久性配置:

interface Dao {
    Entity save(entity);
};

abstract class AbstractDao implements Dao {
    @PersistenceContext
    private EntityManager em; // proxy

    @Override
    public Entity save(entity) {
        return em.persist(entity); // or this.em.merge(entity)
    }

    protected EntityManager getEntityManager() {
        return em;
    }
}

abstract class AbstractUuidDao extends AbstractDao {
    public Entity findByUuid(uuid) {
        em = getEntityManager();
        query = em.getCriteriaBuilder().createquery(...);
        // ...
        return em.createquery(query).setParameter(...,uuid).getSingleResult();
    }
}

interface SomeDao extends Dao {};

@Repository
class SomeDaoImpl extends AbstractUuidDao implements SomeDao {}

@Service("someService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
class SomeService {
    @Autowired
    private SomeDao someDao;

    // the first HTTP request calls this
    @Transactional(propagation = Propagation.required,rollbackFor = Exception.class)
    void createSomething() {
        uuid = "random uuid";
        someDao.save(new SomeEntity(uuid));
        someDao.findByUuid(uuid); // always returns non-null
    }

    // after createSomething() returns,the transaction is committed

    // two HTTP requests can call this simultaneously but they come in after createSomething() has returned,and therefore after the transaction has already been committed
    void getSomething(uuid) {
        // uuid is the same "random uuid"
        someDao.findByUuid(uuid); // can return either null or non-null
    }
}

对于缺少真实的代码和细节感到抱歉,但是如果不使示例变得比所需的复杂,我很难提供一个可行的示例。

任何想法都会受到赞赏。

更新1

修改了原始文章中的示例代码,以更正确地反映实际用例,但变化不大。

我可以看到所有事情都按预期的顺序发生,即事务已提交,随后的查询仅在此之后发生。

我用<bean id="jpavendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpavendorAdapter"> <property name="databasePlatform" value="org.hibernate.dialect.MysqL5Dialect" /> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerfactorybean"> <property name="dataSource" ref="dataSource" /> <property name="persistenceUnitName" value="model" /> <property name="jpaDialect"> <bean class="custom class here" /> </property> <property name="jpavendorAdapter" ref="jpavendorAdapter" /> <property name="jpaPropertyMap"> <map> <entry key="hibernate.connection.release_mode" value="after_transaction" /> <entry key="hibernate.current_session_context_class" value="org.springframework.orm.hibernate4.SpringSessionContext" /> <!-- also tried "org.springframework.orm.hibernate5.SpringSessionContext" --> <entry key="hibernate.cache.use_query_cache" value="true" /> <!-- also tried "false" --> <entry key="hibernate.cache.use_second_level_cache" value="true" /> <!-- also tried "false" --> <entry key="hibernate.cache.use_minimal_puts" value="false" /> <entry key="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory" /> <entry key="hibernate.default_batch_fetch_size" value="8" /> <entry key="hibernate.jdbc.fetch_size" value="10" /> <entry key="hibernate.jdbc.batch_size" value="100" /> <entry key="hibernate.order_inserts" value="true" /> <entry key="hibernate.order_updates" value="true" /> <entry key="hibernate.format_sql" value="false" /> <entry key="hibernate.show_sql" value="false" /> </map> </property> </bean> 替换了自定义JPA方言,因为似乎不再需要自定义类,因为它旨在增加自定义隔离级别的支持。那没有帮助。

我已将Hibernate从5.1.17版本升级到5.2.18版本,并重新实现了一个不兼容的第三方Hibernate用户类型,从而阻止了升级

自从升级以来,我还没有看到任何失败,但是我会继续测试。

更新2

即使在Hibernate 5.2.0版中,该问题似乎也不会持续。

更改日志中提到的以下错误修正与我的问题非常相关:

[HHH-10649]-启用2LC时,刷新会话,然后刷新实体会导致另一个会话/事务中的脏读

如果“ 2LC”表示“二级缓存”,那么即使关闭功能,我也有前面提到的问题。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。