问题1.Column ‘username‘ in field list is ambiguous
### The error may involve com.fx.oa.module.per.leave.api.shared.domain.PositiveEntity.queryListForPage-Inline
### The error occurred while setting parameters
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column ‘username‘ in field list is ambiguous
; SQL []; Column ‘username‘ in field list is ambiguous; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolat
今天正式系统更新后,打开新建工作,页面报出500错误。打开日志有如上错误。负责处理问题的工程师跟我说,可能是有同事在测试系统数据和代码里都增加了一个名为userName的字段,但没有在正式系统数据库更新表结构导致的。我拿到日志信息,看到是ambiguous,这个单词的意思是username这个字段是不明确、有歧义的。这种错误往往是多个表之间关联查询,而表中有相同字段名称,未为该字段指定所属表引起的。在新建工作页面的底端,有查询办理历史的表格。查询历史时会先将所有任务拿出,由于任务的审批人存储的是用户名,需要进一步关联到用户表取出用户姓名,如下:
SELECT task.processExecutionId, process.name as processDefineName, task.activityName, user.userName as createUserCode, task.status, task.description, task.finishTime, task.createTime FROM T_BPM_PROCESS_TASK task LEFT JOIN T_BPM_PROCESS_EXECUTION execution ON task.processExecutionId = execution.id LEFT JOIN t_bpm_process_define process ON execution.processDefineId = process.id LEFT JOIN T_SYS_USER user ON task.createUserCode = user.userCode WHERE task.processExecutionId = #{id} ORDER BY task.createTime DESC, task.finishTime DESC
在这个版本上线里,我们新上线了高级查询的基于用户的管理权限控制,负责的杜工在t_bpm_process_define中新增加了两个字段,他仿照已经存在的roleCode、roleName,增加了userCode、userName两个字段,而这两个字段至少在档案和用户相关的表广泛被使用。其中这两个字段在我们的系统里就可以被看做是保留字,其他表尽量不要使用。我建议将这两个专用于高级查询的字段加上前缀,而不必修改sql。
表结构修改后该问题消失。
问题2.sql:截取中出现的问题
本期OA在正式系统上线后,与moss系统并行了一段时间。在这段时间里oa的所有流程及表单均为测试,正式单据在moss里填写。所以我们在流程的显示名称在表单页面均添加了“(测试)”字样,而且页面中凡包含“(测试)”字样的表单,在填写和审批时都会提示“该表单是测试表单,请到moss里填写”。3月12号是OA表单正式启用,需要将所有的测试字样从系统里抹去。在测试字样添加的时候,由各流程负责同事在系统前端手动添加和保存。如果再在系统页面每张表单打开删除会非常麻烦。经过观察,我发现所有流程的显示名都包含(测试单据)或(测试字样),且几十个流程均未有在名称中包含以上字样。而在测试单据的html和template中,均包含<span style="color:#ff0000;">(测试表单)</span>,所以我写了以下sql:
update t_bpm_process_define set name=SUBSTR(NAME FROM 1 FOR INSTR(name,"(测试单据)")-1) where INSTR(name,"(测试单据)")!=0; update t_bpm_form_define set template=REPLACE(template, ‘(测试单据)‘,‘‘)where INSTR(template,‘(测试单据)‘)!=0; update t_bpm_form_define set html=REPLACE(html, ‘<span style="color:#ff0000;">(测试表单)</span>‘,‘‘)where INSTR(html,‘<span style="color:#ff0000;">(测试表单)</span>‘)!=0;
执行后,表单倒是没有问题,但第一句的执行出现了可怕的现象,很多流程名称是空的。
第一句中我使用的是字符串的截取,第二、第三句都是替换部分字符串。我最初想可能跟这个有关,实际也确实有关,如果我也用replace函数是没有问题的,原因很简单,如果文本中不包含目标字符串肯定是不会替换的。但用字符串截取从逻辑上也讲得通,为什么会出现这么可怕的结果呢?
原因就在于我们update没有使用表内联,sql在执行的时候根本没有什么数据去关联它到底该修改哪条数据。
如果我们将第一条数据修改成这样,就没有问题了:
update t_bpm_process_define a,(select id,name from t_bpm_proces_define where INSTR(name,"(测试单据)")!=0) )b set a.name=SUBSTR(NAME FROM 1 FOR INSTR(name,"(测试单据)")-1) where a.id = b.id;
问题3.由于我们公司使用的邮件服务器是Zimbra服务器,由云计算部门购买和维护。OA中的提醒邮件也是通过Zimbra往外发送。对于一些某时段的大批量邮件,比如逾期工作和考勤异常都是在某个时间点上由quartz触发,同时会发出数百封以至上千封邮件。可能对成熟的商用邮件服务器这点压力算不上什么,但对于Zimbra可能已经吃不消了。在云计算和IT的同事协助下,由我对邮件服务器进行了测试,测试用例如下:
测试用例1:100封,总用时约:16min;实收97封,失败3次,3次错误信息均为:javax.mail.MessagingException: Could not connect to SMTP host
测试用例2:100封,总用时约:16min;实收100封,失败2次,错误同上。加失败重发机制,失败后等待10s重发,最多重发3次;
测试用例3:每发一封,停留10s,总用时32min;实收100封,失败1次,错误同上;重发机制同用例2.
我看了一下几种解释:1.网络;2.防火墙;3.服务器的自我保护,比如防止大批量发送时挂掉或者垃圾邮件,我觉得第三种解释靠谱一些。
后来又做了一些其他压力强度的测试,而且针对MessagingException的错误,一步步跟踪代码,也进行了相关分析
具体错误信息如下:
javax.mail.MessagingException: Could not connect to SMTP host: mail.cn.phicomm.com, port: 25;
nested exception is:
java.net.SocketException: Software caused connection abort: connect
at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1282)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:37
0)
at javax.mail.Service.connect(Service.java:297)
at javax.mail.Service.connect(Service.java:156)
at javax.mail.Service.connect(Service.java:105)
我通读了一下代码,发现是这样的,首先会连接生成serverSocket:
if (serverSocket != null) openServer(); // only happens from connect(socket) else // openServer(host, port); else openServer(host, port);
以下是openServer中的代码:
if (debug) out.println("DEBUG SMTP: got bad greeting from host \"" + server + "\", port: " + port + ", response: " + r + "\n"); throw new MessagingException( "Got bad greeting from SMTP host: " + server + ", port: " + port + ", response: " + r); }
而debug是一个全局变量,是session的一个属性,其值
Since the debug setting can be turned on only after the Session has been created
也就是说session未建立就会报MessagingException这个错误。所以我进一步怀疑,我们的发送邮件代码每次都会去重连认证,这个也是代码的问题之一,所以我加了session失效才重连。
接下来又进行了一次测试,用例如下:
用例1.代码修改如下
if (session == null || session.getDebug()) { System.out.println("第" + j + "次重连"); session = Session.getDefaultInstance(prop, auth); }
结果:没有失败
用例2.仍用1的代码,
结果:但失败1次,失败后未重新获得session,可见失败后session仍有效;
用例3.代码修改如下:
if (session == null || session.getDebug()) { // 如果需要身份验证,创建一个账号密码验证器 if (mail.isValidate()) { auth = new MailAuthenticator(mail.getUserName(), mail.getPassword()); } System.out.println("第" + j + "次重连"); session = Session.getDefaultInstance(prop, auth); }
结果:有1次失败,失败后未重连。
通过以上测试,是否每次重新获得session对效率 没有影响,对异常的发生也没有影响。异常报出后,session仍有效,并且可以重发。
经过三轮测试,其实我们可以发现重发机制已经可以保证邮件的完全送达。
重发的代码如下:
/** * Temporary mechanism: automatic resend mail * @author chao.gao * @date 2015-2-4 上午9:27:59 * @param mail * @return */ public static boolean sendHtmlMail(BasicMail mail){ if(sendHtmlMail_(mail)){ return true; } else{ int i = 0; while(!sendHtmlMail_(mail) && i < 3){ try { i++; Thread.sleep(1000*60); } catch (InterruptedException e) { LOGGER.error("resend mail error", e); } } return true; } }
加入重发机制运行了一段时间,未再发现类似的漏发现象。
Column 'username' in field list is ambiguous、邮件漏发等问题的分析及解决