NETCTOSS代码实现第二版

前言

资费增加功能需求分析与程序设计

  • 那现在呢,我们把查询功能做了,然后呢,我们也了解了这些,相关的一些内容。那说完以后啊,咱们对这个web项目开发,应该有点体会了,体会的是什么呢,我们开发web项目,一定不能乱来,一定是得有套路对吧,得有一个顺序啊,那我想讲的是,这个开发的套路到底是什么,还是回到一开始的这个这个,说的这个思路,还是那句话,一定先搞清楚需求,其次要有设计,你设计明确了,逐个开发,容易解决问题,所以你课后做练习的时候啊,也是啊,也是要把这个设计呢,多想一想,那一开始,你不会,正是因为不会,你才要练,你才要画啊,自己拿个笔,拿个纸,动个手,画一画,看一看。那这个功能呢,说完以后,下面呢,我们要说一下另外一个功能。
    在这里插入图片描述
  • 那第2个功能是什么呢,看一下,我们在查询的基础上,下面要做的是增加的功能,是吧,做这个。
    在这里插入图片描述

根据需求拆分请求

  • 然后呢,这个增加的功能,那我先给大家介绍一下,这个开发的思路,那我现在呢,要开发这个增加功能,我们从何入手,先怎么办,还是先搞清楚需求对吧,就是这个玩应怎么操作,对吧,怎么用,得整明白,不过这个好像也没什么,增加么,一点增加,打开增加页面对吧,填数据呗,填数据点保存,就存呗对吧,存完以后去哪啊,还回到查询对吧,就可以了,是这意思吧,跟以前我们所说的那个增加员工是差不多的( Servlet3:增加员工功能案例 ),是类似的,那大概就这个流程。
  • 那了解了这个业务啊,这个业务简单,了解以后,然后呢,再想下一步怎么办,了解业务以后,你得做设计对吧,做设计怎么做呢,我的建议是,我们要把这个功能,拆分成若干请求,因为web项目,任何功能都是由请求构成的,对吧,一个请求一个请求处理的,一个请求就是一个单元,拆分请求,那你看,那你觉得这个增加有几个请求,几个,两个,第一个是怎么着,点增加时,点开增加页面,是一个请求么,是的;然后呢,保存,存数据是一个,对吧,两个,其实还有一个,你保存完之后,不要跳转到查询么,那还涉及到查询,那查询也是一个请求吧,只不过呢,查询已经完成了,对吧,但不管怎么样啊,整个流程是3个请求对吧,不过最后一个已经有了。
  • 那这样啊,咱们做增加的时候啊,我们当前阶段的主要的内容是在哪呢,是在Servlet和jsp这块,是在服务端,不是在前端,我们增加时,按理来说是要验证,去验证资费名是不是满足这个格式(50长度的字母、数字、汉字和下划线的组合),它的基本时长是满不满足这个格式(1-600之间的整数),是这样吧,你得写正则表达式,那这得写js了,这个js啊,我就不演示了,如果你有兴趣,自己写一写,那怎么写呢,之前的js阶段时,也介绍过正则表达式对吧,演示了一个登录功能,可以做一个参考,和那个差不多,可以参考那个。当然,没有精力的话,这个先不写,那么我们后面,后面阶段做的云笔记项目,那个时候会写大量的js,现在的话,这个阶段不在于,重点不在于js,明白吧,所以这个我就不写了。

根据请求推导程序内部执行过程

  • 那3个请求啊,每个请求,是怎么运行的,3个请求之间是什么关系,我们还得这个画一下。那web项目啊,有浏览器,其次呢,有服务器,那个数据库,我就不画了,反正浏览器访问服务器,数据库我就不画了,就这样了啊。那你看,首先呢,我们增加的起点,是我们在查询页面上,点增加按钮对吧,我点增加,然后呢,点增加的时候呢,就访问服务器了,那访问服务器,打开增加页面,静态的还是动态的,静态的,为什么呢,因为打开以后什么都没有,每个人看都一样对吧,永远都一样,静态的,是这样的,以前模拟增加员工时也是这么说的,但其实呢,并不是这样。这个,当然没做过项目不理解,这也很正常,为什么我说这个增加网页是动态的,不是静态的呢,因为它有上面菜单栏这一排图标:
    在这里插入图片描述
  • 这一排图标是这个软件的功能,那一个软件不同的用户进来,看到的功能,应该有所差别,不同的用户进来,这个图标应该有区别,这个叫权限管理,权限控制,比如说啊,同样的tts这个软件,你进去可以考试,我进去可以阅卷,是这意思吧,不同的身份进去是不一样的,同样的这个淘宝,咱们进去可以买东西,店家进去可以上传宝贝,是这样吧,有差别,对吧,同样一个论坛,我们进去能看帖子,能发个帖子,然后呢,管理员进去能够,能够这个删帖,能够这个加精华,置顶啥的,对吧,是这样的。那几乎任何软件都有权限管理,要控制用户呢,这个访问,不能说任何功能都让他访问,所以我们这个软件也是一样啊,不同的用户进来,看见的图标呢,不一样。
  • 不过呢,因为时间有限,咱们这段课呢,不是以讲项目为主,所以这个权限管理啊,没法讲。因为要想讲权限管理的话,必须讲角色管理这个模块,讲管理员模块,这两模块讲完以后再去讲,这至少得有,得讲个大概5天的时间才能搞定,时间不允许,所以我不细说了,总之呢,这个项目做完整,这应该是动态的明白吧,图标是动态初始化的,所以呢,我也把它做成动态的,行么,就这样,所以呢,我们访问的是服务器端的谁呢,Servlet,那我们这个项目中,只有一个Servlet对吧,因为项目小啊,MainServlet,然后呢,增加,打开增加页面,我这个方法叫什么呢,叫toAddCost(),行吧,就叫这个名字啊,toAddCost,即MainServlet.toAddCost(),就去增加,去增加资费这个意思。
  • 然后啊,那这个路径是什么,也做个规定啊,我规定这个访问路径就是toAddCost.do,是有规律的对吧,其实说白了,这个方法叫什么,路径就是什么,/toAddCost.do,这是我的一个习惯啊,规律啊,叫这个名字啊。那访问它以后,它这个打开增加页面,它这个有什么业务逻辑需要处理么,有没有业务逻辑呢,没有,就打开了页面对吧,没有业务,当然做完整要有,因为你要初始化那个图标对吧,做完整要有,但是现在没有,所以呢,这里我就不调dao了,不需要访问数据库了,直接转发对吧,是吧,有人说,哎,你这,就什么业务都没有,有必要还转发么,直接写个jsp,访问jsp行不行呢,不要这样啊,就是说,我们即便是没有业务,也要遵守MVC的规则,明白吧,就将来,我们会讲什么呢,我们怎么去统一管理,咱们项目中的这个请求,做一些公共的业务,那前提是呢,它们的结构一样,明白吧,套路一样才可以,所以,一旦说你改变了套路,将来代码不好管理。将来再讲啊,反正也是MVC啊。
  • 然后呢,这个页面呢,我把它放到WEB-INF之下,然后呢,放到cost下面,叫add.jsp/WEB-INF/cost/add.jsp,这样的,然后呢,由jsp向浏览器做出了响应,最终呢,浏览器就得到了增加页面,这是增加功能的第一个请求,这样。然后呢,当浏览器得到这个网页之后,它一加载可以看到一个表单对吧,是得到一个表单,表单中有很多框,我就不细画了,然后呢,最终,表单上有一个保存按钮是吧,然后呢,当用户呢,点保存按钮时,是不是向服务器,又发请求,要求存数据是吧,这是我们这个功能的第2个请求,这样,前后的关系就是这样。然后呢,再往后看,那服务器要处理请求,因为有数据了,肯定是动态的对吧,所以还是要访问Servlet,那我还是访问同样的这个MainServlet,然后呢,是保存数据,增加数据,我这个方法叫addCost,行吧,MainServlet.addCost(),刚才是要增加,现在是,就是真增加啊,是这样的。然后呢,它的路径跟这个方法相似,要求呢,是叫/addCost.do,行吧,跟这个方法同名,对应。
  • 然后呢,那你要增加数据的话,我们得接收表单的数据对吧,接收数据,然后呢,要保存数据,保存数据怎么保存,得怎么办呢,还得调dao对吧,还得调这个CostDao,调CostDao什么方法呢,save保存方法对吧,CostDao.save(),就可以。那你要调保存方法的话,是不是要把数据传给它呢,传给CostDao.save(),传入的数据是什么数据呢,Emp,有点走火入魔了,是吧,学Emp学大了是吧,这里切换场景啊,这里是资费啊,是Cost,是吧,是Cost。那你把数据呢,封装好,传进去,然后呢,就存了,只要不报错就是成功对吧,好了,保存完以后然后怎么办, 最后怎么办啊,重定向到查询对吧,那么,在此之前,我们查询早做好了对吧,那查询是什么呢,早做好了,是MainServlet里面的findCost,当然,findCost调了Dao,MainServlet.findCost(),它调了谁呢,调了这个CostDao.findAll()方法对吧,这都已经写好了,然后呢,它已经能调这个Dao,由Dao呢,返回了集合数据。那你只要访问它就行了,访问MainServlet.findCost(),那我们要访问它的话,因为这个增加和查询是两个不同的请求,不同的功能对吧,相互独立,所以重定向就可以了,那这是这个功能当中的第3个请求,3个请求呢,互相就这样串接起来了。
    在这里插入图片描述
  • 那么,最后这个请求呢,因为它的内容啊,是我们不用再写了,已经写过了,所以我把它换个颜色啊,换个灰色,换一个不显眼的颜色,表示说,不用重点关注它,那我们需要重点关注的是前两个请求, 是这样吧,那当你能够把一个项目中的这个请求,分析到这种地步的时候,那么,你每一步就好写了,从MainServlet.toAddCost()/WEB-INF/cost/add.jsp,这不就是转发一下么,对吧;服务器返回/WEB-INF/cost/add.jsp给浏览器,这不就是一个jsp显示个东西,对吧;保存按钮被点击触发时,浏览器发送/addCost.do请求到MainServlet.addCost(),这不就是接收点参数么,MainServlet.addCost()把Cost数据发送给CostDao.save(),不就是把参数给dao,保存一下么,然后,MainServlet.addCost()再建议浏览器发起第3个请求查询资费,不就是重定向么,每一步的逻辑都非常的简单了,都比较小了,就比较容易实现了。
  • 当然你在实现的过程中呢,肯定还会遇到一些障碍,因为我们开发代码的话,是比较精细的,会遇到一些细节问题,遇到问题的时候啊,怎么说呢,自己想一想,憋一憋,哪怕这个事,我憋了半个小时,憋了一个小时没憋出来,你也要忍着,因为什么呢,这是你日后工作的一种常态,我当年啊,老板出差了,走了,剩我个人一个在那写代码,没有人帮我解决,遇到问题怎么办,就憋一天也得憋着,憋着呗,反正也憋不死。只能忍,没有招啊,然后,你工作时你会发现,那同事很多人很奇怪,很有意思啊,每个人解决问题的方式不一样。
  • 以前我有同事,那哥们这个,遇到问题实在是憋的受不了了,他也不好意思问别人,比较这个,比较腼腆,然后怎么办呢,坐在那工位上,在那运气啊,就是实在是不知道该怎么解决,老是以为这是不是电脑有问题啊,tomcat有问题啊,一启tomcat时,运气啊,气功呀,嘿,运气,给我吓坏了,我说你要干嘛,你要拉翔吧,不是,我都解决两个小时解决不了了,然后,我们抢着帮他解决,为啥呢,两个小时没解决,我们解决,能解决了的话,这个牛了是吧,牛大发了,所以说,就愿意帮他解决,就啥意思呢,你工作时啊,遇到一个问题,你就是,困扰了你这个,一个小时,两个小时,甚至是一天都有可能,这都是有可能的,所以这是一种常态,你不要说,哎呀,这个问题都困扰我10分钟了,我实在受不了了,你这太不值一提,都不屑,不屑理你是吧,所以自己多想一想,多呢,有这样的一种体会,因为这是你工作时的一种常态。

代码设计与实现之第一个请求:/toAddCost.do

  • 那继续,刚才我们说了这个增加的这个逻辑,然后呢,还有点细节啊,这个没有介绍全,再说一下,那刚才我们看到增加页面的时候啊,有细心的会发现,增加的时候,这里有几个,可以输入几个数据呢,1,2,3,4,5,6,对吧。
    在这里插入图片描述
  • 能输出6个数据,但是我们表里一共几个字段呢,10个,还差4个,哪去了,差4个的话,你想那个id,我们需要输入么,不需要,id不算对吧,除了id,还有3个,那3个没有,怎么办呢,默认值,默认是多少呢,其实,咱们这业务上有,你看咱们这个查询页面上,这块有个什么呢,业务说明,不知道你看没看到,有个说明,怕你不知道:
    在这里插入图片描述
  • 当然我们正式做项目时啊,以后你们工作时,不会有这个东西,这是我们为了学习方便,写在这了啊,它说了创建资费,就增加对吧,增加资费的时候,状态为暂停,那不用输入状态,默认为暂停对吧,暂停是几呢,1对吧;然后呢,记载创建时间,就是那个creatime,默认为sysdate,是这意思吧,是这样吧,默认为sysdate;然后还有一个,还有一个是那个开通时间,那开通时间,因为默认状态是暂停,有开通时间么,没有,所以开通时间是几呢,null,听明白了么。所以你在这个,在这个地方啊,在这,你这个CostDao.save()里啊,保存数据的时候,那么,我们传入的是6个值对吧,id你用序列生成;其他的3个值,状态,默认为1;然后呢,创建时间,默认为系统时间;开通时间,默认为null,听明白了么,这是一些细节,那再强调一下。
  • 那我们开发的话呢,是按照我们画图这个步骤来写啊,我们先写第一个请求,第一个请求很简单啊,就是我们访问Servlet转发到jsp,对吧,这个应该都会,有人说,那你增加按钮,之前路径没写过,但是呢,我们一看,那个网页上增加按钮,它都给你写上那个格式了,对吧,都写上了onclick等于location.ref,等于一个路径对吧,<input type="button" value="增加" class="btn_add" onclick="location.href='fee_add.html';" />,你只要把路径搞明白就可以了,那有人说,哎呀,路径我还是不会写,那你不会写的话,我以前也说过,你退一步讲,你实在不会写相对路径,你可以写绝对路径,绝对路径的格式是固定的,斜线,项目名,斜线,什么什么,是这样吧,固定的,你别管访问啥,都是这样的。所以说你不行的话,写绝对路径也能做出来,或者说,你写相对路径,你先不写,点点杠,试一下,写点点杠,试一下,你可以随便试试,没准就试对了,所以说,这个呢,自己尝试一下,不要只在想,有的时候,确实想不出来啊。
