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语句不被改变。

结语

很早就想整理这个,一直在写技术文章导致这个拖了很长时间,有些异常可能没什么印象了,描述的也不是很清楚,就不往上写了。写上的这些是我自己比较有印象的一些坑,希望可以帮助到大家,也避免自己再次踩坑。

发布了19 篇原创文章 · 获赞 197 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/NoCortY/article/details/102579258
今日推荐