• 关于

    错误处理怎么看配置

    的搜索结果

问题

Cannot load module modssl.soundefined symbolSSLgetsrpuserinfo

kadinkuo 2019-12-01 22:02:27 2626 浏览量 回答数 2

问题

用的windows2008一键安装web环境,怎么配置伪静态?

白流飘 2019-12-01 21:04:28 3121 浏览量 回答数 3

回答

为什么会有id[]这种格式的传法?想知道一下回复<aclass='referer'target='_blank'>@独孤小败:相办法搞乱系统怎么都可以,出错了你让转向一个页面就行了用测试软件测试的,看看有什么Bug,然后它这么传的,然后报错了. 只配置@RequestMapping("/xxx.html") 不配置@RequestParam。id[]=2的这种格式没见过。 刚才确实有一些理解错误了。当系统输入不合法参数的时候,报错是正常的。SpringMVC支持你配置如何处理出错信息。不过我还是不理解你这个不理是什么意思。 回复<aclass='referer'target='_blank'>@独孤小败:那么就要看你的代码了。如果传入参数名对不上的话,那么这个参数就当null如何处理了。那么你就看你null的时候如何处理就是。比如说,我的javaBean里面有privateintid;这个字段,那么http://xxxx?id=2;就可以set进去了,但我如果是http://xxxx?idddd=2,那么javaBean里没有这个字段,不就是没做处理,就是没理它. 如果要传数组的话可以提供你2种方法. 1.自己组装合法的数组数据,然后传递到后端自己解析成数组 2.如果要在url上传数组这样是不行的,只能是id[0]=1&id[1]=2 Book{   privateint[]ids;   //get   //set } http://xxx/xxx.html?ids[0]=1&ids[1]=2问的问题貌似不合逻辑,你传一个不合法的参数肯定会报错,如你不想看到这个错误,直接在web.xml里做个500错误的指向地址。

爱吃鱼的程序员 2020-06-22 22:39:36 0 浏览量 回答数 0

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

回答

页面500错误就是你写的程序有问题,报的是空指针错误。 最好直接debug看哪里保错下面有我的jsp代码有时间您帮忙看看我的程序应该没有问题,就是正常的加数据然后提交到提交就报错.之前用hibernate做过没错的换了用mytis就出错了 在action里面打个断点,看看哪里抱了空指针错误调过,进的地方看不懂... 异常信息比较没有意义 有可能是你struts的拦截器出现异常 或许你代码中抛出异常,然后被你拦截器捕获这个异常重新处理,配置出现错误,产生了新的异常 对struts2没有深入做过项目,不太了解其某些具体的用法,大概也就我说的这些可能吧 我也是刚学struts2拦截器还没用你这个是修改,我估计你的实体类中肯定有字段没有在jsp页面赋值 我查过都有字段的有做非空验证你都使用了struct怎么还再使用jsp的<%%>语法呢。 @暮念倾指柔这个维护起来很不好吧。用习惯了其他的也差不多这个逻辑性感觉好些

爱吃鱼的程序员 2020-06-09 14:02:41 0 浏览量 回答数 0

回答

     其实我没错,草,SB了,我把那个modules的配置,写到components里面去了。草草!困扰我好几天了这事! 要死了我都。 ######...我和你犯了同样的错误~~.....###### 2.0好用吗? 比1.1如何? ###### 引用来自“笨蛋0007”的评论 2.0好用吗? 比1.1如何? 现在还是开发版!这我说不出了!用了命名空间,这个最明显了,扩展是有bootstrap3.0。 其他我就没不知道怎么说了,迟早要升的,好像1.X也可以升级成2.X,还有就是声明数组的方式换成了[],不是之前的array(); 依赖管理使用 Composer,程序目录结构也有小的改动! ###### 1.x用了好久了。2.x一直是beta版本。 ######看这格式。。。你去看zend2是怎么处理的,然后就应该知道yii是怎么处理的。zend2有很详细文档######不用看了,我没写错,只是写错位置了!###### 先用gii生成,然后在config/web里配置,可以参照里面gii和debug的写法 ###### 'class'=>'app\modules\manager\ Module '

kun坤 2020-05-28 09:52:49 0 浏览量 回答数 0

回答

     其实我没错,草,SB了,我把那个modules的配置,写到components里面去了。草草!困扰我好几天了这事! 要死了我都。 ######...我和你犯了同样的错误~~.....###### 2.0好用吗? 比1.1如何? ###### 引用来自“笨蛋0007”的评论 2.0好用吗? 比1.1如何? 现在还是开发版!这我说不出了!用了命名空间,这个最明显了,扩展是有bootstrap3.0。 其他我就没不知道怎么说了,迟早要升的,好像1.X也可以升级成2.X,还有就是声明数组的方式换成了[],不是之前的array(); 依赖管理使用Composer,程序目录结构也有小的改动! ###### 1.x用了好久了。2.x一直是beta版本。 ######看这格式。。。你去看zend2是怎么处理的,然后就应该知道yii是怎么处理的。zend2有很详细文档######不用看了,我没写错,只是写错位置了!###### 先用gii生成,然后在config/web里配置,可以参照里面gii和debug的写法 ###### 'class'=>'app\modules\manager\ Module '

montos 2020-05-31 13:04:35 0 浏览量 回答数 0

回答

回1楼dongshan8的帖子 'E:\webfile\e\database\v3.mdb'不是一个有效的路径。 确定路径名称拼写是否正确,以及是否连接到文件存放的服务器。 说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。 异常详细信息: System.Data.OleDb.OleDbException: 'E:\webfile\e\database\v3.mdb'不是一个有效的路径。 确定路径名称拼写是否正确,以及是否连接到文件存放的服务器。 ------------------------- 回2楼小柒2012的帖子 我在本地测试是正常访问的,上传服务器就不行。提示错误:'E:\webfile\e\database\v3.mdb'不是一个有效的路径。 确定路径名称拼写是否正确,以及是否连接到文件存放的服务器。 说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。 异常详细信息: System.Data.OleDb.OleDbException: 'E:\webfile\e\database\v3.mdb'不是一个有效的路径。 确定路径名称拼写是否正确,以及是否连接到文件存放的服务器。 数据库路径怎么改 ------------------------- 回5楼dongshan8的帖子 access数据库,哪个链接不能彻底解决。是在web.config配置文件改路径么,我在里面改了还是不行。看图: ------------------------- 回7楼dongshan8的帖子 您知道pageadmin CMS建站系统么?配置文件我也不懂,安装那个系统时选择是Access,sql我就不懂了。 ------------------------- 回10楼dongshan8的帖子 谢谢您这位好人,您那个链接应该是安装环境的教程,我这个在本地测试是可以的,环境都装好了。 我是帮学校一个部门弄个网站,做好了,但我们不是上传管理员,我也不懂那边怎么弄的,上传后却出现问题。所以我来这里注册个域名领了个空间,上传到根目录,一切都OK。 他那边说要上传都一个目录里面,因为学校不止这个一个网站,也许就是虚拟目录吧。 一切都白忙了,搁置好几天了。 ------------------------- 回9楼的帖子 也有可能,但我不是上传管理员,不懂他那边怎么弄的。 ------------------------- 回13楼dongshan8的帖子 是我们这边问题,在万网我不懂怎么放在子目录 ------------------------- 回1楼dongshan8的帖子 是怎么上传在子目录?建个文件夹把文件上传上去就行了?

酸橙子 2019-12-02 02:32:36 0 浏览量 回答数 0

回答