控制层之业务逻辑处理与控制分发:/web/MainServlet.java
  • 那我们来写,那写的话呢,咱们这样,我们先写Servlet,因为jsp依赖于它,然后呢,我们再写jsp,这两个都写完以后呢,再把增加按钮的路径处理好,然后呢,路径我有了要求,我要求呢,它是/toAddCost.do,这是我的规范。那打开这个Servlet,就MainServlet,当然了,我画的图上呢,我说了要加一个新方法,叫toAddCost,但是你直接加一个方法,没有用,因为我们这个类处理请求,靠的是service方法对吧,靠的是它,从父类继承过来的Servlet,你要想调用那个方法,是不是还得判断路径,才去调对吧,显然得在这,service方法里,还加一个判断,这应该能自己想到,因为这里头我都写上了,而且以前在做那个模拟员工管理时(Servlet2和Servlet3中),查询员工增加员工,不也是这么干的么,对吧,都是判断,加判断啊。那这块我判断一下, else if("/toAddCost.do".equals(path))

//根据规范(图)处理路径
if("/findCost.do".equals(path)) {
	findCost(req,res);
} else if("/toAddCost.do".equals(path)) {
	toAddCost(req,res);
} else {
	throw new RuntimeException("没有这个页面");
}

  • 当然有人可能写到这,会想,说现在我们是两个功能,判断两次,那将来项目功能多了,我就无限的判断么,可以这样,但是这样并不好对吧, 我们也可以怎么办呢,也可以说,我们把这个判断,判断之后所调的方法,封装到其他类当中,比如说,一个模块放几个方法,比如资费模块放findCost(),toAddCost(),等等,明白这意思吧,可以把这个相应的方法,分配到其他的类当中去啊。甚至呢,对这段代码我们,也可以给它写配置文件,配置好啊,每个路径对应哪个方法,然后我们去调,也可以。但是呢,其实呢,没有必要这样做,我们这块就简化做就可以了,就是简单处理就可以了,为啥呢,工作时呢,我们一般都会用到SpringMVC框架,或者是Strusts2框架,那个框架呢,会自动的帮我们调这个配置文件,调不同的方法,自动的把方法放到不同的类当中去,所以将来有框架的话,这段代码,不用我们判断了,框架帮我们处理了,会省很多事啊。
  • 那这是toAddCost.do,如果是这个路径,我就调这个方法,toAddCost(req, res);,那么这个方法呢,我需要加以声明,那方法的声明,它和findCost(),没什么区别,我可以呢,复制粘贴进行处理,那这个复制的findCost方法名改一下,改成叫toAddCost,然后呢,给个注释,这是打开增加资费的页面,这是打开那个增加的页面,不是保存,这个要把这个,它所代表的意义搞清楚,别整乱了。然后呢,toAddCost这个方法之内,没有任何的业务,就是转发,那转发到一个jsp,叫什么呢,叫add.jsp,即/WEB-INF/cost/add.jsp。然后你看啊,咱们以前做这个查询时,查询不也转发了么,是吧,查询也转发了,那查询,转发到查询jsp,find.jsp,增加转发到增加jsp,add.jsp1,这个是不是一样的方式啊,所以转发的路径是不是一样啊,对吧,所以你有了查询模块,转发的那个经验,增加时那个路径是一样的,一参考,你也不用去想它为什么会这样,它怎么写,以前怎么写,现在就怎么写对吧,就行啊。
  • 那我就转发了啊,request.getRequestDispatcher(),我记得做查询的转发的时候,我们是直接就写WEB-INF/cost/add.jsp,是吧,就从这开始写,req.getRequestDispatcher("WEB-INF/cost/add.jsp").forward(req, res);好,这就转发了,就一句话,然后呢,有同学问,说这个增加资费,我转发了,这数据 ,没有数据,我怎么去绑定数据呢,这学的有点太,太生硬了啊,这没有数据就别绑定,对吧,没有数据就不绑定了啊,就是说我们啊,给你讲一个项目,一个案例,这只是这个项目这么去做,我们换一个项目,肯定会有很多需要调整的地方,对吧,不可能都一样,参数不一样,有的有参数,有的没有,有的多,有的少啊,有的是转发,有的是重定向,可能是有调整的,这个调整,我们得多想一想啊。
视图层之业务基本逻辑与视图显示:/WEB-INFO/cost/add.jsp
  • 那这个方法有了,我们下面就写jsp就行了,那么还是,我在这个WEB-INF/cost之下,创建这个jsp啊,add.jsp,然后呢,jsp中的代码,我们可以呢,从这个静态页面中贴过来啊,所以去找到这个NETCTOSS_V02,下面的资费增加页面,资费页面在fee目录之下,资费增加为fee_add.html,一目了然对吧,很直观了,或者你打开页面看一下就知道了,那我把这个fee_add.html中的代码复制一下,然后粘贴过来,当然粘贴之前,还得写那个啥呢,还得写page指令,<%@page pageEncoding="utf-8" %>,然后呢,粘贴过来。粘贴过来以后,我们还得从头到尾过一遍,因为这个网页上也需要引入样式和图片对吧,它的路径很有可能也不对,我们看一下,然后呢,七八行有link,link上,它原来写了点点杠,点点杠要么,不要,因为咱们find.jsp,也是没有要对吧,跟它的意思一样,那你找葫芦画瓢也是把它去掉,再看,这个网页上有没有图片,我搜一下。好,60行,有一个图片,也是要把点点杠去掉,那么去掉以后Servlet和jsp都有了,最后呢,我们把增加按钮处理一下就好了。
  • 那打开find.jsp,然后,在find.jsp之上,我找到这个增加按钮,那么增加按钮,它是在63行,找一下,按钮上原来写了,onclick单击时,改变网址,location.href='fee_add.html';,它写的是一个静态的网页,但现在我们不是访问静态网页对吧,我们访问的是谁呢,也不能说jsp,访问的是谁呢,Servlet对吧,我们直接访问的是Servlet,Servlet访问路径是toAddCost.do,那这里面,我们最好写相对路径,但如果说你自己写的时候,我就不会写相对路径,你可以写绝对路径,绝对路径怎么写呢,斜线开头,项目名,然后斜线,toAddCost.do,即<input type="button" value="增加" class="btn_add" onclick="location.href='/netctoss/toAddCost.do';" />,是这意思吧,也可以这样写,因为我们讲路径有3种方式,你再不会写,你可以写个完整路径http什么什么对吧,你把它写完整,肯定没有任何歧义。
  • 那相对路径怎么写呢,那分析一下啊,分析一下,我们要访问的增加页面,是我们的目标,我们是在什么地方去访问增加页面呢,是点增加按钮去访问它对吧,而增加按钮在查询页面上,是这样吧,从查询到增加,是这样的,那我给你写一下,在这啊,就是说我们当前,正在访问的是资费查询页面,它的路径是/netctoss/findCost.do啊,对于浏览器而言,不知道有jsp,它只知道,有点do,有Servlet,我们要访问的目标,是这个啊,/netctoss/toAddCost.do,你会发现,我们所访问这两个Servlet是平级关系,所以,路径就写toAddCost.do这一段就行了,<input type="button" value="增加" class="btn_add" onclick="location.href='toAddCost.do';" />,所以就可以不用写那么麻烦,那当然了,这个路径啊,如果说你已经很熟了,其实你也不用把它列举出来,你脑子里一想,当前是谁,目标是谁,你就能想出来,如果不熟的话,列举一下,对比一下,更清楚,再一个,你就可以随便试,即便是试错了,它肯定会提示你路径不对,你看是哪不对,差哪,你再调整,看浏览器。
  • 那到这啊,可以测试啊,就测一下,我把这个项目呢,重新部署一下,然后呢,启动tomcat,然后呢,我访问一下,我打开浏览器,项目名是netctoss,我先访问查询啊,findCost.do,即localhost:8080/netctoss/findCost.do,查询到以后呢,去点增加,它打开了增加页面,toAddCost.do,即localhost:8080/netctoss/toAddCost.do,没错,就这样了,好,这就是第一个请求,完成了啊。那这是第一个请求。那我们再写第二个请求,增加功能呢,它最麻烦的,也就是在第二个请求上,但是呢,这个请求,和之前模拟的增加员工,跟那个其实没什么大的区别,浏览器要访问服务器传数据,表单要配置对吧,表单要配置,有3项,form上,得有action说明目标对吧,文本框,框上得有name,声明名字对吧,还有,如果是单选多选得怎么办,加value传什么值要声明对吧,这之前都讲过,你看着这个案例,得回顾一下。
  • 然后呢,配好以后保存,数据传给Servlet,Servlet怎么接收数据呢,有人到这,MainServlet这怎么接收数据,这就忘了,服务器怎么接收数据呢,就一种方法,没有第二种,request.getParameter(),是吧,如果是多个数据,getParameterValues(),这回顾一下,这不刚讲过的么,有人这个就忘了,还有人说,我得到getParameter得到的那个数据, 我想判断它是否为空,我怎么判断呢,这个有点让我诧异,你get到的是什么,不就是字符串么对吧,字符串怎么判断为空呢,就判断呗,所以说,你看啊,其实,你问的这个问题,你自己回顾一下,有点不是那么回事对吧,你仔细想,不是那么回事,但为什么会问出来呢,就是说一旦这个案例复杂了,你会把你以前的那些,你所掌握的那些小知识,都颠覆了,一旦你感觉难的时候,你就选择混乱,你的思维就会混乱,就会遗忘,不知道为啥,这就是我们,让你做这个练习的目的。
  • 你得想啊,你工作时的那个项目,绝对是比这个功能要复杂多的多了,如果你工作时的那个项目的代码,就这么简单的话,我跟你讲,你那个软件不用java去开发,用php更快,用java干什么呢,还麻烦,是吧。所以一定是比这复杂多的多,那个时候的话,你看哎呀,我虽然做过电信计费项目,虽然做过云笔记项目,我这个项目又更复杂了,我可能又做不出来了,因为你又会忘一些东西,就是说你在有压力的时候,你就思维就乱了,所以说,这个一定要静下心来,多去想多去练,当然还有人呢,这个问题问的比较深刻,就是说比如说,这个dao里面有一些,数据的细节啊,就有个问题啊,就是问的挺好,我给你解释一下。我们已经做了查询功能,在CostDao里呢,我们写了查询方法,这个查询的时候,我们是对于这个,base_durationbase_costunit_cost,这3个字段,这3个字段都是数字对吧,或整数,或小数,然后的话呢,我说了我们实体类,应该都用封装类型,好支持null对吧,但有同学仔细,突然就看到了说,这个数组得到以后,我数据库里得到为null,我得到的是0对吧,你注意到了么,你看我这个:
    在这里插入图片描述
  • 你看find.jsp页面里的基本时长,这是不是0啊,按理说应该是null,为啥是0呢,因为,因为什么呢,c.setBaseDuration(rs.getInt("base_duration"));,我rs.getInt,int是不是,这个getInt得到的是数字,明白么,就没有null,我getDouble,得到的也是数字,也没有null,所以它默认转为0了,自动的。那有人说,我想不要这个0,我就想空,怎么办呢,你得自己判断,比如说你把这个字段呢,当作字符串处理,getString,然后判断是不是为null,明白吧,如果为null,给它一个null值,如果不为null,我就再getInt,你可以判断一下,那有人说,那这也太麻烦了,那现在没办法,只能是这样,然后呢,将来我们会学到框架,我们学到框架以后就好了,框架比较聪明,它会自动帮我们判断,自动帮我们处理null,现在的话,没办法,那我也没判断,为什么呢,因为反正将来会用框架,先暂时就简单处理了啊。

资费增加功能代码实现之第一个请求:toAddCost.do

1…src/main/java/web/MainServlet.java
package web;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.AdminDao;
import dao.CostDao;
import entity.Admin;
import entity.Cost;
import util.ImageUtil;

public class MainServlet extends HttpServlet {

	@Override
	protected void service(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//获取访问路径
		String path = req.getServletPath();
		//根据规范(图)处理路径
		if("/findCost.do".equals(path)) {
			findCost(req,res);
		} else if("/toAddCost.do".equals(path)) {
			toAddCost(req,res);
		} else {
			throw new RuntimeException("没有这个页面");
		}
	}
	
