本文共 49623 字,大约阅读时间需要 165 分钟。
你不愿意种花,你说,我不愿看见它一点点凋落。是的,为了避免结束,你避免了一切开始。
但是.............这样就好了么?
叮咚购书商城功能说明书:
被管理对象,是系统操作、数据流转、数据封装的主要对象。一般情况下称为领域模型。一张表,与一个JavaBean形成一一对应关系,即领域模型。
通过以下分析,应该生成表结构。JavaBean对象。
分类:types | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id |
| Y | Varchar(32) |
|
name |
|
|
|
|
descr | 说明 |
|
|
|
|
|
|
|
|
图书是系统是管理的主要对象。
图书:books | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id |
| Y | Varchar(32) |
|
name |
|
|
|
|
price |
|
|
| 原价 |
auth |
|
|
|
|
img | 图片名称UUID |
|
| 只保存图片的名称全名。不用打散目录。 |
rebate | 折扣 |
| numeric(3,2) | 0.xx保留两位 |
stock | 库存数量 |
| int |
|
publisher | 出版社 |
|
|
|
publishdate | 出版时间 |
|
| yyyy-MM-dd |
pages | 页数 |
|
|
|
size | 开本 |
|
| 16开,8开等 |
printtimes | 印次 |
| int | 主要记录第几次印刷 |
versions | 版次 |
| int | 主要记录第几个版本 |
brief | 内容简介 |
| Text || varchar(2000) |
|
content | 目录 |
| Varchar(4000) | 必须以HTML形式保存。显示格式良好的HTML文档。 |
|
|
|
|
|
onlinetime | 上架时间 |
|
| 用于设置是何时上架的。Yyyy-MM-dd |
3、图书分类和图书关系表
一本书可能会属于多种分类。所以应该是多对多的关系。至少一本书,要属于一种分类
booktype | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
Bookid |
|
|
| 外键 |
typeid |
|
|
| 外键 |
|
|
|
|
|
|
|
|
|
|
priamy key(booki,typeid)
维护人员,是系统的管理员。对于不同的管理员,应该具有不同的功能。
管理员:admins | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id |
| Y | Varchar(32) |
|
name |
|
|
| 不能相同 |
password |
|
|
| 需要MD5加密,使用Spring的MD5算法 |
sex |
|
|
|
|
|
|
|
|
|
descr |
|
|
|
|
|
|
|
|
|
Users: | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id |
| Y |
|
|
name |
|
|
| 不可以重复 |
password |
|
|
|
|
phone |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
一个用户可以拥有多个收货地址,但多个收货地址,只可以一个有效。一个用户,最多只能拥有5个收货地址。
address: | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id |
| Y |
|
|
name | 收货人姓名 |
|
| 不可以重复 |
|
|
|
|
|
phone | 收货人电话 |
|
| 必须输入 |
zip | 收货人邮编 |
|
|
|
dft | 是否是默认收货地址 |
| Char(1) | 1:是,0:否。 |
userid | Users表的外键 |
|
|
|
mktime | 设置时间 |
|
| 按时间倒序排列 |
|
|
|
|
|
记录用户已经交易成功,或是没有交易成功的所有记录。对于已经发货的交易,用户不可以取消。只可以在收货后再做退货处理。
且退货状态,只可以由维护人员完成。用户无权在页面上设置状态为退货。
orders: | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id | 订单编号 | Y |
| 订单生成规则为User.hashCode+ ddHHmmssSSS不可以使用同步。 |
userid | 所属用户ID | F |
|
|
consignee | 收货人明细信息 |
| Varchar(300) | 将收货人信息全部写入此处。保证在修改了原始收货信息后,不会影响到已经生成的订单。如:张三,广州天河区棠东东路,13146789813。 |
paytype | 付款方式 |
| Char(1) | 1:货到付款,2:网上支付。 |
amt | 订单总金额 |
| Numeric(10,2) |
|
state | 订单状态 |
|
| 0:客户已经提交,等待发货。1商家已经发货2:交易已经完成,3:交易取消。4:客户已经退货。 |
orderdate | 下单时间 |
|
| Yyyy-MM-dd HH:mm:ss |
|
|
|
|
|
订单明细,主要记录一张订单的详细内容。修改源数据后,不应该对已经买过的商品产生任何影响。所以,应该将产品信息,全部拷贝到明细中来。
orderline: | ||||
列名 | 名称 | 主否主键 | 长度 | 备注 |
id |
| Y |
|
|
orderid | 所属于的订单编号 |
|
|
|
bookid | 图书id |
|
| 只需要保存id,查询时也可以带更多的信息。需要JavaBean的信息比表多。需要时再添加即可。 |
Amt | 数量,如购入了几本 |
|
|
|
price | 价格 |
|
|
|
|
|
|
|
|
略
略。注意建立实体之间的关系。
创建一系列的公共组件:gz.itcast.util
3.1 EncodingFilter - 字符编码过滤器(GET和POST字符编码过滤器统一为UTF-8)
3.2 ValidateCodeUtil- 验证码生成工具类
3.3 MD5Util - MD5加密字符串工具类(MessageDigest)
3.4 WebUtil - 拷贝请求数据到javabean的工具类
3.5 BaseServlet - 控制层servlet的基础类, 具体业务servlet继承此类。
3.6 BaseDao<T> - 数据访问层泛型DAO ,具体业务dao继承此类
开发要求:
1、任意用户都可以登录本系统浏览。
2、用户选购图书时,必须是已经注册用户。没有注册的用户不可以在本系统中购买。
3、用户购买的所有图书,可以永久的保在用户的订单系统中。
4、提供用户注册功能。
5、用户登录,查看订单进度。历史订单信息等。
6、用户登录后,修改收货人地址信息。
7、在没有发出货物之前,用户可以取消订单。
实现主页显示。
主要解决的问题:主页如何演示,如何显示分类,如何显示图书。
只有注册用户才可以在本系统是购物。
用户只有在成功登录后才可以将商品添加到购物车中。
用户购物的过程就是将产品不断的向购物车中添加的过程。
用户只有在提交了订单以后,才可以将本次所购买的所有信息保存到订单及订单明细表中。用户在提交了订单以后,可以不支付,也可以继承支付。系统将自动计算本次费用总和。
如果用户选择是货到付款则直接提交订单。如果用户选择银行支付,则转到银行的支付网关。
1、管理员登录。
2、管理员添加分类信息。
3、管理员添加一本图书。
4、管理员修改、删除一本图书。
(更加高级的功能)
仅管理用户登录。
在表单中增加一个新的分类。所有分类,都是顶层分类。目前先不涉及子分类。
在保存图书时,必须要选择此图书是何种分类。至少选择一项。
未来可以扩展的功能:
1, 实现网站在线支付功能。对接第三方支付平台(如易宝支付)
2. 用户注册成功后需通过邮件激活,以及订单生成后,通过发送邮件给用户提醒。
3. 后台区分不同分角色的管理员,实现不同角色的管理员拥有不同系统操作权限,更加灵活。
4. 购物车信息保存到数据库,这样用户下次登录后依然可以看到上次加入购物车的图书。
..........
一、密码采用使用MD5加密
使用MD5加密之后再保存到数据库
md5算法是生成一个32位的16进制的字符串
12345 -> fdfdfrerer
root -> dsdfxcv
二、数据库设计
一对多的关系:在多方表设计一个外键
多对多的关系: 独立设计一个关系表,存放两张表的外键
三、收货地址
Struts2就是基于MVC模式的开发框架,对servlet技术的封装!!!!
回顾MVC模式:
M: Model 模型。使用javabean技术,对业务数据的封装
V: View 视图。 使用jsp技术,对业务数据的显示。
C:Control 控制器。使用servlet技术
控制器的作用:
1)接收请求,获取用户的参数
2)调用业务逻辑处理方法
3)获取结果,导航视图,把结果显示视图中。
需求: 用户登录和注册功能(用户模块)。使用MVC模式进行开发。
设计:
dao层:
UserDao类:
queryUser(User user); //查询登录用户
addUser(User user);//添加用户方法
service层:
UserService类:
login(User user);//登录业务方法
register(User user);// 注册业务方法
web层:
一个功能对应一个Servlet类
登录功能: LoginServlet类 : /login访问
处理登录请求
跳转到视图(jsp)
如果是登录成功
跳转到主页
否则,不成功
跳转到失败页面
注册功能: RegisterServet类: /register访问
处理注册请求
跳转到视图(jsp)
注册成功,
跳转到登录页面
关注: 能不能优化web层的MVC的代码呢???
现状: 一个功能对应一个Servlet处理类,这样的话项目管理的servlet就会很多!!!管理web.xml文件很麻烦!!更多的servlet对象会消耗更多的服务器内存。(单例)
目标: 一个项目只对应一个Servlet处理类。
编写ActionServlet类,这是核心控制器类,在这个类读取mystrus.xml文件
(为了了解struts2框架的核心原理)
1)导入strust2的支持jar包
commons-beautils [beanutils工具包]
commons-filupload.ajr [文件上传]
commons-io.jar
commons-lang [struts2对java.lang.*类的支持]
freemarker.jar [视图显示技术]
javassit [struts2对字节码运算的支持]
ognl.jar [struts2对ognl表达式的支持]
struts2-core.jar [ struts2的核心包 ]
xwork-core.jar [webwork框架的支持,struts2的前身就是webwork(对webwork的封装)]
2)配置启动的全局的过滤器(Filter) - 和ActionServlet类似!!!
在web.xml中配置
<!-- 配置启动strut2的全局过滤器 --> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <!-- 过滤所有请求 --> <url-pattern>/*</url-pattern> </filter-mapping> |
3)编写不同的业务Action类,编写不同业务方法。默认execute方法。
public class LoginAction implements Action{ //默认方法 public String execute() throws Exception { System.out.println("执行了LoginAction的execute方法"); return "success"; }
} |
4)在src目录下(类路径的根目录下),新建一个struts.xml,配置Action对象
<struts> <package name="xxx" extends="struts-default">
<!-- 配置Action http://localhost:8080/day28_struts/login.action--> <action name="login" class="gz.itcast.action.LoginAction"> <result name="success" type="redirect">/index.jsp</result> </action> </package>
</struts> |
5)访问Action的业务方法
http://localhost:8080/day28_struts/login.action
strurs2是基于MVC开发模型的web层框架。
struts1也是MVC开发模式的框架。struts2并不是struts1的升级版。
struts2是基于webwork的升级版。struts2=webwork+sturts1
struts2的具体功能:
请求数据封装
简化国际化
简化文件上传
后台数据校验
......
项目启动:
1)创建核心过滤器StrutsPrepareAndExecuteFilter对象
2)执行核心过滤器的init方法
读取了依次以下的配置:
struts-default.xml [struts2框架的默认配置文件(不需要自定修改)]
struts-plugin.xml [struts2的插件配置文件]
struts.xml [我们写的业务配置文件(包含配置的Action)]
访问资源:
3) 在内存中查询对应的Action配置,得到class内容,创建Action对象。
4)读取Action配置的method内容,执行Action对象对应的方法。
1)声明struts2框架运行过程中使用到的一些对象
<bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" />
|
2)默认包,包名叫struts-default(注意:我们自己写的package必须继承这个默认包,只有继承了这个默认包才可以使用该默认包下的功能)
<package name="struts-default" abstract="true"> |
2.1 )跳转类型:
常用的跳转类型
redirect: 重定向到页面
dispatcher: 转发到页面
redirectAction: 重定向到Action
chain: 转发到Action
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> |
2.2 ) 声明拦截器(Interceptor)
struts2默认的拦截器(32个): 完成strutrs2的核心功能。(请求封装,文件上传,国际化..)
拦截器(Intercptor) vs 过滤器(Filter)
过滤器: 可以过滤任何类型的请求(html、servlet、jsp)和响应。加入通用的代码逻辑。
拦截器: 是sturts2的特有功能。只能过滤Action!!在执行Action的时候加入通用的代码。
2.3) 声明拦截器栈 (<interceptor-stack name="basicStack">)
默认拦截器:
<interceptor-stack name="defaultStack"> (18个拦截器) |
2.4 ) 默认包当前使用的拦截器:
<default-interceptor-ref name="defaultStack"/> |
注意:
我们写的包(package)就是继承struts-default默认包的,那么就继承了默认的18个拦截器
2.5) 当前默认包下的默认Action
<default-class-ref class="com.opensymphony.xwork2.ActionSupport" /> |
该文件是开发者自行配置的业务配置文件。(关键是Action的配置)
package: 代表一个包。管理action配置。在用一个包下面不能有同名的action name: 包名.在一个项目中不能出现同名的包。 extends: 继承。类似于类的继承。如果一个包继承另一个包,那么就会把父包的功能继承下来。 我们开发的包就必须继承struts-default包。 namespace: 名称空间。区分不同的包的访问路径。默认值 “/” 用户访问Action的路径搜索规则: http://localhsot:8080/day28/namespace/action的name
http://localhost:8080/day28_struts/user/a/b/login2.action ok http://localhost:8080/day28_struts/user/a/login2.action ok http://localhost:8080/day28_struts/user/login2.action ok http://localhost:8080/day28_struts/a/user/login2.action 不行!!
用户访问: http://localhost:8080/day28_struts/user/a/b/login2.action 先搜索: http://localhost:8080/day28_struts/user/a/b/login2.action 没有, 有就返回 接着搜索: http://localhost:8080/day28_struts/user/a/login2.action 没有, 有就返回 再搜索 : http://localhost:8080/day28_struts/user/login2.action 有, 有就返回 (为止)
abstract: 表示当前包是否抽象。如果当前包是抽象包,那么不能含有action. 抽象包中一般用来定义拦截器,公共视图,不做具体的业务。
|
action配置: name: action的名称。用于访问该Action的路径 class: Action业务对象的类名。一定是全名(包名+类名),这里struts2是用反射构造Action对象的 method: 执行的业务方法。不写默认值 execute
result配置: name: 视图的标记。在一个Action中名称不要重复! type: 跳转的类型。 redirect: 重定向 dispatcher: 转发(默认值) redirectAction: 重定向到Action chain: 转发到Action。可以在不同Action中通过request共享数据 stream: 用在下载文件的时候。 文本内容: 跳转到的页面或者Action |
<!-- 包含读取其他xml文件 注意: 声明的顺序就是读取的顺序!!!!--> <include file="config/struts-book.xml"></include> <include file="config/struts-user.xml"></include> |
Struts2就是一基于MVC模式的开发框架,对servlet技术的封装!!!!
回顾MVC模式:
M: Model 模型。使用javabean技术,对业务数据的封装
V: View 视图。 使用jsp技术,对业务数据的显示。
C:Control 控制器。使用servlet技术
控制器的作用:
1)接收请求,获取用户的参数
2)调用业务逻辑处理方法
3)获取结果,导航视图,把结果显示视图中。
需求: 用户登录和注册功能(用户模块)。使用MVC模式进行开发。
设计:
dao层:
UserDao类:
queryUser(User user); //查询登录用户
addUser(User user);//添加用户方法
service层:
UserService类:
login(User user);//登录业务方法
register(User user);// 注册业务方法
web层:
一个功能对应一个Servlet类
登录功能: LoginServlet类 : /login访问
处理登录请求
跳转到视图(jsp)
如果是登录成功
跳转到主页
否则,不成功
跳转到失败页面
注册功能: RegisterServet类: /register访问
处理注册请求
跳转到视图(jsp)
注册成功,
跳转到登录页面
关注: 能不能优化web层的MVC的代码呢???
现状: 一个功能对应一个Servlet处理类,这样的话项目管理的servlet就会很多!!!管理web.xml文件很麻烦!!更多的servlet对象会消耗更多的服务器内存。(单例)
目标: 一个项目只对应一个Servlet处理类。
编写ActionServlet类,这是核心控制器类,在这个类读取mystrus.xml文件
1)导入strust2的支持jar包
2)配置启动的全局的过滤器(Filter) - 和ActionServlet类似!!!
<!-- 配置启动strut2的全局过滤器 --> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <!-- 过滤所有请求 --> <url-pattern>/*</url-pattern> </filter-mapping> |
3)编写Action类,业务方法方法
4)在src目录下,新建一个struts.xml,配置Action对象
<struts> <package name="xxx" extends="struts-default">
<!-- 配置Action http://localhost:8080/day28_struts/login.action--> <action name="login" class="gz.itcast.action.LoginAction"> <result name="success" type="redirect">/index.jsp</result> </action> </package>
</struts> |
5)访问Action的业务方法
http://localhost:8080/day28_struts/login.action
回顾重点内容:
struts2开发步骤:
1)导入jar包
2)在web.xml配置全局过滤器(启动时候创建)
3)写业务的Action类
4)在src目录下写struts.xml文件,在这个文件配置action
大纲:
struts2的配置相关:
1)struts2的Action三种使用方式
2)struts2的路径通配符
3)struts2的常量配置
4)struts2的全局视图配置和默认配置
5)Action的属性注入
struts2的核心业务功能:
1)sruts2的数据共享的三种方式
2)请求参数数据的封装
3)自定义类型转换
4)文件上传和下载
/** * 第一种方式:不需要实现或继承任何接口或类 * @author APPle * */ public class UserAction2 {
public String login()throws Exception{ System.out.println("UserAction2.login()"); return "success"; }
} |
/** * 第二种方式:实现Action接口 * 1)定义了默认的execute方法的标准 * 2)提供了项目中常用的视图标记 * @author APPle * */ public class UserAction implements Action {
public String login() throws Exception { System.out.println("执行了UserAction的login方法"); return SUCCESS; }
public String execute() throws Exception { return null; }
} |
/** * 第三种方式: 继承ActionSupport类(推荐使用) * 好处: * 1)提供了常用的视图标记 * 2)提供了数据校验功能 * * @author APPle * */ public class UserAction3 extends ActionSupport{
public String login()throws Exception{ System.out.println("UserAction3.login()"); return SUCCESS; } } |
<!-- 一个模块(Action对象)使用一个action配置 --> <!-- * (星号) :表示路径的通配符。 使用通配符的内容: {1} (表示获取第一个通配符的实际内容) 好处: 大大地减少action的配置 --> <!-- <action name="user_*" class="gz.itcast.b_path.UserAction" method="{1}"> <result name="{1}">/{1}.jsp</result> </action> -->
<!-- 多个模块使用一个action配置 约定前提: 第一个*代表模块,第二个代表方法 User_login --> <action name="*_*" class="gz.itcast.b_path.{1}Action" method="{2}"> <result name="{2}">/{1}/{2}.jsp</result> </action> |
struts2的常量就是用于在strut2的程序运行过程中使用的一些常量参数。
指定默认编码集,作用于HttpServletRequest的setCharacterEncoding方法 和freemarker 、velocity的输出 <constant name="struts.i18n.encoding" value="UTF-8"/> 自定义后缀修改常量 <constant name="struts.action.extension" value="do"/> 设置浏览器是否缓存静态内容,默认值为true(生产环境下使用),开发阶段最好关闭 <constant name="struts.serve.static.browserCache" value="false"/> 当struts的配置文件修改后,系统是否自动重新加载该文件,默认值为false(生产环境下使用),开发阶段最好打开 <constant name="struts.configuration.xml.reload" value="true"/> 开发模式下使用,这样可以打印出更详细的错误信息 <constant name="struts.devMode" value="true" /> 默认的视图主题 <constant name="struts.ui.theme" value="simple" /> 与spring集成时,指定由spring负责action对象的创建 <constant name="struts.objectFactory" value="spring" /> 该属性设置Struts 2是否支持动态方法调用,该属性的默认值是true。如果需要关闭动态方法调用,则可设置该属性 为 false <constant name="struts.enable.DynamicMethodInvocation" value="false"/> 上传文件的大小限制 <constant name="struts.multipart.maxSize" value=“10701096"/>
|
注意:
通过struts.xml文件声明<constant name="struts.action.extension" value="action,do,,"></constant>修改常量配置。
4.1 全局视图作用: 当该包下的所有action都使用到的一些视图就是可以放到全局视图配置中
注意:
当action中也有相同名称的视图,那么action的局部视图会覆盖全局视图。
<!-- 全局视图配置: 把该包下的所有action共用的视图都机集中在这里写 --> <global-results> <result name="success">/login.jsp</result> </global-results> |
4.2 action的默认配置
<!-- 默认配置 name: 必填项 class: 可选项 。默认配置: ActionSupport类 该类继承自struts-default (<default-class-ref class="com.opensymphony.xwork2.ActionSupport" />) method: 可选。默认配置 result: name: 可选。默认配置: success type: 可选。默认配置: dispatcher --> <!-- 全部使用默认配置的action的作用 :专门用于转发到WEB-INF下的页面 --> <action name="book"> <result>/WEB-INF/jsp/login.jsp</result> </action> |
作用: 如果Action对象中需要把一些经常改变的参数提取到配置文件中,那么就可以使用属性注入的方法。
Action属性注入的步骤
1)在Action类中声明一个成员变量,用于接收xml配置文件传入内容
2)在Action类提供一个该变量的setter方法,该方法接收了xml配置的内容
//1)在action中提供一个属性 private String savePath; //2)提供属性的setter方法,用于外部的action的参数进行注入 public void setSavePath(String savePath) { this.savePath = savePath; } |
3)在对应的struts.xml文件中,找到对应的action对象的配置,然后在action中使用
<param name=""></param> 这个标签来向Action对象的属性注入内容
<action name="upload" class="gz.itcast.d_ioc.UploadAction" method="upload"> <!-- 3)使用该配置可以往Action对象的属性注入内容(只要有setter都可以使用param进行注入) param: name: setter方法名。setSavePath -> savePath --> <param name="savePath">e:/images/</param> <result>/login.jsp</result> </action> |
在web项目中都是使用域对象来共享数据。
struts2提供给开发者使用域对象来共享数据的方法一共有三种。
ServletActionContext类
getRequest() : 获取request对象
getRequest().getSession() : 获取session对象
getServletContext() : 获取ServletContext对象
注意:
1)该方式依赖servlet的api,耦合比较高
2)如果要通过域对象来获取域对象的相关信息必须使用该方式
ActionContext类
getContextMap() : 获取操作request域对象数据的map集合
getSession() : 获取操作session域对象数据的map集合
getApplication() 获取操作context域对象数据的map集合
注意:
1)不依赖servlet的api,耦合性低
2)只能用在Action对象的一个方法中。不能在所有方法中都是用同一个ActionContext
使用 RequestAware , SessionAware ApplicationAware 接口
注入操作对应域对象数据的Map集合
注意:
1)不依赖servlet的api
2)可以在Action对象的所有方法中共享Map集合
public class UserAction extends ActionSupport{ //参数赋值(注入方式) private String name; private String password; private String gender; private String[] hobit; //参数通过这个set方法注入到Action中 public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } public void setGender(String gender) { this.gender = gender; } public void setHobit(String[] hobit) { this.hobit = hobit; } |
<form action="${pageContext.request.contextPath }/data/user_register.action" method="post"> 用户名: <input type="text" name="user.name"/><br/> 密码: <input type="password" name="user.password"/><br/> 性别: <input type="radio" name="user.gender" value="男"/>男 <input type="radio" name="user.gender" value="女"/>女<br/> 爱好: <input type="checkbox" name="user.hobit" value="篮球"/>篮球 <input type="checkbox" name="user.hobit" value="足球"/>足球 <input type="checkbox" name="user.hobit" value="羽毛球"/>羽毛球<br/> <input type="submit" value="注册"/>
</form> |
public class UserAction2 extends ActionSupport{ //使用一个javabean对象接收 private User user;
public User getUser() { return user; }
public void setUser(User user) { this.user = user; } |
注意:请求参数的封装通过struts2的ParametersInterceptor拦截器进行赋值.
作用: 默认情况下,页面的日期类型只能接收 yyyy-MM-dd类型,如果要转换yyyy/MM/dd这种类型,则需要使用自定义类型转换器进行转换。
strut2提供了自定义类型转换器的基类: StrutsTypeConverter类
/** * 自定义日期类型转换器 * @author APPle * */ public class MyDateConverter extends StrutsTypeConverter{
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd");
/** * 从页面的数据到服务器的类型转换 * 参数一: context: 值栈上下文对象 * 参数二: values: 从页面传递过来的参数值 * 参数三: toClass: 转换到的类型。String->java.util.Date */ @Override public Object convertFromString(Map context, String[] values, Class toClass) { System.out.println("进入了转换器"); try { //1)判断values是否为空 if(values==null || values.length==0){ return null; }
if(values.length>1){ //多个值的情况 Date[] dates = new Date[values.length]; //2)取出数据进行转换 for(int i=0;i<dates.length;i++){ Date date = sdf1.parse(values[i]); dates[i] = date; } return dates; }else{ //一个值的情况 Date date = sdf1.parse(values[0]); return date; } } catch (ParseException e) { e.printStackTrace(); } return null; } /** * 从服务器到页面的类型转换 */ @Override public String convertToString(Map context, Object o) { return null; } } |
绑定自定义转换器的方式:
方式一: 局部绑定(只能绑定一个Action)
1)建立一个 Action的文件名-conversion.properties
2)一定和绑定的Action放在同一个目录下。
user.birth=gz.itcast.g_convert.MyDateConverter |
方式二: 全局绑定(绑定整个项目多个Action)(推荐使用)
1)建立一个xwork-conversion.properties
2)该文件一定放在src目录下。
java.util.Date=gz.itcast.g_convert.MyDateConverter |
1)三个条件:
表单有file
post提交
enctype="multipart/form-data"
2)在Action中接收文件内容
File attach; (attach是file表单的name属性)
String attachContentType; 文件类型
String attachFileName; 文件名称
细节:
修改上传大小
<!-- 修改默认文件上传大小 --> <constant name="struts.multipart.maxSize" value="100000000"></constant> |
修改允许上传的文件类型和文件后缀
<action name="upload" class="gz.itcast.h_upload_down.UploadAction" > <!-- 往FileUploadInterceptor拦截器的属性注入值(调用setter方法) -->
<interceptor-ref name="defaultStack"> <!-- 改变当前文件上传拦截器的允许文件类型 --> <param name="fileUpload.allowedTypes">image/jpeg,image/jpg</param> <!-- 允许的文件后缀 --> <param name="fileUpload.allowedExtensions">jpg,jpeg,gif</param> <!-- 如果以上配置都写了,那么取他们的交集 --> </interceptor-ref>
<param name="savePath">e:/images/</param> <result>/login.jsp</result> <result name="input">/error.jsp</result> </action> |
视图类型一定是stream类型
<action name="down_*" class="gz.itcast.h_upload_down.DownAction" method="{1}"> <param name="serverPath">e:/images/</param> <result name="list">/listFile.jsp</result> <!-- 文件下载的关键: 视图类型一定是stream --> <result name="down" type="stream"> <!-- 往StreamResult类中的属性注入内容 --> <!-- 返回给浏览器的文件类型。返回通用的二进制 --> <param name="contentType">application/octet-stream</param> <!-- 返回给浏览器的输入流 --> <param name="inputName">inputStream</param> <!-- 告诉浏览器的方式下载资源 ${name}: 获取Action中的getName()方法的数据 --> <param name="contentDisposition">attachment;filename=${name}</param> <!-- 缓存大小 --> <param name="bufferSize">1024</param> </result> </action> |
在Action对象中提供一个对应的获取输入流的方法
//需要提供给struts写出数据的输入流 public InputStream getInputStream(){ try { FileInputStream fis = new FileInputStream(new File(serverPath+name)); return fis; } catch (FileNotFoundException e) { e.printStackTrace(); throw new RuntimeException(e); } }
public String getName() { return name; } |
回顾重点内容:
struts2第二天:
1)struts2使用Action方法:
1.1 不实现任何接口,不继承任何类
1.2 实现Action接口(定了标准,提供了视图的常量)
1.3 继承ActionSupport类(只需选择覆盖方法,提供了视图常量,提供数据验证的功能)
2)struts2的常量配置
struts-core.jar default.properties 常量文件
在struts.xml文件使用<constant name="" value=""/> 修改常量值
2.1 默认请求编码 : struts.i18n.encoding
2.2 动态方法调用: struts. *** .DynamicMethodInvocation actionName ! methodName
2.3 修改上传文件的临时目录: struts.multipart.saveDir
2.4 修改上传文件的大小: struts.multipart.maxSize
2.5 访问action的后缀名: struts.**.actionExtesion
3)全局视图:
<grobal-results> 当前包下的所有action共享这些视图
4)(域对象)数据共享三种方法
4.1 ServletActionContext 得到servlet的api(依赖servlet)
4.2 ActionContext得到操作域对象数据的Map集合(不依赖servlet,方法局部)
4.3 Action类实现RequestAware,SessionAware ,ApplicationAware接口(action全局)
实现提供的方法: 获取操作域对象数据的Map集合
5)请求参数封装
5.1 基本数据类型接收参数(action中提供setter方法)
5.2 javabean类型接收参数: user(action提供getter和setter)
表单的name属性值: user.name/user.passowrd.......
6) 类型转换
默认情况下: 基本数据类型可以直接从String转换,日期只能转yyyy-MM-dd类型
如果按照自己需要转换,定义自定义类型转换器。
继承 StrutsTypeConverter类
convertFromString() 从页面的String数据转换为后台需要的类型
局部绑定:
1)在action的同一个包下建立 ActionName-conversion.properties (约定)
2)user.birth=xxxx.xxxxx
全局绑定:
1)在src下建立 xwork-conversion.properties(约定)
2)java.util.Date=xxxxxx
7)上传与下载:
上传:
表单: <input type="file" name="img"/>
单文件Action处理文件:
File img;
String imgFileName;
String imgContentType;
表单:
<input type="file" name="img"/>
<input type="file" name="img"/>
<input type="file" name="img"/>
多文件Action处理文件:
File[] img;
String[] imgFileName;
String[] imgContentType;
下载:
<action name="down" class="xxxxx">
<result type="stream">
<param name="contentType">xxxxx</param>
<param name="inputName">Action中定义获取输入流的方法(方法名)</param>
<param name="contentDispostion">xxxxx</param>
<param name="bufferedSize"></param>
</result>
</action>
大纲:
拦截器(interceptor)(重点)
拦截器编写规则
拦截器的生命周期
拦截器栈(引用多个拦截器)
案例
值栈(value stack): (重点)
OgnlContext对象使用
分析值栈的内部组成结构(关键点)
页面获取值栈数据
struts2的常用逻辑标签
拦截器 类似于 过滤器的功能,过滤器可以过滤项目的任何请求(servlet/jsp/html/img),但拦截器只能拦截Action资源。拦截完Action,添加逻辑代码
struts2提供Interceptor接口用于自定义拦截器
步骤:
1)编写类,实现Interceptor接口
public class MyInterceptor1 implements Interceptor{
public MyInterceptor1(){ System.out.println("1)创建了拦截器1对象"); }
public void destroy() {
}
public void init() { System.out.println("2)调用了拦截器1的init方法"); }
public String intercept(ActionInvocation invocation) throws Exception { System.out.println("4)拦截了Action---前面的代码");
//放行,调用下一个拦截器,如果没有下一个拦截器,那么调用目录的action invocation.invoke();
System.out.println("6)拦截了Action---后面的代码");
return null; }
} |
2)在struts.xml文件中定义和使用拦截器
<package name="inter" extends="struts-default" namespace="/inter"> <!-- 定义拦截器 --> <interceptors> <interceptor name="inter1" class="gz.itcast.a_interceptor.MyInterceptor1"></interceptor> </interceptors>
<action name="user_*" class="gz.itcast.a_interceptor.UserAction" method="{1}"> <!-- 使用拦截器 --> <interceptor-ref name="inter1"></interceptor-ref> <result>/index.jsp</result> </action>
</package> |
注意1:拦截器的执行顺序:
启动:
1)拦截器对象创建,调用拦截器的init方法
访问
2)创建Action对象
3)执行拦截器的intercepot方法
3.1 执行拦截器前面的代码(invocation.invoke();方法之前的)
3.2 执行 invocation.invoke(); 放行执行下一个拦截器或Action的方法
3.3 执行拦截器后面的代码(invocation.invoke();方法之后的)
注意2:拦截器范围:
局部使用:action其作用
<action name="user_*" class="gz.itcast.a_interceptor.UserAction" method="{1}"> <!-- 局部起作用的使用拦截器 --> <interceptor-ref name="inter1"></interceptor-ref> <result>/index.jsp</result> </action> |
全局使用: 当前包其作用:
<package name="inter" extends="struts-default" namespace="/inter"> <!-- 定义拦截器 --> <interceptors> <interceptor name="inter1" class="gz.itcast.a_interceptor.MyInterceptor1"></interceptor> </interceptors>
<!-- 全局其作用的拦截器 --> <default-interceptor-ref name="inter1"></default-interceptor-ref>
|
概念: 当一个或多个Action同时被多个拦截器所拦截,就可以使用拦截器。
<interceptors> <interceptor name="inter1" class="gz.itcast.a_interceptor.MyInterceptor1"></interceptor> <interceptor name="inter2" class="gz.itcast.a_interceptor.MyInterceptor2"></interceptor> <!-- 定义栏截器栈 --> <interceptor-stack name="interStack"> <interceptor-ref name="inter1"></interceptor-ref> <interceptor-ref name="inter2"></interceptor-ref> </interceptor-stack> </interceptors> |
注意1:定义拦截器栈的时候,引用拦截器的顺序决定了创建拦截器对象的顺序。(先指定就先创建)
注意2: 当前有了拦截器栈(多个拦截器)的执行顺序:
启动:
1)创建拦截器inter1对象,调用init方法
2)创建拦截器inter2对象,调用init方法
访问:
3)执行inter1的interceptor方法前面代码
4)执行inter2的interceptor方法前面代码
5)Action的业务方法
6)执行inter2的interceptor方法后面代码
7)执行inter1的interceptor方法前面代码
注意3: 当我们的包下引用了自定以拦截器,则会把默认包下的default-stack拦截器给覆盖掉!!!
这时需要这么做:
<interceptor-stack name="myStack"> <interceptor-ref name="defaultStack"></interceptor-ref><!-- 引入了默认的18个拦截器 --> <interceptor-ref name="interStack"></interceptor-ref><!-- 引入自定义的2个拦截器 --> </interceptor-stack> |
struts2的核心流程图(关键):
OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一个开源项目。 Struts2框架使用OGNL作为默认的表达式语言。
在struts2项目中导入ognl.jar 包来实现支持ognl表达式。
ognl表达式 vs EL表达式
EL表达式: 获取域对象的数据。 不能存放数据,不能调用方法
Ognl表达式: 获取域对象的数据。 可以存放数据,可以调用方法。
OGNL的优势:
1、支持对象方法调用,如xxx.doSomeSpecial(); 2、支持类静态的方法调用和值访问,表达式的格式: @[类全名(包括包路径)]@[方法名 | 值名],例如: @java.lang.String@format('foo %s', 'bar') 或@tutorial.MyConstant@APP_NAME; 3、支持赋值操作和表达式串联,如price=100, discount=0.8, calculatePrice(),这个表达式会返回80; 4、访问OGNL上下文(OGNL context)和ActionContext; 5、操作集合对象。
|
//目标: 学习了解Ognl表达式的核心对象OgnlContext对象的使用 public class OgnlDemo {
@Test public void test1(){ User user = new User(); user.setName("eric"); user.setAge(20);
//1)创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //2)把user对象存入OgnlContext对象 context.put("user", user); //3)从OgnlContext对象取出数据 User user2 = (User)context.get("user"); System.out.println(user2.getName()+"\t"+user2.getAge()); }
//使用Ognl表达式取出OgnlContext的数据,如果是非根对象数据,那么使用#号去取 @Test public void test2()throws Exception{ User user = new User(); user.setName("eric"); user.setAge(20);
//1)创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //2)把user对象存入OgnlContext对象 context.put("user", user);//往OgnlContext的非根对象 存数据
//使用Ognl表达式从OgnlContext对象取出数据 Object ognlObj = Ognl.parseExpression("#user.age");//表达式对象 Object result = Ognl.getValue(ognlObj, context, context.getRoot()); //获取数据方法 System.out.println(result);
}
//使用Ognl表达式取出OgnlContext的数据,如果是根对象数据,不需要使用#号,不需要key名称,直接写存入对象的属性即可!!! @Test public void test3()throws Exception{ User user = new User(); user.setName("eric"); user.setAge(20);
//1)创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //2)把user对象存入OgnlContext对象 context.setRoot(user);往OgnlContext的根对象 存数据
//使用Ognl表达式从OgnlContext对象取出数据 Object ognlObj = Ognl.parseExpression("name");//表达式对象 Object result = Ognl.getValue(ognlObj, context, context.getRoot()); //获取数据方法 System.out.println(result);
}
/** * java.util.Math * @throws Exception */ //Ognl表达式调用静态方法 @Test public void test4()throws Exception{ //Math.round(10.3);
//1)创建一个OgnlContext对象 OgnlContext context = new OgnlContext(); //使用Ognl表达式从OgnlContext对象取出数据 Object ognlObj = Ognl.parseExpression("@Math@round(10.5)");//表达式对象 Object result = Ognl.getValue(ognlObj, context, context.getRoot()); //获取数据方法 System.out.println(result);
} } |
结论:
1)从OgnlContext对象的根对象取出数据,不需要#号
2)从OgnlContext对象的非根对象取出数据,需要#号
ValueStack接口,最终在项目中存储数据的对象是ValueStack的实现类OgnlValueStack
ValueStack的数据存储结构:分为 List栈(根栈) 和 Map栈(非根栈)
1)List栈主要存储Action对象和Provider代理对象
2)Map栈主要存放各个域存放的数据和用户的参数信息
一、List栈的数据(不需要#号,不需要key)
输入Ognl: user
作用: 直接到List栈的每个对象去搜索对应的属性。 搜索规则: 从List栈的栈顶开始搜素 -> user -> 返回,找不到继续向下找 栈的第二个元素 -> user -> 返回,找不到继续向下找 ....... -> user - > 空
注意:在List栈中尽量不要在两个栈对象中出现相应的属性名,如果出现相应的属性名,只获取第一个对象的属性。 |
使用struts2的标签结合Ognl表达式获取值栈数据:
<%-- 1)取出List栈的数据 --%> <s:property value="user.name"/> - <s:property value="user.age"/> <br/> <s:property value="book.user.name"/> - <s:property value="name"/> <%--查看值栈的所有数据 --%> <hr/> <%--2)取出Map栈的数据 --%> <s:property value="#request.request_data"/><br/> <%-- 因为值栈对象是通过request域专递到页面,所以可以直接忽略#request去取request域的数据 --%> <s:property value="#request_data"/><br/> <s:property value="#session.session_data"/><br/> <s:property value="#application.application_data"/><br/> <%-- #attr : 类似于findAttribute. 依次从小到大在三个域中搜索数据: #request-> #session -> #application --%> <s:property value="#attr.session_data"/><br/> <s:property value="#parameters.name"/><br/> <hr/> ${session_data } <hr/> <%-- 遍历集合 :List--%> <s:iterator value="#request.userList" var="user"> 姓名: <s:property value="#user.name"/> - <s:property value="#user.age"/><br/> </s:iterator> <hr/> <%-- 遍历集合:Map --%> <s:iterator value="#request.userMap" var="entry"> 编号: <s:property value="#entry.key"/> - 姓名: <s:property value="#entry.value.name"/> - <s:property value="#entry.value.age"/><br/> </s:iterator> <s:debug></s:debug> |
防止表单重复提交:
回顾重点内容:
拦截器:是struts2核心业务功能实现的机制。
1)自定义拦截器 : 只能拦截action资源
实现Interceptor接口
init() : 在创建拦截器对象的时候,拦截器对象在加载web应用时创建。
interceptor() : 每次访问action的业务方法的时候调用!!!!
destory(); 拦截器对象销毁的时候调用。web应用重新部署或web服务器停止。
2)拦截器栈: 当前action同时被多个拦截器拦截,就使用拦截器栈。
<interceptor-stack>
<interceptot-ref name="拦截器1"/>
<interceptot-ref name="拦截器2"/>
........
</interceptor-stack>
注意:当使用了自己定义的拦截器(拦截器栈)的时候,记住在调用我们的拦截器之前加上struts提供的默认拦截器(defaultStack)
值栈:是struts2的数据存取的核心!!!!!(重点)
1)Ognl表达式:存取数据
Ognl存取数据的核心 OgnlContext对象
OgnlContext对象数据结构:
Object root: 根对象 不用带#,直接访问属性
Map values: 非根对象 带#,根据key获取value
2)struts2的值栈对象: ValueStack(OgnlValueStack对象)
OgnlValueStack对象数据结构:
List<Objet> root: 根对象(List栈)
OgnlContext对象:
Object root: 根对象 不用带#,直接访问属性
Map values: 非根对象(Map栈) 带#,根据key获取value
OgnlValueStack对象分为两个部分存储数据:
List对象栈:Action对象,Provider对象,ValueStack.push(obj)
Ognl表达式访问List栈(不要#,直接写属性名):
第一个对象: 找user,找到返回,找不到往下一个对象找。
第二个对象: 找user,找到返回,找不到往下一个对象找。
第三个对象: 找user,找到返回,找不到往下一个对象找。
。。。。。。。所有对象找完,都没有,就返回空。
Map栈:request域的Map
session域的Map
application域的Map,
attr的Map,
paramter参数的Map
Ognl表达式访问Map栈(必须要#,根据key找value):
request域的Map : #request.user
session域的Map : #session.user
application域的Map,: #application.user
attr的Map, : #attr.uesr
paramter参数的Map : #parameter.user
注意:如果想使用Ognl表达式查找值栈的数据,必须结合struts2的标签
数据共享
请求参数封装
类型转换(自定义类转换)
文件上传和下载
大纲:
struts2核心业务功能:
国际化
数据验证
模型驱动
struts2的知识补充:
常用的struts2的标签(逻辑标签+UI标签)
数据回显
防止重复提交
文字国家化
以前: ResourceBundle类
getBundle(); 绑定指定国家的资源包。
资源包规则:
包名_语言简写_国家简称.properties
e.g 中国: message_zh_CN.properties
美国: message_en_US.properties
英国: message_en_GB.properties
jsp页面中:
<fmt:setBundle name="message"/> 绑定资源
<fmt:message key="user"/> 获取资源包的内容
1)准备不同国家的资源包
中国: message_zh_CN.properties
user=用户名
美国: message_en_US.properties
英国: message_en_GB.properties
2)在struts.xml文件中(修改)指定资源包加载路径的常量
struts.custom.i18n.resources=message
3)在页面上使用资源包的内容
<s:text name="user"/>
struts2提供给开发者进行表单数据的后台数据验证的功能。
分析:
前台: 注册表单 user_register.action
user.username
user.password
后台:
class UserAction extends ActionSupport{
private User user;
用于存储错误信息
private Map actionErrors;
public String validate(){
表单校验的逻辑(不好,影响当前业务逻辑)
if(user.getName()==null || user.getName().eqauls("")){
出现异常
保存错误信息
actionErrors.put("name","用户名不能为空");
转发到错误显示显示jsp
}
。。。。。。。
actionErrors.put("password","密码不能为空");
}
//注册方法
public String register(){
//validate(); 这里不写这个方法调用,而把方法的调用交给拦截器去做!!!
具体的注册业务逻辑
}
}
struts2的数据验证的核心拦截器:
<interceptor name="validation" class="com.opensymphony.xwork2.validator.ValidationInterceptor"/> |
开发步骤:
1)Action类继承ActionSupport(为了实现Valiateable接口)
2)Action类覆盖validate方法(验证所有方法)
public void validate() { //在这里写表单数据验证的逻辑 //System.out.println("调用了validate方法"); if(user.getName()==null || user.getName().equals("")){ //用户名为空 //把错误信息放入错误信息Map集合 super.addFieldError("user.error.requried", "用户名不能为空!"); } if(user.getPassword()==null || user.getPassword().equals("")){ //密码为空 //把错误信息放入错误信息Map集合 super.addFieldError("password.error.requried", "密码不能为空!"); }
} |
3)在struts.xml文件中对应的action配置加上input视图,然后struts2就会自动把错误信息转发到input视图的页面上去
4)在input视图页面上,打印出错误信息
<s:fielderror></s:fielderror> |
注意:
validate()方法对当前Action下的所有方法都会有效!!!!
指定方法验证:
第二步骤的验证方法名称为 validate+需要验证的方法名称
例如: validateRegister() -> 可以验证register()方法
以上这两种方式都是使用代码在程序写死了!!!
希望更加灵活,扩展型更好!
xml配置数据验证的规则:
1)编写一个xml文件,名称: Action文件名-validation.xml
2)该xml文件必须放在Action文件的同一目录
注意: 这种配置方式会对action下的所有方法都生效!!!
xml配置数据验证的规则:
1)编写一个xml文件,名称: Action文件名-访问方法路径-validation.xml
例如: UserAction的register方法: user_register路径访问
文件名: UserAction-user_register-validation.xml
2)该xml文件必须放在Action文件的同一目录
<validators> <!-- 验证用户名 --> <field name="user.name"> <!-- type: 代表xwork中定义的可以使用的验证类型 --> <field-validator type="requiredstring"> <!-- 当出现错误时,提示的错误信息 --> <message>用户名必填</message> </field-validator> <field-validator type="stringlength"> <param name="minLength">6</param> <param name="maxLength">16</param> <message>用户名必须是6-16位</message> </field-validator> </field>
</field>
</validators> |
<validators> <validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/> <validator name="requiredstring" class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/> <validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/> <validator name="long" class="com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator"/> <validator name="short" class="com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator"/> <validator name="double" class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/> <validator name="stringlength" class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/> <validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/> <validator name="conditionalvisitor" class="com.opensymphony.xwork2.validator.validators.ConditionalVisitorFieldValidator"/> </validators> |
请求数据封装:
方式一: 表单: name -》 基本数据类型: String name
方式二: 表单: user.name - 》 javabean数据类型: User user (User: String name)
方式三: 表单: name -> javabean数据类型: User user (User: String name) (使用模型驱动方式)
public class UserAction extends ActionSupport implements ModelDriven<User>{ //注意:使用模型驱动的方式,存放数据的模型引用不能为空 private User user = new User();
/** * 该方法struts2可以把值栈中的请求参数封装到User对象 */ public User getModel() { return user; }
} |
1) { } 创建List集合
<%-- ognl表达式: 在jsp页面中创建List集合(不需要#) --%> <s:iterator value="{'eric','jacky','rose','lucy'}" var="name"> <s:property value="#name"/><br/> </s:iterator> |
2) #{ } 创建Map集合
<%-- ognl表达式: 在jsp页面中创建Map集合(需要#号) --%> <s:iterator value="#{1:'eric',2:'jacky',3:'rose',4:'lucy'}" var="entry"> 编号: <s:property value="#entry.key"/>-姓名:<s:property value="#entry.value"/><br/> </s:iterator>
|
3)%{ } 把内容引入Ognl运行环境
<s:textfield name="userName" value="%{#request.userName}"></s:textfield> |
默认情况下,struts2不支持防止重复提交的
需要在struts.xml文件中打开功能:
1)
<interceptors> <interceptor-stack name="myStack"> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="token"></interceptor-ref> </interceptor-stack> </interceptors> |
2)在jsp页面指定一个标签
<s:token></s:token>
总结:
1)struts2基础:
struts2执行原理(理解)
2)struts2核心功能:(web开发常用的功能)
数据共享功能(三种方式)
请求数据封装(三种方式)
类型转换(自定义类型转换器)
文件上传和下载
国家化
数据验证
回顾:
核心业务:
一、国际化
1)写好资源包
2)在struts.xml配置国际化的资源包。 message_zh_CN.properties
3)使用:
jsp页面: <s:text name="user"/>
action: ActinoSupport.getText("user")
二、数据验证:后台数据验证
1)代码方式验证所有方法:
重写Validatable接口的方法:valiate() 写验证逻辑
2)代码方式验证指定的方法:
修改validate方法名: validateRegister() validate+方法名()
3)xml配置方式验证所有方法:(插拔式)
ActionName-validation.xml(和action方法在同一目录)
<validators>
<field name="user.name">
<field-validator type="requriredstring/int/regex...">
<message>错误提示信息</message>
<field-validator>
</field>
<validators>
4)xml配置方式验证指定方法
修改验证文件名称: ActionName-方法调用路径-validation.xml(和action方法在同一目录)
UserAction-user_register-validation.xml
三、模型驱动
接口: ModelDriven<T>
<input type="text" name="name"/> -> UserAction User user=new User();
四、struts2的标签:
<%@ taglib uri="/struts-tags" prefix="s"%>
逻辑标签:
迭代: <s:iterator value="ognl表达式" var="" status="">
赋值: <s:set var="" value="ognl表达式"/>
获取: <s:property value="ognl表达式"/>
判断: <s:if/> <s:elseif/> <s:else/>
UI标签: 1)节省html代码编写 2)数据回显功能
<s:form/> -> <form>
<s:textfield/> -> <input type="text"/>
<s:password/> -> <input type="password"/>
<s:checkbox/> -> <input type="checkbox"/>
<s:checkboxlist list="多个值"/> -》 多个<input type="checkbox"/>
<s:radio list="多个值"/> -> 多个<input type="radio"/>
<s:select list="多个值"/> -》 <select>多个<option/></select/>
五、防止重复提交:
1) 打开功能: TokenIntecetpot拦截器
2)在jsp页面上加入 <s:token/> 标签
今天目标: 改造之前的 《网上图书销售系统》
之前的架构:
web层 -》 service层 -》 dao层 》 mysql数据层
servlet+jsp(jstl+el) ( 普通java类 ) jdbc(dbutils)
(遵守MVC模式)
现在架构:
web层 -》 service层 -》 dao层 》 mysql数据层
struts2+jsp(struts标签) ( 普通java类 ) jdbc(dbutils)
(遵守MVC模式)
1)项目的目录结构:
|-bookstore
|-gz.itcast.entity
|-gz.itcast.filter
|-gz.itcast.interceptor(拦截器包)
|-gz.itcast.utils
|-gz.itcast.biz
|-front
|-book
|-dao
|-service
|-web
|-action (struts2的action类)
|-servlet (写servlet)
|-admin
|-config (配置文件 struts2配置、hiberate配置文件、spring配置文件)
|-struts.xml
|-WebRoot(保持不变)
2)设计strut2的配置文件(config目录)
struts-constant.xml 存放struts框架的常量值
struts-front.xml 存放前台业务的action配置
<package namespace="/front">
e.g 模块: <action name="book_*" class="gz.itcast.biz.front.book.web.action.BookAction" method="{1}">
struts-admin.xml 存放后台业务的action配置
<package namespace="/admin">
e.g 模块: <action name="book_*" class="gz.itcast.biz.admin.book.web.action.BookAction" method="{1}">
3)struts开发步骤:
3.1 导入jar包
3.2 配置全局过滤器
3.3 在src下放struts.xml文件
4)设计一个BaseAction公共Action类
============================================
Struts2:
1. 简化Servlet开发,用Action取代之前的Servlet
2. 开发流程
3. 请求数据自动封装 (params拦截器)
3. struts 数据处理
Map---》域对象
4. 文件上传/下载
5. 数据效验、国际化
6. Ognl表达式语言、Struts标签
(OgnlContext/ValueStack)
传统的开发模式:
基于MVC模式项目体系结构!
ORM, Object Relation Mapping 对象关系映射!
目标:
通过orm(对象关系映射), 可以做到:
a. 不写sql,包对象直接保存到数据库中
b. 不写sql, 从数据库中读取,直接获取的是封装好的对象!
ORM是一种思想,或者说是一种规则的定义, 基于orm的应用:
1. hibernate 框架
2. mybatis 框架
3. 自定义一套程序!
基于ORM的持久层框架,对jdbc操作进行了封装!
Hibernate与具体数据库无关的技术,即可以框数据库平台!
Hibernate版本: 3.6版本
整体步骤:
1. 下载源码,引入jar文件
2. 写配置
* hibernate.cfg.xml 主配置
* Users.hbm.xml 映射配置
3. Api
Configuration
SessionFactory
Transaction
Session
Query
步骤实现:
1. 建库、建表 |
-- 删除数据库 DROP DATABASE hib_demo;
-- 创建数据库 CREATE DATABASE hib_demo DEFAULT CHARACTER SET utf8;
-- 建表 CREATE TABLE users( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), age INT ) |
2. 引入hibernate核心jar文件 |
源码中: hibernate3.jar + required 目录中所有 + jpa + 数据库驱动包
antlr-2.7.6.jar commons-collections-3.1.jar dom4j-1.6.1.jar hibernate-jpa-2.0-api-1.0.0.Final.jar hibernate3.jar javassist-3.12.0.GA.jar jta-1.1.jar mysql-connector-java-5.1.12-bin.jar slf4j-api-1.6.1.jar |
|
3. 主配置文件: hibernate.cfg.xml |
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration> <session-factory> <!-- 数据库连接的参数配置 --> <property name="hibernate.connection.url">jdbc:mysql:///hib_demo</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <!-- 通过数据库方言,告诉hibernate如何生产sql。 hibernate会根据配置的方言,生产符合当前数据库语言的SQL语句 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> </session-factory> </hibernate-configuration> |
4. javabean/ 映射文件 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- 通过XML映射,把对象映射到数据库的表中! --> <hibernate-mapping package="cn.itcast.a_config">
<class name="Users" table="users"> <id name="userId" column="id"> <generator class="native"></generator> </id> <property name="userName" column="name"></property> <property name="age" column="age"></property> </class>
</hibernate-mapping>
|
|
5. App |
// 测试类 public class App {
@Test // 保存 public void testSave() throws Exception {
// 对象 Users users = new Users(); users.setUserName("Jack"); users.setAge(30);
// 1. 创建配置管理器对象 Configuration config = new Configuration(); // 2. 加载主配置文件, 默认加载src/hibernate.cfg.xml config.configure(); // 3. 根据加载的主配置文件,创建对象 SessionFactory sf = config.buildSessionFactory(); // 4. 创建Session对象 Session session = sf.openSession(); // 5. 开启事务 Transaction tx = session.beginTransaction();
// --- 保存 session.save(users);
// 6. 提交事务/关闭session tx.commit(); session.close();
}
@Test // 获取对象 public void testGet() throws Exception {
// 1. 创建配置管理器对象 Configuration config = new Configuration(); // 2. 加载主配置文件, 默认加载src/hibernate.cfg.xml config.configure(); // 3. 根据加载的主配置文件,创建对象 SessionFactory sf = config.buildSessionFactory(); // 4. 创建Session对象 Session session = sf.openSession(); // 5. 开启事务 Transaction tx = session.beginTransaction();
// --- 获取 (根据主键查询) Users users = (Users) session.get(Users.class, 1);
System.out.println(users);
// 6. 提交事务/关闭session tx.commit(); session.close();
} }
|
|-- Session 一个与数据库连接的会话信息
Sesison里面维护了一个连接对象,且对常用操作进行封装!
更新:
session.save(obj); 保存一个对象
session.update(obj) 更新一个对象, 注意修改对象的主键一定要在数据库存在!
session.saveOrUpdate(obj) 保存或更新
如果有设置主键且主键存在执行更新!
没有设置主键执行保存!
session.delete(obj) 删除一个对象; 注意修改对象的主键一定要在数据库存在!
主键查询:
Session.get(clazz,id); 根据主键查询
Session.load(clazz,id); 根据主键查询
HQL查询:
Hibernate Query language
Hibernate提供的面向对象的查询语言!
查询的是对象、对象的属性!
HQL与SQL区别:
Hql 查询的是对象、对象的属性, 区分大小写!
Hql查询的对象一定要有映射!
SQL 查询的是表、字段,不区分大小写!
在主配置文件配置的时候,hibernate 前缀可以省略!
d
查看配置提示:
hibernate-distribution-3.6.0.Final\project\etc\hibernate.properties
里面有常用配置,如:
#hibernate.dialect org.hibernate.dialect.MySQLDialect
#hibernate.dialect org.hibernate.dialect.MySQLInnoDBDialect
#hibernate.dialect org.hibernate.dialect.MySQLMyISAMDialect
#hibernate.connection.driver_class com.mysql.jdbc.Driver
#hibernate.connection.url jdbc:mysql:///test
#hibernate.connection.username gavin
#hibernate.connection.password
自动建表:
#hibernate.hbm2ddl.auto create-drop 每次在创建sessionFactory的时候创建
表;执行sf.close()删除表。
#hibernate.hbm2ddl.auto create 每次都先删除表,再创建新的表
#hibernate.hbm2ddl.auto update 如果表不存在则创建,存在就不创建!
#hibernate.hbm2ddl.auto validate 检查映射配置与数据库结构是否一致,不一致就报错! (严格)
也可以通过代码的方式,自动建表:
/** * 代码方式自动建表 * @param args */ public static void main(String[] args) { // 创建配置管理器对象,加载主配置文件(会加载映射) Configuration cfg = new Configuration().configure(); // 自动建表工具类 SchemaExport export = new SchemaExport(cfg); // 创建表 // 第一个参数: 是否打印建表语句到控制台 // 第二个参数: 是否执行脚本,生成表 export.create(true, true); } |
映射文件,
命名: *.hbm.xml
作用: 描述“对象”与“表”的映射关系!
通过映射文件可以描述一张完整的表!
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- 通过XML映射,把对象映射到数据库的表中!
package 表示包名; 可选,如果没有写,后面的类必须指定类的全名! auto-import="true" 默认为true,即在写hql的时候,会自动引入包名; 如为false,hql中对象要写上包名称 Query q = session.createQuery("from Users"); --> <hibernate-mapping package="cn.itcast.b_api" auto-import="true"> <!-- class 表示映射的一个javabean对象 (可以有多个class节点,但一般一个映射文件对应一个class)
name 表示映射的类的名称; table (可选)类的名称,对应的表名, 如果不写默认与类名称一样
--> <class name="Users" table="t_users"> <!--
id 表示映射的是主键 注意: 在hibernate的映射文件中,必须要有主键映射! 那就要求表必须要有主键!
generator 表示的是主键生成策略 (Api : 5.1.2.2.1. Various additional generators) class
identity 主键自增长, mysql/sqlservlet等数据库使用的自增长的方式 sequence 以序列的方式实现自增长; native 表示主键自增长: 根据底层数据库的能力选择 identity、sequence等其中一个。
assigned 手动指定主键的值 uuid uuid的值作为主键,确保一定唯一 --> <id name="userId" column="id"> <generator class="native"></generator> </id>
<!-- property 表示普通字段的映射 name 属性的名称 column 可选,如果没有指定,与属性名称一致 length 表示字符长度,只对字符类型有效 type 数据库中字段的类型 (如果没有指定,会匹配属性的类型) hibernate中指定的类型: 小写开头 java中类型: 写类的全名 unique 设置为true,表示给当前列添加唯一约束 (主键约束 = 唯一约束 + 非空) --> <property name="userName" type="java.lang.String" column="username" length="50" unique="true"></property>
<property name="age" column="age" type="int"></property>
<!-- 注意:如果列名称对应的是数据库的关键字,需要处理 --> <property name="desc" column="`desc`" length="200"></property> </class>
</hibernate-mapping>
|
联合主键,
多列共同作为主键!
数据库,联合主键: |
-- 联合主键 (hbm: <composite-id></composite-id> ) CREATE TABLE test( id1 INT, id2 INT, PRIMARY KEY(id1,id2) );
SELECT * FROM test; -- 插入 INSERT INTO test VALUES(1,1); INSERT INTO test VALUES(1,2); INSERT INTO test VALUES(1,2); -- 错误 |
需求:
保存员工信息: 名称、地址、部门、年龄
(联合主键:名称、地址)
设计javabean:
写映射:
App:
/** * 联合主键对象, 必须要实现可课序列化标记! */ public class CompositeKeys implements Serializable{
private String name; private String address; .. } |
// 员工 public class Employee {
// 名称、地址、部门、年龄
// 联合主键对象 private CompositeKeys keys; private String dept; private Date birth; .. }
|
Employee的映射: |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.itcast.c_composite"> <class name="Employee" table="t_employee"> <composite-id name="keys"> <key-property name="name"></key-property> <key-property name="address"></key-property> </composite-id>
<property name="dept" length="50"></property> <property name="birth" type="date"></property> </class>
</hibernate-mapping>
|
|
测试 |
public class App { private static SessionFactory sf; static { sf = new Configuration().configure().buildSessionFactory(); }
@Test public void testSave() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 联合主键对象 CompositeKeys keys = new CompositeKeys(); keys.setName("Jack"); keys.setAddress("棠东...");
// 对象 Employee emp = new Employee(); emp.setKeys(keys); emp.setDept("研发部"); emp.setBirth(new Date());
// 保存 session.save(emp);
session.getTransaction().commit(); session.close();
}
@Test public void testGet() throws Exception { Session session = sf.openSession(); session.beginTransaction();
// 联合主键对象 CompositeKeys keys = new CompositeKeys(); keys.setName("Jack"); keys.setAddress("棠东...");
Employee employee = (Employee) session.get(Employee.class, keys); System.out.println(employee.getKeys().getName()); System.out.println(employee.getKeys().getAddress()); System.out.println(employee.getDept()); System.out.println(employee.getBirth());
session.getTransaction().commit(); session.close();
} }
|
提问:
1. Hibernate与ORM 关系
2. Hibenate开发步骤、执行流程
3. Hibernate 中方言作用
4. 映射文件起什么作用?
5. 如何进行联合主键映射?
<composite-id>
执行流程
1. 创建Configuration对象
2. 加载主配置文件:hibernate.cfg.xml
3. 接着,创建SessionFactory对象
4. 创建Session
5. 开启事务
6. – 执行操作 –
7. 提交/关闭
目标:
1. hibernate对象状态
2. hibenate 一级缓存
3. 集合映射/多对一/一对多映射
session.save(对象);
1 . 直接new出来的对象
2. 不处于session的管理(即没有与任何一个session关联)
3. 对象在数据库中没有对应的记录!
Employee e = new Employee();
e.setId(2); 只要主键在数据库中存在,就说这个对象在数据库中有对应记录!
在数据库中叫主键!
OID, object identified 对象的唯一标识 (对应数据库中的主键)
1. 处于session的管理范围
当执行session的方法如:save/update/saveOrUpdate/get/load
对象就会自动转变为持久化状态!
2. 在数据库中有对应的记录
3. 处于持久化状态的对象,当对对象属性进行更改的时候,提交事务更改会反映到数据库中!
1. 对象不处于session的管理范围; 通常指session关闭后对象的状态
2. 对象在数据库中有对应的记录
Session缓存,也叫做一级缓存!
当执行session的相关方法,如: save()/update()/get()/load()等方法的时候,对象会自动放入一级缓存中。
当Session关闭后,一级缓存内容失效!
特点:
1)缓存有效范围,只在当前session范围内有效! 缓存时间很短、作用范围小!
2)一级缓存,可以在短时间内多次操作数据库的时候,才会明显提升效率!
一级缓存的结构:Ma<主键,对象>
3)在提交事务时候,
Hibernate会同步缓存数据到数据库中,会对比缓存数据与数据库数据是否一致,如果不一致,才提交更改到数据库(生成update)!
4) hibernate提供的一级缓存有hibernate自身维护,如果想操作一级缓存内容,必须通过hibernate提供的方法;
session.flush(); 手动让让一级缓存内容与数据库同步
session.evict(emp1); 清空一级缓存中对象: 清除指定的对象
session.clear(); 清空一级缓存中对象: 清除所有的对象
list: (通常使用频率较高)
Query接口定义的list查询方法,
一次查询所有满足需要的数据!
iterator:
Query接口定义的iterator查询方法,
1. 先查询所有满足条件记录的主键 (查询1次)
2. 再根据每一个id,进行主键查询,有多少记录,查询多少次 (查询n次)
3. iterator查询: N + 1
4. iterator查询,迭代数据的时候,只有用到数据的时候,才会查找数据库(懒加载)
区别:
1. 查询数据方式不同: 查询全部与查询N+1
2. 一级缓存
List查询,查询的结果会放入一级缓存,但不会从一级缓存中获取!
Iteraotro查询,会放入一级缓存,同时也会从一级缓存中获取!
Hibernate为了提升程序运行效率,提供了懒加载!
lazy 属性表示懒加载!
true 支持懒加载
false 关闭懒加载
extra (集合属性时候使用)
懒加载: 用到数据的时候,才向数据库发送查询的sql!
(1) 主键查询:
get/load
get:
及时加载, 只要get就立刻查询数据库
如果查询的主键不存在,返回null
Load:
懒加载, 只有在用到数据的时候,才向数据库发送sql语句!
如果查询的主键不存在,只要使用就报错!
(2) 懒加载作用位置
类级别,默认支持懒加载,但只有在使用load使用才会应用这个特性!
字段级别, 普通字段无效,大数据类型会有影响(long/longtext)
集合属性, 默认支持懒加载
(3) 懒加载异常
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
解决思路:
在关闭后,不能能懒加载数据, 那么就要求关闭前把懒加载使用的数据先查询出来!
1. 需求:购物填写地址,一个用户,对应多个地址!
2. 数据库设计:
3. 设计javabean封装数据
4. 写映射
5. App.java 测试
思考:
集合映射,如果保存的数据只有一个可以使使用List/Set集合!
如果保存的数据有2个,使用Map集合
如果保存的数据是2个以上,
集合元素类型,能否为对象?
需求: 保存用户信息,用户对应的多个地址!
周颖,年龄25, 广州天河,gzth,510000
欧阳光,年龄25, 广州天河,gzth,510000
用户与地址的关系:
一对多 【一个用户对应多个地址】
地址与用户的关系
多对一 【多个地址对应一个用户】
数据库设计:
Javabean封装数据库数据:
映射:
App:
目的:
通过多对一映射,能完全描述地址表、用户表的数据库的关系!
public class Address {
private int id; private String name; private String shortName; private String code;
// 地址与用户, 是多对一的关系 private Users user; … } |
多对一映射: <!-- 多对一的配置: 1.映射的对象 2.对象对应的外键字段 3.对象的类型 注意: 对象一定是有映射文件进行映射! (cn.itcast.d_many2one.Users 对应有映射文件) --> <many-to-one name="user" column="user_id" class="Users"></many-to-one> |
|
|
public class Users {
private int id; private String name; private int age; // 用户与地址,是一对多的关系 【注意一定要用接口接收!】 private Set<Address> address = new HashSet<Address>(); } |
一对多映射: <set name="address" table="t_address"> <key column="user_id"></key> <one-to-many class="Address"/> </set> |
|
|
一对多与多对一映射,
可以只配置多对一, 只能通过的多的一方维护关系!
可以只配置一对多, 只能通过一的一方维护关系
双向配置: 一对多与多对一, 可以通过双向维护关系!
作业:
一对多与多对一练习,需求自拟!
-à 用户与地址
à 用户与订单
à 老师与学员
à 部门/ 员工
关系:一对多与多对一的关系!
主表: 部门表
外键表: 员工表 (部门的外键,引入部门的主键)
关系维护:
A. 保存
B. 通过其中一方获取另外一方
C. 解除关系
D. 删除数据,对关系的影响!
Inverse属性在维护关联关系的时候的作用!
Inverse属性,表示的是控制权是否转移!
Inverse属性, 在一的一方设置,如<set> 标签节点
Inverse 反转
true 反转, 控制权转移,当前方没有控制权
false 不反转, 控制权不转移,当前方有控制权!
老师与学员
项目与开发人员
一个项目,对应多个开发人员
一个开发人员,参与多个项目
这种就是多对多关系!
总结:
一对多与多对多种,inverse属性在维护关联关系时候作用的区别?
一对多, 是否设置外键的值!
多对多, 是否删除中间表数据!
需求: 存储用户、身份证信息
用户: 姓名、性别、年龄、身份证编号;
身份证:身份证编号、签发地、有效日期;
他们的关系就是一对一的关系!
数据库:
对象之间的关系:
组合关系, 一个类中包含对另外一个类的引用,这2个类就是组合关系!
继承关系,一个类继承另外一个类!
映射,
组合关系的映射就是“组件映射”!
继承映射!
需求:
汽车与车轮
映射:
<component name="wheel" class="Wheel"> <property name="count"></property> <property name="size"></property> </component> |
需求:
动物,猫,狗!
特点:
父类、子类写到一个映射文件中!
有多少个子类,要写多少个映射文件.
总结:
缺点就是映射文件过多!
<hibernate-mapping package="cn.itcast.f_extends1"> <class name="Dog" table="t_dog"> <!-- 继承父类的属性,直接写 --> <id name="id"> <generator class="native"></generator> </id> <property name="name" length="20"></property>
<!-- 子类属性 --> <property name="play" length="20"></property>
</class> </hibernate-mapping> |
特点:
所有的子类都写到一个映射文件中!
用一张表存储所有的子类信息!
<subclass name="Cat" discriminator-value="cat_"> <property name="catching"></property> </subclass> |
总结:
生成的表,不符合数据库设计原则!
因为所有子类都用一张表存储,存在很大的冗余字段!
特点:
完全按照面向对象设计表结构!
<joined-subclass name="Cat" table="t_cat"> <key column="id"></key> <property name="catching"></property> </joined-subclass> |
总结:
设计的表,是符合数据库设计原则(三大范式)!
但是,表结构、关系变得负责,影响数据访问效率!
特点:
一个映射文件
有多少个子类对应多少个表,父类不对应表!
<!-- 注意:使用union-subclass要求主键不能自增长! --> <!-- 子类:猫 --> <union-subclass name="Cat" table="t_cat"> <property name="catching"></property> </union-subclass> |
总结:
推荐使用!
注意:主键不能为自增长!
cascade 表示级联操作,在一对多、多对一、一对一种都可以设置
save-update 级联保存或更新
delete 级联删除
save-update,delete 级联保存、更新、删除
all 同上
none 不级联(默认值)
未完成:
hibernate查询:
二级缓存/连接池支持
Hibernate对session管理(案例)
Hibernate提供的查询:
get/load
2) 对象导航方式
3) HQL查询
Hibernate提供的面向对象的查询方式!
转载地址:http://efbo.baihongyu.com/