如何解决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
。我还可以看到它是调试器中的某种代理。现在我不知道它是如何工作的,以及我们使用它的方式是否真的安全。
尽管如此,我注意到以下内容:
- 传入的HTTP请求导致实体通过ORM保存到数据库中。
- 此后不久,另一个传入的HTTP请求希望通过ORM查找相同的实体。
- 大多数情况下,实体是按预期的方式找到的,但有时即使已将其保存也没有找到。
- 如果我尝试在保存后立即在同一线程上找到该实体,则始终会找到它,但是如果我在保存后立即启动新线程,则除非在我{{1} }。
我在调试器中看到,下面的示例代码中的Thread.sleep()
,SomeService
和SomeDaoImpl
代理的相同实例被重用于多个传入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 举报,一经查实,本站将立刻删除。