一、NGINX 502错误排查 NGINX 502 Bad Gateway错误是FastCGI有问题,造成NGINX 502错误的可能性比较多。将网上找到的一些和502 Bad Gateway错误有关的问题和排查方法列一下,先从FastCGI配置入手: 1.FastCGI进程是否已经启动 2.FastCGI worker进程数是否不够 运行 netstat -anpo | grep “php-cgi” | wc -l 判断是否接近FastCGI进程,接近配置文件中设置的数值,表明worker进程数设置太少 3.FastCGI执行时间过长 根据实际情况调高以下参数值 fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; 4.FastCGI Buffer不够 nginx和apache一样,有前端缓冲限制,可以调整缓冲参数 fastcgi_buffer_size 32k; fastcgi_buffers 8 32k; 5.Proxy Buffer不够 如果你用了Proxying,调整 proxy_buffer_size   16k; proxy_buffers    4 16k; 参见:http://www.server110.com 6.https转发配置错误 正确的配置方法 server_name www.mydomain.com; location /myproj/repos { set $fixed_destination $http_destination; if ( $http_destination ~* ^https(.*)$ ) { set $fixed_destination http$1; } proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Destination $fixed_destination; proxy_pass http://subversion_hosts; } 当然,还要看你后端用的是哪种类型的FastCGI,我用过的有php-fpm,流量约为单台机器40万PV(动态页面), 现在基本上没有碰到502。 7.php脚本执行时间过长 将php-fpm.conf的<value name="request_terminate_timeout">0s</value>的0s改成一个时间 二、Nginx 413错误的排查:修改上传文件大小限制 在上传时nginx返回了413错误,查看log文件,显示的错误信息是:”413 Request Entity Too Large”, 于是在网上找了下“nginx 413错误”发现需要做以下设置: 在nginx.conf增加 client_max_body_size的相关设置, 这个值默认是1m,可以增加到8m以增加提高文件大小限制; 如果运行的是php,那么还要检查php.ini,这个大小client_max_body_size要和php.ini中的如下值的最大值一致或者稍大,这样就不会因为提交数据大小不一致出现的错误。 post_max_size = 8M upload_max_filesize = 2M 三、Nginx 400错误排查:HTTP头/Cookie过大 今天有人汇报nginx的HTTP400错误,而且这个HTTP400错误并不是每次都会出现的,查了一下发现nginx400错误是由于request header过大,通常是由于cookie中写入了较长的字符串所引起的。 解决方法是不要在cookie里记录过多数据,如果实在需要的话可以考虑调整在nginx.conf中的client_header_buffer_size(默认1k) 若cookie太大,可能还需要调整large_client_header_buffers(默认4k),该参数说明如下: 请求行如果超过buffer,就会报HTTP 414错误(URI Too Long) nginx接受最长的HTTP头部大小必须比其中一个buffer大,否则就会报400的HTTP错误(Bad Request)。 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Nginx 502 Bad Gateway的含义是请求的PHP-CGI已经执行,但是由于某种原因(一般是读取资源的问题)没有执行完毕而导致PHP-CGI进程终止。 Nginx 504 Gateway Time-out的含义是所请求的网关没有请求到,简单来说就是没有请求到可以执行的PHP-CGI。 解决这两个问题其实是需要综合思考的,一般来说Nginx 502 Bad Gateway和php-fpm.conf的设置有关,而Nginx 504 Gateway Time-out则是与nginx.conf的设置有关。 而正确的设置需要考虑服务器自身的性能和访客的数量等多重因素。 以我目前的服务器为例子CPU是奔四1.5G的,内存1GB,CENTOS的系统,访客大概是50人左右同时在线。 但是在线的人大都需要请求PHP-CGI进行大量的信息处理,因此我将nginx.conf设置为: fastcgi_connect_timeout 300s; fastcgi_send_timeout 300s; fastcgi_read_timeout 300s; fastcgi_buffer_size 128k; fastcgi_buffers 8 128k;#8 128 fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_intercept_errors on; 这里最主要的设置是前三条,即 fastcgi_connect_timeout 300s; fastcgi_send_timeout 300s; fastcgi_read_timeout 300s; 这里规定了PHP-CGI的连接、发送和读取的时间,300秒足够用了,因此我的服务器很少出现504 Gateway Time-out这个错误。最关键的是php-fpm.conf的设置,这个会直接导致502 Bad Gateway和504 Gateway Time-out。 下面我们来仔细分析一下php-fpm.conf几个重要的参数: php-fpm.conf有两个至关重要的参数,一个是”max_children”,另一个是”request_terminate_timeout” 我的两个设置的值一个是”40″,一个是”900″,但是这个值不是通用的,而是需要自己计算的。 计算的方式如下: 如果你的服务器性能足够好,且宽带资源足够充足,PHP脚本没有系循环或BUG的话你可以直接将”request_terminate_timeout”设置成0s。0s的含义是让PHP-CGI一直执行下去而没有时间限制。而如果你做不到这一点,也就是说你的PHP-CGI可能出现某个BUG,或者你的宽带不够充足或者其他的原因导致你的PHP-CGI能够假死那么就建议你给”request_terminate_timeout”赋一个值,这个值可以根据你服务器的性能进行设定。一般来说性能越好你可以设置越高,20分钟-30分钟都可以。由于我的服务器PHP脚本需要长时间运行,有的可能会超过10分钟因此我设置了900秒,这样不会导致PHP-CGI死掉而出现502 Bad gateway这个错误。 而”max_children”这个值又是怎么计算出来的呢?这个值原则上是越大越好,php-cgi的进程多了就会处理的很快,排队的请求就会很少。设置”max_children”也需要根据服务器的性能进行设定,一般来说一台服务器正常情况下每一个php-cgi所耗费的内存在20M左右,因此我的”max_children”我设置成40个,20M*40=800M也就是说在峰值的时候所有PHP-CGI所耗内存在800M以内,低于我的有效内存1Gb。而如果我的”max_children”设置的较小,比如5-10个,那么php-cgi就会“很累”,处理速度也很慢,等待的时间也较长。如果长时间没有得到处理的请求就会出现504 Gateway Time-out这个错误,而正在处理的很累的那几个php-cgi如果遇到了问题就会出现502 Bad gateway这个错误。 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// nginx中配置php fastcgi组解决莫名其妙的502 Bad Gateway错误 一般nginx搭配php都采用这样的方式: location ~ .php$ { proxy_pass        http://localhost:9000; fastcgi_param   SCRIPT_FILENAME   /data/_hongdou$fastcgi_script_name; include        fastcgi_params; } 这个方式只能连接到一组spawn-fcgi开启的fastcgi,在服务器负载稍高时常常出现502 bad gateway错误。 起先怀疑这是php-cgi的进程开得太少,增加后仍然有反映时常有错,偶然间发现php-cgi会报出这样的错误: zend_mm_heap corrupted 看来是php-cgi在执行某些代码时有问题,以致于该线程中止。 在服务器上可能还会看到php-cgi进程在不断变少,估计是出现错误的php-cgi的进程自动退出了。 php的问题总是不太容易能解决,所以在nginx方面想想办法,nginx的好处是它总是能爆出一些稀奇古怪的做法出来。 在nginx的proxy中,规避莫名其妙错误的办法无非是proxy到一个upstream的服务器组中,然后配置 proxy_next_upstream,让nginx遇到某种错误码时,自动跳到下一个后端上。这样,应用服务器即使不稳定,但是在nginx后面就变成了稳定服务。想到nginx的fastcgi和proxy是一路东西,所以proxy能用的经验,移植到fastcgi也能跑得起来。 照着这个思路,用spawn-fcgi多开同样一组php进程,所不同的仅仅是端口: spawn-fcgi -a 127.0.0.1 -p 9000 -u nobody -f php-cgi -C 100 spawn-fcgi -a 127.0.0.1 -p 9001 -u nobody -f php-cgi -C 100 然后把fastcgi的这段配置改成用upstream的方式: upstream backend { server 127.0.0.1:9000; server 127.0.0.1:9001; } location ~ .php$ { fastcgi_pass        backend; fastcgi_param   SCRIPT_FILENAME   /data/_hongdou$fastcgi_script_name; include        fastcgi_params; } 检查配置结果正确,能跑起来;同时在服务器上netstat -n|grep 9000和grep 9001都有记录,证明连接无误;在前台查阅页面,一切运行正常。 这个配置是最简单的配置,既然能连接上upstream,那么很显然upstream的一些东西都可以拿来用,比如ip_hash、weight、max_fails等。 这样的配置在单机下不知能不能共享session,没有测试,如果有问题,可以加上ip_hash,或者配置php把session存进memcached中。 然后就是fastcgi_next_upstream的配置,nginx wiki中没有介绍到这个配置,查了一下,在nginx的CHANGES中有提到,而且出生年月是和proxy_next_upstream一样的。既然如此,那就照proxy_next_upstream一样配吧。一般按默认的值error timeout就可以工作,因为php出现502错误的异常是返回的500错误,所以我把fastcgi_next_upstream定为: fastcgi_next_upstream error timeout invalid_header http_500; 通过这个配置,就可以基本杜绝任何时常性的500错误,出问题的几率会变小很多,如果客户反映仍然激烈,那么就多增加几组fastcgi进程。

sosyxg 2019-12-02 02:43:07 0 浏览量 回答数 0

回答

请贴出完整的异常堆栈######已补异常信息###### 对此不熟,貌似xml中的属性名写错了。driverClass? ######回复 @ldl-1023 : 大部分时间都是用jfinal。ssh很少用了。######你用的都是spring或其他方式###### "Druid是根据url前缀来识别DriverClass的,这样使得配置更方便简洁。" 结合JdbcUtils的代码 https://github.com/alibaba/druid/blob/master/src/main/java/com/alibaba/druid/util/JdbcUtils.java 来看, 应该是没有读到hibernate的配置信息才导致的. 可以往这个方向排查看看. 如果还是解决不了, 最好提供一下你的项目目录结构, 以及hibernate的配置文件相关的目录. ######没有 oracle-jdbc的jar包?###### 引用来自“JacarriChan”的评论没有 oracle-jdbc的jar包? 如果引入了,看是不是重复了。 如果没有重复,建议你断点到“JdbcUtils.java:354”看它在找哪个class ######你好,你的问题解决了没呢?我也是报这个错误,断点进去和你一样的错,发现是jdbcUrl 的值为null,怎么处理呢 ?###### 数据库连接信息要放在数据库连接池之前 ######<property name="driverClassName">com.mysql.jdbc.Driver</property> <property name="url"> jdbc:mysql://127.0.0.1:3306/lian?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false </property> <property name="username">root</property> <property name="password">root</property> 需要配置你的数据连接信息

kun坤 2020-05-31 19:00:33 0 浏览量 回答数 0

回答

请贴出完整的异常堆栈######已补异常信息###### 对此不熟,貌似xml中的属性名写错了。driverClass? ######回复 @ldl-1023 : 大部分时间都是用jfinal。ssh很少用了。######你用的都是spring或其他方式###### "Druid是根据url前缀来识别DriverClass的,这样使得配置更方便简洁。" 结合JdbcUtils的代码 https://github.com/alibaba/druid/blob/master/src/main/java/com/alibaba/druid/util/JdbcUtils.java 来看, 应该是没有读到hibernate的配置信息才导致的. 可以往这个方向排查看看. 如果还是解决不了, 最好提供一下你的项目目录结构, 以及hibernate的配置文件相关的目录. ######没有 oracle-jdbc的jar包?###### 引用来自“JacarriChan”的评论没有 oracle-jdbc的jar包? 如果引入了,看是不是重复了。 如果没有重复,建议你断点到“JdbcUtils.java:354”看它在找哪个class ######你好,你的问题解决了没呢?我也是报这个错误,断点进去和你一样的错,发现是jdbcUrl 的值为null,怎么处理呢 ?###### 数据库连接信息要放在数据库连接池之前 ######<property name="driverClassName">com.mysql.jdbc.Driver</property> <property name="url"> jdbc:mysql://127.0.0.1:3306/lian?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false </property> <property name="username">root</property> <property name="password">root</property> 需要配置你的数据连接信息

kun坤 2020-06-12 22:09:15 0 浏览量 回答数 0

回答

请贴出完整的异常堆栈######已补异常信息###### 对此不熟,貌似xml中的属性名写错了。driverClass? ######回复 @ldl-1023 : 大部分时间都是用jfinal。ssh很少用了。######你用的都是spring或其他方式###### "Druid是根据url前缀来识别DriverClass的,这样使得配置更方便简洁。" 结合JdbcUtils的代码 https://github.com/alibaba/druid/blob/master/src/main/java/com/alibaba/druid/util/JdbcUtils.java 来看, 应该是没有读到hibernate的配置信息才导致的. 可以往这个方向排查看看. 如果还是解决不了, 最好提供一下你的项目目录结构, 以及hibernate的配置文件相关的目录. ######没有 oracle-jdbc的jar包?###### 引用来自“JacarriChan”的评论没有 oracle-jdbc的jar包? 如果引入了,看是不是重复了。 如果没有重复,建议你断点到“JdbcUtils.java:354”看它在找哪个class ######你好,你的问题解决了没呢?我也是报这个错误,断点进去和你一样的错,发现是jdbcUrl 的值为null,怎么处理呢 ?###### 数据库连接信息要放在数据库连接池之前 ######<property name="driverClassName">com.mysql.jdbc.Driver</property> <property name="url"> jdbc:mysql://127.0.0.1:3306/lian?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false </property> <property name="username">root</property> <property name="password">root</property> 需要配置你的数据连接信息

montos 2020-05-31 22:45:33 0 浏览量 回答数 0

问题

[精品问答]Java一百问第一期

问问小秘 2019-12-01 21:51:20 791 浏览量 回答数 1

回答

