9月-10月踩坑記錄(2019)

前言

很早就想整理自己的踩坑記錄發上來,每次把自己踩過的坑發給自己的小號,想着有一天能整理一下。畢竟這些經驗自己也是一步一個坑踏過來的。

第一個坑:關於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語句不被改變。

結語

很早就想整理這個,一直在寫技術文章導致這個拖了很長時間,有些異常可能沒什麼印象了,描述的也不是很清楚,就不往上寫了。寫上的這些是我自己比較有印象的一些坑,希望可以幫助到大家,也避免自己再次踩坑。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章