網(wǎng)站做鏈輪會(huì)被懲罰嗎公司網(wǎng)站推廣方法
記Mybaits緩存踩的坑
1.問題提出
最近開發(fā)一個(gè)記錄操作前后修改內(nèi)容的功能,獲取修改前數(shù)據(jù)比較簡(jiǎn)單,直接從數(shù)據(jù)庫獲取,記錄修改后的功能也比較簡(jiǎn)單,直接將用戶修改的內(nèi)容封裝成po對(duì)象,然后兩個(gè)比對(duì)就可以了,問題就出在這。
2.場(chǎng)景復(fù)現(xiàn)
下面是出現(xiàn)問題的代碼,簡(jiǎn)化版(操作分為用戶端和管理端):
用戶端
public class UserServiceImpl{@Autowiredprivate IUserDao userDao;@Autowiredprivate IUserLogDao dao;@Autowiredprivate StreamAction action;@Override@Transactionalpublic void modifyUser(UserDto dto){Long id = dto.getUserId;//修改前的beanUserBean beforeBean = userDao.queryUser(id);//修改后的beanBeanUtils.copyProperties(dto,beforeBean);//更新和用戶有關(guān)的內(nèi)容//....用戶信息填充 UserLogBean userLogBean//這里需要修改和用戶關(guān)聯(lián)的記錄,所以插入一次userDao.insert(userLogBean);//用戶端操作流action.request(beforeBean);}
}
- StreamAction.request
@Override
@Transactional
public void request(UserBean userBean){/*** 獲取數(shù)據(jù)庫原內(nèi)容*/UserBean afterBean = userDao.queryUser(id);//比對(duì)返回修改內(nèi)容ModifyBean bean = modify(userBean,afterBean);//...其他 業(yè)務(wù)//插入修改后的內(nèi)容userBeanModifyDao.insert(bean);
}
此時(shí)去查看數(shù)據(jù)內(nèi)添加的內(nèi)容,可以看到,修改的部分被正確的比對(duì)出來了。
管理端
public class UserServiceImpl{@Autowiredprivate IUserDao userDao;@Autowiredprivate IUserLogDao dao;@Autowiredprivate StreamActionManager action;@Override@Transactionalpublic void modifyUser(UserDto dto){Long id = dto.getUserId;//修改前的beanUserBean beforeBean = userDao.queryUser(id);//修改后的beanBeanUtils.copyProperties(dto,beforeBean);//更新和用戶有關(guān)的內(nèi)容//....用戶信息填充 UserLogBean userLogBean//管理端操作流action.request(beforeBean);}
}
- StreamActionManager.request
@Override
@Transactional
public void request(UserBean userBean){/*** 獲取數(shù)據(jù)庫原內(nèi)容*/UserBean afterBean = userDao.queryUser(id);//比對(duì)返回修改內(nèi)容ModifyBean bean = modify(userBean,afterBean);//...其他 業(yè)務(wù)//插入修改后的內(nèi)容userBeanModifyDao.insert(bean);
}
此時(shí)我們?nèi)?shù)據(jù)庫查看內(nèi)容,你就驚奇發(fā)現(xiàn)并沒有見到修改的內(nèi)容。不對(duì)啊,我們確實(shí)已經(jīng)修改過了,那為什么數(shù)據(jù)庫里不顯示呢?
3.發(fā)現(xiàn)問題
帶著疑問,我們很容易的想到,是不是兩個(gè)對(duì)象內(nèi)容一模一樣?帶著疑問,我們嘗試著打印一下用戶端和管理端,在進(jìn)行比對(duì)前的兩個(gè)對(duì)象內(nèi)容:
public void request(UserBean userBean){/*** 獲取數(shù)據(jù)庫原內(nèi)容*/UserBean afterBean = userDao.queryUser(id);System.out.println(userBean); System.out.println(afterBean); //比對(duì)返回修改內(nèi)容ModifyBean bean = modify(userBean,afterBean);//......
}
用戶端
我們?cè)诒葘?duì)修改內(nèi)容前打印兩個(gè)bean,結(jié)果如下:
cn.example.core.core.bean.UserBean@5e5a2b74
cn.example.core.core.bean.UserBean@5e5a2b75
兩個(gè)bean不是同一個(gè)對(duì)象,符合我們的預(yù)期,所以用戶端正確入庫。
我們?cè)賮砜垂芾矶?/p>
管理端
管理端執(zhí)行結(jié)果
cn.example.core.core.bean.UserBean@5e5a2b77
cn.example.core.core.bean.UserBean@5e5a2b77
!!!oi!!!,我們驚奇的發(fā)現(xiàn),這兩個(gè)對(duì)象是一樣的,那就奇了怪了,用戶端和管理端業(yè)務(wù)代碼甚至基本都一樣的,為什么會(huì)造成這個(gè)原因呢?我們?cè)佥敵鲞@兩個(gè)對(duì)象的內(nèi)容
System.out.println(userBean.toString());
System.out.println(afterBean.toString());
很驚奇的是,這兩個(gè)對(duì)象內(nèi)容都是修改過的內(nèi)容,也就是service內(nèi)通過BeanUtil
s屬性賦值過的內(nèi)容,那我們mysql里的內(nèi)容去哪了??我們明明還沒更新啊!我們趕緊去數(shù)據(jù)庫看一眼,發(fā)現(xiàn)數(shù)據(jù)庫里并沒更新。數(shù)據(jù)庫里內(nèi)容沒更新,業(yè)務(wù)里的bean已經(jīng)被更新過了,這是為什么?
4.排查問題
我們找到出現(xiàn)問題的原因了,是因?yàn)楣芾矶藘蓚€(gè)對(duì)象一樣。那為什么會(huì)一樣呢?
我們?cè)跇I(yè)務(wù)邏輯里很容易想到類似的場(chǎng)景,比如我們?cè)谑褂胷edis的時(shí)候,當(dāng)redis內(nèi)有數(shù)據(jù)時(shí),我們希望走redis返回結(jié)果而不是走數(shù)據(jù)庫,以提高查詢性能,那會(huì)不會(huì)兩個(gè)對(duì)象一樣也是走了緩存呢?我們通過查詢數(shù)據(jù)庫我們知道,mysql的查詢也會(huì)存在緩存,但是按道理來說,我們最后的結(jié)果應(yīng)該是mysql的內(nèi)容,應(yīng)該不會(huì)是后面的內(nèi)容。所以只有一種情況,是mybaits的緩存。
5.尋找答案
我們已經(jīng)確定了是mybaits的緩存導(dǎo)致的問題,但是為什么管理端和用戶端還不一樣呢?為什么用戶端就沒有這個(gè)問題呢?
我們百度之后發(fā)現(xiàn),mybaits有一、二級(jí)緩存之分,二級(jí)緩存默認(rèn)不開啟。
哎會(huì)不會(huì)是用戶端的緩存過期了?因?yàn)橛脩舳说挠幸粋€(gè)插入其他表的操作,肯定比管理端慢,對(duì)對(duì)一定是這個(gè)問題,好我們?nèi)フ叶饶?#xff0c;度娘說緩存沒有過期時(shí)間。好好好這樣玩,好好好。
那么問題是什么?我們已經(jīng)確定不是過期時(shí)間的問題了,那我們現(xiàn)在想的就是緩存過期,也就是緩存失效了,我們換個(gè)方法去查找內(nèi)容,“mybatis緩存失效的原因”,我們找到以下結(jié)果:
1.不在同一個(gè)sqlSession中
2.如果是增刪改操作,程序會(huì)clear緩存。
3.一級(jí)緩存未開啟
4.手動(dòng)清空緩存數(shù)據(jù),調(diào)用sqlsession.clearCache().
5.更改查詢條件
我們一點(diǎn)點(diǎn)往下看:
- 不在同一個(gè)sqlSession中
同一個(gè)事務(wù)內(nèi)會(huì)復(fù)用同一個(gè)sqlSession。
具體我們查看控制臺(tái),可以看到第一個(gè)sql執(zhí)行之前,會(huì)有一句話
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3663af34]
在之后的sql執(zhí)行時(shí)會(huì)有一句話
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3663af34] from current transaction
都是同一個(gè)sqlSession
我們業(yè)務(wù)層面的代碼是在同一個(gè)事務(wù)里,因?yàn)闆]有設(shè)置事物傳播機(jī)制,雖然有兩個(gè)事物注解,但是最后都在同一個(gè)事務(wù)那,可以用
String txName = TransactionSynchronizationManager.getCurrentTransactionName();
查看,會(huì)發(fā)現(xiàn)事務(wù)沒有失效且都在一個(gè)事物內(nèi),顯然不是這個(gè)原因。
- 如果是增刪改操作,程序會(huì)clear緩存。
我們用戶端涉及的增刪改是另外一張表的,排除
- 一級(jí)緩存未開啟
顯然開啟了,不然不會(huì)有這篇博客
4、5不用看了,都不符合
我們分析完了,仍然沒找到原因。那么問題到底在哪呢?
目前最有可能的就是2,但是我們明明更改的是其他表啊,那么不妨,我們就試試改其他表。
@Test
@Transactional //這里的事務(wù)一定要加,mybatis的緩存存在條件就是需要有事務(wù),否則你查詢會(huì)發(fā)現(xiàn)兩個(gè)對(duì)象怎么樣都不會(huì)相同
public void test(){UserBean beforeBean = userDao.queryUser(id);//更新userLogDao.update("1","1");UserBean afterBean = userDao.queryUser(id);System.out.println(beforeBean); System.out.println(afterBean); }
哎,神奇,兩個(gè)對(duì)象不一樣了,緩存失效了!所以問題就在這,所以這就是造成這個(gè)bug的原因,知道了問題,那我們就開始做解決方案
6.解決方案
解決方案有一下幾種:
- 關(guān)閉mybatis一級(jí)緩存,無法關(guān)閉,只能修改狀態(tài)
mybatis:configuration:cache-enabled: false #禁用二級(jí)緩存local-cache-scope: statement #一級(jí)緩存指定為statement級(jí)別 默認(rèn)為session級(jí)別
- 使用不同的對(duì)象傳遞
在傳遞前后bean的時(shí)候,用其他的bean賦值傳遞。
- 使用不同的查詢方式,拼接條件等
- 使用不同的事物隔離級(jí)別,sqlSession是依賴于mysql的事物,所以如果數(shù)據(jù)庫不支持事物那么Spring的事物 也不會(huì)生效。我們可以使用不同的事物隔離級(jí)別,以創(chuàng)建不同的sqlSessio,此時(shí)就不存在bean不同的問題:
@Override@Transactionalpublic void modifyUser(UserDto dto){Long id = dto.getUserId;//修改前的beanUserBean beforeBean = userDao.queryUser(id);//修改后的beanBeanUtils.copyProperties(dto,beforeBean);//更新和用戶有關(guān)的內(nèi)容//....用戶信息填充 UserLogBean userLogBean//管理端操作流action.request(beforeBean);}//另外一個(gè)類的request方法@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void request(UserBean userBean){/*** 獲取數(shù)據(jù)庫原內(nèi)容*/UserBean afterBean = userDao.queryUser(id);//比對(duì)返回修改內(nèi)容ModifyBean bean = modify(userBean,afterBean);//...其他 業(yè)務(wù)//插入修改后的內(nèi)容userBeanModifyDao.insert(bean);}
使用不同的事物傳播機(jī)制,我們可以看到控制臺(tái)創(chuàng)建了兩個(gè)sqlSession:
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7488c183]//....
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4797023d]//再次比較兩個(gè)bean
cn.example.core.core.bean.UserBean@4b86a656
cn.example.core.core.bean.UserBean@4c3c31a5
到此為止,我們的問題就解決了。