在mybatis的配置文件中找到 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:sqlSessionFactory-ref="sqlSessionFactory" p:basePackage="com.amar.db.ibatis"/> 原因不明,但删除掉这行就可以了 p:sqlSessionFactory-ref="sqlSessionFactory" ######https://code.google.com/p/mybatis/issues/detail?id=414 一直都存在的问题了,已解决。######配置多数据源的时候,不能去掉这个属性,看来只能修改源代码了###### <context:property-placeholder location="classpath:config/config.properties"/> 看看jdbc.maxPoolSize在 config.properties的配制名称对应是否正确吗?~~######直接写值没问题, 一用${} 就报错 , 貌似是c3p0加载优先级太高######回复 @amar : 跟c3p0应该没关系的,同样的配制我测试是Ok的~估计在哪儿犯了个低级错误######回复 @ideajava : 这样修改一切正常, 所以认为是c3p0将 ${} 作为字符串处理了.######回复 @amar : <property name="maxPoolSize" value="${jdbc.maxPoolSize}" />   改成 <property name="maxPoolSize" value="10" /> 试试会不会报错?######检查过了,应该没问题###### jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8 jdbc.user=root jdbc.password=123456 jdbc.initialPoolSize=3 jdbc.minPoolSize=3 jdbc.maxPoolSize=10 上面是config.properties内容######我觉得是  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值 莫非是我c3p0版本的问题? 我的版本是0.9.1.2######试一试把dataSource中的所有值用${}代替一下,说不定就可以了呢######不用 ${} ,而直接赋值一切正常,  不会是c3p0不支持${} 吧?######很负责任地告诉你这跟c3p0没有半毛钱关系。仔细看看你报的是什么错?######2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}###### dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值######报的错就是 2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}    ######前段时间工期干得紧,没时间处理,现在有时间了, 在慢慢找找原因. 目前认为原因可能是spring加载bean顺序的问题, 通过断点发现spring先加载ComboPooledDataSource,之后才加载PropertyPlaceholderConfigurer.######我也遇到java.lang.ClassNotFoundException: ${jdbc.driverClass}这个报错, 想知道怎么解决的

kun坤 2020-05-31 23:01:39 0 浏览量 回答数 0

回答

在mybatis的配置文件中找到 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:sqlSessionFactory-ref="sqlSessionFactory" p:basePackage="com.amar.db.ibatis"/> 原因不明,但删除掉这行就可以了 p:sqlSessionFactory-ref="sqlSessionFactory" ######https://code.google.com/p/mybatis/issues/detail?id=414 一直都存在的问题了,已解决。######配置多数据源的时候,不能去掉这个属性,看来只能修改源代码了###### <context:property-placeholder location="classpath:config/config.properties"/> 看看jdbc.maxPoolSize在 config.properties的配制名称对应是否正确吗?~~######直接写值没问题, 一用${} 就报错 , 貌似是c3p0加载优先级太高######回复 @amar : 跟c3p0应该没关系的,同样的配制我测试是Ok的~估计在哪儿犯了个低级错误######回复 @ideajava : 这样修改一切正常, 所以认为是c3p0将 ${} 作为字符串处理了.######回复 @amar : <property name="maxPoolSize" value="${jdbc.maxPoolSize}" />   改成 <property name="maxPoolSize" value="10" /> 试试会不会报错?######检查过了,应该没问题###### jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8 jdbc.user=root jdbc.password=123456 jdbc.initialPoolSize=3 jdbc.minPoolSize=3 jdbc.maxPoolSize=10 上面是config.properties内容###### 我觉得是  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值 莫非是我c3p0版本的问题? 我的版本是0.9.1.2######试一试把dataSource中的所有值用${}代替一下,说不定就可以了呢######不用 ${} ,而直接赋值一切正常,  不会是c3p0不支持${} 吧?###### 很负责任地告诉你这跟c3p0没有半毛钱关系。仔细看看你报的是什么错?######2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}######  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值###### 报的错就是 2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}    ###### 前段时间工期干得紧,没时间处理,现在有时间了, 在慢慢找找原因. 目前认为原因可能是spring加载bean顺序的问题, 通过断点发现spring先加载ComboPooledDataSource,之后才加载PropertyPlaceholderConfigurer.###### 我也遇到java.lang.ClassNotFoundException: ${jdbc.driverClass}这个报错, 想知道怎么解决的

montos 2020-06-02 22:11:52 0 浏览量 回答数 0

回答

在mybatis的配置文件中找到 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:sqlSessionFactory-ref="sqlSessionFactory" p:basePackage="com.amar.db.ibatis"/> 原因不明,但删除掉这行就可以了 p:sqlSessionFactory-ref="sqlSessionFactory" ######https://code.google.com/p/mybatis/issues/detail?id=414 一直都存在的问题了,已解决。######配置多数据源的时候,不能去掉这个属性,看来只能修改源代码了###### <context:property-placeholder location="classpath:config/config.properties"/> 看看jdbc.maxPoolSize在 config.properties的配制名称对应是否正确吗?~~######直接写值没问题, 一用${} 就报错 , 貌似是c3p0加载优先级太高######回复 @amar : 跟c3p0应该没关系的,同样的配制我测试是Ok的~估计在哪儿犯了个低级错误######回复 @ideajava : 这样修改一切正常, 所以认为是c3p0将 ${} 作为字符串处理了.######回复 @amar : <property name="maxPoolSize" value="${jdbc.maxPoolSize}" />   改成 <property name="maxPoolSize" value="10" /> 试试会不会报错?######检查过了,应该没问题###### jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8 jdbc.user=root jdbc.password=123456 jdbc.initialPoolSize=3 jdbc.minPoolSize=3 jdbc.maxPoolSize=10 上面是config.properties内容###### 我觉得是  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值 莫非是我c3p0版本的问题? 我的版本是0.9.1.2######试一试把dataSource中的所有值用${}代替一下,说不定就可以了呢######不用 ${} ,而直接赋值一切正常,  不会是c3p0不支持${} 吧?###### 很负责任地告诉你这跟c3p0没有半毛钱关系。仔细看看你报的是什么错?######2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}######  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值###### 报的错就是 2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}    ###### 前段时间工期干得紧,没时间处理,现在有时间了, 在慢慢找找原因. 目前认为原因可能是spring加载bean顺序的问题, 通过断点发现spring先加载ComboPooledDataSource,之后才加载PropertyPlaceholderConfigurer.###### 我也遇到java.lang.ClassNotFoundException: ${jdbc.driverClass}这个报错, 想知道怎么解决的

kun坤 2020-06-14 07:24:50 0 浏览量 回答数 0

回答

这个问题特别的简单   只需要你从nginx拦截  *.js *.html *.png *.jpg *.css 这些静态资源  然后你把动态的资源全部甩到后端的动态服务器######回复 @一个搞IT的人 : 肯定是用nginx拦截静态资源了,tomcat处理静态资源会很慢的,直接从nginx过滤就好了,先让nginx检测是不是静态资源,如果是的话用nginx 处理 其他的全部转到tomcat处理######先全局拦截,后匹配拦截?先后顺序有影响吗?######你说的关于gzip压缩的问题 你可以从nginx 上做gzip压缩  交给nginx压缩比交给tomcat压缩更合适######恩,是的。都是在nginx中配置开启gzip######然后关于集群的问题就更简单了 你从nginx 的  upstream  tomcatserver  {             server   127.0.0.1:8080;             server   127.0.0.1:8080;  }配置多个  然后 还可以设置权重跟主备用 这个你可以看一下nginx的文档 ###### #运行用户 user root; #启动进程,通常设置为cpu核心数相等 worker_processes 1; #全局错误日志和PID文件 error_log logs/error.log; pid logs/nginx.pid; #工作模式及连接上线 events{ #epoll是多路复用IO(I/O Multiplexing)中的一种方式,但是仅用于linux2.6以上内核,可以大大提高nginx的性能 use epoll; #单个后台worker process进程的最大并发链接数 worker_connections 1024; } #设定http服务器 http{ #设定MIME类型 include /usr/local/nginx/conf/mime.types; default_type application/octet-stream; #设定access Log access_log logs/access.log; #sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,对于普通应用, #必须设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络I/O处理速度,降低系统的uptime. sendfile on; #连接超时时间 #keepalive_timeout 0; keepalive_timeout 65; tcp_nodelay on; #开启gzip压缩 gzip on; gzip_disable "MSIE [1-6]\.(?!.*SV1)"; #设定请求缓冲 client_header_buffer_size 1k; large_client_header_buffers 4 4k; #设定负载均衡的服务器列表 upstream mysvr { #weigth参数表示权值,权值越高被分配到的几率越大 #本机上的Squid开启3128端口 #server 192.168.8.1:3128 weight=5; #server 192.168.8.2:80 weight=1; #server 192.168.8.3:80 weight=6; server localhost:8080 weight=5; } server{ #监听端口 listen 80; #主机名 server_name www.dc.com; #设定本虚拟主机访问日志 # access logs/access.log main; #默认请求 location / { proxy_pass http://localhost:8080; index index.jsp index.html index.ftl; include proxy.conf; } #静态文件 Nginx 自己处理 location ~ ^/(img|images|javascript|js|css|flash|media|static)/{ root /home/web/resources; #过期30天,静态文件不怎么更新,过期可以设大一点,如果频繁更新,则可以设置得小一点。 expires 1h; } # 定义错误提示页面 error_page 500 502 503 504 /50x.html; location = /50x.html{ root html; } #脚本文件 交给后端处理 #location ~ \.jsp${ # proxy_passs http://localhost:8080; #} #设定查看Nginx状态的地址 #location /NginxStatus { #stub_status on; #access_log on; #auth_basic "NginxStatus"; #auth_basic_user_file conf/htpasswd; #} } } 我是根据 js、img、css的不同路径进行处理的,集群哪里用的是upstream tomcatserver###### @一个搞IT的人 你细心找找有好多,我也是在osc上找到的:)######谢谢,谢谢。。。有代码真好!!###### @前路 @...... 正解,简单说:先分别将多个tomcat启动在不同的端口(非80端口)下面,然后在nginx中配置upstream指向这些tomcat,最后通过location+正则使nginx接管所有静态资源请求即可###### JFinal就是java项目,所以集群肯定是没有问题的,具体怎么配置,网上可以找找资料 ######谢谢,正在网上找。

kun坤 2020-05-29 13:10:26 0 浏览量 回答数 0

回答

