BeanUtils.copyProperties的一个大坑:为null的包装类(Integer等)复制后自动变为0

最近业务里经常要在两个对象间复制属性,于是想当然的就用了BeanUtils.copyProperties来复制。然而有一天突然发现,用复制后的对象调用一个方法一直失败,用被复制的那个去调用却没有任何问题。检查了很久才发现,原来BeanUtils.copyProperties方法在遇到包装类时,竟然会把原来为null的属性,复制后赋值为0…(对于Boolean,复制后会变为false),难怪用复制后的对象会出错……

解决方法就很简单了,换用PropertyUtils.copyProperties即可
不过这两者之间还是有一定区别的,PropertyUtils.copyProperties比起BeanUtils.copyProperties来说,少了类型转换的支持,也就是说必须要类型和name一样才可以复制。比如源对象有一个类型为Long的属性,而目标对象同名属性的类型为Date,BeanUtils.copyProperties可以复制,而PropertyUtils.copyProperties会报错。

解决Spring Boot自定义错误页导致文件无法上传的问题

网上的Spring Boot自定义错误页教程,基本上都是自己自定义一个ServletRegistrationBean,把DispatcherServlet里的setThrowExceptionIfNoHandlerFound设置为true,代码样例如下:

@Configuration
public class ErrorConfiguration {
    @Bean
    public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean registration = new ServletRegistrationBean(
                dispatcherServlet);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
        return registration;
    }

    @Bean
    public EmbeddedServletContainerCustomizer containerCustomizer() {
        return (container -> {
            ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html");
            ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
            ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");
            container.addErrorPages(error401Page, error404Page, error500Page);
        });
    }
}

但这种配置方式存在一个问题,我们自定义了ServletRegistrationBean之后,无法使用系统自带的DispatcherServletAutoConfiguration自动配置,于是缺少了很多配置,其中就包含上传文件所需要的配置。

解决方法很简单。只为了自定义错误页的话,根本无需像上面代码段中粗体部分那样自己自定义ServletRegistrationBean。直接在application.yml(或application.properties)里配置一句spring.mvc.throw-exception-if-no-handler-found: true,就可以把DispatcherServlet里的setThrowExceptionIfNoHandlerFound设置为true了。

Spring整合Hive

最近项目中的一个页面要从Hive中取数据,研究了一下如何把Hive整合进Spring中。

其实还是很简单的,Spring提供了spring-data-hadoop这个包,引进来之后新增一个配置文件就可以了。配置文件样例如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/hadoop"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/hadoop http://www.springframework.org/schema/hadoop/spring-hadoop.xsd">

    <beans:bean id="hiveDriver" class="org.apache.hive.jdbc.HiveDriver"/>

    <beans:bean id="hiveDataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
        <beans:constructor-arg name="driver" ref="hiveDriver"/>
        <beans:constructor-arg name="url" value="jdbc:hive2://填数据库地址"/>
    </beans:bean>

    <hive-client-factory id="hiveClientFactory" hive-data-source-ref="hiveDataSource"/>

    <hive-template id="hiveTemplate"/>
</beans:beans>

用的时候直接autowire hiveTemplate就可以了。

不过在实际使用的时候遇到了另一个问题,HiveTemplate的功能非常弱鸡,连QueryForObject都没有。于是我用Hive的DataSource配置了一个JdbcTemplate,需要查Object的地方就直接用JdbcTemplate了。

<beans:bean id="hiveJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <beans:property name="dataSource" ref="hiveDataSource"/>
</beans:bean>

最后说句题外话,用多了MyBatis之后,再用JdbcTemplate,还真是不习惯。最麻烦的就属QueryForList不能直接返回实体的List了,要么自己写一个映射的方法,要么手动一个个set到实体对象里,无论如何都很麻烦……哎,还是MyBatis好。