前言
很早就想整理自己的踩坑記錄發上來,每次把自己踩過的坑發給自己的小號,想着有一天能整理一下。畢竟這些經驗自己也是一步一個坑踏過來的。
第一個坑:關於MyBatis參數類型爲String的問題
-
問題描述
當時使用MyBatis框架寫了一個查詢數據庫的功能,入參是用戶名 username(string)。
public User queryUserByUsername(String username);
<select id="queryUserByUsername" parameterType="String"resultType="com.coorperation.entity.User"> SELECT user_id,user_name,password,user_email,user_phone_number,real_name,profile_img,user_type,user_status,salt FROM tb_user <where> <if test="username!=null and username!=''"> user_name = #{username} </if> </where> </select>
然後拋了這個異常:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'username' in 'class java.lang.String'
其實翻譯一下也知道,它的意思是說String中沒有username這個屬性,但是MyBatis的確是用#{}來獲取入參的,這種方法要怎麼解決呢。
-
解決方案一
因爲MyBatis要求如果爲參數爲String的話,不管接口方法的形參是什麼,在Mapper.xml中引用需要改變爲_parameter才能使識別。
<select id="queryUserByUsername" parameterType="String" resultType="com.coorperation.entity.User"> SELECT user_id,user_name,password,user_email,user_phone_number,real_name,profile_img,user_type,user_status,salt FROM tb_user <where> <if test="username!=null and username!=''"> user_name = #{_username}<!--解決方法--> </if> </where> </select>
-
解決方案二
在接口參數中加@param
public void queryUserByUsername(@Param("username")String username);
然後在xml中正常使用#{username}即可正常運行。
第二個坑:JQuery中爲動態生成的按鈕綁定點擊事件
-
問題描述
在JQuery中爲一個動態渲染生成的按鈕綁定監聽時間,如果直接用 button.click(function{//邏輯});是沒有辦法綁定成功的。
-
解決方案
在JQuery中如果需要動態渲染按鈕,然後給這個按鈕直接綁定click事件是無法生效的,必須使用父容器來爲這個按鈕委託指派點擊事件。假設按鈕的id爲button,按鈕父容器的id爲parent,代碼如下:
button.click(function(){ //邏輯 }); button.bind("click",function(){ //邏輯 }); /*用以上兩種方法綁定點擊事件是無效的*/ /*必須得使用父容器委託綁定*/ $(parent).on('click','#button',function(){ //邏輯 })
第三個坑:使用getResourceAsStream獲取配置文件
-
問題描述
使用getResourceAsStream獲取配置文件,默認從項目目錄開始,如果要是傳如同c:\xxx\xxx這樣的絕對路徑,是沒有辦法讀到的。
-
解決方案
我們通常會使用getClassAsStream獲取properties配置文件。代碼如下:
public class test{ public static void main(String[] args){ Properties pro = new Properties(); //這裏只能傳相對路徑,而不能傳絕對路徑 InputStream in = test.class.getClassLoader().getResourceAsStream("config/xxx.properties"); } }
如果一定要用絕對路徑,要用FileInputStream來讀。
第四個坑:Shiro自定義攔截器無限重定向(集成SpringBoot)
-
問題描述
這是一個Shiro框架集成Spring Boot產生的問題,我們在使用Shiro框架時,通常會自己實現一個攔截器,來基於url控制權限的訪問,那麼假設我在配置文件中配置了
filterChainDefinitionMap.put("/login", "anon");
,這段配置就表示了我訪問login頁面是不需要權限的,然後我再自己實現一個過濾器,過濾器的內部邏輯爲,如果檢測到這個用戶沒有登錄,那麼跳轉到登陸界面,這個攔截器的名字就叫url,和anon區分開,然後就會發生一個神奇的現象:當我訪問/login的時候,出現無限重定向。我們希望的結果是,/login走anon攔截器,但是實際結果爲,/login走了我們自定義的url過濾器,而過濾器內部實現是如果用戶沒有登錄,那麼跳轉到/login進行登錄,這就造成了跳轉到/login,檢測到沒登錄,又跳轉到/login一直循環往復下去。 -
解決方案一
經過排查得知,這個過濾器本來是應該交給Shiro進行管理,但是Spring Boot會默認託管過濾器。
看看官方定義:
- SpringBoot文檔:任何Servlet或Filter bean都將自動註冊到servlet容器中。
- 要禁用特定Filter或Servlet bean的註冊,請爲其創建註冊bean並將其標記爲禁用。
由於這個定義,我們在訪問/login這個頁面的時候,首先會訪問Shiro的anon過濾器,然後程序並不會在這裏停下來,會繼續訪問我們Spring Boot中管理的我們的自定義過濾器url,於是就會造成循環重定向的問題。
解決方案就是,關閉SpringBoot註冊該過濾器
public FilterRegistrationBean registration(MyFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; }
-
解決方案二
配置ShiroFilter的自定義過濾器時直接new而不使用 @Bean方式配置。
/*開啓註釋會產生循環重定向問題 * 癥結大概存在於SpringBoot和Shiro會爲該攔截器都加載到自己的容器中 * 導致有些頁面先走anno攔截器再走該自定義攔截器 * 而該攔截器內部邏輯是未登錄自動跳轉到登陸界面 * 於是每次在訪問login頁面時都會產生循環重定向問題 * * 解決方案:配置時直接new而不使用 @Bean方式配置。*/ //@Bean(name="urlPathMatchingFilter") public URLPathMatchingFilter URLPathMatchingFilter() { URLPathMatchingFilter urlPathMatchingFilter = new URLPathMatchingFilter(); return urlPathMatchingFilter; }
ShiroFilter:
filters.put("url", URLPathMatchingFilter());
在配置過濾器時,原先是採用@Bean的方式自動注入到ShiroFilter中,但是現在我們直接通過new的方式手動注入,避開Spring的依賴注入,同樣可以達到正確的效果。
第五個坑:Spring Boot Bean加載順序導致依賴注入爲null(二級緩存)
-
問題描述
在使用Redis做MyBatis二級緩存時,想把緩存交給Spring託管,然後自動注入RedisUtil來完成Redis的操作,但是我發現在RedisUtil正常的狀況下,發現怎麼注入都是null。
經過日誌排查,發現cache總是在RedisUtil生成bean之前就已經被生成了。(加@DependOn也無效,很迷,如果有知道爲什麼的大佬希望可以告訴我)
-
解決方案
自己寫一個SpringUtil工具類,當Cache使用到RedisUtil時,使用getBean的方式獲取RedisUtil對象,相當於配置了一個懶加載。
@Component @Lazy(false) public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; //獲取applicationContext public static ApplicationContext getApplicationContext() { return applicationContext; } //通過name獲取 Bean. public static Object getBean(String name) { return getApplicationContext().getBean(name); } //通過class獲取Bean. public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } //通過name,以及Clazz返回指定的Bean public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; } } private SpringUtil() { } }
Cache中使用:
private synchronized RedisUtil getRedisUtil() { if(redisUtil==null) { redisUtil = SpringUtil.getBean(RedisUtil.class); } return redisUtil; }
public void putObject(Object key, Object value) { RedisUtil redisUtil = getRedisUtil(); redisUtil.set(serializeUtil.serialize(key), serializeUtil.serialize(value)); }
第六個坑:Ajax異步導致頁面顯示異常
-
問題描述
使用Ajax獲取後端數據渲染頁面時,發現一個異常狀況,當頁面打開的時候一切顯示正常,但過了一秒之後頁面的所有數據都消失了。
頁面的顯示條件是通過日期查詢數據庫中的數據,當時間爲null時,默認取數據庫中保存時間最晚的數據。
所以經過排查發現,JQuery中存在兩個取數據的ajax,第一個ajax會獲取後端回傳的時間數據,並再發送請求獲取數據,在頁面初始化時被顯式調用,但是由於ajax是異步的,所以頁面上的所有ajax都會一起去向服務器發起請求,所以這種情況下就是第二個ajax首先向服務器發送了獲取數據的請求,但是此時第一個ajax還沒有正常返回時間,導致第二個請求的時間是null,所以服務器返回給他一個數據庫中最新的數據,但是第一個請求此時獲取到了時間,發送回服務器,服務器中並沒有那個時間的數據,所以返回空表,導致前臺數據一閃而過。
-
解決方案
將第一個ajax的async屬性設置爲false,讓他同步執行,這樣由於他是顯式調用的,所以一定是先執行的,而且在它執行完畢之前,在它後面的ajax無法執行。
第七個坑:MyBatis關於大於號小於號無法識別的問題
-
問題描述
在MyBatis中如果在查詢條件裏寫了xxxx>xxxx或者xxxx<xxxx諸如此類的消息,需要對其進行特殊處理。
-
解決方案
使用
<![CDATA[ sql語句 ]]>中的<![CDATA[ ]]>
在mybatis中,保證sql語句不被改變。
結語
很早就想整理這個,一直在寫技術文章導致這個拖了很長時間,有些異常可能沒什麼印象了,描述的也不是很清楚,就不往上寫了。寫上的這些是我自己比較有印象的一些坑,希望可以幫助到大家,也避免自己再次踩坑。