" 在mybatis的配置文件中找到 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:sqlSessionFactory-ref="sqlSessionFactory" p:basePackage="com.amar.db.ibatis"/> 原因不明,但删除掉这行就可以了 p:sqlSessionFactory-ref="sqlSessionFactory" ######https://code.google.com/p/mybatis/issues/detail?id=414 一直都存在的问题了,已解决。######配置多数据源的时候,不能去掉这个属性,看来只能修改源代码了###### <context:property-placeholder location="classpath:config/config.properties"/> 看看jdbc.maxPoolSize在 config.properties的配制名称对应是否正确吗?~~######直接写值没问题, 一用${} 就报错 , 貌似是c3p0加载优先级太高######回复 @amar : 跟c3p0应该没关系的,同样的配制我测试是Ok的~估计在哪儿犯了个低级错误######回复 @ideajava : 这样修改一切正常, 所以认为是c3p0将 ${} 作为字符串处理了.######回复 @amar : <property name="maxPoolSize" value="${jdbc.maxPoolSize}" />   改成 <property name="maxPoolSize" value="10" /> 试试会不会报错?######检查过了,应该没问题###### jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8 jdbc.user=root jdbc.password=123456 jdbc.initialPoolSize=3 jdbc.minPoolSize=3 jdbc.maxPoolSize=10 上面是config.properties内容###### 我觉得是  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值 莫非是我c3p0版本的问题? 我的版本是0.9.1.2######试一试把dataSource中的所有值用${}代替一下,说不定就可以了呢######不用 ${} ,而直接赋值一切正常,  不会是c3p0不支持${} 吧?###### 很负责任地告诉你这跟c3p0没有半毛钱关系。仔细看看你报的是什么错?######2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}######  dataSource 直接将 ${jdbc.maxPoolSize} 作为字符串来处理了, 没有将 ${jdbc.maxPoolSize}转化为配置文件中相对映的值###### 报的错就是 2012-06-12 21:14:11,234 WARN com.mchange.v2.c3p0.DriverManagerDataSource : Could not load driverClass ${jdbc.driverClass} java.lang.ClassNotFoundException: ${jdbc.driverClass}    ###### 前段时间工期干得紧,没时间处理,现在有时间了, 在慢慢找找原因. 目前认为原因可能是spring加载bean顺序的问题, 通过断点发现spring先加载ComboPooledDataSource,之后才加载PropertyPlaceholderConfigurer.###### 我也遇到java.lang.ClassNotFoundException: ${jdbc.driverClass}这个报错, 想知道怎么解决的"

montos 2020-05-31 15:45:23 0 浏览量 回答数 0

问题

程序员报错QA大分享(1)

问问小秘 2020-06-18 15:46:14 8 浏览量 回答数 1

回答

你是在eclipse里面启动的吧,把项目打个war包,扔到tomcatwebapps里面,启动看下,是否报错,如果报错,说明eclipse容器配置的问题。eclipseserver容器删除重新创建添加一下CouldnotpublishserverconfigurationforTomcatv7.0Serveratlocalhost.MultipleContextshaveapathof"/cloudnote".这个是什么意思呢?多个上下文一条路劲?恩,好的,我试试,蟹蟹tomcat启动失败,检查数据库是否连接,检查端口是否占用或关闭,检查项目是否有错误严重:Exceptionstartingfilterloginjava.lang.ClassCastException:web.LoginFiltercannotbecasttojavax.servlet.Filter嗯呐,目前就剩最后一个啦,百度说的看不大懂,帮忙看下,这个怎么处理 你用的是Eclipse自带的Tomcat进行项目发布的,估计是发布的项目不同步了,右击那个Tomcat,clean一下吧,有好几个clean,每个都要来一下。 再不行,在Servers视图里面把Tomcat删除掉,重新new一个然后发布就行了一楼正解 已解决,谢谢!! 按照一楼办法解决了一大半,剩下一个路劲引用的报错,百度找了办法已解决,原来是之前弄过一个tomcat,后来重新添加了一个之后,和前面的文件冲突啦。已解决!谢谢

爱吃鱼的程序员 2020-06-08 11:05:49 0 浏览量 回答数 0

问题

NGINX配置优化

codeisee 2019-12-01 21:15:29 7547 浏览量 回答数 1

回答

nginx的静态页面都进不去的话应该是OS的TCP出了问题吧,要不看看你这个服务器当前的文件描述符数量`ulimit -n`。######回复 @snailkky : 解决了没?是这个原因吗?######回复 @口口口S口口口 : 真是太感谢你了! 我现在很怀疑就是这个原因造成的,我去试试看。######回复 @snailkky : :cold_sweat:我也只是知道这个东西,或许你可以写个测试用例试一下,用Jmeter定个6000进程访问看看######一般每个进程最多允许同时打开1024个文件,这1024个文件中还得除去每个进程必然打开的标准输入,标准输出,标准错误,服务器监听socket,进程间通讯的unix域socket等文件,那么剩下的可用于客户端socket连接的文件数就只有大概1024-10=1014个左右。也就是说缺省情况下,基于Linux的通讯程序最多允许同时1014个TCP并发连接。 是这个原因造成的吗?######65535###### 负载均衡,可以先开3个nginx,9个项目。######你这种就是性能不行, 上来就是分布式, 负载均衡。 明显几千连接,资源系统资源占用不高,是什么环节出了问题。 搞分布式,负载均衡就是掩耳盗铃######回复 @天空-sky : worker_connections 我配置了65535,另外worker_processes 我配置为8######nginx 还有worker_connections配置,可以了解下###### 密集IO情况下的Nginx调优方案: nginx进程数调到CPU核数的1.5倍,并且把每个进程平均绑到每个CPU上,多出来的让Linux自动调度到空闲CPU。 突然并发的情况下,Tomcat调优方法: 调成多进程+多线程运行模式,并且空闲时保留进程不少于CPU核数的一半。 nginx和tomcat采用fastcgi方式互交。###### 我简单看了一下,服务器很正确,监控正常,你的高峰瘫痪是指?######就是很多用户进不来,我自己也进不去,加载不出来,nginx部署的另一个静态页也无法访问###### 单机的配置降一降,拆分成多节点呗。###### 使用令牌桶做限流处理###### 我更好奇你这个监控是怎么看到的,为什么我的云监控没你的这么细######额,懂了,但是你好像是升级版的,我的是免费版的! 看了下升级版的,一个月都要1099:joy:######在云服务监控-云服务器ECS,你点进去,再点单个的监控,就可以显示这个图表了。###### 光看这个不太好分析,信息量太少。首先tomcat本身支持的并发就不高,到了6000肯定是没办法支持的,即使你将你的maxThread设置为2000,以你机器的性能也无法发挥线程的优势,反而增加了CPU线程切换的资源耗费。如果流量真的有这么大,那需要考虑做集群来承载。另外如果你的应用是IO型的,可以试试tomcat的NIO方式。######我是spring boot2,tomcat默认就是nio######高版本的tomcat好像默认都是nio,那如果是这样的话,高配服务器就发挥不了它的性能,感觉挺鸡肋的。######看症状貌似你的服务器是共享实例,积分用完之后cpu占用率不能超过20%######我这个是通用型实例 不是共享型###### 看下带宽######没有,因为我也怕是带宽的问题,然后我又升级到了60M,程序也没有变化。######回复 @snailkky : 出问题的那天,带宽使用情况怎么样?有没有出现不足######现在是5M带宽,但我那天临时升级到了20M

kun坤 2020-06-07 09:02:49 0 浏览量 回答数 0

问题

荆门开诊断证明-scc

游客5k2abgdj3m2ti 2019-12-01 22:09:00 1 浏览量 回答数 0

问题

JNI中调用任何标准输入输出处理流导致JVM崩溃?403.10 禁止访问:配置无效 

kun坤 2020-05-28 13:23:32 6 浏览量 回答数 1

问题

【精品问答】Java技术1000问(1)

问问小秘 2019-12-01 21:57:43 39926 浏览量 回答数 17

问题

深入分析字符编码之五-常见问题分析

夏天的日子 2019-12-01 21:13:23 4211 浏览量 回答数 0

回答

-Xms2500不知道行不行恩,我也发现了这个问题。-Xms2500不能这样写-Xms2500不能这样写-Xms2500M不能这样写-Xms2500M这样写 <spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;"> PermGenspace,报的是持久代内存溢出,只能通过调整<spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">-XX:PermSize来解决,另一点,jvm对持久代的内存是不经常进行回收的,这里面一般存的是类的结构信息,你看下你的程序是否大量的使用反射技术。我应该怎么让这个使用的内存回收啊?我有大量使用select语句。我看了当我的程序执行完后,我配置的信息显示eclipse的使用内存是700M,但是此时就没有任何方法去执行了,为什么这700M的内容没有释放啊? 一般只有fullgc的时候才会回收持久代的内存或者显示的调用System.gc(),不过不建议这样做,这样消耗会很大的,还是从程序上找问题吧,你查出的内容看下是否可以分批处理,一次不要查太多数据。<atarget='_blank'>@pengcheng_1024jvm每次fullgc的时机是不定的,只有jvm认为持久代已经将要超出配置的内存大小才会去回收不是很明白!!我有个方法调用别的方法当这个方法执行完以后,程序的使用内容有700M,但是程序不再执行后,为什么他不自动回收那?<spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">-Xms1024M <spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">-Xmx2500M <spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">-XX:PermSize=1224M  <spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">-XX:MaxPermSize=2500M <spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">你先看下你装的是多少位的jdk 上面的配置都有问题啊!<spanstyle="font-family:微软雅黑,Verdana,sans-serif,宋体;font-size:14px;line-height:22px;background-color:#FFFFFF;">-Xmx2500M直接设置为4000M看下设这么大还溢出。你代码肯定有问题。可是eclipse有时现在只是运行占用400M也会有这个错误!!为什么那?你为啥调整eclipse的参数呢,你用的是eclipse插件来查询吗,如是的eclipse启动的tomcat你应该调整tomcat启动参数呀

爱吃鱼的程序员 2020-06-22 13:10:54 0 浏览量 回答数 0

回答