	//查询资费
	protected void findCost(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//查询所有的资费
		CostDao dao = new CostDao();
		List<Cost> list = dao.findAll();
		//将请求转发到jsp
		req.setAttribute("costs", list);
		//当前:/netctoss/findCost.do
		//目标:/netctoss/WEB-INF/cost/find.jsp
		req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);
	}

	//打开增加资费
	protected void toAddCost(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		req.getRequestDispatcher("WEB-INF/cost/add.jsp").forward(req, res);
	}
}
2.src/main/webapp/WEB-INF/cost/find.jsp
<%@page pageEncoding="utf-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>达内-NetCTOSS</title>
        <link type="text/css" rel="stylesheet" media="all" href="styles/global.css" />
        <link type="text/css" rel="stylesheet" media="all" href="styles/global_color.css" />
        <script language="javascript" type="text/javascript">
            //排序按钮的点击事件
            function sort(btnObj) {
                if (btnObj.className == "sort_desc")
                    btnObj.className = "sort_asc";
                else
                    btnObj.className = "sort_desc";
            }

            //启用
            function startFee() {
                var r = window.confirm("确定要启用此资费吗?资费启用后将不能修改和删除。");
                document.getElementById("operate_result_info").style.display = "block";
            }
            //删除
            function deleteFee() {
                var r = window.confirm("确定要删除此资费吗?");
                document.getElementById("operate_result_info").style.display = "block";
            }
        </script>        
    </head>
    <body>
        <!--Logo区域开始-->
        <div id="header">
            <img src="images/logo.png" alt="logo" class="left"/>
            <a href="#">[退出]</a>            
        </div>
        <!--Logo区域结束-->
        <!--导航区域开始-->
        <div id="navi">                        
            <ul id="menu">
                <li><a href="../index.html" class="index_off"></a></li>
                <li><a href="../role/role_list.html" class="role_off"></a></li>
                <li><a href="../admin/admin_list.html" class="admin_off"></a></li>
                <li><a href="../fee/fee_list.html" class="fee_on"></a></li>
                <li><a href="../account/account_list.html" class="account_off"></a></li>
                <li><a href="../service/service_list.html" class="service_off"></a></li>
                <li><a href="../bill/bill_list.html" class="bill_off"></a></li>
                <li><a href="../report/report_list.html" class="report_off"></a></li>
                <li><a href="../user/user_info.html" class="information_off"></a></li>
                <li><a href="../user/user_modi_pwd.html" class="password_off"></a></li>
            </ul>            
        </div>
        <!--导航区域结束-->
        <!--主要区域开始-->
        <div id="main">
            <form action="" method="">
                <!--排序-->
                <div class="search_add">
                    <div>
                        <!--<input type="button" value="月租" class="sort_asc" onclick="sort(this);" />-->
                        <input type="button" value="基费" class="sort_asc" onclick="sort(this);" />
                        <input type="button" value="时长" class="sort_asc" onclick="sort(this);" />
                    </div>
                    <input type="button" value="增加" class="btn_add" onclick="location.href='toAddCost.do';" />
                </div> 
                <!--启用操作的操作提示-->
                <div id="operate_result_info" class="operate_success">
                    <img src="images/close.png" onclick="this.parentNode.style.display='none';" />
                    删除成功!
                </div>    
                <!--数据区域:用表格展示数据-->     
                <div id="data">            
                    <table id="datalist">
                        <tr>
                            <th>资费ID</th>
                            <th class="width100">资费名称</th>
                            <th>基本时长</th>
                            <th>基本费用</th>
                            <th>单位费用</th>
                            <th>创建时间</th>
                            <th>开通时间</th>
                            <th class="width50">状态</th>
                            <th class="width200"></th>
                        </tr>
                        <c:forEach var="c" items="${costs }">                
                        <tr>
                            <td>${c.costId }</td>
                            <td><a href="fee_detail.html">${c.name }</a></td>
                            <td>${c.baseDuration }</td>
                            <td>${c.baseCost }</td>
                            <td>${c.unitCost }</td>
                            <td>${c.creatime }</td>
                            <td>${c.startime }</td>
                            <td>
                            	<c:if test="${c.status==0 }">开通</c:if>
                            	<c:if test="${c.status==1 }">暂停</c:if>
                            </td>
                            <td>                                
                                <input type="button" value="启用" class="btn_start" onclick="startFee();" />
                                <input type="button" value="修改" class="btn_modify" onclick="location.href='fee_modi.html';" />
                                <input type="button" value="删除" class="btn_delete" onclick="deleteFee();" />
                            </td>
                        </tr>
                        </c:forEach>
                    </table>
                    <p>业务说明:<br />
                    1、创建资费时,状态为暂停,记载创建时间;<br />
                    2、暂停状态下,可修改,可删除;<br />
                    3、开通后,记载开通时间,且开通后不能修改、不能再停用、也不能删除;<br />
                    4、业务账号修改资费时,在下月底统一触发,修改其关联的资费ID(此触发动作由程序处理)
                    </p>
                </div>
                <!--分页-->
                <div id="pages">
        	        <a href="#">上一页</a>
                    <a href="#" class="current_page">1</a>
                    <a href="#">2</a>
                    <a href="#">3</a>
                    <a href="#">4</a>
                    <a href="#">5</a>
                    <a href="#">下一页</a>
                </div>
            </form>
        </div>
        <!--主要区域结束-->
        <div id="footer">
            <p>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</p>
            <p>版权所有(C)加拿大达内IT培训集团公司 </p>
        </div>
    </body>
</html>

代码设计与实现之第二个请求:/addCost.do

业务层之业务数据处理与数据存储:/dao/CostDao.save()
  • 那我们再回到这个增加的案例当中啊,第2个请求,那么,这个请求的过程呢,咱们还是按照倒序开发,就是先把被依赖的开发完,然后呢,我们先开发CostDao.save(),按理呢,先开发实体类,但实体类已经有了对吧,那我们就先开发这个save方法,保存方法,那我来写这个方法,那么再打开这个CostDao这个类,我们在这个类当中呢,写一下这个方法。那我们在这个CostDao这个类当中啊,声明这个保存方法,这个方法呢,要被别人调用,肯定是公有的,保存方法呢,不需要返回值,它只要不报错就是成功了,报错,我们就抛出去了,然后呢,保存方法一般呢,习惯于叫,就是新增保存叫save啊,这是跟这个,跟什么习惯来的呢,跟这个框架,以后我们学框架会发现呢,框架习惯叫这个名字,我们跟它学,那么,保存的时候呢,你需要把数据呢,封装一下给我,给我一堆数据,所以给我一个实体,所以呢,这个传入的是,就是Cost,public void save(Cost cost) {},这是我对这个方法的声明。
  • 那么,我们写这个保存方法,那么它和这个查询也大同小异,只不过不用处理返回值而已,对吧,所以首先呢,也是创建个连接啊,创建连接的方式和查询结构一样,Connection conn = null;,那我们声明这个连接啊,然后呢,创建连接:

public void save(Cost cost) {
	Connection conn = null;
	try {
		conn = DBUtil.getConnection();
	} catch (SQLException e) {
		e.printStackTrace();
		throw new RuntimeException("增加资费失败",e);
	} finally {
		DBUtil.close(conn);
	}
}

  • 那写到这,刚才有人问啊,问这样一个问题,你看我做完这个功能了,做完查询了,我第一次查询,哎,出来数据了,没问题,我一刷新,再查一次,就报错了,报的要么是连接池的错,要么是连接的问题,那你大概想一想,应该是什么问题,应是怎么样,第一次就能查到,第2次就坏了,第3次,第4次,以后就不好使了,你觉得是怎么回事,很显然是,应该是连接怎么样呢,肯定是没close,没归还对吧,没归还以后,你看咱们这个连接池的设置啊,我们看这个参数,这个这件事,我们在jdbc时讲过,你看这个db.properties这个配置文件:

# database connection parameters
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
user=SYSTEM
pwd=Oracle123
# datasource parameters
initsize=1
maxsize=2

  • 这个db.propeties是我们从jdbc那个地方copy过来的,对吧。啊,然后你看,这个initsize=1,初始连接数是1,maxsize=2,那可能是,可能是第3次有问题,就是说,一开始好使,后来过一两次就不好使了,就是什么呢,连接数都被占满了对吧,很快就崩溃了,可能是这个问题,往这个方向看一看,就是说有些问题啊,你要大胆的去猜测啊,应该是这个问题。
  • 那目前呢,我已经创建好了连接,然后,捕获到异常以后,也向上抛了,然后呢,这个也归还连接了,连接建好以后,下面呢,我得写一个sql,要执行它对吧,写一个insert啊,String sql = "insert into cost_lhh values("+"cost_seq_lhh.nextval,"+"?,?,?,?,?,?,?,?,?)";,我那个序列名,我加没加后缀,忘了啊,行我先写上吧,不行再说吧,然后你写这个sql时,你要注意,你的表和我的表不是一样的啊。然后呢,第一个字段是序列,cost_seq_lhh.nextval,其他的字段呢,都是问号,一共9个。然后呢,之前也强调了,增加的一个隐含的业务,那么,我们在增加的网页上,只能够看到6个字段,对吧,6个,能传入6个数据,那一共10个数据,除了id之外,是9个对吧,传入6个,那还有3个,那3个没传的,要给予默认值对吧,那默认值给什么呢,我们看那个资费查询页面上的那个业务说明,从中能够得到一些信息,它说了,它说什么来着,看一下啊。
  • 它说了创建资费时,状态为暂停对吧,记载创建时间,状态为暂停,所以状态默认为1,是这样吧,记载创建时间,创建时间默认为系统时间,把这个两个都写一下。那你看啊,第一列id,第2列,资费名, 然后呢,基本时长,基本费用,单位费用,状态,按顺序来,表里建表顺序,就是这样,状态默认为1啊,char类型,‘1’,就第五个问号,改为1啊。然后,再往后看,第6个呢,是描述,这个呢,是创建时间对吧,默认为系统时间sysdate,然后呢,第8个问号,这是呢,开通时间,那因为默认状态是1,没开通对吧,开通时间没法给,所以给了null,String sql = "insert into cost_lhh values("+"cost_seq_lhh.nextval,"+"?,?,?,?,'1',?,sysdate,null,?)";,这个sql就写好了啊。总之呢,写好以后,那么在sql之内,还剩下6个问号,这6个值,正好是页面传入的,那这个值,最终传入给谁呢,给我们的参数,所以,我们通过参数,给问号赋值就可以了。
  • 那我们把这个打开增加页面完成了,主要呢,就是Servlet处理请求,然后呢,转发到jsp,jsp中呢,也没什么逻辑,就是把那个代码贴过来就好了,目前是这样,然后呢,增加按钮,稍微处理了一下, 那之后啊,咱们写第二个请求啊,第2个请求呢,就是写个Dao,但dao已经有了,我们写了个save方法,我们方法刚才写了一半啊,继续。那这个dao单独的一个方法可以单独去写,这个Servlet单独的这个方法,也可以单独去写,不就接收参数么,然后调这个dao存数据,和那个模拟增加员工的时候,也差不多,类似的,重定向么,所以说每一个单个组件啊,它都不难,都应该会写,如果说这一个组件,我还不知道怎么写,基本上这段课,你就白学了啊。
  • 那我们回到刚才的这个dao啊,CostDao,那么刚才呢,我创建了连接,创建以后呢,我又写了一个sql,增加sql,然后呢,插入了字段,按顺序来,中间呢,有一些有默认值,那写完以后呢,还剩6个问号,需要赋值,那么,这6个问号的值,来源于页面,来源于这个参数,我们给这个参数呢,赋值。那下面呢,我要给这个参数赋值,但是我们赋值之前呢,还得先创建能执行sql的这个对象对吧,创建哪个对象呢,是PreparedStatement啊,因为这个对象呢,它能处理这个问号啊,PreparedStatement ps = conn.prepareStatement(sql);,那一共有6个问号,我们逐个赋值,第一个呢,是name,它的类型呢,是字符串,然后呢,值啊,就是cost.getName(),以此类推,一共6个:

PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, cost.getName());
ps.setInt(2, cost.getBaseDuration());
ps.setDouble(3, cost.getBaseCost());
ps.setDouble(4, cost.getUnitCost());
ps.setString(5, cost.getDescr());
ps.setString(6, cost.getCostType());
ps.executeUpdate();

  • 那一共呢,6个参数,咱们逐个赋值,要保证这个顺序,然后呢,都赋完值以后呢,执行sql,调这个executeUpdate方法,那写完以后,要测一下,因为能不能成功,有没有问题,这个还不一定,因为万一呢,这个sql写错了,对吧,万一呢,咱们哪个条件呢,赋值时没有处理好,有可能会有一些问题,最好及时测试,那测过以后,再往下写,那这个地方没问题了,咱们以后出问题,也不用去考虑这个地方啊。那我们就回到这个CostDao中的main方法,在这里呢,加以测试,之前啊,我们是测了这个查询,那现在这个查询的测试呢,这个代码没有用了,删了,然后呢,在此基础上测试增加啊,要测试增加的话,这个数据应该是来源于这个网页,但目前网页咱们还提交不过来对吧,还没完成,所以呢,这个数据呢,我们先模拟一下,那么网页上呢,我们能够接收到的是6个参数,6个值啊,看一下。
    在这里插入图片描述
  • 那网页上能够给我们传入6个值啊,一个是名称,类型,时长,基本费用,单位费用,资费说明啊,那我呢,下面在main方法里呢,把这个数据呢模拟一下,当然数据传到dao这里来,是需要呢,封装成对象,所以我直接呢,模拟出一个对象Cost,

public static void main(String[] args){
	CostDao dao = new CostDao();
	Cost cost = new Cost();
	cost.setName("66元套餐");
	cost.setBaseDuration(660);
	cost.setBaseCost(66.0);
	cost.setUnitCost(0.6);
	cost.setDescr("66元套餐实惠");
	cost.setCostType("2");
	dao.save(cost);
}

单元测试的意义及CostDao.save()单元测试
  • 那就模拟出这样一份数据,然后呢,save保存一下,看行不行,这个数据模拟好了,我们在这个,就是自己做测试的话,这个叫单元测试,这是我们每开发一个,一个完成的一部分,测一下,这叫单元测试,或者叫阶段性测试。那我们测的话,这个数据啊,最好也是要贴合这个业务,你不要随便写个abc什么的就过去了,因为什么呢,有可能,你写英文没问题,中文就有问题,有可能呢,这个数字,你写小没问题,写大就有问题,所以呢,最好是按实际的业务的情况,去这个杜撰这个数据,这个数据呢,要合理,你比如说,当前我杜撰的数据呢,是66元套餐,是个套餐,然后呢,它的基本时长是660小时,基本费用66元钱,单位费用0.6元,然后描述啊,写一句话,合情合理的一句话,然后呢,类型,这个如果是套餐的话,是2,1是包月,2是套餐,3是计时,你要合理。
  • 那声明完这个合理的数据以后,我就执行一下,看一下,执行一下,那么,执行以后没报错,如果报错的话,那就赶紧检查一下,检查的话,主要看你的那个sql,还有看那个你set的那个值,就那个字符串,字符串容易写错,检查一下。那打开sqldeveloper工具,可以利用sqldeveloper/sqldeveloper/bin/sqldeveloper.bat直接打开,
    在这里插入图片描述
  • 连接数据库lhh,select * from cost_lhh order by cost_id;,那么,没有问题以后,执行一下,执行完以后看一下你的表,那我一看,我的表里确实多了一条数据,66元套餐,没错。
    在这里插入图片描述
  • 当然了,目前呢,我所模拟的这个数据,我所测的这个数据啊,有点片面,我们这个增加的资费的业务,咱们增加的资费,不止是有套餐,还得有包月,还得有计时,有可能我们增加套餐时没问题,有可能增加包月和计时,就有问题,我们工作时,测试也是这样,尽量要把这个业务都涵盖到,不然的话,你可能只有一种普通业务,没问题,特殊业务有问题,那也是bug,你这个bug,你自己没找到,很明显,然后的话,测试一测,给你提一堆bug,最后的话,你也得花半天时间把它改好,这个时间也没节约,最后呢,口碑又不好,所以你最好是自己,就是主动的把这个测试涵盖到,然后测试给你测的时候,只是找出一些非常极端的,非常特别特殊的情况,几个小问题,几个问题,你再去解决,这样比较合适。所以,我这个数据啊,我再重新做一份,再模拟一个,另外一个场景。
  • 刚才呢,模拟的是套餐,我下面模拟一个包月,setName改下,那包月,你注意包月,基本时长,包月有时长限制么,没有,你看咱们这个页面上也是啊,看页面上,如果说默认是套餐啊,都可以输入对吧,如果说我点包月,这是不是有不能输入的东西了,为啥不能输入呢,因为包月这两个基本费用和基本时长不用输入,当然了,那我们点击这个单选,它这个readonly就只读,会切换对吧,这个切换呢,是美工做好了,这个美工会写js,它把这个切换的方法已经写好了,我们几不用处理了。总之你看啊,包月时基本时长,不用输入,因为它不限制对吧,单位费用不用输入,永远都不会超出,那如果说网页上,这两值没输入的话,我们接收到的应该是什么,应该是null对吧,没有值,那我们在模拟时也要考虑这一点啊。
  • 当前,你看我这是包月,那包月这个没有输入,就没有对吧,注释掉了,注释掉以后,它的默认值是不是就null啊,是的,然后基本费用,这包月肯定是比较贵了,包月比如说,1200吧,假设1200.0啊,然后呢,单位费用,因为是包月,永远都不会超出对吧,单位费用页面不输入,传过来的是空值,然后呢,cost.setDescr("包月最爽");,描述这块写一下,改一下,合情合理啊,一句话,包月最爽啊,然后呢,类型,包月是1,对吧,都把它写的匹配一点,符合实际的业务情况,我改好了:

public static void main(String[] args){
	CostDao dao = new CostDao();
	Cost cost = new Cost();
	cost.setName("包月");
	//cost.setBaseDuration(660);
	cost.setBaseCost(1200.0);
	//cost.setUnitCost(0.6);
	cost.setDescr("包月最爽");
	cost.setCostType("1");
	dao.save(cost);
}

测试报错解决及相关经典事例
  • 改好以后呢,我就执行了啊,执行,好,执行一下啊,一执行,报错了对吧,那我为什么要多测一步,为什么要把业务尽量的涵盖全,也就是怕这一点,我就怕正常的业务没问题,稍微特殊一点,就有问题,现在确实发现问题了,对吧,测到位了,好,那我们看一下,这个问题是怎么出现的,怎么看呢,以前我讲js时讲过,我们遇到问题的基本的解决的原则,对吧,第一点,看一看报没报错啊,这里报错了,报错了是好事,我们不怕报错的问题,就怕什么错都没有,数据就死活不对明白吧,那种问题就是,不知道该从何调起,还怕什么呢,以前我遇到一个非常奇葩的问题,这个问题,你说它是问题,也不能算是问题。
  • 因为以前我们给这个,我们做一个就是,房地产行业的一个投资的管理系统,卖给了万科啊,不是卖给万科,相当于是送给万科,没要钱啊,你随便用,送给他以后呢,我们还挺重视这客户的,因为万科是整个房地产行业的一个标杆,是信息化的标杆,万科用我们软件,我们软件就牛了,就这种感觉啊,然后呢,整天蹲着,一个实施在那蹲着,遇到什么问题问实施,实施再反馈给我们,然后万科就遇到一个问题,说我这软件呢,用了一年多了,我这个,这个商业这个,那叫什么什么商业,一个商业,就是类似于那个,类似于购物中心的那个商业啊,他收那个租金还是物业费,收物业费,收费的话呢说,一年多了,这个收了几个亿。
  • 然后的话呢,收完之后,核查,就是我们系统里面的钱,和它人工算的钱,差了两毛多钱,几个多亿啊,差了2毛钱,一年多啊,然后说,你看,你这不能差,你给我解决一下吧,拿过来一看,领导让我解决,我解决,我解决3天都没解决了,为啥呢,程序任何一个地方,都没有任何报错,没有任何问题,然后呢,你还没法跟踪这个程序,为啥呢,因为人家是执行了一年,数据太多了,明白吧,你说我想做几个数据模拟一下,模拟不了,这数据太多了,你去分析吧,每一笔数据都是对的,那为啥不对呢,其实,大概一分析也明白了,它是怎么产生的呢,是误差 ,我们当时给的精度呢,是每一个数据精度是4位小数,但是4位小数,当你数据里,累积的太大的时候,也会有一定的误差对吧,它也有误差,误差就是上亿的时候,误差个几毛钱,2毛钱。
  • 那这个问题怎么解决呢,如果要解决,我们得把我们所有精度调成更大,比如说8位,是这意思吧,但是我们已经做成4位了,要调的话,都得调一遍,代价太高了,没调,没调反正,最后又给它附加了一个功能,说这个任何软件在开发时,在这个运行时,一定都会有所误差,这个误差是微小的,你手动调一下,给它开发了这个功能,没办法,这种问题是,真的是,就是当初没有想那么细,没解决好这个问题,是很难解决的,我解决3天啊,真是解决3天,我做了一份数据,又一份数据,我是跟了一段又一段,看了日志,一遍又一遍,都解决不了,最后大家一合计,可能应该是这个问题,调一下吧,没办法啊,所以这种问题才是最头疼的问题啊,像这种遇到bug啊,遇到bug就一定可以解决,有修复的办法。
  • 那你看啊,当然有人啊,说我就不会看这个bug啊,这个怎么说呢,慢慢看,别着急,再一个你看啊,你看到异常,比如说你看到NullPointerException,空指针对吧,肯定某值为空了,还有人看到什么,什么ClassCastException对吧,什么NumberFormatException,像这种异常,如果说你不理解,你去API看一下,那异常是啥意思对吧,在什么时候,会发生这种异常,你就往上猜,你得自己去想办法分析,你不能说哎,遇到异常,我就干看着啊,无从下手,就不动了啊,你还得想办法解决,那空指针异常我们都知道,是某值为空,然后的话,我们看一下,到底这个异常发生在某一行:
    在这里插入图片描述
  • 它这行数也是非常明确对吧,离我们最近的啊,就最后(执行)的是这一行,63行save方法,我点一下进去,你看63:
    在这里插入图片描述
  • 你看,谁为空可能导致空指针异常,有两种情况,一种情况,cost为空,有没有可能空指针异常啊 ,假设cost是null,ps.setInt(2, null.getBaseDuration());,肯定不对是吧,肯定会有问题,但是你看cost为null么,我刚才不是,我都已经写死了,CostDao dao = new CostDao();,这都new了吧,它绝对不是null,这个是绝对的,那cost不为null,很明显,可能是getBaseDuration()为null对吧,那这个是null么,baseDuration这个值你看一下:
    在这里插入图片描述
  • 我模拟时不都注释掉了么,因为包月这个不用传对吧,它确实就是null,那这个值不让为null么,让不让为null呢,你看,我把它改成null,试一下,ps.setInt(2, null);,让么,报叉了对吧,它确实不让。同理啊,像那个Double,也不允许为null,为啥呢,因为,因为它是int明白吧,它必须是个数字,它不支持null,这个方法,那得怎么办呢,对,这个就得判断了,一个是判断,我们判断一下,如果说,我得到的这个值为null,我就别setInt了,我set别的,比如setString,或者set别的,传入个null值对吧,或者我就不set了,对吧,那不set了,还不行,有问号,我还必须得set,比如说你就,你就把这个字段呢,当成字符串来处理,任何字段都能当字符串处理,setString,ps.setString(2, cost.getBaseDuration());,null也可以,如果说呢,它不为null,我们再setInt明白吧,你可以判断一下,不过这种方式还是麻烦,还是有另外的的方式啊,给你说一下。
  • 这解释一下啊,就是说,setInt,setDouble这样的方法,不允许传入null,但实际业务中,该字段确实可能为null,并且数据库也支持为null,那可以将这样的字段当做,当做什么呢,Object处理。就总之吧,这块解释一下,因为这块可能有同学,不太会处理,可能有同学自己做个判断,或者怎么样的,感觉比较别扭啊,那这呢,我们说一下怎么处理更合适啊,就总之啊,setInt,setDouble,这个方法,它不让传null,这是jdbc这样设计的,但数据库呢,支持null,业务中也可以为null,怎么办呢,我们可以把这个字段呢,当做Object处理,然后呢,数据库的底层会自动转型,自动转型啊,它底层还比较聪明,怎么就当做Object处理呢,我set时就别setInt了,set什么呢,setObject,ps.setObject(2, cost.getBaseDuration());,这样的话,你可以传null,也可以传数字,它底层会自动转型,自动判断啊,这样就可以了。
  • 当然其他的不用,你像String啊,像什么Date啊,Timestamp啊,等等,那些字段是可以为null的,就这个数字不可以,这样处理就好了,那处理完以后呢,我们再执行一下,看这回行不行啊,再执行一遍,ok了,这样就可以了,没报错,然后呢,我看一下表里的数据啊,这条数据是否插入进去,那数据也有了,就这样了。
    在这里插入图片描述
控制层之业务逻辑处理与控制分发:/web/MainServlet.java
  • 当然了,还是那句话,我们当前,在直接使用jdbc的时候,就这样写就行,那么将来我们基本上,都会使用框架,那么框架对于类型的处理,都很方便,都是自动的,甚至呢,像这样的set什么什么的话,不用写,自动就做好了,所以说,将来呢,这些代码都会简化,都会容易啊。好了,那这个dao呢完成了,那我们看下一个环节啊,那可能是,我们写到这,就是花了比较多的时间和精力,去解决了这样一个问题的时候,可能是有同学,哎,不知道该怎么判断,不知道该怎么处理,花了很多时间去解决了,最终啊,反正别管用什么方式去解决完以后,可能是,此时呢,你的脑子啊,已经这个怎么说呢,有点混乱,被这个问题影响的,那么,可能思路呢,就有点中断了,那回顾一下,回到之前,我们画的图中来,然后,赶紧串一下,我们当前到哪一步了,是CostDao().sava(),这一步对吧,是第二个请求,/addCost.doCostDao().sava()这一步,完成了,那CostDao.save(),这一步完成以后,下一步要做的是Servlet。
  • 那么MainServlet.addCost(),它呢,需要接收表单传入的数据,然后呢,将数据传给dao对吧,当然传之前,要对数据呢,加以封装,传进去,保存,只要这个dao,没有报错,就成功对吧,成功以后,我们就重定向啊,那这个逻辑,和模拟增加员工一模一样啊,那下面我们来写这个逻辑啊,当然我们还得处理请求,这个请求的路径叫/addCost.do,那下面呢,我们再打开这个MainServlet,就打开它,然后,我们还是回到那个service方法,在那方法之内,再判断一次请求,那我在service之内,再加一个else if,进行判断,这个路径啊,路径叫做/addCost.doelse if( "/addCost.do".equals(path)){addCost(req,res); },那下面我来写这个方法,addCost方法,这个方法呢,我还是写在其他方法之后啊,按照顺序来写,以免这个乱,那我在这个toAddCost方法之后,再写一个方法,这个方法声明啊,和其他方法一样,所以我就复制呢,toAddCost方法,粘贴,改名,就行了。
  • 那么这个addCost方法啊,处理请求,咱们一般的方法处理请求,大致是3步,第一步接收参数,第二步处理业务,第3步或者转发,或者重定向对吧,反正呢,通过不同的手段,向浏览器做出响应,当然了,有的方法会经过简化,你比如说,我们上一个toAddCost方法 ,打开增加页面,因为没有业务,没有参数,前两步都省略对吧 ,就只有最后转发。那目前呢,是我们真的要增加数据了,要保存数据了,所以呢,我们有数据要接收,我们有数据要存,我们需要呢,重定向啊。那进行第一步啊,我们先呢,这个接收,接收呢,页面传入的这个参数啊。那接收参数时,这参数可不可能有中文呢,有可能,因为有这个资费名对吧,有资费说明,等等,或者我们可以养成习惯,就无论有没有中文,我都啊,设置好,都按照有中文来处理,这可以吧,也可以。
  • 所以要写,要怎么办呢,就如果说传入的有中文,我们要避免这个乱码怎么办呢,有同学说set,这个你得这样回答,你得说什么呢,得看是get,还是post,因为get请求和post,不是处理方式不一样么,如果是get请求,我们简单的改一下那个配置文件对吧,在此之前,早就改好了,对吧,所以就没有问题,如果是post,我们得加一句话对吧,那我们用get,还是post呢,post,因为这个数据相对来说多一点对吧,而且当前,这个资费是,一共是传入6个字段,一共整个表是10个字段,那有可能将来这个项目在进行维护升级的时候,可能会加字段,明白吧,可能会多的,所以说,最好是post,一步到位啊。那如果是post请求,要解决乱码问题,我们需要写一句话,是写什么呢,request.setCharacterEncoding("utf-8");
  • 那么声明完变量以后,我们接收那6个参数,当然目前呢,页面上那个名字还没取对吧,我这先写,回头取名时参考这边,可以吧,颠倒过来,6个啊,都是字符串,分别是资费名,我看一下网页,按顺序来,别乱啊,资费名,类型…

//增加资费数据
protected void addCost(
	HttpServletRequest req, 
	HttpServletResponse res) throws ServletException, IOException {
	//接收传入的参数
	req.setCharacterEncoding("utf-8");
	String name = req.getParameter("name");
	String costType = req.getParameter("costType");
	String baseDuration = req.getParameter("baseDuration");
	String baseCost = req.getParameter("baseCost");
	String unitCost = req.getParameter("unitCost");
	String descr = req.getParameter("descr");
} 

  • 好了,get写了6个,那么从网页上取到了6个数据,那我们在网页给服务器传参的时候啊,这个统一传的都是byte,但是byte之前是String,把String转为byte传过来,我们再还原为String,所以我们得到的都是字符串,没有别的类型,但我们实际使用时,有些是需要转换的对吧,是要自己转的。那下一步,这个数据得到以后,下一步,就处理业务,就保存数据啊,写一下,保存该数据。
  • 那么保存的时候呢,我们需要调dao,而dao呢,它要求我们传入的是一个对象对吧,是要封装的,不然的话,传这么零碎,太乱了啊,所以呢,下面呢,我们把这个,我们实例化一个实体类,然后呢,把这个数据呢,加以封装,我实例化一个Cost,Cost c = new Cost();,然后呢,往里传数据,就set呗,挨个set啊,setname(name);,就是name啊,setcostType(costType);,就是costType,下一个呢,下一个是这个基本时长,基本时长,咱们对象中要求的,我们业务上它是整数对吧,所以对象中要求的是Integer,但我们得到的是String,那我们可以把String强转为Integer,是这样吧,转,我转了啊,c.setBaseDuration(baseDuration),把一个字符串强转成整数,我们可以new Integer,传入字符串,可以Integer.parseInt(),对吧,传入字符串,valueOf(),传入字符串,都行,你用哪个都可以,随便找一个啊,比如说Integer.valueOf(baseDuration);,我传入一个,那个字符串叫baseDuration,即c.setBaseDuration(Integer.valueOf(baseDuration));
  • 那我在这传参,我是接收到字符串转成Integer,但是有的时候会报转换异常对吧,报错啊,为啥呢,因为你看,我们这个增加的时候,如果我选择的是包月,这个基本时长有么,没有,没有就传不进来对吧,传不进来以后,我得到的这个baseDuration,这个是空值对吧,那Integer.valueOf(),没法将一个空值转换为整数对吧,就报错,那这种情况怎么办呢,判断啊,那怎么判断呢,可以这样判断啊,如果说啊,baseDuration它不等于null,又或者说它的长度大于0,写的有点啰嗦啊,但是这样严谨。

