如果MySQL表的一些字段被设置为不允许为空,使用MyBatis写入的数据中相应字段的值是null就有可能会报类似下面的错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
org.springframework.dao.DataIntegrityViolationException: ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null ### The error may exist in org/chobit/service/mapper/MyMapper.java (best guess) ### The error may involve org.chobit.service.mapper.MyMapper.updateById-Inline ### The error occurred while setting parameters ### SQL: update my_table set name=?, `desc`=? where id=? ### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null ; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:87) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446) at com.sun.proxy.$Proxy88.update(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:294) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:67) |
对于这个问题,我通常的做法是为对应字段的null值直接赋值一个空字符串(或其它默认值)。但是这次,不知道为什么那么多不必要的字段被设置为了不能为空:七八个字段,要判断是否为空,还要一个个赋默认值 —— 想想就头疼。一定要想办法优化掉这种啰嗦且难看的东西。
参考分页插件 PageHelper 的实现后找到了思路:添加一个拦截器,获取参数实例,为实例的值为null的Field设置一个默认值。
拦截器的实现如下:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
/** * Mybatis拦截器,为null字段设置默认值 * * @author robin */ @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) public class MybatisNullInterceptor implements Interceptor { private static final Logger logger = LoggerFactory.getLogger(MybatisNullInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; padNullFields(parameter); Executor executor = (Executor) invocation.getTarget(); return executor.update(ms, parameter); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } private void padNullFields(Object parameter) { PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(parameter.getClass()); for (PropertyDescriptor pd : targetPds) { try { padNullField(pd, parameter); } catch (Exception e) { logger.error("pad null fields failed", e); } } } private void padNullField(PropertyDescriptor pd, Object parameter) throws InvocationTargetException, IllegalAccessException { Method readMethod = pd.getReadMethod(); Method writeMethod = pd.getWriteMethod(); if (null == readMethod || null == writeMethod) { return; } if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } Object value = readMethod.invoke(parameter); if (null != value) { return; } if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } Class<?> clazz = pd.getPropertyType(); setDefaultValue(parameter, clazz, writeMethod); } /** * 为字段设置默认值 * * @param parameter 参数 * @param fieldClass 字段类型 * @param writeMethod 字段写方法 */ private void setDefaultValue(Object parameter, Class<?> fieldClass, Method writeMethod) throws InvocationTargetException, IllegalAccessException { if (fieldClass.isAssignableFrom(String.class)) { writeMethod.invoke(parameter, ""); } if (fieldClass.isAssignableFrom(Boolean.class)) { writeMethod.invoke(parameter, false); } if (fieldClass.isAssignableFrom(Byte.class)) { writeMethod.invoke(parameter, (byte) 0); } if (fieldClass.isAssignableFrom(Integer.class)) { writeMethod.invoke(parameter, 0); } if (fieldClass.isAssignableFrom(Long.class)) { writeMethod.invoke(parameter, 0L); } if (fieldClass.isAssignableFrom(LocalDateTime.class)) { writeMethod.invoke(parameter, LocalDateTime.now()); } } } |
在这个拦截器中通过反射的方式为参数实例值为null的字段完成了赋默认值。这里只为几个我常用的类型(字符串、整型和日期类型)设置了默认值,如果还会用到其他值请自行修改setDefaultValue方法。
代码中有一个重要的点就是拦截器类上的注解@Signature
:
1 2 3 4 |
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) |
这个注解指明了具体要拦截的类和方法。在这里,数据库操作主要是通过Executor
类完成;并且不论是新增或是更新都是通过update
方法实现的,args属性则明确了方法参数只有一个Object
类型的值。
然后是让这个拦截器生效。代码如下:
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 |
@Configuration public class MybatisExtConfigure { @Autowired private List<SqlSessionFactory> sqlSessionFactoryList; @PostConstruct public void addInterceptor() { MybatisNullInterceptor interceptor = new MybatisNullInterceptor(); for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) { org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration(); if (!containsInterceptor(configuration, interceptor)) { configuration.addInterceptor(interceptor); } } } private boolean containsInterceptor(org.apache.ibatis.session.Configuration configuration, Interceptor interceptor) { try { return configuration.getInterceptors().contains(interceptor); } catch (Exception e) { return false; } } } |
这里通过@Configuration
完成的自定义拦截器的注入。
配置完成后,再次执行写入和更新的测试案例,一次通过,搞定!
发表评论