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