初识 MyBatis MyBatis 是第一个支持自定义 SQL、存储过程和高级映射的类持久框架。MyBatis 消除了大部分 JDBC 的样板代码、手动设置参数以及检索结果。MyBatis 能够支持简单的 XML 和注解配置规则。使 Map 接口和 POJO 类映射到数据库字段和记录。 MyBatis 的特点 那么 MyBatis 具有什么特点呢?或许我们可以从如下几个方面来描述 MyBatis 中的 SQL 语句和主要业务代码分离,我们一般会把 MyBatis 中的 SQL 语句统一放在 XML 配置文件中,便于统一维护。 解除 SQL 与程序代码的耦合,通过提供 DAO 层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。SQL 和代码的分离,提高了可维护性。 MyBatis 比较简单和轻量 本身就很小且简单。没有任何第三方依赖,只要通过配置 jar 包,或者如果你使用 Maven 项目的话只需要配置 Maven 以来就可以。易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。 屏蔽样板代码 MyBatis 回屏蔽原始的 JDBC 样板代码,让你把更多的精力专注于 SQL 的书写和属性-字段映射上。 编写原生 SQL,支持多表关联 MyBatis 最主要的特点就是你可以手动编写 SQL 语句,能够支持多表关联查询。 提供映射标签,支持对象与数据库的 ORM 字段关系映射 ORM 是什么?对象关系映射(Object Relational Mapping,简称ORM) ,是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。 提供 XML 标签,支持编写动态 SQL。 你可以使用 MyBatis XML 标签,起到 SQL 模版的效果,减少繁杂的 SQL 语句,便于维护。 MyBatis 整体架构 MyBatis 最上面是接口层,接口层就是开发人员在 Mapper 或者是 Dao 接口中的接口定义,是查询、新增、更新还是删除操作;中间层是数据处理层,主要是配置 Mapper -> XML 层级之间的参数映射,SQL 解析,SQL 执行,结果映射的过程。上述两种流程都由基础支持层来提供功能支撑,基础支持层包括连接管理,事务管理,配置加载,缓存处理等。 接口层 在不与Spring 集成的情况下,使用 MyBatis 执行数据库的操作主要如下: InputStream is = Resources.getResourceAsStream("myBatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); sqlSession = factory.openSession(); 其中的SqlSessionFactory,SqlSession是 MyBatis 接口的核心类,尤其是 SqlSession,这个接口是MyBatis 中最重要的接口,这个接口能够让你执行命令,获取映射,管理事务。 数据处理层 配置解析 在 Mybatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configration 对象中。之后,根据该对象创建SqlSessionFactory 对象。待 Mybatis 初始化完成后,可以通过 SqlSessionFactory 创建 SqlSession 对象并开始数据库操作。 SQL 解析与 scripting 模块 Mybatis 实现的动态 SQL 语句,几乎可以编写出所有满足需要的 SQL。 Mybatis 中 scripting 模块会根据用户传入的参数,解析映射文件中定义的动态 SQL 节点,形成数据库能执行的SQL 语句。 SQL 执行 SQL 语句的执行涉及多个组件,包括 MyBatis 的四大核心,它们是: Executor、StatementHandler、ParameterHandler、ResultSetHandler。SQL 的执行过程可以用下面这幅图来表示 MyBatis 层级结构各个组件的介绍(这里只是简单介绍,具体介绍在后面): SqlSession: ,它是 MyBatis 核心 API,主要用来执行命令,获取映射,管理事务。接收开发人员提供 Statement Id 和参数。并返回操作结果。Executor :执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成以及查询缓存的维护。StatementHandler : 封装了JDBC Statement 操作,负责对 JDBC Statement 的操作,如设置参数、将Statement 结果集转换成 List 集合。ParameterHandler : 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。ResultSetHandler : 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。TypeHandler : 用于 Java 类型和 JDBC 类型之间的转换。MappedStatement : 动态 SQL 的封装SqlSource : 表示从 XML 文件或注释读取的映射语句的内容,它创建将从用户接收的输入参数传递给数据库的 SQL。Configuration: MyBatis 所有的配置信息都维持在 Configuration 对象之中。 基础支持层 反射模块 Mybatis 中的反射模块,对 Java 反射进行了很好的封装,提供了简易的 API,方便上层调用,并且对反射操作进行了一系列的优化,比如,缓存了类的 元数据(MetaClass)和对象的元数据(MetaObject),提高了反射操作的性能。 类型转换模块 Mybatis 的别名机制,能够简化配置文件,该机制是类型转换模块的主要功能之一。类型转换模块的另一个功能是实现 JDBC 类型与 Java 类型的转换。在 SQL 语句绑定参数时,会将数据由 Java 类型转换成 JDBC 类型;在映射结果集时,会将数据由 JDBC 类型转换成 Java 类型。 日志模块 在 Java 中,有很多优秀的日志框架,如 Log4j、Log4j2、slf4j 等。Mybatis 除了提供了详细的日志输出信息,还能够集成多种日志框架,其日志模块的主要功能就是集成第三方日志框架。 资源加载模块 该模块主要封装了类加载器,确定了类加载器的使用顺序,并提供了加载类文件和其它资源文件的功能。 解析器模块 该模块有两个主要功能:一个是封装了 XPath,为 Mybatis 初始化时解析 mybatis-config.xml配置文件以及映射配置文件提供支持;另一个为处理动态 SQL 语句中的占位符提供支持。 数据源模块 Mybatis 自身提供了相应的数据源实现,也提供了与第三方数据源集成的接口。数据源是开发中的常用组件之一,很多开源的数据源都提供了丰富的功能,如连接池、检测连接状态等,选择性能优秀的数据源组件,对于提供ORM 框架以及整个应用的性能都是非常重要的。 事务管理模块 一般地,Mybatis 与 Spring 框架集成,由 Spring 框架管理事务。但 Mybatis 自身对数据库事务进行了抽象,提供了相应的事务接口和简单实现。 缓存模块 Mybatis 中有一级缓存和二级缓存,这两级缓存都依赖于缓存模块中的实现。但是需要注意,这两级缓存与Mybatis 以及整个应用是运行在同一个 JVM 中的,共享同一块内存,如果这两级缓存中的数据量较大,则可能影响系统中其它功能,所以需要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品。 Binding 模块 在调用 SqlSession 相应方法执行数据库操作时,需要制定映射文件中定义的 SQL 节点,如果 SQL 中出现了拼写错误,那就只能在运行时才能发现。为了能尽早发现这种错误,Mybatis 通过 Binding 模块将用户自定义的Mapper 接口与映射文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。注意,在开发中,我们只是创建了 Mapper 接口,而并没有编写实现类,这是因为 Mybatis 自动为 Mapper 接口创建了动态代理对象。 MyBatis 核心组件 在认识了 MyBatis 并了解其基础架构之后,下面我们来看一下 MyBatis 的核心组件,就是这些组件实现了从 SQL 语句到映射到 JDBC 再到数据库字段之间的转换,执行 SQL 语句并输出结果集。首先来认识 MyBatis 的第一个核心组件 SqlSessionFactory 对于任何框架而言,在使用该框架之前都要经历过一系列的初始化流程,MyBatis 也不例外。MyBatis 的初始化流程如下 String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSessionFactory.openSession(); 上述流程中比较重要的一个对象就是SqlSessionFactory,SqlSessionFactory 是 MyBatis 框架中的一个接口,它主要负责的是 MyBatis 框架初始化操作 为开发人员提供SqlSession 对象 SqlSessionFactory 有两个实现类,一个是 SqlSessionManager 类,一个是 DefaultSqlSessionFactory 类 DefaultSqlSessionFactory : SqlSessionFactory 的默认实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。 SqlSessionManager : 已被废弃,原因大概是: SqlSessionManager 中需要维护一个自己的线程池,而使用MyBatis 更多的是要与 Spring 进行集成,并不会单独使用,所以维护自己的 ThreadLocal 并没有什么意义,所以 SqlSessionManager 已经不再使用。 ####SqlSessionFactory 的执行流程 下面来对 SqlSessionFactory 的执行流程来做一个分析 首先第一步是 SqlSessionFactory 的创建 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 1 从这行代码入手,首先创建了一个 SqlSessionFactoryBuilder 工厂,这是一个建造者模式的设计思想,由 builder 建造者来创建 SqlSessionFactory 工厂 然后调用 SqlSessionFactoryBuilder 中的 build 方法传递一个InputStream 输入流,Inputstream 输入流中就是你传过来的配置文件 mybatis-config.xml,SqlSessionFactoryBuilder 根据传入的 InputStream 输入流和environment、properties属性创建一个XMLConfigBuilder对象。SqlSessionFactoryBuilder 对象调用XMLConfigBuilder 的parse()方法,流程如下。 XMLConfigBuilder 会解析/configuration标签,configuration 是 MyBatis 中最重要的一个标签,下面流程会介绍 Configuration 标签。 MyBatis 默认使用 XPath 来解析标签,关于 XPath 的使用,参见 https://www.w3school.com.cn/xpath/index.asp 在 parseConfiguration 方法中,会对各个在 /configuration 中的标签进行解析 重要配置 说一下这些标签都是什么意思吧 properties,外部属性,这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。 <properties> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="root" /> </properties> 一般用来给 environment 标签中的 dataSource 赋值 <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> 还可以通过外部属性进行配置,但是我们这篇文章以原理为主,不会介绍太多应用层面的操作。 settings ,MyBatis 中极其重要的配置,它们会改变 MyBatis 的运行时行为。 settings 中配置有很多,具体可以参考 https://mybatis.org/mybatis-3/zh/configuration.html#settings 详细了解。这里介绍几个平常使用过程中比较重要的配置 一般使用如下配置 <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings> typeAliases,类型别名,类型别名是为 Java 类型设置的一个名字。 它只和 XML 配置有关。 <typeAliases> <typeAlias alias="Blog" type="domain.blog.Blog"/> </typeAliases> 当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。 typeHandlers,类型处理器,无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 在 org.apache.ibatis.type 包下有很多已经实现好的 TypeHandler,可以参考如下 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很方便的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。 objectFactory,对象工厂,MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。 public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(Class type, List constructorArgTypes, List constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public boolean isCollection(Class type) { return Collection.class.isAssignableFrom(type); } } 然后需要在 XML 中配置此对象工厂 <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory> plugins,插件开发,插件开发是 MyBatis 设计人员给开发人员留给自行开发的接口,MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。MyBatis 允许使用插件来拦截的方法调用包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler 接口,这几个接口也是 MyBatis 中非常重要的接口,我们下面会详细介绍这几个接口。 environments,MyBatis 环境配置,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。 这里注意一点,虽然 environments 可以指定多个环境,但是 SqlSessionFactory 只能有一个,为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties); databaseIdProvider ,数据库厂商标示,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 <databaseIdProvider type="DB_VENDOR"> <property name="SQL Server" value="sqlserver"/> <property name="DB2" value="db2"/> <property name="Oracle" value="oracle" /> </databaseIdProvider> mappers,映射器,这是告诉 MyBatis 去哪里找到这些 SQL 语句,mappers 映射配置有四种方式 上面的一个个属性都对应着一个解析方法,都是使用 XPath 把标签进行解析,解析完成后返回一个 DefaultSqlSessionFactory 对象,它是 SqlSessionFactory 的默认实现类。这就是 SqlSessionFactoryBuilder 的初始化流程,通过流程我们可以看到,初始化流程就是对一个个 /configuration 标签下子标签的解析过程。 SqlSession 在 MyBatis 初始化流程结束,也就是 SqlSessionFactoryBuilder -> SqlSessionFactory 的获取流程后,我们就可以通过 SqlSessionFactory 对象得到 SqlSession 然后执行 SQL 语句了。具体来看一下这个过程‘ 在 SqlSessionFactory.openSession 过程中我们可以看到,会调用到 DefaultSqlSessionFactory 中的 openSessionFromDataSource 方法,这个方法主要创建了两个与我们分析执行流程重要的对象,一个是 Executor 执行器对象,一个是 SqlSession 对象。执行器我们下面会说,现在来说一下 SqlSession 对象 SqlSession 对象是 MyBatis 中最重要的一个对象,这个接口能够让你执行命令,获取映射,管理事务。SqlSession 中定义了一系列模版方法,让你能够执行简单的 CRUD 操作,也可以通过 getMapper 获取 Mapper 层,执行自定义 SQL 语句,因为 SqlSession 在执行 SQL 语句之前是需要先开启一个会话,涉及到事务操作,所以还会有 commit、 rollback、close 等方法。这也是模版设计模式的一种应用。 MapperProxy MapperProxy 是 Mapper 映射 SQL 语句的关键对象,我们写的 Dao 层或者 Mapper 层都是通过 MapperProxy 来和对应的 SQL 语句进行绑定的。下面我们就来解释一下绑定过程 这就是 MyBatis 的核心绑定流程,我们可以看到 SqlSession 首先调用 getMapper 方法,我们刚才说到 SqlSession 是大哥级别的人物,只定义标准(有一句话是怎么说的来着,一流的企业做标准,二流的企业做品牌,三流的企业做产品)。 SqlSession 不愿意做的事情交给 Configuration 这个手下去做,但是 Configuration 也是有小弟的,它不愿意做的事情直接甩给小弟去做,这个小弟是谁呢?它就是 MapperRegistry,马上就到核心部分了。MapperRegistry 相当于项目经理,项目经理只从大面上把握项目进度,不需要知道手下的小弟是如何工作的,把任务完成了就好。最终真正干活的还是 MapperProxyFactory。看到这段代码 Proxy.newProxyInstance ,你是不是有一种恍然大悟的感觉,如果你没有的话,建议查阅一下动态代理的文章,这里推荐一篇 (https://www.jianshu.com/p/95970b089360) 也就是说,MyBatis 中 Mapper 和 SQL 语句的绑定正是通过动态代理来完成的。 通过动态代理,我们就可以方便的在 Dao 层或者 Mapper 层定义接口,实现自定义的增删改查操作了。那么具体的执行过程是怎么样呢?上面只是绑定过程,别着急,下面就来探讨一下 SQL 语句的执行过程。 MapperProxyFactory 会生成代理对象,这个对象就是 MapperProxy,最终会调用到 mapperMethod.execute 方法,execute 方法比较长,其实逻辑比较简单,就是判断是 插入、更新、删除 还是 查询 语句,其中如果是查询的话,还会判断返回值的类型,我们可以点进去看一下都是怎么设计的。 很多代码其实可以忽略,只看我标出来的重点就好了,我们可以看到,不管你前面经过多少道关卡处理,最终都逃不过 SqlSession 这个老大制定的标准。 我们以 selectList 为例,来看一下下面的执行过程。 这是 DefaultSqlSession 中 selectList 的代码,我们可以看到出现了 executor,这是什么呢?我们下面来解释。 Executor 还记得我们之前的流程中提到了 Executor(执行器) 这个概念吗?我们来回顾一下它第一次出现的位置。 由 Configuration 对象创建了一个 Executor 对象,这个 Executor 是干嘛的呢?下面我们就来认识一下 Executor 的继承结构 每一个 SqlSession 都会拥有一个 Executor 对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为 JDBC 中 Statement 的封装版。 也可以理解为 SQL 的执行引擎,要干活总得有一个发起人吧,可以把 Executor 理解为发起人的角色。 首先先从 Executor 的继承体系来认识一下 如上图所示,位于继承体系最顶层的是 Executor 执行器,它有两个实现类,分别是BaseExecutor和 CachingExecutor。 BaseExecutor 是一个抽象类,这种通过抽象的实现接口的方式是适配器设计模式之接口适配 的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。BaseExecutor 的子类有三个,分别是 SimpleExecutor、ReuseExecutor 和 BatchExecutor。 SimpleExecutor : 简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个Statement 对象,用完就直接关闭 Statement 对象(可以是 Statement 或者是 PreparedStatment 对象) ReuseExecutor : 可重用执行器,这里的重用指的是重复使用 Statement,它会在内部使用一个 Map 把创建的Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,如果存在 Statement 对象并且对应的 connection 还没有关闭的情况下就继续使用之前的 Statement 对象,并将其缓存起来。因为每一个 SqlSession 都有一个新的 Executor 对象,所以我们缓存在 ReuseExecutor 上的 Statement作用域是同一个 SqlSession。 BatchExecutor : 批处理执行器,用于将多个 SQL 一次性输出到数据库 CachingExecutor: 缓存执行器,先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器。 Executor 的创建和选择 我们上面提到 Executor 是由 Configuration 创建的,Configuration 会根据执行器的类型创建,如下 这一步就是执行器的创建过程,根据传入的 ExecutorType 类型来判断是哪种执行器,如果不指定 ExecutorType ,默认创建的是简单执行器。它的赋值可以通过两个地方进行赋值: 可以通过 标签来设置当前工程中所有的 SqlSession 对象使用默认的 Executor <settings> <!--取值范围 SIMPLE, REUSE, BATCH --> <setting name="defaultExecutorType" value="SIMPLE"/> </settings> 另外一种直接通过Java对方法赋值的方式 session = factory.openSession(ExecutorType.BATCH); Executor 的具体执行过程 Executor 中的大部分方法的调用链其实是差不多的,下面是深入源码分析执行过程,如果你没有时间或者暂时不想深入研究的话,给你下面的执行流程图作为参考。 我们紧跟着上面的 selectList 继续分析,它会调用到 executor.query 方法。 当有一个查询请求访问的时候,首先会经过 Executor 的实现类 CachingExecutor ,先从缓存中查询 SQL 是否是第一次执行,如果是第一次执行的话,那么就直接执行 SQL 语句,并创建缓存,如果第二次访问相同的 SQL 语句的话,那么就会直接从缓存中提取。 上面这段代码是从 selectList -> 从缓存中 query 的具体过程。可能你看到这里有些觉得类都是什么东西,我想鼓励你一下,把握重点,不用每段代码都看,从找到 SQL 的调用链路,其他代码想看的时候在看,看源码就是很容易发蒙,容易烦躁,但是切记一点,把握重点。 上面代码会判断缓存中是否有这条 SQL 语句的执行结果,如果没有的话,就再重新创建 Executor 执行器执行 SQL 语句,注意, list = doQuery 是真正执行 SQL 语句的过程,这个过程中会创建我们上面提到的三种执行器,这里我们使用的是简单执行器。 到这里,执行器所做的工作就完事了,Executor 会把后续的工作交给 StatementHandler 继续执行。下面我们来认识一下 StatementHandler 上面代码会判断缓存中是否有这条 SQL 语句的执行结果,如果没有的话,就再重新创建 Executor 执行器执行 SQL 语句,注意, list = doQuery 是真正执行 SQL 语句的过程,这个过程中会创建我们上面提到的三种执行器,这里我们使用的是简单执行器。 到这里,执行器所做的工作就完事了,Executor 会把后续的工作交给 StatementHandler 继续执行。下面我们来认识一下 StatementHandler StatementHandler 的继承结构 有没有感觉和 Executor 的继承体系很相似呢?最顶级接口是四大组件对象,分别有两个实现类 BaseStatementHandler 和 RoutingStatementHandler,BaseStatementHandler 有三个实现类, 他们分别是 SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler。 RoutingStatementHandler : RoutingStatementHandler 并没有对 Statement 对象进行使用,只是根据StatementType 来创建一个代理,代理的就是对应Handler的三种实现类。在MyBatis工作时,使用的StatementHandler 接口对象实际上就是 RoutingStatementHandler 对象。 BaseStatementHandler : 是 StatementHandler 接口的另一个实现类,它本身是一个抽象类,用于简化StatementHandler 接口实现的难度,属于适配器设计模式体现,它主要有三个实现类 SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句。CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。 StatementHandler 的创建和源码分析 我们继续来分析上面 query 的调用链路,StatementHandler 的创建过程如下 MyBatis 会根据 SQL 语句的类型进行对应 StatementHandler 的创建。我们以预处理 StatementHandler 为例来讲解一下 执行器不仅掌管着 StatementHandler 的创建,还掌管着创建 Statement 对象,设置参数等,在创建完 PreparedStatement 之后,我们需要对参数进行处理了。 如 如果用一副图来表示一下这个执行流程的话我想是这样 这里我们先暂停一下,来认识一下第三个核心组件 ParameterHandler ParameterHandler - ParameterHandler 介绍 ParameterHandler 相比于其他的组件就简单很多了,ParameterHandler 译为参数处理器,负责为 PreparedStatement 的 sql 语句参数动态赋值,这个接口很简单只有两个方法 ParameterHandler 只有一个实现类 DefaultParameterHandler , 它实现了这两个方法。 getParameterObject: 用于读取参数setParameters: 用于对 PreparedStatement 的参数赋值ParameterHandler 的解析过程 上面我们讨论过了 ParameterHandler 的创建过程,下面我们继续上面 parameterSize 流程 这就是具体参数的解析过程了,下面我们来描述一下 下面用一个流程图表示一下 ParameterHandler 的解析过程,以简单执行器为例 我们在完成 ParameterHandler 对 SQL 参数的预处理后,回到 SimpleExecutor 中的 doQuery 方法 上面又引出来了一个重要的组件那就是 ResultSetHandler,下面我们来认识一下这个组件 ResultSetHandler - ResultSetHandler 简介 ResultSetHandler 也是一个非常简单的接口 ResultSetHandler 是一个接口,它只有一个默认的实现类,像是 ParameterHandler 一样,它的默认实现类是DefaultResultSetHandler ResultSetHandler 解析过程 MyBatis 只有一个默认的实现类就是 DefaultResultSetHandler,DefaultResultSetHandler 主要负责处理两件事 处理 Statement 执行后产生的结果集,生成结果列表 处理存储过程执行后的输出参数 按照 Mapper 文件中配置的 ResultType 或 ResultMap 来封装成对应的对象,最后将封装的对象返回即可。 其中涉及的主要对象有: ResultSetWrapper : 结果集的包装器,主要针对结果集进行的一层包装,它的主要属性有 ResultSet : Java JDBC ResultSet 接口表示数据库查询的结果。 有关查询的文本显示了如何将查询结果作为java.sql.ResultSet 返回。 然后迭代此ResultSet以检查结果。 TypeHandlerRegistry: 类型注册器,TypeHandlerRegistry 在初始化的时候会把所有的 Java类型和类型转换器进行注册。 ColumnNames: 字段的名称,也就是查询操作需要返回的字段名称 ClassNames: 字段的类型名称,也就是 ColumnNames 每个字段名称的类型 JdbcTypes: JDBC 的类型,也就是 java.sql.Types 类型 ResultMap: 负责处理更复杂的映射关系 在 DefaultResultSetHandler 中处理完结果映射,并把上述结构返回给调用的客户端,从而执行完成一条完整的SQL语句。 内容转载自:CSDN博主:cxuann 原文链接:https://blog.csdn.net/qq_36894974/article/details/104132876?depth_1-utm_source=distribute.pc_feed.none-task&request_id=&utm_source=distribute.pc_feed.none-task

问问小秘 2020-03-05 15:44:27 0 浏览量 回答数 0

问题

Apache Flink常见问题汇总【精品问答】

黄一刀 2020-05-19 17:51:47 11230 浏览量 回答数 2

回答

Layout Go工程项目的整体组织 首先我们看一下整个 Go 工程是怎么组织起来的。 很多同事都在用 GitLab 的,GitLab 的一个 group 里面可以创建很多 project。如果我们进行微服务化改造,以前很多巨石架构的应用可能就拆成了很多个独立的小应用。那么这么多小应用,你是要建 N 个 project 去维护,还是说按照部门或者组来组织这些项目呢?在 B 站的话,我们之前因为是 Monorepo,现在是按照部门去组织管理代码,就是说在单个 GitLab 的 project 里面是有多个 app 的,每一个 app 就表示一个独立的微服务,它可以独立去交付部署。所以说我们看到下面这张图里面,app 的目录里面是有好多个子目录的,比方说我们的评论服务,会员服务。跟 app 同级的目录有一个叫 pkg,可以存放业务有关的公共库。这是我们的一个组织方式。当然,还有一种方式,你可以按照 GitLab 的 project 去组织,但我觉得这样的话可能相对要创建的 project 会非常多。 如果你按部门组织的话,部门里面有很多 app,app 目录怎么去组织?我们实际上会给每一个 app 取一个全局唯一名称,可以理解为有点像 DNS 那个名称。我们对业务的命名也是一样的,我们基本上是三段式的命名,比如账号业务,它是一个账号业务、服务、子服务的三段命名。三段命名以后,在这个 app 目录里面,你也可以按照这三层来组织。比如我们刚刚说的账号目录,我可能就是 account 目录,然后 VIP,在 VIP 目录下可能会放各种各样的不同角色的微服务,比方说可能有一些是做 job,做定时任务或者流式处理的一些任务,有可能是做对外暴露的 API 的一些服务,这个就是我们关于整个大的 app 的组织的一种形式。 微服务中的 app 服务分类 微服务中单个 app 的服务里又分为几类不同的角色。我们基本上会把 app 分为 interface(BFF)、service、job(补充:还有一个 task,偏向定时执行,job 偏向流式) 和 admin。 Interface 是对外的业务网关服务,因为我们最终是面向终端用户的 API,面向 app,面向 PC 场景的,我们把这个叫成业务网关。因为我们不是统一的网关,我们可能是按照大的业务线去独立分拆的一些子网关,这个的话可以作为一个对外暴露的 HTTP 接口的一个目录去组织它的代码,当然也可能是 gRPC 的(参考 B 站对外的 gRPC Moss 分享)。 Service 这个角色主要是面向对内通信的微服务,它不直接对外。也就是说,业务网关的请求会转发或者是会 call 我们的内部的 service,它们之间的通讯可能是使用自己的 RPC,在 b 站我们主要是使用 gRPC。使用 gRPC 通讯以后,service 它因为不直接对外,service 之间可能也可以相互去 call。 Admin 区别于 service,很多应用除了有面向用户的一些接口,实际上还有面向企业内部的一些运营侧的需求,通常数据权限更高,从安全设计角度需要代码物理层面隔离,避免意外。 第四个是 ecode。我们当时也在内部争论了很久,我们的错误码定义到底是放在哪里?我们目前的做法是,一个应用里面,假设你有多种角色,它们可能会复用一些错误码。所以说我们会把我们的 ecode 给单独抽出来,在这一个应用里面是可以复用的。注意,它只在这一个应用里面复用,它不会去跨服跨目录应用,它是针对业务场景的一个业务错误码的组织。 App 目录组织 我们除了一个应用里面多种角色的这种情况,现在展开讲一下具体到一个 service 里面,它到底是怎么组织的。我们的 app 目录下大概会有 api、cmd、configs、 internal 目录,目录里一般还会放置 README、CHANGELOG、OWNERS。 API 是放置 api 定义以及对应的生成的 client 代码,包含基于 pb 定义(我们使用 PB 作为 DSL 描述 API) 生成的 swagger.json。 而 cmd,就是放 main 函数的。Configs 目录主要是放一些服务所需的配置文件,比方说说我们可能会使用 TOML 或者是使用 YAML 文件。 Internal 的话,它里面有四个子目录,分别是 model、dao、service 和 server。Model 的定位职责就是对我们底层存储的持久化层或者存储层的数据的映射,它是具体的 Go 的一个 struct。我们再看 dao,你实际就是要操作 MySQL 或者 Redis,最终返回的就是这些 model(存储映射)。Service 组织起来比较简单,就是我们通过 dao 里面的各个方法来完成一个完整的业务逻辑。我们还看到有个 server,因为我一个微服务有可能企业内部不一定所有 RPC 都统一,那我们处于过渡阶段,所以 server 里面会有两个小目录,一个是 HTTP 目录,暴露的是 HTTP 接口,还有一个是 gRPC 目录,我们会暴露 gRPC 的协议。所以在 server 里面,两个不同的启动的 server,就是说一个服务和启动两个端口,然后去暴露不同的协议,HTTP 接 RPC,它实际上会先 call 到 service,service 再 call 到 dao,dao 实际上会使用 model 的一些数据定义 struct。但这里面有一个非常重要的就是,因为这个结构体不能够直接返回给我们的 api 做外对外暴露来使用,为什么?因为可能从数据库里面取的敏感字段,当我们实际要返回到 api 的时候,可能要隐藏掉一些字段,在 Java 里面,会抽象的一个叫 DTO 的对象,它只是用来传输用的,同理,在我们 Go 里面,实际也会把这些 model 的一些结构体映射成 api 里面的结构体(基于 PB Message 生成代码后的 struct)。 Rob Pike 当时说过的一句话,a little copying is better than a little dependency,我们就遵循了这个理念。在我们这个目录结构里面,有 internal 目录,我们知道 Go 的目录只允许这个目录里面的人去 import 到它,跨目录的人实际是不能直接引用到它的。所以说,我们看到 service 有一个 model,那我的 job 代码,我做一些定时任务的代码或者是我的网关代码有可能会映射同一个 model,那是不是要把这个 model 放到上一级目录让大家共享?对于这个问题,其实我们当时内部也争论过很久。我们认为,每一个微服务应该只对自己的 model 负责,所以我们宁愿去做一小部分的代码 copy,也不会去为了几个服务之间要共享这一点点代码,去把这个 model 提到和 app 目录级别去共用,因为你一改全错,当然了,你如果是拷贝的话,就是每个地方都要去改,那我们觉得,依赖的问题可能会比拷贝代码相对来说还是要更复杂的。 这个是一个标准的 PB 文件,就是我们内部的一个 demo 的 service。最上面的 package 是 PB 的包名,demo.service.v1,这个包使用的是三段式命名,全局唯一的名称。那这个名称为什么不是用 ID?我见过有些公司对内部做的 CMDB 或者做服务树去管理企业内部微服务的时候,是用了一些名称加上 ID 来搞定唯一性,但是我们知道后面那一串 ID 数字是不容易被传播或者是不容易被记住的,这也是 DNS 出来的一个意义,所以我们用绝对唯一的一个名称来表示这个包的名字,在后面带上这一个 PB 文件的版本号 V1。 我们看第二段定义,它有个 Service Demo 代码,其实就表示了我们这个服务要启动的服务的一个名称,我们看到这个服务名称里面有很多个 RPC 的方法,表示最终这一个应用或者这个 service 要对外暴露这几个 RPC 的方法。这里面有个小细节,我们看一下 SayHello 这个方法,实际它有 option 的一个选项。通过这一个 PB 文件,你既可以描述出你要暴露的是 gRPC 协议,又暴露出 HTTP 的一个接口,这个好处是你只需要一个 PB 文件描述你暴露的所有 api。我们回想一下,我们刚刚目录里面有个 api 目录,实际这里面就是放这一个 PB 文件,描述这一个工程到底返回的接口是什么。不管是 gRPC 还是 HTTP 都是这一个文件。还有一个好处是什么?实际上我们可以在 PB 文件里面加上很多的注释。用 PB 文件的好处是你不需要额外地再去写文档,因为写文档和写服务的定义,它本质上是两个步骤,特别容易不一致,接口改了,文档不同步。我们如果基于这一个 PB 文件,它生成的 service 代码或者调用代码或者是文档都是唯一的。 依赖顺序与 api 维护 就像我刚刚讲到的,model 是一个存储层的结构体的一一映射,dao 处理一些数据读写包,比方说数据库缓存,server 的话就是启动了一些 gRPC 或者 HTTP Server,所以它整个依赖顺序如下:main 函数启动 server,server 会依赖 api 定义好的 PB 文件,定义好这些方法或者是服务名之后,实际上生成代码的时候,比方说 protocbuf 生成代码的时候,它会把抽象 interface 生成好。然后我们看一下 service,它实际上是弱依赖的 api,就是说我的 server 启动以后,要注册一个具体的业务代码的逻辑,映射方法,映射名字,实际上是弱依赖的 api 生成的 interface 的代码,你就可以很方便地启动你的 server,把你具体的 service 的业务逻辑给注入到这个 server,和方法进行一一绑定。最后,dao 和 service 实际上都会依赖这个 model。 因为我们在 PB 里面定义了一些 message,这些 message 生成的 Go 的 struct 和刚刚 model 的 struct 是两个不同的对象,所以说你要去手动 copy 它,把它最终返回。但是为了快捷,你不可能每次手动去写这些代码,因为它要做 mapping,所以我们又把 K8s 里类似 DeepCopy 的两个结构体相互拷贝的工具给抠出来了,方便我们内部 model 和 api 的 message 两个代码相互拷贝的时候,可以少写一些代码,减少一些工作量。 上面讲的就是我们关于工程的一些 layout 实践。简单回溯一下,大概分为几块,第一就是 app 是怎么组织的,app 里面有多种角色的服务是怎么组织的,第三就是一个 app 里面的目录是怎么组织的,最后我重点讲了一下 api 是怎么维护的。 Unittest 测试方法论 现在回顾一下单元测试。我们先看这张图,这张图是我从《Google 软件测试之道》这本书里面抠出来的,它想表达的意思就是最小型的测试不能给我们的最终项目的质量带来最大的信心,它比较容易带来一些优秀的代码质量,良好的异常处理等等。但是对于一个面向用户场景的服务,你只有做大型测试,比方做接口测试,在 App 上验收功能的这种测试,你应用交付的信心可能会更足。这个其实要表达的就是一个“721 原则”。我们就是 70% 写小型测试,可以理解为单元测试,因为它相对来说好写,针对方法级别。20% 是做一些中型测试,可能你要连调几个项目去完成你的 api。剩下 10% 是大型测试,因为它是最终面向用户场景的,你要去使用我们的 App,或者用一些测试 App 去测试它。这个就是测试的一些简单的方法论。 单元测试原则 我们怎么去对待 Go 里面的单元测试?在《Google 软件测试之道》这本书里面,它强调的是对于一个小型测试,一个单元测试,它要有几个特质。它不能依赖外部的一些环境,比如我们公司有测试环境,有持续集成环境,有功能测试环境,你不能依赖这些环境构建自己的单元测试,因为测试环境容易被破坏,它容易有数据的变更,数据容易不一致,你之前构建的案例重跑的话可能就会失败。 我觉得单元测试主要有四点要求。第一,快速,你不能说你跑个单元测试要几分钟。第二,要环境一致,也就是说你跑测试前和跑测试后,它的环境是一致的。第三,你写的所有单元测试的方法可以以任意顺序执行,不应该有先后的依赖,如果有依赖,也是在你测试的这个方法里面,自己去 setup 和 teardown,不应该有 Test Stub 函数存在顺序依赖。第四,基于第三点,你可以做并行的单元测试,假设我写了一百个单元测试,一个个跑肯定特别慢。 doker-compose 最近一段时间,我们演进到基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行 unittest 场景下的容器依赖问题。 首先,你要跑单元测试,你不应该用 VPN 连到公司的环境,好比我在星巴克点杯咖啡也可以写单元测试,也可以跑成功。基于这一点,Docker 实际上是非常好的解决方式。我们也有同学说,其他语言有一些 in-process 的 mock,是不是可以启动 MySQL 的 mock ,然后在 in-process 上跑?可以,但是有一个问题,你每一个语言都要写一个这样的 mock ,而且要写非常多种,因为我们中间件越来越多,MySQL,HBase,Kafka,什么都有,你很难覆盖所有的组件 Mock。这种 mock 或者 in-process 的实现不能完整地代表线上的情况,比方说,你可能 mock 了一个 MySQL,检测到 query 或者 insert ,没问题,但是你实际要跑一个 transaction,要验证一些功能就未必能做得非常完善了。所以基于这个原因,我们当时选择了 docker-compose,可以很好地解决这个问题。 我们对开发人员的要求就是,你本地需要装 Docker,我们开发人员大部分都是用 Mac,相对来说也比较简单,Windows 也能搞定,如果是 Linux 的话就更简单了。本地安装 Docker,本质上的理解就是无侵入式的环境初始化,因为你在容器里面,你拉起一个 MySQL,你自己来初始化数据。在这个容器被销毁以后,它的环境实际上就满足了我们刚刚提的环境一致的问题,因为它相当于被重置了,也可以很方便地快速重置环境,也可以随时随地运行,你不需要依赖任何外部服务,这个外部服务指的是像 MySQL 这种外部服务。当然,如果你的单元测试依赖另外一个 RPC 的 service 的话,PB 的定义会生成一个 interface,你可以把那个 interface 代码给 mock 掉,所以这个也是能做掉的。对于小型测试来说,你不依赖任何外部环境,你也能够快速完成。 另外,docker-compose 是声明式的 API,你可以声明你要用 MySQL,Redis,这个其实就是一个配置文件,非常简单。这个就是我们在单元测试上的一些实践。 我们现在看一下,service 目录里面多了一个 test 目录,我们会在这个里面放 docker-compose 的 YAML 文件来表示这次单元化测试需要初始化哪些资源,你要构建自己的一些测试的数据集。因为是这样的,你是写 dao 层的单元测试的话,可能就需要 database.sql 做一些数据的初始化,如果你是做 service 的单元测试的话,实际你可以把整个 dao 给 mock 掉,我觉得反而还相对简单,所以我们主要针对场景就是在 dao 里面偏持久层的,利用 docker-compose 来解决。 容器的拉起,容器的销毁,这些工作到底谁来做?是开发同学自己去拉起和销毁,还是说你能够把它做成一个 Library,让我们的同学写单元测试的时候比较方便?我倾向的是后者。所以在我们最终写单元测试的时候,你可以很方便地 setup 一个依赖文件,去 setup 你的容器的一些信息,或者把它销毁掉。所以说,你把环境准备好以后,最终可以跑测试代码也非常方便。当然我们也提供了一些命令函,就是 binary 的一些工具,它可以针对各个语言方便地拉起容器和销毁容器,然后再去执行代码,所以我们也提供了一些快捷的方式。 刚刚我也提到了,就是我们对于 service 也好,API 也好,因为依赖下层的 dao 或者依赖下层的 service,你都很方便 mock 掉,这个写单元测试相对简单,这个我不展开讲,你可以使用 GoMock 或者 GoMonkey 实现这个功能。 Toolchain 我们利用多个 docker-compose 来解决 dao 层的单元测试,那对于我刚刚提到的项目的一些规范,单元测试的一些模板,甚至是我写了一些 dao 的一些占位符,或者写了一些 service 代码的一些占位符,你有没有考虑过这种约束有没有人会去遵循?所以我这里要强调一点,工具一定要大于约束和文档,你写了约束,写了文档,那么你最终要通过工具把它落实。所以在我们内部会有一个类似 go tool 的脚手架,叫 Kratos Tool,把我们刚刚说的约定规范都通过这个工具一键初始化。 对于我们内部的工具集,我们大概会分为几块。第一块就是 API 的,就是你写一个 PB 文件,你可以基于这个 PB 文件生成 gRPC,HTTP 的框架代码,你也可以基于这个 PB 文件生成 swagger 的一些 JSON 文件或者是 Markdown 文件。当然了,我们还会生成一些 API,用于 debug 的 client 方便去调试,因为我们知道,gRPC 调试起来相对麻烦一些,你要去写代码。 还有一些工具是针对 project 的,一键生成整个应用的 layout,非常方便。我们还提了 model,就是方便 model 和 DTO,DTO 就是 API 里面定义的 message 的 struct 做 DeepCopy,这个也是一个工具。 对于 cache 的话,我们操作 memcache,操作 Redis 经常会要做什么逻辑?假如我们有一个 cache aside 场景,你读了一个 cache,cache miss 要回原 DB,你要把这个缓存回塞回去,甚至你可能这个回塞缓存想异步化,甚至是你要去读这个 DB 的时候要做归并回源(singleflight),我们把这些东西做成一些工具,让它整个回源到 DB 的逻辑更加简单,就是把这些场景描述出来,然后你通过工具可以一键生成这些代码,所以也是会比较方便。 我们再看最后一个,就是 test 的一些工具。我们会基于项目里面,比方说 dao 或者是 service 定义的 interface 去帮你写好 mock 的代码,我直接在里面填,只要填代码逻辑就行了,所以也会加速我们的生产。 上图是 Kratos 的一个 demo,基本就是支持了一些 command。这里就是一个 kratos new kratos-demo 的一个工程,-d YourPath 把它导到某一个路径去,--proto 顺便把 API 里面的 proto 代码也生成了,所以非常简单,一行就可以很快速启动一个 HTTP 或者 gRPC 服务。 我们知道,一个微服务的框架实际非常重,有很多初始化的方式等等,非常麻烦。所以说,你通过脚手架的方式就会非常方便,工具大于约定和文档这个这个理念就是这么来的。 Configuration 讲完工具以后,最后讲一下配置文件。我为什么单独提一下配置文件?实际它也是工程化的一部分。我们一个线上的业务服务包含三大块,第一,应用程序,第二,配置文件,第三,数据集。配置文件最容易导致线上出 bug,因为你改一行配置,整个行为可能跟 App 想要的行为完全不一样。而且我们的代码的开发交付需要经过哪些流程?需要 commit 代码,需要 review,需要单元测试,需要 CD,需要交付到线上,需要灰度,它的整个流程是非常长的。在一步步的环境里面,你的 bug 需要前置解决,越前置解决,成本越低。因为你的代码的开发流程是这么一个 pipeline,所以 bug 最终流到线上的概率很低,但是配置文件没有经过这么复杂的流程,可能大家发现线上有个问题,决定要改个线上配置,就去配置中心或者配置文件改,然后 push 上线,接着就问题了,这个其实很常见。 从 SRE 的角度来说,导致线上故障的主因就是来自配置变更,所以 SRE 很大的工作是控制变更管理,如果能把变更管理做好,实际上很多问题都不会出现。配置既然在整个应用里面这么重要,那在我们整个框架或者在 Go 的工程化实践里面,我们应该对配置文件做一些什么事情? 我觉得是几个。第一,我们的目标是什么?配置文件不应该太复杂,我见过很多框架,或者是业务的一些框架,它实际功能非常强大,但是它的配置文件超级多。我就发现有个习惯,只要有一个同事写错了这个配置,当我新起一个项目的时候,一定会有人把这个错误的配置拷贝到另外一个系统里面去。然后当发现这个应用出问题的时候,我们一般都会内部说一下,你看看其他同事有没有也配错的,实际这个配错概率非常高。因为你的配置选项越多,复杂性越高,它越容易出错。所以第一个要素就是说,尽量避免复杂的配置文件。配得越多,越容易出错。 第二,实际我们的配置方式也非常多,有些用 JSON,有些用 YAML,有些用 Properties,有些用 INI。那能不能收敛成通用的一种方式呢?无论它是用 Python 的脚本也好,或者是用 JSON 也好,你只要有一种唯一的约定,不需要太多样的配置方式,对我们的运维,对我们的 SRE 同时来说,他跨项目的变更成本会变低。 第三,一定要往简单化去努力。这句话其实包含了几个方面的含义。首先,我们很多配置它到底是必须的还是可选的,如果是可选,配置文件是不是就可以把它踢掉,甚至不要出现?我曾经有一次看到我们 Java 同事的配置 retry 有一个重试默认是零,内部重试是 80 次,直接把 Redis cluster 打故障了,为什么?其实这种事故很低级,所以简单化努力的另外一层含义是指,我们在框架层面,尤其是提供 SDK 或者是提供 framework 的这些同事尽量要做一些防御编程,让这种错配漏配也处于一个可控的范围,比方重试 80 次,你觉得哪个 SDK 会这么做?所以这个是我们要考虑的。但是还有一点要强调的是,我们对于业务开发的同事,我们的配置应该足够的简单,这个简单还包含,如果你的日志基本上都是写在这个目录,你就不要提供这个配置给他,反而不容易出错。但是对于我们内部的一些 infrastructure,它可能需要非常复杂的配置来优化,根据我的场景去做优化,所以它是两种场景,一种是业务场景,足够简单,一种是我要针对我的通用的 infrastructure 去做场景的优化,需要很复杂的配置,所以它是两种场景,所以我们要想清楚你的业务到底是哪一种形态。 还有一个问题就是我们配置文件一定要做好权限的变更和跟踪,因为我们知道上线出问题的时候,我们的第一想法不是查 bug,是先止损,止损先找最近有没有变更。如果发现有变更,一般是先回滚,回滚的时候,我们通常只回滚了应用程序,而忘记回滚了配置。每个公司可能内部的配置中心,或者是配置场景,或者跟我们的二进制的交付上线都不一样,那么这里的理念就是你的应用程序和配置文件一定是同一个版本,或者是某种意义上让他们产生一个版本的映射,比方说你的应用程序 1.0,你的配置文件 2.0,它们之间存在一个强绑定关系,我们在回滚的时候应该是一起回滚的。我们曾经也因为类似的一些不兼容的配置的变更,二进制程序上线,但配置文件忘记回滚,出现过事故,所以这个是要强调的。 另外,配置的变更也要经过 review,如果没问题,应该也是按照 App 发布一样,先灰度,再放量,再全量等等类似的一种方式去推,演进式的这种发布,我们也叫滚动发布,我觉得配置文件也是一样的思路。 加入阿里云钉钉群享福利:每周技术直播,定期群内有奖活动、大咖问答 原文链接

有只黑白猫 2020-01-09 17:29:54 0 浏览量 回答数 0

问题

程序为什么会超时? 7月4日 【今日算法】

游客ih62co2qqq5ww 2020-07-06 23:15:34 113 浏览量 回答数 1
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 企业建站模板