if(baseDuration != null && baseDuration.length()>0){
	c.setBaseDuration(Integer.valueOf(baseDuration));
}

  • 这句话写完,看一下,那我的意思呢,就比较明确了,如果说传入的参数不是null,并且呢,它也不是空串,baseDuration.length()>0,这句话是不是空串对吧,是这样吧,我怕万一传入空串啊,那么总之呢,如果说,baseDuration这个字段呢,不为空的话,我再去把它转为整数并赋值,是这样吧,就这样。那如果说,传入的是空值,我怎么办,如果它传入空值怎么办呢,有人说给它个null,我再else,我写个,c.setBaseDuraition(null),有必要么,没必要,因为我new的这个Cost,Cost c = new Cost();,那个字段默认就是null对吧,是这样吧,所以,如果它是空值,你就不用管了,默认为null就可以了,是这样吧,就这样。
  • 我在想啊,有同学可能是,在写代码时啊,也能想到这一点,只是呢,你很纠结,你说哎呀,这也太麻烦了是吧,难道我每次都这么麻烦么,你可能是比较怀疑,所以,不想这样写,但是又想不到一个更好的办法,对吧,最后呢,迫于无奈,你只能是这样写了,那这个编程就是这样,一开始啊,我们作为一个新手,我们在,我们这个脑子里,或者我们所见到的解决方案比较少,遇到问题的话,我们说白了,就只会用一个比较笨的,比较这个麻烦的方式去解决,然后我们经常怀疑自己,这么麻烦,这可能么,一定有更简单的办法,那简单办法是什么呢,我们老想这样的一个问题。但是呢,因为这个经验有限,能力有限,与其瞎想,不如先把它写出来,明白吧,将来呢,你遇到了更好的办法以后,你自然而言就会了,以后,你就进步了,这个需要一个台阶一个台阶往上走,你别着急,先这样写着吧,反正谁都是从那个时候过来的。
  • 那有人说这以后,会不会有办法解决呢,有,第一个办法我可以,比如说,我把这个判断空值的办法,可以封装一下对吧,让它简短一点,可以,然后呢,还有什么办法呢,可以这个,将来还是,我们将来可以用框架,用框架的话呢,它把数据判断,转型,封装,全都搞定,就这句话,if(baseDuration != null &&baseDuration.length()>0){ c.setBaseDuration(Integer.valueOf(baseDuration)); },不用我们写,自动就搞定了啊,但那是以后啊,现在只能是这样,好,那除了baseDuration以外,还有两个数字啊,什么baseCost,unitCost,也是数字对吧,也得这样处理,我们把另外两个再处理一下啊,也是要判断啊,最后呢,还有unitCost,也是一个Double啊,也要这样处理:

if(baseCost != null && baseCost.length()>0) {
	c.setBaseCost(Double.valueOf(baseCost));
}

if(unitCost != null && unitCost.length()>0) {
	c.setUnitCost(Double.valueOf(unitCost));
}
c.setDescr(descr);

  • 那最后把这个描述,c.setDescr(descr);,也设置好啊,总之啊,从前到后,我们一共set了6个对吧,挺麻烦的,那么如果工作时啊,我们真的是这样去写代码的话,就麻烦死了,因为工作时呢,字段太多,你得判读多少次,所以将来讲框架以后,这段代码,会有所改善,会改善很多的,就这个set这段话,都不用你写,框架一下就搞定。那数据呢,封装完以后,我们得调dao,得把数据呢,保存了,那我们就实例化这个CostDao啊,CostDao dao = new CostDao();,然后呢,调它的方法,dao.save(c);,保存数据。
  • 那数据保存完,然后,再往后,保存完数据以后,它要么报异常,报异常的话,我们不用管,它自动往上抛,最后呢,我们会讲,tomcat可以统一处理啊,就别着急,然后呢,要么就是没报错,没报错,向下执行,那下一步,之前说了,我们需要的是重定向,对吧,没有问题,重定向,重定向到查询,那你注意啊,我们重定向是从保存到查询,对吧,是从addCost到findCost,那写一下,那么重定向到查询,当前我们所访问的路径是,/netctoss/addCost.do,目标:/netctoss/findCost.do,那经过分析呢,我们当前,正在保存,我们将要去查询,重定向两者路径平级啊,我们通过response实现重定向,那res.sendRedirect("findCost.do");,重定向这句话我们以前在这个模拟员工增加的时候也写过。那到这,我们这个Servlet,接收参数,处理业务,重定向到查询,这3步终于完成了。
视图层之业务基本逻技与视图显示:/WEB-INFO/cost/add.jsp
  • 那关于这个资费增加,我们把这个CostDao.save(),还有这个MainServlet.addCost(),就开发完了,那最后一步啊,就是处理这个网页上的表单啊,那表单的处理啊,咱们之前在模拟做这个增加员工时做过,和那个的套路一样啊,那咱们打开这个那个网页啊,那个页面呢,就是add.jsp,然后呢,我们在这个网页上呢,找到这个页面上的表单所处的位置,那发现呢,这个表单呢在,83行,<form action="" method="" class="main_form">,那首先呢,我要声明action,它要提交给谁,那你看,要提交给/addCost.do对吧,addCost,那我们是从哪提交给这个保存呢,addCost呢,我们是在增加的页面上,对吧,那对于浏览器而言,增加网页的访问路径是谁呢,看图:
    在这里插入图片描述
  • 对于浏览器而言,增加网页的访问路径是谁呢,是不是/toAddCost.do啊,因为浏览器去访问的保存(/addCost.do)么对吧,浏览器访问的保存(按钮),它在哪个上访问保存呢,增加页面上,增加页面的路径,对于浏览器而言是/toAddCost.do,不是jsp啊,因为浏览器呢,不知道有这个东西,一定要强化这个观念,就是很多这个习惯,这个观念,需要我们不断的去强化,不断的去适应,因为一开始确实这个规则,不舒服啊,因为确实么,技术它有这个难度,和我们平时所这个认知呢,有点冲突啊,所以呢,这个时候,你要强化,所以呢,当前是,/toAddCost.do,目标是/addCost.do,两者是平级对吧,平级,你就写个addCost.do,就行了。
  • 那所以呢,在这个action上,我们写上这个路径,相对路径,写上这个addCost.do,然后method,咱们之前也预期了,说我们最好用什么呀,post,就是说白了,一般情况下,我们只要是提交表单,你想都别想,就是post,然后呢,<form action="addCost.do" method="post" class="main_form">,这是表单的配置啊,然后呢,每一个数据得取名字对吧,我们先做第2个内容,每个数据取个名字啊。

<form action="" method="" class="main_form">
    <div class="text_info clearfix"><span>资费名称:</span></div>
    <div class="input_info">
        <input type="text" class="width300" value=""/>
        <span class="required">*</span>
        <div class="validate_msg_short">50长度的字母、数字、汉字和下划线的组合</div>
    </div>
    <div class="text_info clearfix"><span>资费类型:</span></div>
    <div class="input_info fee_type">
        <input type="radio" name="radFeeType" id="monthly" onclick="feeTypeChange(1);" />
        <label for="monthly">包月</label>
        <input type="radio" name="radFeeType" checked="checked" id="package" onclick="feeTypeChange(2);" />
        <label for="package">套餐</label>
        <input type="radio" name="radFeeType" id="timeBased" onclick="feeTypeChange(3);" />
        <label for="timeBased">计时</label>
    </div>
    <div class="text_info clearfix"><span>基本时长:</span></div>
    <div class="input_info">
        <input type="text" value="" class="width100" />
        <span class="info">小时</span>
        <span class="required">*</span>
        <div class="validate_msg_long">1-600之间的整数</div>
    </div>
    <div class="text_info clearfix"><span>基本费用:</span></div>
    <div class="input_info">
        <input type="text" value="" class="width100" />
        <span class="info">元</span>
        <span class="required">*</span>
        <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
    </div>
    <div class="text_info clearfix"><span>单位费用:</span></div>
    <div class="input_info">
        <input type="text" value="" class="width100" />
        <span class="info">元/小时</span>
        <span class="required">*</span>
        <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
    </div>
    <div class="text_info clearfix"><span>资费说明:</span></div>
    <div class="input_info_high">
        <textarea class="width300 height70"></textarea>
        <div class="validate_msg_short error_msg">100长度的字母、数字、汉字和下划线的组合</div>
    </div>                    
    <div class="button_info clearfix">
        <input type="button" value="保存" class="btn_save"  onclick="showResult();" />
        <input type="button" value="取消" class="btn_save" />
    </div>
</form>  

  • 那你看,首先呢是86行,资费名称,<input type="text" class="width300" value=""/>,资费名称啊,它给了个value,这value没有用吧,value是设置默认值,咱们这个不需要默认值对吧,增加时空的,所以这个value没用,我把它改成name得了啊,不要了,就改成name,<input type="text" class="width300" name=""/>,name改成什么,我们在Servlet当中,已经写了,name对吧,我们get时都写上了,得跟那个保持一致啊,两者要一致,<input type="text" class="width300" name="name"/>
  • 然后,再往下看,下面呢是资费类型,这个是92,94,96行,因为它是单选对吧,3个radio啊,那这个radio得加name,它本来就有name,对吧,name还得相同,得互斥么,是吧,对于radio啊,它这个name有两层含义,一个是组名,互斥,一个是呢,提交数据的数据的名字,那你看这个,它这个现在自带的name是radFeeType,符合我的要求么,这个name,不太符合,我在Servlet里接收这个参数,我用的是什么呢,我用的是,我是getParameter("CostType");对吧,所以要得跟那个一致啊,把它改了,改成叫costType啊,92,94,96,这得一致啊,92行改为<input type="radio" name="costType" id="monthly" onclick="feeTypeChange(1);" />,94和96同样都是costType。
  • 再往后看,然后呢,下面一个是101行,是基本时长,<input type="text" value="" class="width100" />,这个value不要 ,我把它改成name啊,基本时长,baseDuration,<input type="text" name="baseDuration" class="width100" />。然后再看,下一个呢 ,是基本费用,108行,<input type="text" value="" class="width100" />,value也没用,把它改成name=“baseCost”,<input type="text" name="baseCost" class="width100" />,再看,下一个呢,是单位费用啊,单位费用115,<input type="text" value="" class="width100" />,也是value不用,改成name,叫unitCost,<input type="text" name="unitCost" class="width100" />,最后一个啊,是资费说明,资费说明呢,它是这个122行啊,<textarea class="width300 height70"></textarea>,这个不是input,它是一个文本域,textarea,那文本域也可以写name啊,我在textarea之上,加一个name ,name=“descr”,<textarea class="width300 height70" name="descr"></textarea>
  • 这样,我第2个配置就做好了,就是给这个网页上呢,每一个框,都加了name,数据呢,都有了名字,和Servlet当中啊,getParameter那个参数,保持一致。还有第3点,那么如果呢,网页上有单选多选,那么单选多选要加value,我们这里恰好有个单选对吧,再回到之前单选的那个地方,往上啊,92,94,96,资费类型啊,那么,如果呢,你不给它加value,它传过去了,应该是个单词on,明白吧,但我们表里要存的是什么呢,1,2,3,那个数据的类型是char 类型的1,对吧,你传个on过去,存不下对吧,它直接报错了,所以呢,我们需要改,我们需要手动声明一个值,那我在这个,92,94,96上,分别加上value,给它指定一个值啊,value=“1” ,value=“2”, value=“3”。

<div class="input_info fee_type">
    <input type="radio" name="costType"  value="1"  id="monthly" onclick="feeTypeChange(1);" />
    <label for="monthly">包月</label>
    <input type="radio" name="costType"  value="2"  checked="checked" id="package" onclick="feeTypeChange(2);" />
    <label for="package">套餐</label>
    <input type="radio" name="costType"  value="3"   id="timeBased" onclick="feeTypeChange(3);" />
    <label for="timeBased">计时</label>
</div>

  • 那写完了value等于1,2,3啊,因为库里存的是1,2,3, 我们和那个对应就可以了啊。到现在为止啊,我们这个表单啊,配置好了,那还差点事,我们往后看啊,后面还有俩按钮,对吧,看那俩按钮有没有问题,那第一个那保存按钮,type="submit",这个可以吧,一点它就提交数据了,其次呢,还有取消按钮,那取消的话,就是普通的button对吧,默认没有任何的功能,那你想,我这个取消,点的话,应该会怎么样,点取消怎么样,就是不保存了对吧,应该回到上一页,对吧,回到那个查询,那我点取消,想回到查询,这个button,我得怎么处理,肯定得用到事件,对吧,onclick等于,这里面,得写那句话呢,能让它回到查询页面呢,写什么,location,你可以写啊,location.href="findCost.do",可以吧。
  • 还有别的办法么,返回是怎么返回,那么在js中,外部对象的根是window,window的下级,有什么location,history,document,screen,navigator,对吧,其中,history干什么啊,历史记录,它有几个方法,能前进和后退对吧,是不是可以调那个直接后退一下呢,可以吧,那个可以后退 ,就是浏览器呢,访问过一个网页之后,它会有缓存,我们后退的话,它就利用那个缓存就可以了,明白吧,就不用重新访问,这样效率高一点,这样也行。那我们就用那个history得了,那这样写啊,history.back();

