Loading...

问题场景

在@Transactional类修饰的同一方法下,对同一张表进行操作。

场景一:

hibernate.query()拿到list数据 // 1 hibernate.merge()合并修改后的list // 2 method2:hibernate.query() // 在方法1里面调用方法2,拿到的是状态1的数据

场景二:

hibernate.query()拿到list数据 //1 jdbcTemplate.removeById()根据id删除数据 // 2,实际调用的是org.springframework.jdbc.core.update() method2:hibernate.query() // 在方法1里面调用方法2,拿到的是状态2的数据

问题原因

通过hibernate进行数据库连接时,autocommit默认是 false,因此须显式调用 commit()方法(或者 flush()方法)。
而如果使用jdbcTemplate进行数据库连接的话,无需显式执行 commit()方法,因为此时 autocommit默认为 true

Hibernate的flush()

flush()方法的主要作用就是清理缓存,强制数据库与Hibernate缓存同步,以保证数据的一致性。
执行时会清除session缓存并向数据库发送SQL语句并执行,但此时如果数据库当前存在一个事务,数据库会先将这些SQL语句缓存起来,那么此时在数据库中是无法看到SQL语句执行结果的。除非执行commit提交了事务。只要没有执行commit()方法,就能通过rollback()方法进行回滚。

Hibernate的commit()

执行时会先隐式调用flush()方法,再提交事务。执行之后无法rollback()进行回滚。即commit()操作才是真正的将实体数据持久化至数据库。

修改方案

hibernate.query()拿到list数据 // 1 hibernate.merge()合并修改后的list // 2 hibernate.flush()强制同步 method2:hibernate.query() // 在方法1里面调用方法2,拿到的是状态2的数据

这里替换成commit()也是可以的,因为它会先隐式调用flush()方法,再提交事务。
需要注意的是,flush()后虽然 method2:hibernate.query()能拿到状态2的数据,但如果此时打断点会发现数据库中还没有更新(只运行了SQL但未提交事务),需要等整个方法结束后(事务提交),才会更新。

暗坑

老代码中用的不是MP,而是自己封装的xxxDao。可以看到一个Dao下将Hibernate和JDBCTemplate混用了,所以经常会出现事务相关的问题,用的时候需要谨慎。

public class BaseDaoImpl<PK extends Serializable, T> implements BaseDao<PK, T> { protected Logger logger = LoggerFactory.getLogger(this.getClass()); @PersistenceContext/*(unitName = "secondary")*/ private EntityManager em; // JPA的CRUD接口,由Hibernate实现 @Autowired private JdbcTemplate jt; protected Class<T> clazz; private String tn; @SuppressWarnings("unchecked") public BaseDaoImpl() { // 获取泛型T的类型 Type t = this.getClass().getGenericSuperclass(); if (t != null && t instanceof ParameterizedType) { Type[] args = ((ParameterizedType) t).getActualTypeArguments(); if (args != null && args.length > 1) { clazz = (Class<T>) args[1]; tn = DbUtils.tableName(clazz); } } } ... }
Last modification:August 22, 2022
喵ฅฅ