Loading... ## 问题场景 在@Transactional类修饰的同一方法下,对同一张表进行操作。 场景一: ```method1 hibernate.query()拿到list数据 // 1 hibernate.merge()合并修改后的list // 2 method2:hibernate.query() // 在方法1里面调用方法2,拿到的是状态1的数据 ``` 场景二: ```method1 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()操作才是真正的将实体数据持久化至数据库。 ## 修改方案 ```method1 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混用了,所以经常会出现事务相关的问题,用的时候需要谨慎。 ```java 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 © Allow specification reprint Like 0 喵ฅฅ