<div class="button_info clearfix">
    <input type="submit" value="保存" class="btn_save"  onclick="showResult();" />
    <input type="button" value="取消" class="btn_save"  onclick="history.back()" />
</div>

  • 那这个增加功能,就算是处理好了,处理完以后呢,测试一下,看行不行啊,我把这个项目呢,重新部署一下,然后呢,启动tomcat,启动以后,我先访问这个查询,访问查询,我查到了,这么些条数据:
    在这里插入图片描述
  • 然后呢,在此基础上,我去增加,我随便编一个数据吧,比如说,99元套餐,990小时,99块钱,0.9元/时,99元套餐,就随便填一点数据,然后保存,成功了。
    在这里插入图片描述
    在这里插入图片描述
  • 再来一个,这回不是套餐,换一种,比如说,我再来个计时吧,计时收费啊,没有基本费用,只有单位费用,比如单位费用是3元,比较贵啊,资费说明,就随便写几个字啊,计时收费,保存,也可以。
    在这里插入图片描述
    在这里插入图片描述
  • 还比如说,我点增加,不填数据,点取消,回去了,这个增加功能就ok了。

资费增加功能代码实现之第二个请求:/addCost.do

1.src/main/java/dao/CostDao.save()
package dao;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import com.sun.corba.se.spi.orbutil.fsm.Guard.Result;

import entity.Cost;
import util.DBUtil;

public class CostDao implements Serializable {
	public List<Cost> findAll(){
		Connection conn	= null;
		try {
			conn = DBUtil.getConnection();
			//String sql = "select * form cost_lhh "+"order by cost_id";//测试错误页面error.jsp
			String sql = "select * from cost_lhh "+"order by cost_id";
			Statement smt = conn.createStatement();
			ResultSet rs = smt.executeQuery(sql);
			List<Cost> list = new ArrayList<Cost>();
			while(rs.next()) {
				Cost c = new Cost();
				c.setCostId(rs.getInt("cost_id"));
				c.setName(rs.getString("name"));
				c.setBaseDuration(rs.getInt("base_duration"));
				c.setBaseCost(rs.getDouble("base_cost"));
				c.setUnitCost(rs.getDouble("unit_cost"));
				c.setStatus(rs.getString("status"));
				c.setDescr(rs.getString("descr"));
				c.setCreatime(rs.getTimestamp("creatime"));
				c.setStartime(rs.getTimestamp("startime"));
				c.setCostType(rs.getString("cost_type"));
				list.add(c);
			}
			return list;
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("查询资费失败",e);
		}finally {
			DBUtil.close(conn);
		}
	}
	
	public void save(Cost cost) {
		Connection conn = null;
		try {
			conn = DBUtil.getConnection();
			String sql = 
					"insert into cost_lhh values("
					+"cost_seq_lhh.nextval,"
					+"?,?,?,?,'1',?,sysdate,null,?)";
			PreparedStatement ps = conn.prepareStatement(sql);
			ps.setString(1, cost.getName());
			//setInt,setDouble不允许传入null,
			//但实际业务中该字段却是可能为null,
			//并且数据库也支持为null,可以将
			//这样的字段当做Object处理
//			ps.setInt(2, cost.getBaseDuration());
//			ps.setDouble(3, cost.getBaseCost());
//			ps.setDouble(4, cost.getUnitCost());
			ps.setObject(2, cost.getBaseDuration());
			ps.setObject(3, cost.getBaseCost());
			ps.setObject(4, cost.getUnitCost());
			ps.setString(5, cost.getDescr());
			ps.setString(6, cost.getCostType());
			ps.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException("增加资费失败",e);
		} finally {
			DBUtil.close(conn);
		}
	}
	
	public static void main(String[] args) {
		CostDao dao = new CostDao();
		Cost cost = new Cost();
		cost.setName("包月");
		//cost.setBaseDuration(660);
		cost.setBaseCost(1200.0);
		//cost.setUnitCost(0.6);
		cost.setDescr("包月最爽");
		cost.setCostType("1");
		dao.save(cost);
		/*Cost cost = new Cost();
		cost.setName("66元套餐");
		cost.setBaseDuration(660);
		cost.setBaseCost(66.0);
		cost.setUnitCost(0.6);
		cost.setDescr("66元套餐实惠");
		cost.setCostType("2");
		dao.save(cost);*/
	}
}
2.src/main/java/web/MainServlet.java
package web;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.AdminDao;
import dao.CostDao;
import entity.Admin;
import entity.Cost;
import util.ImageUtil;

public class MainServlet extends HttpServlet {

	@Override
	protected void service(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//获取访问路径
		String path = req.getServletPath();
		//根据规范(图)处理路径
		if("/findCost.do".equals(path)) {
			findCost(req,res);
		} else if("/toAddCost.do".equals(path)) {
			toAddCost(req,res);
		} else if("/addCost.do".equals(path)){
			addCost(req,res);
		} else {
			throw new RuntimeException("没有这个页面");
		}
	}
	
	//查询资费
	protected void findCost(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//查询所有的资费
		CostDao dao = new CostDao();
		List<Cost> list = dao.findAll();
		//将请求转发到jsp
		req.setAttribute("costs", list);
		//当前:/netctoss/findCost.do
		//目标:/netctoss/WEB-INF/cost/find.jsp
		req.getRequestDispatcher("WEB-INF/cost/find.jsp").forward(req, res);
	}

	//打开增加资费
	protected void toAddCost(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		req.getRequestDispatcher("WEB-INF/cost/add.jsp").forward(req, res);
	}
	
	//增加资费数据
	protected void addCost(
		HttpServletRequest req, 
		HttpServletResponse res) throws ServletException, IOException {
		//接收传入的参数
		req.setCharacterEncoding("utf-8");
		String name = req.getParameter("name");
		String costType = req.getParameter("costType");
		String baseDuration = req.getParameter("baseDuration");
		String baseCost = req.getParameter("baseCost");
		String unitCost = req.getParameter("unitCost");
		String descr = req.getParameter("descr");
		//保存该数据
		Cost c = new Cost();
		c.setName(name);
		c.setCostType(costType);
		if(baseDuration != null && baseDuration.length()>0){
			c.setBaseDuration(Integer.valueOf(baseDuration));
		}
		if(baseCost != null && baseCost.length()>0) {
			c.setBaseCost(Double.valueOf(baseCost));
		}
		if(unitCost != null && unitCost.length()>0) {
			c.setUnitCost(Double.valueOf(unitCost));
		}
		c.setDescr(descr);
		CostDao dao = new CostDao();
		dao.save(c);
		//重定向到查询
		//当前:/netctoss/addCost.do
		//目标:/netctoss/findCost.do
		res.sendRedirect("findCost.do");	
	}
}
3.servlet-doc/NETCTOSS_HTML/fee/fee_add.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>达内-NetCTOSS</title>
        <link type="text/css" rel="stylesheet" media="all" href="../styles/global.css" />
        <link type="text/css" rel="stylesheet" media="all" href="../styles/global_color.css" />
        <script language="javascript" type="text/javascript">
            //保存结果的提示
            function showResult() {
                showResultDiv(true);
                window.setTimeout("showResultDiv(false);", 3000);
            }
            function showResultDiv(flag) {
                var divResult = document.getElementById("save_result_info");
                if (flag)
                    divResult.style.display = "block";
                else
                    divResult.style.display = "none";
            }

            //切换资费类型
            function feeTypeChange(type) {
                var inputArray = document.getElementById("main").getElementsByTagName("input");
                if (type == 1) {
                    inputArray[4].readOnly = true;
                    inputArray[4].value = "";
                    inputArray[4].className += " readonly";
                    inputArray[5].readOnly = false;
                    inputArray[5].className = "width100";
                    inputArray[6].readOnly = true;
                    inputArray[6].className += " readonly";
                    inputArray[6].value = "";
                }
                else if (type == 2) {
                    inputArray[4].readOnly = false;
                    inputArray[4].className = "width100";
                    inputArray[5].readOnly = false;
                    inputArray[5].className = "width100";
                    inputArray[6].readOnly = false;
                    inputArray[6].className = "width100";
                }
                else if (type == 3) {
                    inputArray[4].readOnly = true;
                    inputArray[4].value = "";
                    inputArray[4].className += " readonly";
                    inputArray[5].readOnly = true;
                    inputArray[5].value = "";
                    inputArray[5].className += " readonly";
                    inputArray[6].readOnly = false;
                    inputArray[6].className = "width100";
                }
            }
        </script>
    </head>
    <body>
        <!--Logo区域开始-->
        <div id="header">
            <img src="../images/logo.png" alt="logo" class="left"/>
            <a href="#">[退出]</a>            
        </div>
        <!--Logo区域结束-->
        <!--导航区域开始-->
        <div id="navi">
            <ul id="menu">
                <li><a href="../index.html" class="index_off"></a></li>
                <li><a href="../role/role_list.html" class="role_off"></a></li>
                <li><a href="../admin/admin_list.html" class="admin_off"></a></li>
                <li><a href="../fee/fee_list.html" class="fee_on"></a></li>
                <li><a href="../account/account_list.html" class="account_off"></a></li>
                <li><a href="../service/service_list.html" class="service_off"></a></li>
                <li><a href="../bill/bill_list.html" class="bill_off"></a></li>
                <li><a href="../report/report_list.html" class="report_off"></a></li>
                <li><a href="../user/user_info.html" class="information_off"></a></li>
                <li><a href="../user/user_modi_pwd.html" class="password_off"></a></li>
            </ul>
        </div>
        <!--导航区域结束-->
        <!--主要区域开始-->
        <div id="main">            
            <div id="save_result_info" class="save_fail">保存失败,资费名称重复!</div>
            <form action="" method="" class="main_form">
                <div class="text_info clearfix"><span>资费名称:</span></div>
                <div class="input_info">
                    <input type="text" class="width300" value=""/>
                    <span class="required">*</span>
                    <div class="validate_msg_short">50长度的字母、数字、汉字和下划线的组合</div>
                </div>
                <div class="text_info clearfix"><span>资费类型:</span></div>
                <div class="input_info fee_type">
                    <input type="radio" name="radFeeType" id="monthly" onclick="feeTypeChange(1);" />
                    <label for="monthly">包月</label>
                    <input type="radio" name="radFeeType" checked="checked" id="package" onclick="feeTypeChange(2);" />
                    <label for="package">套餐</label>
                    <input type="radio" name="radFeeType" id="timeBased" onclick="feeTypeChange(3);" />
                    <label for="timeBased">计时</label>
                </div>
                <div class="text_info clearfix"><span>基本时长:</span></div>
                <div class="input_info">
                    <input type="text" value="" class="width100" />
                    <span class="info">小时</span>
                    <span class="required">*</span>
                    <div class="validate_msg_long">1-600之间的整数</div>
                </div>
                <div class="text_info clearfix"><span>基本费用:</span></div>
                <div class="input_info">
                    <input type="text" value="" class="width100" />
                    <span class="info">元</span>
                    <span class="required">*</span>
                    <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
                </div>
                <div class="text_info clearfix"><span>单位费用:</span></div>
                <div class="input_info">
                    <input type="text" value="" class="width100" />
                    <span class="info">元/小时</span>
                    <span class="required">*</span>
                    <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
                </div>
                <div class="text_info clearfix"><span>资费说明:</span></div>
                <div class="input_info_high">
                    <textarea class="width300 height70"></textarea>
                    <div class="validate_msg_short error_msg">100长度的字母、数字、汉字和下划线的组合</div>
                </div>                    
                <div class="button_info clearfix">
                    <input type="button" value="保存" class="btn_save"  onclick="showResult();" />
                    <input type="button" value="取消" class="btn_save" />
                </div>
            </form>  
        </div>
        <!--主要区域结束-->
        <div id="footer">
            <span>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</span>
            <br />
            <span>版权所有(C)加拿大达内IT培训集团公司 </span>
        </div>
    </body>
</html>
4.src/main/webapp/WEB-INFO/cost/add.jsp
<%@page pageEncoding="utf-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>达内-NetCTOSS</title>
        <link type="text/css" rel="stylesheet" media="all" href="styles/global.css" />
        <link type="text/css" rel="stylesheet" media="all" href="styles/global_color.css" />
        <script language="javascript" type="text/javascript">
            //保存结果的提示
            function showResult() {
                showResultDiv(true);
                window.setTimeout("showResultDiv(false);", 3000);
            }
            function showResultDiv(flag) {
                var divResult = document.getElementById("save_result_info");
                if (flag)
                    divResult.style.display = "block";
                else
                    divResult.style.display = "none";
            }

            //切换资费类型
            function feeTypeChange(type) {
                var inputArray = document.getElementById("main").getElementsByTagName("input");
                if (type == 1) {
                    inputArray[4].readOnly = true;
                    inputArray[4].value = "";
                    inputArray[4].className += " readonly";
                    inputArray[5].readOnly = false;
                    inputArray[5].className = "width100";
                    inputArray[6].readOnly = true;
                    inputArray[6].className += " readonly";
                    inputArray[6].value = "";
                }
                else if (type == 2) {
                    inputArray[4].readOnly = false;
                    inputArray[4].className = "width100";
                    inputArray[5].readOnly = false;
                    inputArray[5].className = "width100";
                    inputArray[6].readOnly = false;
                    inputArray[6].className = "width100";
                }
                else if (type == 3) {
                    inputArray[4].readOnly = true;
                    inputArray[4].value = "";
                    inputArray[4].className += " readonly";
                    inputArray[5].readOnly = true;
                    inputArray[5].value = "";
                    inputArray[5].className += " readonly";
                    inputArray[6].readOnly = false;
                    inputArray[6].className = "width100";
                }
            }
        </script>
    </head>
    <body>
        <!--Logo区域开始-->
        <div id="header">
            <img src="../images/logo.png" alt="logo" class="left"/>
            <a href="#">[退出]</a>            
        </div>
        <!--Logo区域结束-->
        <!--导航区域开始-->
        <div id="navi">
            <%@include file="../menu.jsp" %>
        </div>
        <!--导航区域结束-->
        <!--主要区域开始-->
        <div id="main">            
            <div id="save_result_info" class="save_fail">保存失败,资费名称重复!</div>
            <form action="addCost.do" method="post" class="main_form">
                <div class="text_info clearfix"><span>资费名称:</span></div>
                <div class="input_info">
                    <input type="text" class="width300" name="name"/>
                    <span class="required">*</span>
                    <div class="validate_msg_short">50长度的字母、数字、汉字和下划线的组合</div>
                </div>
                <div class="text_info clearfix"><span>资费类型:</span></div>
                <div class="input_info fee_type">
                    <input type="radio" name="costType"  value="1"  id="monthly" onclick="feeTypeChange(1);" />
                    <label for="monthly">包月</label>
                    <input type="radio" name="costType"  value="2"  checked="checked" id="package" onclick="feeTypeChange(2);" />
                    <label for="package">套餐</label>
                    <input type="radio" name="costType"  value="3"   id="timeBased" onclick="feeTypeChange(3);" />
                    <label for="timeBased">计时</label>
                </div>
                <div class="text_info clearfix"><span>基本时长:</span></div>
                <div class="input_info">
                    <input type="text" name="baseDuration" class="width100" />
                    <span class="info">小时</span>
                    <span class="required">*</span>
                    <div class="validate_msg_long">1-600之间的整数</div>
                </div>
                <div class="text_info clearfix"><span>基本费用:</span></div>
                <div class="input_info">
                    <input type="text" name="baseCost" class="width100" />
                    <span class="info">元</span>
                    <span class="required">*</span>
                    <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
                </div>
                <div class="text_info clearfix"><span>单位费用:</span></div>
                <div class="input_info">
                    <input type="text" name="unitCost" class="width100" />
                    <span class="info">元/小时</span>
                    <span class="required">*</span>
                    <div class="validate_msg_long error_msg">0-99999.99之间的数值</div>
                </div>
                <div class="text_info clearfix"><span>资费说明:</span></div>
                <div class="input_info_high">
                    <textarea class="width300 height70" name="descr"></textarea>
                    <div class="validate_msg_short error_msg">100长度的字母、数字、汉字和下划线的组合</div>
                </div>                    
                <div class="button_info clearfix">
                    <input type="submit" value="保存" class="btn_save"  onclick="showResult();" />
                    <input type="button" value="取消" class="btn_save"  onclick="history.back();"/>
                </div>
            </form>  
        </div>
        <!--主要区域结束-->
        <div id="footer">
            <span>[源自北美的技术,最优秀的师资,最真实的企业环境,最适用的实战项目]</span>
            <br />
            <span>版权所有(C)加拿大达内IT培训集团公司 </span>
        </div>
    </body>
