Object's Blog

9月-10月踩坑记录(2019)

字数统计: 2.4k阅读时长: 9 min
2019/10/16 分享

前言

很早就想整理自己的踩坑记录发上来,每次把自己踩过的坑发给自己的小号,想着有一天能整理一下。毕竟这些经验自己也是一步一个坑踏过来的。

第一个坑:关于MyBatis参数类型为String的问题

  • 问题描述

    当时使用MyBatis框架写了一个查询数据库的功能,入参是用户名 username(string)。

    1
    public User queryUserByUsername(String username);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <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>

    然后抛了这个异常:

    1
    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才能使识别。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <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

    1
    public void queryUserByUsername(@Param("username")String username);

    然后在xml中正常使用#{username}即可正常运行。

第二个坑:JQuery中为动态生成的按钮绑定点击事件

  • 问题描述

    在JQuery中为一个动态渲染生成的按钮绑定监听时间,如果直接用 button.click(function{//逻辑});是没有办法绑定成功的。

  • 解决方案

    在JQuery中如果需要动态渲染按钮,然后给这个按钮直接绑定click事件是无法生效的,必须使用父容器来为这个按钮委托指派点击事件。假设按钮的id为button,按钮父容器的id为parent,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    button.click(function(){
    //逻辑
    });
    button.bind("click",function(){
    //逻辑
    });
    /*用以上两种方法绑定点击事件是无效的*/
    /*必须得使用父容器委托绑定*/
    $(parent).on('click','#button',function(){
    //逻辑
    })

第三个坑:使用getResourceAsStream获取配置文件

  • 问题描述

    使用getResourceAsStream获取配置文件,默认从项目目录开始,如果要是传如同c:\xxx\xxx这样的绝对路径,是没有办法读到的。

  • 解决方案

    我们通常会使用getClassAsStream获取properties配置文件。代码如下:

    1
    2
    3
    4
    5
    6
    7
    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注册该过滤器

    1
    2
    3
    4
    5
    public FilterRegistrationBean registration(MyFilter filter) {  
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
    }
  • 解决方案二

    配置ShiroFilter的自定义过滤器时直接new而不使用 @Bean方式配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*开启注释会产生循环重定向问题
    * 症结大概存在于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对象,相当于配置了一个懒加载。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @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中使用:

    1
    2
    3
    4
    5
    6
    private synchronized RedisUtil getRedisUtil() {
    if(redisUtil==null) {
    redisUtil = SpringUtil.getBean(RedisUtil.class);
    }
    return redisUtil;
    }
    1
    2
    3
    4
    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语句不被改变。

结语

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

原文作者:Object

原文链接:http://blog.objectspace.cn/2019/10/16/9月-10月踩坑记录(2019)/

发表日期:2019 October 16th, 9:13:00 am

更新日期:2019 November 29th, 5:52:26 pm

版权声明:未经作者授权请勿转载

目录
  1. 1. 前言
  2. 2. 第一个坑:关于MyBatis参数类型为String的问题
    1. 2.1. 问题描述
    2. 2.2. 解决方案一
    3. 2.3. 解决方案二
  3. 3. 第二个坑:JQuery中为动态生成的按钮绑定点击事件
    1. 3.1. 问题描述
    2. 3.2. 解决方案
  4. 4. 第三个坑:使用getResourceAsStream获取配置文件
    1. 4.1. 问题描述
    2. 4.2. 解决方案
  5. 5. 第四个坑:Shiro自定义拦截器无限重定向(集成SpringBoot)
    1. 5.1. 问题描述
    2. 5.2. 解决方案一
    3. 5.3. 解决方案二
  6. 6. 第五个坑:Spring Boot Bean加载顺序导致依赖注入为null(二级缓存)
    1. 6.1. 问题描述
    2. 6.2. 解决方案
  7. 7. 第六个坑:Ajax异步导致页面显示异常
    1. 7.1. 问题描述
    2. 7.2. 解决方案
  8. 8. 第七个坑:MyBatis关于大于号小于号无法识别的问题
    1. 8.1. 问题描述
    2. 8.2. 解决方案
  9. 9. 结语