前言
很早就想整理自己的踩坑记录发上来,每次把自己踩过的坑发给自己的小号,想着有一天能整理一下。毕竟这些经验自己也是一步一个坑踏过来的。
第一个坑:关于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语句不被改变。
结语
很早就想整理这个,一直在写技术文章导致这个拖了很长时间,有些异常可能没什么印象了,描述的也不是很清楚,就不往上写了。写上的这些是我自己比较有印象的一些坑,希望可以帮助到大家,也避免自己再次踩坑。