</html>

资费修改功能需求分析与程序设计(独立完成)

根据需求拆分请求,并推导程序内部执行过程

  • 那么增加功能完成以后啊,下面我们再说一下这个修改的功能,这个修改的功能,其实和增加呢,非常的相似,那我们看一下这个修改功能怎么做啊。我们拿到一个功能以后,3步,第一步搞清楚需求对吧,需求很简单,修改,怎么操作呢,就是在查询页面上,点修改按钮,对吧,点修改按钮,点了以后,能打开修改页面吧,打开以后你就改呗,改完以后就存呗,那存完以后怎么办呢,还是跳到查询对吧,所以其实你看修改和增加差不多,那修改和增加,这两个功能,有区别么,区别在于哪,有人说sql语句不同,这个就不叫区别,它俩不同的功能,sql语句一定不同,是吧,你比如说,我们再做一个模块,做个账务账号模块,我们就做查询,你说sql不同,这是区别么,那换了一个模块,换了一个表,肯定是不同对吧,这是必然的。就是怎么说呢,典型的区别,能够影响你开发的那个区别,动态的,增加也是动态的啊,这是7个字段,那个是6个,是吧,这也不叫区别,你换个模块,你说我增加数据,那个还是20个字段呢,但是处理方式有区别么,你说我传6个字段,传7个字段,传9个字段有区别么,没啥区别,这不叫区别。
  • 这个页面上是不是有,会显示数据啊,增加页面上是什么没有的,修改的时候,是不是得有旧的数据啊,对吧 ,那旧的数据怎么能,摆到这个网页上来,是这意思吧,这个是一个关键,这个就是关键,你把这个解决了,其余的就好办了,你说我点修改按钮,打开修改页面,是不跟增加一个套路,我在增加页面上填数据保存,保存给服务器 传数据,服务器接收数据,调那个dao,有区别么,没有,仅仅是dao里调是update对吧,那都一样,逻辑都一样,所以,唯一的区别 是什么呢,就是我们打开修改页面时,得有默认值对吧,这是主要区别。
  • 我把这个思路呢,串接一下啊,重点是花在这个默认值上,画一下啊,web项目,左边是浏览器,右边是服务器,就这个套路啊,然后呢,浏览器要访问服务器的修改功能,那显然我之前说了啊,咱们这个项目当中,基本所有的网页都是动态的,因为顶上的图标是动态的对吧,都是动态的,是这样吧,所以,我们修改,打开修改页面,也是动态的,那什么时候,打开修改页面呢,是我们在页面上点了修改 按钮的时候,那当用户在网页上点击了修改按钮,那么,我们要发请求给服务器,然后呢,服务器,我们需要访问的是,还是Servlet是吧,动态的么,还是访问Servlet啊,MainServlet,那这个方法,我叫,toUpdateCost(),行不行啊,MainServlet.toUpdateCost(),和那个toAddCost差不多,就是一个模式,一个套路,然后呢,那你看啊,我这个打开修改页面,调MainServlet.toUpdateCost(),它是不是得转发啊,也得转发对吧,它得把请求转发给谁呢,/WEB-INF/cost/update.jsp,转发给它,这个套路和增加,目前来说是一样的,然后呢,update.jsp,它向浏览器响应,这是我们的第一个请求,这个请求,它的路径有要求,我要求它的路径是,现在你能猜到了,是什么呢,/toUpdateCost.do对吧,我这个规则啊,这个风格都是统一的,就都一样,那你看到这个方法以后,就应该知道这个路径了啊,就这样。
  • 然后,那你注意了,这回啊,和这个增加不一样,因为增加我们打开的表单上,什么都没有,对吧,就空的,没有数据,现在是要求有数据,那你说这个数据怎么来的,怎么给的,怎么让这个页面上有数据呢。就首先说,网页上这个文本框里要有默认值,你得怎么办,框里有默认值怎么办,就是你怎么体现这个默认值啊,你怎么在框里体现这个默认值,怎么给它默认值,提取数据,不是,我说表单里,文本框里,怎么给它显示一个默认值,就是你要想要框里有默认值,得写个value="abc",是不是这意思,是这样吧,我想问的是这个话题,别那么跨度太大,不用着急,慢慢来。那当然你这样,就把值写死了 对吧,我们应该写个动态的值,动态的值是什么呢,不就el表达式么,el表达式,不就能取动态的值么,而这个,我们这个,value="${}",这句话里得在jsp上写对吧,因为jsp上,我们写表单么,对吧,得在jsp上写,所以这el,value="${}",它的值显然是从jsp这里来取的,对吧,jsp得给value传值。
  • 我们倒推啊,那传的值,传的是一条,要修改的数据,那最好封装为对象,是这样么,是吧,最好是个对象,Cost,那这个Cost对象从哪来呢,有人说,我们从查询页面上来,因为你看,我是在查询页面上,点修改,那我点修改那一刻,那个数据是知道的,对吧,我给它传过去,这是可以的,但是有两个弊端啊,第一个弊端是什么呢,要修改的话,比如说这里面只显示了6个字段,但数据库里,它要修改的话,它可能还有更多的字段,明白吧,听明白了么,可能有更多的字段,可能你传不全,再一个,即便是传全了,你想你传那么多参数,会影响这个流量的,明白吧,影响网络的这个效率,因为传数据多会慢,那而且还有个弊端是什么呢,有可能是什么呢,我打开这个查询页面,看到这个数据了,我要改,突然接个电话,有点急事我就出去了,接个电话,这个电话比较长,我过了20分钟回来的,回来以后,我再点修改的时候,那你想,有没有可能,在这20分钟之内,有人,把这数据已经改了,有没有可能,是不是有可能,在我出去接电话的那个时候, 这数据已经被别人改了,那我再点修改时,我再把这个数据传过去,是不是旧的,旧了,不够新,是这意思吧,也有这个时效的问题,是这样吧。
  • 就总之啊,点修改时,你直接从查询页面上取数,传过去,数据比较多,影响这个速度,然后呢,数据还可能是旧的,明白吧,不是特别好啊。那比较好的方式是什么呢,从数据库里查对吧,但你查,得不得有个条件来查啊,你把id传过去,用id来查,明白这意思吧,这样的话,就ok了,所以最好是这样啊,所以我们修改时啊,传个id进去,然后MainServlet.toUpdateCost()这呢,通过id来查,那你要通过id来查的话,你想啊,我是不是得在dao里再写个方法啊,是吧,我们在CostDao里再加个方法,这个方法,我叫CostDao.findById(),行吧,根据id来查询数据啊,那你给我传入的是id,我给你返回的是什么,Cost对象,是这意思吧,就可以了,然后把这个Cost对象给MainServlet.toUpdateCost(),是这样吧,就完了。那当然你得给我的,给CostDao.findById()的是id。
  • 那要求什么呢,浏览器是不是得传个id过去,对吧,传个id怎么传呢,按理来讲,我们传的数据得有表单对吧,是吧,但这个太麻烦了,我们就点修改,就传一个参,还用个表单,太麻烦,我们可以这样做,这件事,我们之前还做过,我们在讲el表达式时做过,我可以这样。我们在写路径之后啊,问号,id等于,我直接把查询页面上的那个id值,就给它传过去,/toUpdateCost.do?id=${c.costId},能看明白这意思么,就是说,咱们讲el表达式的时候,不也这么干过么,我们当时需要一个参数,要演示怎么给传参,取参数,然后我就直接在地址栏敲了个参数对吧,是吧,那当其修改,只需要一个参数,我可以这样做么,也是可以的,这样省事,不用表单了,get请求,就行了,因为参数少,这样方便,而这个参数c.costId,是从查询页面中取到的,查询页面上咱们c.costId,不是能取到这个id么,我们在页面上显示的这个id,不就是写c.costId得到的么,是这样吧,所以,你也可以 用这种方式呢,把它往后传,也可以。
  • 所以,那第一步,我们的关键是什么,其实是这两个地方,就是说,我怎么给服务器传个id进去,服务器呢,怎么去查数据,查完数据 给jsp,jsp呢,value里,怎么去把这个值,显示出来,这个是关键的,其他的,就和增加一样了,这关键步骤呢,我们就说完了。
    在这里插入图片描述
  • 这个资费修改的功能,也是按照这个查询啊,增加啊,一样的方式,来分析这个问题,那我们每一个功能,都这样分析的话,主要是为了把这个分析问题的这个方式方法,基本原则掌握了,熟悉了,然后呢,我们遇到其他的新的问题,这样的话,在做这个案例时,有章可循,当然这个问题呢,只是一个就是比较概括的,比较笼统的,分析问题的方式,很多细节还需要我们画图时呢,或者是呢,在详细想的时候呢,再深刻思考一下,但是呢,毕竟这是一个我们解决问题的一个入口,或者说一个入手的一个点。然后呢,这个修改,我们发现它的操作方式呢,和增加一样,然后经过分析发现呢,唯一的区别就是修改时有默认值,然后,增加时没有,那我们可以反推一下,我想让网页有默认值,我想让框里有默认值,我就得在框上写value,因为value是给框设置默认值,对吧,以前在这个html阶段的时候,就是这样讲的,所以,如果你把以前的内容忘了的话呢,可能这一点,你也想不到啊,你记住的话呢,能想出来啊。
  • 但是呢,value里面这个值,不能写死,我们要是动态,所以呢,我们要写el表达式,因为el表达式呢,它是获取一个动态的值,获取一个变量,那么,我们这句话,肯定是在jsp上写,因为jsp上会写标签,那么el表达式,它所取的值,那很显然,我们是应该是Servlet转发过来,就行了。然后呢,因为是修改是一条数据,那你转发过来的话,最好是一个Cost对象,那这个对象的数据怎么来,咱们也分析了,我们可以啊,就是查询时,点修改时,你把整个查询页面上的数据给传过去,就把点修改时,把这个,那一行数据传过来,转发到jsp这也可以,但我说了,那样有缺点,第一个是,这个传数据啊,那数据比较多,没必要,再一个呢,那个数据呢,容易这个过期,容易这个比较旧,所以最好重查一遍,因此呢,通常,咱们修改时,就只是,只传一个值,传id,/toUpdateCost.do?id=${c.costId},传这个数据的主键,传给服务器以后,服务器呢,通过这个id把数据,把你要修改的数据得到,得到以后呢,转发过来,就能显示。
  • 然后呢,我们这个流程当中呢,需要根据id去查询数据,那这个比较容易了,根据id查一条数据,你查询全部数据,这个也没有什么问题啊,因为和以前模拟员工查询案例时也是类似的,只不过啊,那时候我们写的是,根据id查员工,现在是根据id查资费,这一样,一样的逻辑,然后呢,点修改时,我怎么把id传给服务器呢,就是浏览器给服务器传数据,我们以前呢,都是写的表单,但是呢,这个场景里啊,咱们这个传的数据太少了,而且这个查询页面上不止一个修改按钮,好多个,对吧,每一行都有一个,那你要是,加表单话,你这可能得加好多个表单,你有10条数据,加10个表单,这个有点啰嗦了,那你像这种啊,传少量数据的情况下,你传的又不是什么密码,所以呢,往往我们可以用get请求去传,就别用表单提交,别用post了,我们干脆呢,就是点按钮时,然后呢,访问的这个路径/toUpdateCost.do,我们直接呢,在路径之后,拼上这个参数c.costId,当然了,这个参数值呢,你不能写死啊,是变量,我们的查询页面上,/toUpdateCost.do?id=${c.costId},这句话,就是获取id,把它拼给这个参数之后,传进去就可以了,就这样做。
  • 这是咱们这个打开修改页面啊,这是第一个请求,它的一个关键所在,第一个就传这个参数,然后呢,我们怎么再利用这个参数,得到数据,然后呢,给这个修改页面显示默认值,就这两个地方是比较关键的,其他的还好,然后呢,像这种情况,我们传一个参数啊,咱们以前在讲el表达式时,也干过这个事,当时呢,我们是为了演示el表达式第3个功能,就是我们在使用el表达式,要取一个请求参数对吧,没有请求参数,我们自己这么杜撰了一个,那这个场景下,也可以这样用。这是第一个请求啊,那当然修改的话,还有后续的请求,保存,以及后续的一些个逻辑,我把它画全,画完善,但其实呢,后面的逻辑呢,和增加就一样了。
  • 那么,/toUpdateCost.do?id=${c.costId},这个请求完成以后啊,我们浏览器会得到一个网页,页面上有表单,表单里会有很多框,框里呢,有默认值,这块注意啊,这块可能是有一个地方,还不是这样,就是通常文本框,我们是value设成默认值,但我们表单里,还有那个textarea对吧,文本域,文本域的默认值不是value,文本域的默认值怎么办,是它的内容对吧,双标签中间的内容,就是默认值,所以对于文本域呢,你把这个el表达式,写到那个双标签之间就行了,那不单如此,这个网页上还有什么呢,单选,单选也不是显示一个值是吧,它是选中,那么你怎么办,你得判断啊,就是如果说呢,发送过来的这个Cost,它的costType是1的话,我应该选中第一个radio,如果是2的话,选中第2个radio,如果是3的话,选中第3个radio,应该是加以判断,然后呢,在不同的radio上加上check,那判断用什么呢,if标签,jstl对吧,用jstl就可以了。
  • 当然了,这个得怎么写呢,我得怎么写这个if呢,在哪上写呢,怎么处理呢,自己好好想一想,因为,咱们不能把每一个细节,都讲的那么细,如果讲的太细的话,你这个开发的时候,一点障碍都没有,咱们这个开发就没劲了,所以,你需要遇到一些障碍,自己好好想一想,然后的话呢,看看自己能不能解决出来,尽量去想办法解决。因为这是我们工作时的一种常态。总之啊,就是说第一个请求啊,大概是这样,中间肯定还会有些细节,要再好好想一想。然后呢,后面就跟增加一样了,那用户得到了表单以后,他就看到了表单上面的保存按钮,他填完数据以后呢,可以点保存,一点保存的话,肯定得像服务器传数据,对吧,这是第2个请求。
  • 那这个请求呢,也是啊,因为你有数据么,也是由Servlet处理,我们还是调MainServlet,然后呢,写一个updateCost()方法,上面这个叫,MainServlet.toUpdateCost(),下面这个叫,MainServlet.updateCost(),然后呢,这个路径也有要求啊,但是现在,大家应该能猜出来了啊,就是,/updateCost.do,就访问它。然后呢,访问到它以后,把数据呢,传给它,传给它以后呢,它要修改数据,那就,我们在dao里呢,再写个方法,这个方法就叫update就行了,CostDao.update(),把这这数据呢,传给CostDao.update(),它去修改数据,当然了,我们传入的数据也需要包装成Cost对象,只要程序不报错,就继续啊,那保存完以后,修改完以后,怎么办呢,重定向到查询对吧,这个和增加的处理一样,所以最后呢,重定向到查询,而查询呢,咱们目前已经完成了,它的方法叫findCost,MainServlet.findCost(),当然呢,findCost会调用这个,CostDao.findAll()方法,这也是我们已经完成的东西,就总之呢,浏览器可以访问查询MainServlet.findCost(),查询呢,调dao得到数据,dao将数据呢返回,然后呢,MainServlet.findCost(),它向浏览器做出响应,但它不是直接响应,它是转发到jsp,/WEB-INF/cost/find.jsp,由jsp响应的啊,把数据呢,转发给jsp,然后呢,由jsp做出响应。
    在这里插入图片描述
  • 那这些内容啊,是我们目前呢,已经实现的,已经完成的,我把它标成黑色,表示呢,它不是我们关注的重点,但是呢,为了把思路串起来,我还是把它画出来,那这是整个功能当中的第3个请求, 那第3个请求呢,已经完成了,然后呢,第2个请求MainServlet.updateCost(),结束的时候,程序执行完的时候,你要这个,重定向,建立浏览器自己去访问/findCost.do,到这,就行了。那总体来说,就是修改的这个开发的流程,这3步和增加差不多,只是呢,它比增加,增加就直接就转发就完了,对吧,没有任何的逻辑,它这多了一点点逻辑,多传个id,多查id,显示默认值,多了这么一个环节。那这个思路说完了,那开发重点呢,就是集中在第一个请求。那这个资费修改功能,以及资费模块的,删除啊,还有这个资费查询的分页,这些功能自己独立完成。

