今天處理數據批量的更新,場景是這樣子的,web站管理的字典功能,需要添加一個記錄的整體描述,以及詳細內容的描述。一個字典整體概述只有一組信息,但是其詳細內容,會有很多項,不確定。
這個場景,在關系型數據庫操作中,就是典型的1vN的問題,即一對多的問題。
做內容修改時,涉及到批量的更新過程。這里,只針對具體的問題描述細節,不過多介紹字典的設計。
字典的查詢沒有問題,mybatis的mapper函數如下:
<resultMap id="FullConfResultMap" type="com.tg.ecs.system.model.SysConfElement" > <!-- 注意,這種基於has-one,以及has-many的模式,會造成結果映射出錯。個人理解,這種復合查詢中,頂層元素中 必須有基本數據類型的成員,否則會出現結果個數映射轉換失敗。 <association property="scc" javaType="com.tg.ecs.system.model.SysConfCategory"> <id column="id" property="id" jdbcType="INTEGER" /> <result column="code_name" property="codeName" jdbcType="VARCHAR" /> <result column="info_name" property="infoName" jdbcType="VARCHAR" /> <result column="remark" property="remark" jdbcType="VARCHAR" /> <result column="who_cm" property="whoCm" jdbcType="INTEGER" /> <result column="create_time" property="createTime" jdbcType="TIMESTAMP" /> <result column="update_time" property="updateTime" jdbcType="TIMESTAMP" /> <result column="in_use" property="inUse" jdbcType="BIT" /> </association> --> <id column="id" property="id" jdbcType="INTEGER" /> <result column="code_name" property="codeName" jdbcType="VARCHAR" /> <result column="info_name" property="infoName" jdbcType="VARCHAR" /> <result column="c_remark" property="remark" jdbcType="VARCHAR" /> <result column="who_cm" property="whoCm" jdbcType="INTEGER" /> <result column="create_time" property="createTime" jdbcType="TIMESTAMP" /> <result column="update_time" property="updateTime" jdbcType="TIMESTAMP" /> <result column="in_use" property="inUse" jdbcType="BIT" /> <collection property="scds" ofType="com.tg.ecs.system.model.SysConfDetail"> <result column="did" property="id" jdbcType="INTEGER" /> <result column="code" property="code" jdbcType="VARCHAR" /> <result column="info" property="info" jdbcType="VARCHAR" /> <result column="d_remark" property="remark" jdbcType="VARCHAR" /> <result column="category_id" property="categoryId" jdbcType="INTEGER" /> </collection> </resultMap> <select id="selectOneFullConf" resultMap="FullConfResultMap"> select tscc.id, tscc.code_name, tscc.info_name, tscc.remark as c_remark, tscc.who_cm, tscc.create_time, tscc.update_time, tscc.in_use, tscd.id as did, tscd.code, tscd.info, tscd.remark as d_remark, tscd.category_id from sys_conf_category tscc, sys_conf_detail tscd where tscd.category_id = tscc.id and tscc.id = #{cid, jdbcType=INTEGER} </select>
這里,我的SysConfDetail的POJO代碼如下:
public class SysConfDetail { private Integer id; private String code; private String info; private String remark; private Integer categoryId; public String getInfo() { return info; } public void setInfo(String info) { this.info = info == null ? null : info.trim(); } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark == null ? null : remark.trim(); } public Integer getCategoryId() { return categoryId; } public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; } /** * @return the code */ public String getCode() { return code; } /** * @param code the code to set */ public void setCode(String code) { this.code = code; } /** * @return the id */ public Integer getId() { return id; } /** * @param id the id to set */ public void setId(Integer id) { this.id = id; } }
就是查詢中collection的數據類型。
我的設計中,字典的操作是下面這個樣子的,請看下圖:
當我更新上圖中的內容后,點擊保存,我的WEB應用后台總是爆出一個很奇怪的問題:
org.springframework.jdbc.BadSqlGrammarException: ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UPDATE sys_conf_detail SET code = '2', info = '产�详' at line 7 ### The error may involve com.tg.ecs.system.dao.SysConfDetailMapper.updateOneConfDetail-Inline ### The error occurred while setting parameters ### SQL: UPDATE sys_conf_detail SET code = ?, info = ?, remark = ? WHERE id = ? and category_id = ? ; UPDATE sys_conf_detail SET code = ?, info = ?, remark = ? WHERE id = ? and category_id = ? ; UPDATE sys_conf_detail SET code = ?, info = ?, remark = ? WHERE id = ? and category_id = ? ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UPDATE sys_conf_detail SET code = '2', info = '产�详' at line 7 ; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'UPDATE sys_conf_detail SET code = '2', info = '产�详' at line 7 at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:237) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72) at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:74) at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:421) at com.sun.proxy.$Proxy11.update(Unknown Source) at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:270) at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:55) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53) at com.sun.proxy.$Proxy42.updateOneConfDetail(Unknown Source) at com.tg.ecs.system.service.impl.SysConfDetailService.updateOneConfDetail(SysConfDetailService.java:55) at com.tg.ecs.system.service.impl.SysConfDetailService$$FastClassBySpringCGLIB$$373671b0.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:700) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:633) at com.tg.ecs.system.service.impl.SysConfDetailService$$EnhancerBySpringCGLIB$$f6432ee7.updateOneConfDetail(<generated>) at com.tg.ecs.system.service.impl.SysConfDetailService$$FastClassBySpringCGLIB$$373671b0.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:700) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:633) at com.tg.ecs.system.service.impl.SysConfDetailService$$EnhancerBySpringCGLIB$$b9909d6d.updateOneConfDetail(<generated>) at com.tg.ecs.system.controller.ConfController.saveCategory(ConfController.java:223) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81) at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144) at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161) at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:205) at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99) at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389) at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347) at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102) at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:326) at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) at org.glassfish.jersey.internal.Errors.process(Errors.java:315) at org.glassfish.jersey.internal.Errors.process(Errors.java:297) at org.glassfish.jersey.internal.Errors.process(Errors.java:267) at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473) at org.glassfish.jersey.servlet.ServletContainer.serviceImpl(ServletContainer.java:408) at org.glassfish.jersey.servlet.ServletContainer.doFilter(ServletContainer.java:583) at org.glassfish.jersey.servlet.ServletContainer.doFilter(ServletContainer.java:524) at org.glassfish.jersey.servlet.ServletContainer.doFilter(ServletContainer.java:461) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at com.tg.ecs.core.xss.XssFilter.doFilter(XssFilter.java:43) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61) at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108) at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449) at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365) at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383) at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:106) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1502) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1458) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:744)
分析上面的錯誤日志,開始總是不知道什么原因,以為是其中的info字段亂碼。分析自己的update的SQL語句:
<update id="updateOneConfDetail" parameterType="java.util.List"> <!-- 注意這里,index以及open,close沒有用,所以就不做配置了,默認的 --> <foreach collection="scds" item="ocd" separator=";"> UPDATE sys_conf_detail <set> code = #{ocd.code}, info = #{ocd.info}, remark = #{ocd.remark} </set> <where> id = #{ocd.id, jdbcType=INTEGER} and category_id = #{ocd.categoryId, jdbcType=INTEGER} </where> </foreach> </update>
上面的SQL語句,紅色部分separator=“;”,是重點。 回想到官方的描述,關於一行SQL語句中執行多個子SQL的時候(比如這里,MySQL默認是用分號最為一個語句的結束),會出問題。請參考MySQL官方文檔描述
allowMultiQueries
Allow the use of ';' to delimit multiple queries during one statement (true/false), defaults to 'false', and does not affect the addBatch() and executeBatch() methods, which instead rely on rewriteBatchStatements.
Default: false
Since version: 3.1.1
這個是問題的關鍵,我們可以在jdbc的地址url中添加一下這個allowMultiQueries=true的參數。
jdbc.url=jdbc\:mysql\://10.90.7.2\:3306/rdcenter?useUnicode\=true&characterEncoding\=UTF-8&zeroDateTimeBehavior\=convertToNull&allowMultiQueries\=true
修改后,再次啟動我的應用,就不再報上述的exception問題了。
總結:
mysql數據庫,要在一個statement中執行多條sql子句的時候,需要打開這個功能,默認該功能是關閉的,也就是在url地址欄開啟allowMultiQueries=true.