关于资费修改功能的一些问题

  • 资费修改功能在开发时,可能会有一些问题,有些疑问,比如像这样一个话题,就是说,在开发这个资费修改的时候,然后呢,点修改按钮,向服务器呢,就发送请求,发送请求的时候,我们直接在地址上,就是拼了一个参数,/toUpdateCost.do?id=${c.costId},这样的话呢,就不用写表单了,就省事了啊,然后呢,这个地方呢,我是大概是这样一写,那你具体写的时候呢,还得看那个,咱们项目代码的实际情况,但写的时候有一些困扰啊,什么困扰呢,说一下,我们这项目中啊,这个地方是这样写的啊,它是一个标签啊,是input,<input type="button" value="修改" onclick="fee_modi.html"/>,然后呢,修改上,直接写了,onclick等于,里面原来写的是fee_modi.html,是这样,那我们呢,需要对它进行修改,把它改为呢,显然是,把这个请求fee_modi.html,静态网页,改成这个路径对吧。
    在这里插入图片描述
  • 那得这么改啊,改成什么呢,当然,你要改成相对路径啊,toUpdateCost.do,是吧,然后带参数问号,问号之后,这个参数名,可以叫id吧,可以叫costId吧,<input type="button" value="修改" onclick="toUpdateCost.do?id"/>,叫什么都行,参数名随便取,但是你这里啊,你无论是叫什么参数名,我们在接收时一定是,通过这个参数名来接收对吧,然后有人就问了,老师你看,这地方我这是,这种请求应该是get请求了,没有表单对吧,没写post,就get请求,get请求,我怎么接收参数呢,怎么接收呢,就是学到这啊,就学的有点乱,对以前学的有点乱。
  • 以前我们讲了get和post有区别,区别在于什么呢,get请求用路径传参,post用实体传参对吧,然后呢, 传参的能力有区别,get请求传的少,post请求多对吧,但是我们取值时,有区别么,没有,都一样,都是getParameter,对吧,这搞乱了,你把这个get和post的区别,给搞乱了,你遇到这种情况,你得回顾一下,你一回顾下发现,它俩,这两者的区别在于传参的方式不同,但取值的方式是一样的对吧,是这样的,当然还有,get请求和post,我们在出现乱码时,解决方式不一样对吧,这有区别,但还是,接收参数的时候呢,接收的参数没区别,你看,所以呢,咱们学,以往学的那个东西,也是讲的很清楚的,但是呢,一做项目时,也很容易会把一些内容给搞混了,这个是最可怕的,那么,一定是要把那个知识点呢,好好复习一下。
  • 就是我们很多地方呢,你感觉有一些困扰,困惑,往往都是,就是这么产生的,是对以前的知识点有了遗忘,遗忘以后呢,你一想,也没去看那个笔记,就脑子一想,想的记错了,记串了啊,但这只是一个小问题啊,还有,有人说,我有写这个,接着写啊,id=${c.costId}<input type="button" value="修改" onclick="toUpdateCost.do?id=${c.costId}"/>,那我们在这个地方能够得到,这个c.costId么,是可以的,因为修改按钮,它是在那个table里,是吧,它在table里,它在那个数据行里,那么,在它之外,我们写的是<c:forEach>,是这样吧,我们在此之外写了forEach,然后呢,写了什么呢,写了这个item等于什么什么,var等于c,所以在循环之内,当然,后面是循环结束啊。

<c:forEach items="" var="c">
	...
	<input type="button" value="修改" onclick="toUpdateCost.do?id=${c.costId}"/>
	...
</c:forEach>

  • 当然,不只是这一行代码,这个forEach之内,是整个tr,而tr里包含了修改按钮,所以,这只是呢,forEach之内的一部分,那我在forEach之内,我能不能得到这个c啊,可以,我就可以得到这个c.costId,这个数据对吧,这是可以的,那所以呢,可以得到这个数据,我可以呢,把数据呢,输出到这个位置,get请求路径上,拼到id之后,那有人说了,我这样写,也可以,我还可以这样写,<input type="button" value="修改" onclick="toUpdateCost.do?id="+${c.costId}/>,也可以这样写,因为你看onclick前面,toUpdateCost.do,这一部分是一个固定的字符串对吧,然后我加个变量,那为什么,这样写也行,onclick="toUpdateCost.do?id=${c.costId}",这样写也可呢,onclick="toUpdateCost.do?id="+${c.costId},我不去加也可以呢,这很奇怪啊,这为什么呢,对,这还是你对这个翻译,没有理解。
  • 你注意啊,我们这段代码,<input type="button" value="修改" onclick="toUpdateCost.do?id="+${c.costId}/>,这些固定的标签和字符串,是什么时候执行的呢,是浏览器加载网页时执行的,那这el是什么时候执行的呢,是在这个服务器端执行的,这两者执行顺序不一样,当我们呢,打开浏览器,得到了网页之后,这个el表达式,早就被服务器执行为了一个数字,比如说5,<input type="button" value="修改" onclick="toUpdateCost.do?id=5"/>,当我们浏览器得到了这个网页以后,这个id这个地方变成了5 ,那这个5你是,直接输出到这个等号的后面,或者是你这样加5,<input type="button" value="修改" onclick="toUpdateCost.do?id="+5/>,没有区别明白吧。
  • 可能有人还想不到,想不明白,如果你实在想不明白怎么办呢,你打开你的那个tomcat/work/catelina/localhost,你去看一下,你所写的那个jsp,它所生成的那个servlet,你看一下生成的那个servlet是怎么print的,明白吧,一看就明白了,所以,很多时候呢,就是说,我们知道jsp该怎么写,我也大概知道,它返回的是一个网页,但是呢,它到底是怎么去返回的网页,这件事,很多时候,老是想不明白,那建议呢,大家去看下那个生成的servlet,那里面,jsp生成的那个servlet,它怎输出的,一看啊,是这样做的,就知道了,所以呢,以后啊,如果说jsp,它里面有一些规则搞不懂,看生成的那个类。还有人呢,是想做分页还是想做什么功能啊,它在这个网页之前呢,写js,然后呢,在这个head里写js,想在js里呢,去得到这个对象,<c:forEach items="" var="c">中的Cost对象c,用el得到Cost对象c。
  • 比如说,var str = ${c.costId},可能会想到这样写,你注意,我们在js里,可不可以写el呢,不是不可以,也可以,但是如果说这个js是在head里,是在这个forEach之外,<c:forEach items="" var="c">之外。

var str = ${c.costId}
...
<c:forEach items="" var="c">
	...
	<input type="button" value="修改" onclick="toUpdateCost.do?id=${c.costId}"/>
	...
</c:forEach>

  • 你想一想,能不能得到这个数据啊,能不能得到,得不到,因为这个肯定,这个c.costId,一定是在这个forEach之内才能得到对吧,如果你js写在外面去了,就得不到,这是一方面,再一方面,即便是能得到,也不建议这样写,为啥呢,这是js,el是什么呢,本质上是java对吧,你最好别去把java和js耦合到一起,我们把这个java和html耦合到一起,已经耦合度够高了,你再把它和js,甚至和样式耦合到一起,这件事就很恶心了,明白吧,遇到问题的话,是很难解决的,就很难维护的,所以说,一般不会这样写,所以,你就怎么做呢,你在写js的时候,就不要在js里直接写el。那有人说,我写js,我想得到这个id呢,你从那个表格里,咱们不是把,用el把这个c.costId输出到了表格里么,你用js去那个表格里,得到那个数据后输出后的结果,而不是要调el,什么意思呢,看一下啊
    在这里插入图片描述
  • 就说比如说啊,我做了一个,做这个查询功能,我想做这个分页localhost:8080/netctoss/findCost.do,有同学想通过js去处理,然后呢,在head里写js,那么写js时,比如说,我想得到id,或者说,做某些功能,我想得到id,那这个页面上,这表格里步是有id么对吧,资费ID,你从页面上的表格里去取,你就别直接调el,我可以通过el把数据,输出到table之内,而js是从table之内,去取这个输出的结果,就怎么说呢,再看那个图,随便找个图吧,怎么说呢,就是说,你一定要理解这一点,那我们我们写的是jsp,那么对于jsp当中的el表达式和jstl标签,服务器就执行了,执行以后,它转化为什么呢,它转化为一个数据,比如说一个123,一个数字,或者说一个名字,它变成了一个具体的值,或者是变成了一段逻辑 if,for啊。
  • 总之呢,jsp,在服务器执行时,那上面的el表达式,和jstl标签呢,已经被变成了一个值,或者是一段逻辑。最终呢,就是,这段逻辑啊,把这个我们写的标签,然后表达式也好,转化为一个最终的结果,转化为什么,数字啊,字符串啊,一个结果,然后呢,最终输出给浏览器的,浏览器得到的,就是一个纯html,浏览器得到的不是jsp,浏览器得到的内容当中,不再有jstl标签,不再有el表达式,它就是一个,怎么说呢,一本正经的html,没有别的东西,所以,你就不要尝试说,我在这里啊,去访问el,我在这里去访问这个,什么什么东西,不要啊,因为它就是html,它只有什么呢,html,css,js啊,所以,你在写js的时候,只能访问css,只能访问html啊, 就总之呢,这一点要多想一想啊。
  • 然后呢,你多去看一看,那个翻译而成的那个类啊,好了,就是说,说这么一个话题吧,就是大家呢,可能很多同学,对这个程序执行,其实还是有一些困惑,这个困惑呢,主要是大家,还是对这个jsp,或者是Servlet啊,它的运行原理,或者说它的本质,理解的有偏差,那这个偏差呢,需要你去复习的,你要复习啊,什么是Servlet,它能解决什么问题,Servlet,它就是一个web组件,它能够给,我们浏览器拼一个动态网页,对吧,它是个动态网页,那它最终,给浏览器返回的,是拼好以后的结果,是一个html啊,然后呢,你还要复习,什么Servlet运行原理,jsp运行原理,jsp翻译的过程,甚至去看那个jsp翻译之后的那个类,你把这些呢,都串接起来,这些呢,都了解清楚了,然后的话呢,你才能够,对这件事,理解的比较透啊。
  • 然后你说,让我几句话,把它阐述清楚,这个是比较难的,因为咱们之前花了那么多时间呢,讲这个原理,那个原理,其实呢,是一步一步的,把这个最终的结论,推导出来,中间呢,一个环节你缺失了,这个结论,你确实是理解不上去啊,需要复习一下。这是,可能呢,开发时主要是集中在这个问题之上,就是说一下啊。

写在后面

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值