Struts+Hibernate+Spring Web开发流行架构
课程总体目标 我们的目标是:能够熟练运用当前流行的java开源框架:Struts、Hibernate以及Spring来构建灵活、易于扩展的多层Web应用程序。
多层架构概述 C/S:以数据库为中心 B/S:多层架构才是真正的目的 呈现层(UI Layer/Presentation Layer) struts 业务逻辑层(Business Layer) spring 持久化层(Persistence Layer) hibernate
Struts 开源MVC框架
Struts课程目标 深入理解MVC模式 掌握Struts体系架构 掌握Struts开发流程 熟练掌握Struts的配置方法
从Servlet说开去 什么是Servlet? 如何编写Servlet? 如何映射Servlet? 相对路径与绝对路径的基本概念 JavaBeans JavaBean是一种java类 JavaBean必须是具体的和公共的,并且具备无参构造器 JavaBean通过提供符合一致性设计模式的公共方法将内部域暴露称为属性 JavaBean提供两种方法来访问Bean的内部状态: 访问器(getters)用来读JavaBean状态 – 以小写get前缀开始,后跟属性名,属性名的第一个字母必须大写,返回值必须匹配相应修改器的方法的参数;如果访问器返回boolean值,则使用is前缀开始,后跟属性名,属性名第一个字母必须大写。 修改器(setters)用来改变JavaBean状态 – 以小写set前缀开始,后跟属性名,属性名的第一个字母必须大写,修改器的返回值通常为void
Struts是什么? Struts的目标是提供一个开发Web应用的开源框架。Struts鼓励基于M2模式(即MVC设计模式)来开发程序。 Model View Controller
Model1与Model2设计模式简介 以JSP为中心的开发模型,称为Model1(JSP+JAVABEAN) 业务逻辑与表示逻辑混和,不利维护与重用 HTML中嵌入了大量的JAVA代码 验证、流程控制、更新程序的状态全部在JSP中完成 基于MVC模式的框架 MVC将问题进行分解 模型包含应用程序的核心功能。模型封装了应用程序的状态。它对视图或控制器一无所知。 视图提供模型的表示。它是应用程序的 外观。视图可以访问模型的读方法,但不能访问写方法。此外,它对控制器一无所知。 控制器对用户的输入作出反应。它创建并设置模型。
Model2
Struts框架概览
Struts框架概览 浏览器 web容器将对来自HTTP的每个请求创建一个request对象,并用一个response对象作出响应 控制器 控制器接收来自浏览器的请求,在struts中,是由一个servlet来充当控制器的角色,struts-config.xml文件配置控制器 模型 在struts中,由Action类充当业务逻辑的包装器,ActionForm是程序的状态 视图 JSP文件
Struts框架组件 ActionServlet类控制导航流 ActionServlet根据URI来决定哪个Action类 被用于处理请求,Action可以校验输入,并访 问业务层以便从数据库检索信息 Action需要知道页面提交了哪些内容,所以 由ActionServlet根据请求URI来决定将请求 参数绑定到哪个ActionForm中,并传入Action Action在完成业务逻辑后,返回一个ActionForward 对象,ActionServlet根据ActionForward对象中的路径来调用页面完成响应 Struts将这些信息绑定在一个ActionMapping对象中,一个ActionMapping对应一个请求URI,当请求路径到达的时候,ActionServlet就会查询ActionMapping对象,ActionMapping对象将告诉ActionServlet哪个Action类会被调用、哪个ActionForm类被用于传递页面数据以及哪些ActionForward将被用于转向 有关Action、ActionForm、ActionForward等信息,Struts通过一个配置文件:struts-config.xml文件来定义。
Struts1.X 和Struts2.X Struts1.X 与Struts2.X 的差异较大 Struts1.X 应用更加广泛 Struts2.X 实际上是另外一个框架Webwork发展而来的 后续课程将会有对webwork/Struts2.X的介绍以及实例操作
快速开始一个Struts项目 第一个项目,实现用户登录操作 用户将看到一个登录页面,要求用户输入用户名以及密码 如果用户名以及密码都是admin,提示登录成功 否则提示登录失败 1、用Eclipse创建一个J2EE Web应用项目,如右图所示 2、下载并解压Struts项目 *从Apache网站下载struts最新版 *将压缩包解压到一个目录,此目录为STRUTS_HOME目录 3、将STRUTS_HOME/lib目录下的所有.jar文件拷贝到刚创建的 web项目的WebContent/WEB-INF/lib目录下 4、配置ActionServlet: 修改web项目的web.xml文件,添加如下Servlet映射配置 (转下一页)
web.xml 的配置 <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <url-pattern>*.do</url-pattern> </servlet-mapping>
我们将需要创建如下文件 一个ActionForm – LoginActionForm.java 一个Action – LoginAction.java struts-config.xml文件 三个页面 登录页面 – login.jsp 登录成功提示页面 – login_success.jsp 登录失败提示页面 – login_error.jsp 就这些!没别的了!!
创建LoginActionForm.java ActionForm是一个JavaBean,需继承org.apache.struts.action.ActionForm类,它捕获通过HTTP请求传送的参数 ActionForm针对每个HTML表单中的字段具有一个对应的属性 ActionServlet匹配请求中的参数和ActionForm中的属性,并调用ActionForm中的setter方法,将参数传入ActionForm 我们的login.jsp有username和password两个表单字段(下面将会看到),所以,我们需要定义ActionForm中相应的setter方法:setUsername和setPassword方法 ActionForm中的getter/setter方法,可以通过Eclipse集成环境,自动生成 ActionForm中的内部属性全部定义为私有的(private),并通过公共(public)的getter/setter方法来访问 package com.bjsxt.strutstest; import org.apache.struts.action.ActionForm; public class LoginActionForm extends ActionForm { private String username; private String password; /** * @return Returns the password. */ public String getPassword() { return password; } * @param password The password to set. public void setPassword(String password) { this.password = password; * @return Returns the username. public String getUsername() { return username; * @param username The username to set. public void setUsername(String username) { this.username = username;
创建LoginAction.java Action是一个Java类,需继承org.apache.struts.action.Action类 ActionServlet将会组装ActionForm,并将它传递给Action Action 通常负责: 输入校验 调用业务逻辑类执行业务逻辑操作 决定返回哪个ActionForward 我们的LoginAction做了如下事情,这些是一个Action通常都会做的最典型的事情: 将输入的ActionForm强制转换为LoginActionForm 从LoginActionForm对象中获取用户名以及密码的数据信息 执行用户名及密码的逻辑判断操作(在通常的情况下,要将这些业务逻辑交给专门的类去处理,这里这样做是为了演示的需要) 根据业务逻辑执行的结果,决定返回哪个ActionForward,我们在这里使用success这个标识来表示登录成功页面,用error标识来表示登录失败页面 public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { //将ActionForm强制转换为LoginActionForm LoginActionForm loginForm = (LoginActionForm)form; //从LoginActionForm中提取从页面表单传递过来的参数 String username = loginForm.getUsername(); String password = loginForm.getPassword(); //根据这些参数,执行业务逻辑操作 if("admin".equals(username) && "admin".equals(password)){ //如果用户名和密码均为admin,则转向登录成功页面 return mapping.findForward("success"); }else{ //否则转向登录失败页面 return mapping.findForward("error"); }
创建Struts配置文件struts-config.xml 在WebContent/WEB-INF目录下创建struts-config.xml文件 并添加如下内容(空白的struts-config.xml),紧接着,我们将往这个空白的配置文件中添加其它配置信息 <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd"> <struts-config> </struts-config> struts-config.xml文件,是由ActionServlet读取的配置文件,它定义了所有关于Action、ActionForm、ActionForward等的详细信息
添加ActionForm配置,配置LoginActionForm 我们在struts-config.xml文件中,在<struts-config>标签的内部,添加如下配置: <form-beans> <form-bean name="loginForm" type="com.bjsxt.strutstest.LoginActionForm"/> </form-beans> <form-beans>标签内部可以包含多个<form-bean>标签 <form-bean>标签必须指定name和type属性 name属性是给此ActionForm一个标识名称 type属性指定了此ActionForm是哪个类,必须是全路径的类名
添加Action配置,配置LoginAction 我们在struts-config.xml文件中,紧接着<form-beans>标签的下面,添加对LoginAction的配置 <action>标签可以配置的重要属性包括: path-从页面上通过一个什么样的URL路径来访问Action(不包含.do) type – 访问这个URL的时候,调用哪个Action类,这是Action的全路径类名 name – 这个属性用来标识哪个ActionForm将被创建,并将提交的表单组件给它 scope – FormBean的作用域范围,可以取值为session和request,一般取值都是request <action-mappings> <action path="/login“ type="com.bjsxt.strutstest.LoginAction“ name="loginForm“ scope=“request” > <forward name="success" path="/login_success.jsp"/> <forward name="error" path="/login_error.jsp"/> </action> </action-mappings>
创建login.jsp 在WebContent目录下创建login.jsp文件,如右边所示 添加一个表单,action为login.do,这个login.do的意思,将会告诉struts的ActionServlet,它将需要调用哪个Action来处理这个表单的请求 添加输入域username,这个username的表单字段,必须跟LoginActionForm中的属性一致 添加密码输入域password <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GB18030"> <title>请登录</title> </head> <body> <form action="login.do" method="post"> 请输入用户名:<input type="text" name="username"> <br/> 请输入密码:<input type="password" name="password"> <br/> <input type="submit" name="submit1" value="登录"> </form> </body> </html>
创建login_success.jsp和login_error.jsp login_success.jsp <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GB18030"> <title>登录成功</title> </head> <body> 欢迎您,您已经成功登录!您创建的第一个Struts应用程序已成功运行!!! </body> </html> login_error.jsp <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GB18030"> <title>登录失败</title> </head> <body> 您的登录失败了,可能原因是用户名或密码不正确,请返回重新输入 <a href="login.jsp">返回登录页面</a> </body> </html>
启动Tomcat并运行login.jsp 运行login.jsp之后,能看到如下所示的登录表单 输入用户名admin和密码admin,将能看到登录成功的界面 输入其它用户名或密码,将能看到登录失败的界面
在这个简单的应用程序背后发生了什么? 当你从浏览器输入地址:http://localhost:8088/Struts-Test/login.jsp,Tomcat将按通常情况来处理这个JSP并返回浏览器 当你提交表单,实际上是提交到了这样一个URL地址:http://localhost:8088/Struts-Test/login.do,Tomcat将会根据web.xml的配置,将这个请求发送给相应的Servlet,在我们的应用中,Tomcat将会把这个请求发送给org.apache.struts.action.ActionServlet这个类(请参看web.xml的配置) 然后ActionServlet根据struts-config.xml的配置信息,调用LoginAction对象去处理这个请求,在此之前,它会将页面表单的请求数据封装到LoginActionForm对象中,并传递给LoginAction LoginAction返回一个ActionForward对象,包含了将要转向的路径信息 ActionServlet根据这个ActionForward对象所包含的路径信息,调用相应的页面去执行响应 流程图请参考下一页
LoginAction应用程序的流程图
Struts项目架构图
Struts工作流程图
Struts与MVC 视图(View) 在使用Struts框架的web应用程序中,JSP以及相关的技术(如Taglib)等共同组成视图层,这一层的主要职责是显示用户界面。Struts提供了很多机制让我们能更加轻松地创建视图 控制器(Controller) Struts中,ActionServlet是控制器层组件 模型(Model) 模型包括:系统的内部状态以及改变系统状态的动作 Struts中的Action和ActionForm是模型的一部分 Struts建议把”做什么”(Action)和”如何做”(业务逻辑)相分离
创建业务逻辑处理类(Model) 使用单例模式(Singleton)来创建业务逻辑处理类 创建UserManager业务逻辑处理类 创建validate方法 创建UserNotFoundException 创建PasswordErrorException package com.bjsxt.strutstest; public class UserManager { private static UserManager userManager; private UserManager(){ } public static synchronized UserManager getInstance(){ if(userManager == null){ userManager = new UserManager(); return userManager; public void validate(String username,String password) throws UserNotFoundException,PasswordErrorException { if(!"admin".equals(username)){ throw new UserNotFoundException(); if(!"admin".equals(password)){ throw new PasswordErrorException();
Action中如何调用业务逻辑处理类? 我们看下面的代码: try { UserManager.getInstance().validate(username,password); return mapping.findForward("success"); } catch (UserNotFoundException e) { e.printStackTrace(); } catch (PasswordErrorException e) { } return mapping.findForward("error"); 通过添加业务逻辑处理类,我们将验证逻辑转移到了业务逻辑处理层
页面之间数据的传递 如何将数据从Action中传递到下一个JSP页面? 一般使用request.setAttribute方法: 在Action中,使用request.setAttribute(String name,Object data)方法往request中设置参数 在JSP中,使用request.getAttribute(String name)来获取相应的参数 在原来LoginAction的基础上编写相应的代码,测试页面数据传递 传递登录成功者的帐号信息到成功页面,并显示
进一步理解Struts控制流
细节:所有的页面请求由容器接收 Struts的核心组件是ActionServlet,像其它所有Servlet一样,它是生存在容器中的,比如Tomcat、WebLogic等,当容器启动的时候,它会读取web.xml文件(部署描述符),告诉容器它会装入哪些Servlet 一个标准的Servlet是通过servlet-mapping来设定,哪些请求,将会被提交到哪些servlet中 Struts的servlet-mapping配置一般是: <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> 这样配置的意思是:任何以.do结尾的URL请求,都会被发送到ActionServlet进行处理
小结 MVC基本结构 Struts的主要组成部分 如何编写ActionForm 如何编写Action 如何在配置文件中定义映射URL、Action以及ActionForm 如何获取从页面传递到Action的数据 如何将数据从Action传递到下一个页面 如何将业务逻辑与表示层分离 需牢记原则:不要在Action中进行业务逻辑的处理,业务逻辑应交给专门的Model层去做 在业务逻辑层抛出异常,并在Action中捕捉和处理
Struts Taglib 易于使用,能代替直接在页面上写JAVA脚本 便于重用 用Struts Taglib实现成功页面
尝试简单的tag lib使用 在JSP文件的头部添加如下声明: <%@ taglib prefix="bean" uri="http://struts.apache.org/tags-bean" %> <%@ taglib prefix="logic" uri="http://struts.apache.org/tags-logic" %> <%@ taglib prefix="html" uri="http://struts.apache.org/tags-html" %> 关于struts tag lib的说明,可以查看相关的参考文档 常用的struts tag lib <bean:write> <logic:empty>和<logic:notEmpty> <logic:present>和<logic:notPresent> <logic:iterator>
JSTL 简介:SUN的标准Taglib库 JSP标准标签库(JSP Standard Tag Library,JSTL ) 迭代和条件判断 数据管理格式化 XML 操作 数据库访问 函数标签库 表达式语言(EL) EL隐式对象 存取器 运算符
在项目中使用JSTL 拷贝jstl.jar和standard.jar到WEB-INF/lib目录下 在JSP中添加伪指令 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> 常用JSTL标记 <c:out> <c:if> <c:choose>、<c:when>和<c:otherwise> <c:forEach> <fmt:formatNumber> <fmt:formatDate>
进一步理解ActionForm ActionForm的要求 必须扩展自org.apache.struts.action.ActionForm 如果要获取表单的值,必须定义一个public属性 如果要求在将ActionForm传递到Action之前进行校验,必须实现validate方法 如果想要在组装前初始化属性,必须实现reset方法 DynaActionForm 举例说明DynaActionForm的配置 DynaActionForm的使用
ActionForm作为表单字段收集器 通过HTTP提交表单数据 通过HTTP上传文件 通过例子演示文件上传的简易方法 通过HTTP提交空字段 修改原来的JSP文件,提交空字段
ActionForm作为类型转换器 所有从表单提交到后台的数据均为字符串类型 如何利用ActionForm自动转换int数据类型 举例说明 如何利用ActionForm自动转换boolean数据类型 如何利用ActionForm自动转换Date类型 定义Converter 注册Converter 如何利用ActionForm自动转换Double类型
ActionForm作为传输对象 ActionForm 可以被其它bean或者过程作为数据载体。 Transfer 对象 (也称为值对象(value object) )用来通过发送粗糙—规整的数据视图来交换精细规整的数据。 ActionForm 的各个属性都必须是可变的。 提示: 使用粗糙—规整 ActionForm来减小类维护。 应用中的表单一般共享属性 创建一个基本的 ActionForm,具有表单需要的所有属性
ActionForward ActionForward能做什么? 在Action中,经常问的问题是:“OK,操作成功了,然后呢?” ActionForward会回传给ActionServlet ActionForward中的路径,可以是一个带参数的URI ActionForward的属性 name path redirect className 转发(forward)与重定向(redirect) 全局ActionForward与局部ActionForward
动态创建ActionForward 将ActionForward定义在一个Struts 配置文件中是个好的选择 但也可以在Action中动态创建ActionForward,而不需要在配置文件中指定 如下所示: ActionForward forward = new ActionForward("/do/itemEdit?action=edit"); 举例说明
ActionMapping 理解ActionMapping path forward type name scope validate input parameter unknow ActionMapping
Scope属性 在Action映射配置中,Scope属性可以取值为:request或session Scope属性表示:Struts框架在将ActionForm对象(与目标Action匹配的ActionForm)传送到Action之前,会将ActionForm对象保存的位置 如:scope=“request”配置,将指示struts调用request.setAttribute(“ActionForm名称”,ActionForm对象)方法,将ActionForm对象保存到request。 其中,ActionForm名称与struts-config.xml配置中的ActionForm名称一致,如:<form-bean name=“uploadForm” type=“com.bjsxt.struts.actionform.UploadActionForm”/>,其中uploadForm就是其名称。 我们明白scope属性的意义之后,就可以利用struts的这些特性,来解决开发过程中的某些常见问题
常见问题:如何在程序出现异常的时候返回录入界面重新录入 假设现在要在一个页面上输入用户的信息(可能会有十几个属性值需要输入),用户不小心输入了一个重复的帐号,而帐号是不允许重复的,这个时候,系统应该提示用户有关帐号重复的信息,同时让用户重新选择一个帐号。 这种情况下,我们需要返回用户录入界面,让用户修改帐号字段。 现在的问题是:如何在返回这个录入界面的时候,将用户输入的其它信息保持住?
搞定Action对象 如果应用需要保存一个记录到数据库中,典型的过程可能是 ActionForward提供一个链接到输入页面 ActionForm捕获输入 ActionMapping配置Action Action将输入送到数据库中(通常会将这一步操作委托给业务逻辑类去实现) J2EE是一个多线程的环境,服务器针对每个请求启动一个线程来处理。所以有可能会有多个线程同时访问一个Servlet实例的情况 在Struts里面也是一样的,有可能会有多个线程同时访问一个Action实例的情况 所以必须保证Action类中的方法具有“可重入性”,即不能在Action的方法里改变实例变量的值
Action的主要职责 校验输入数据 调用业务逻辑方法 检测处理异常 根据逻辑进行转向操作
I18N问题 什么是I18N问题? 在英文中, 国际化(Internationalization)被缩写为I18N, 即只取首尾两个字母, 中间字母为18个 问题在哪里? 页面字符串硬编码 异常消息的硬编码 提示信息的硬编码
Java如何支持I18N? 一个简单的例子 不支持I18N的例子 – NoI18NSample.java 支持I18N的例子 我们需要将硬编码文本转移到外部的资源文件 编写MessagesBundle.properties 编写MessagesBundle_zh_CN.properties 编写MessagesBundle_en_US.properties 编写I18NSample.java 运行I18NSample.java 乱码? 因为资源文件必须是Latin-1或Unicode编码(如\udddd)的字符 使用native2ascii工具,将中文资源文件进行转换
Java支持I18N的编码过程总结 1、创建属性文件(可能要用native2ascii工具转换) 2、定义Locale对象 3、创建一个ResourceBundle对象 ResourceBundle对象用于分离跟本地相关的数据 如果找不到相应语言或国家代码的属性文件,将使用默认的属性文件(即没有标识语言和国家代码的属性文件:MessagesBundle.properties) 其创建方式如下: message = ResourceBundle.getBundle("MessagesBundle", currentLocale); 第一个参数,表示要从哪些资源属性文件中(MessagesBundle_XX.properties)获取数据 第二个参数,是一个Locale对象,表示要选择哪个资源属性文件 4、从ResourceBundle对象中获取数据
Locale对象 我们通过指定一个语言代码和国家代码来创建一个Locale对象 国家代码是可选的 语言代码是小写字母;国家代码是大写字母 Locale.getDefault()可以获得系统当前的Locale Java都支持哪些语言代码和国家代码? DateFormat.getAvailableLocales() 语言代码标准:http://ftp.ics.uci.edu/pub/ietf/http/related/iso639.txt 国家代码标准:http://userpage.chemie.fu-berlin.de/diverse/doc/ISO_3166.html 与Locale相关的数据: 消息文本(带参数?) 日期(时间) 货币(数字) 等等。。。
Struts如何支持I18N 1、需要在struts配置文件中指定资源属性文件的位置和名称,如 <message-resources parameter="MessageResources" /> 2、在相应的位置放置相应的文件 3、在JSP页面中使用<bean:message key=“key string”/>来输出文本,以避免硬编码 以登录页面的国际化作为例子讲解 创建相应的资源属性文件 用<bean:message/>标签替换登录页面的硬编码文本 测试(更改网页显示语言,以便测试不同的版本)
关于message-resources 配置中parameter的值 举例: <message-resources parameter="MessageResources" /> 表示在类路径根目录(WEB-INF/classes目录)下有MessageResources_XX_XX.properties文件(注意:国家代码可以省略,跟java中对资源属性文件的处理一样) <message-resources parameter="resources.application"/> 表示在类路径根目录下,有一个resources目录,在这个resources目录中存放着所有的application_XX_XX.properties资源属性文件
如何用程序切换网页显示的语言 struts利用在session中存放一个Locale对象来达到设置当前语言的目的 默认的情况下,struts根据网页向后台提交时所包含的语言编码信息来提供缺省的Locale对象,这就是我们为什么可以通过更改网页显示语言设置,就能显示不同的语言文字的原因。 struts在session中存放的这个Locale对象,取名为:Globals.LOCALE_KEY 的值,Globals是struts框架提供的一个对象 利用这个原理,我们可以用编程的方式来手工切换整个应用系统的语言。 举例说明 ChangeLanguageAction
Struts消息处理 为什么需要消息处理? 比如登录成功的提示 比如创建失败的提示 等等……总之,程序总是要通过界面来跟用户交互,所以,在交互的过程中,就产生了众多的消息文本 struts提供了专门的处理机制,来将这些消息文本国际化,避免消息文本的硬编码 消息处理,就是在Action和JSP之间传递的消息文本的处理(区别于JSP页面硬编码文本的消息,JSP页面消息可以使用<bean:message/>标签来处理) Struts交互消息,是通过ActionMessages等对象,以及相应的<html:messages/>标签来处理的
如何创建消息对象? ActionMessages与ActionMessage对象 ActionMessages messages = new ActionMessages(); 如何创建ActionMessage对象? ActionMessage msg = new ActionMessage(“key”); 其构造方法带的参数,就是一个在资源属性文件中的key,所以,它能表示一个国际化消息文本 如何将ActionMessage对象添加到ActionMessages对象中? messages.add(“message_id”,msg); 第一个参数(message_id)表示本ActionMessage对象在ActionMessages对象中区别于其它ActionMessage对象的标识符
如何将消息对象从Action中传递到下一个页面(JSP)? 首先我们要决定的是,我们要传递的消息是普通消息还是错误消息? 普通消息:即普通的消息文本 错误消息:即提示错误的消息文本 本质上,这两种消息没有什么区别,都是消息文本,但是如果一个页面同时需要显示普通的消息文本和错误消息文本的时候,就需要进行区分了,比如不同类型的消息文本可能要用不同的样式来显示 通过一句简单的代码,将ActionMessages对象保存到HttpServletRequest对象中 保存普通消息:this.saveMessages(request,messages); 保存错误消息:this.saveErrors(request,messages); 这就是调用父类(Action)所提供的方法saveMessages()/saveErrors()来保存消息对象 实际上,父类的saveMessages()方法,将消息对象保存在了request中,并命名为Globals.MESSAGE_KEY saveErrors()方法,将消息对象保存在了request中,并命名为Globals.ERROR_KEY
如何在JSP中使用消息对象? 使用<html:messages/>标签来显示消息 name – 消息对象的名称,如果我们调用saveMessages/saveErrors方法来传递消息,那么这个名字不需要标识(struts使用缺省的名称,即Globals.MESSAGE_KEY 或Globals.ERROR_KEY ) id – (这是必需的属性)因为我们传递的是ActionMessages对象,而不是ActionMessage对象,ActionMessages对象相当于一个集合,我们需要在JSP上依次输出它所包含的消息,因此需要一个id标识一个变量来临时存放其每条消息(与<logic:iterate/>标签的id属性的意义是一样的) property – 我们传递的ActionMessages对象,包含了多条消息文本,如果我们只需要显示其中一条,则可以通过property属性来指定显示哪条消息 message – 可以取值为true或false,如果取值为true,将显示普通消息,如果取值为false,将显示错误消息
<html:errors/>标签 <html:errors/>标签与<html:messages/>标签类似,但无id属性 <html:errors/>标签通过提供header/footer属性以及prefix/suffix属性来定制每条消息的显示格式 header/footer – 定义整个错误消息显示之前(之后)要显示的内容,这些内容也是在资源属性文件中定义的一些key值,默认的情况下,它们的取值分别为:errors.header和errors.footer prefix/suffix – 定义每条错误消息显示之前(之后)要显示的内容,这些内容也是在资源属性文件中定义的一些key值,默认的情况下,它们的取值分别为:errors.prefix和errors.suffix 举例如下: errors.header=<UL> errors.prefix=<LI> errors.suffix=</LI> errors.footer=</UL>
Struts的异常自动处理机制 编程式异常处理 即我们在Action中调用业务逻辑层对象的方法时,用try{ }catch的方式来截获异常之后,手工对异常进行处理 我们以前的开发过程中,都是使用编程式的异常处理 在编程式异常处理的时候,我们可以使用struts的消息处理机制(前面所讲的内容)来对这些异常信息进行处理 自动异常处理机制 即在Action中不捕捉异常,而是将异常抛出给struts框架处理 我们需要在配置文件中指示struts如何处理这些被抛出的异常 使用<exception/>元素来定义自动异常处理
<exception/>元素的配置及使用 <exception/>元素的配置,指示了struts如何处理异常的方式 在通常的情况下,我们得到异常以后,需要将页面导航到一个错误提示的页面,提示错误信息 <exception/>元素配置的关键属性是: key – 即这个异常所对应的错误提示消息文本的key,这个key的值,需要在资源属性文件中进行定义 type – 即定义需要处理哪种类型的Exception path – 定义一旦出现异常,需要转向哪个页面来进行提示,如果不定义path属性,默认情况下,将使用Action配置中的input属性的值来作为转向的页面 如何显示错误消息? 在JSP页面中,使用<html:errors/>标签,即可将其异常对应的错误消息文本进行显示(测试login.jsp页面)
标准Action Struts框架缺省提供了一些Action,来完成一些常见的功能 ForwardAction - ForwardAction仅仅简单的转发控制到其他资源 为什么需要ForwardAction? 目的是发出一个RequestDispatcher 转发 ForwardAction 的绝大多数使用是作为Action的占位符 ForwardAction 创建一个请求分派器,并根据ActionMapping提供的上下文相关的URI转发控制 许多 Struts 开发人员避免从一个页面直接连接到其他地方而是通过Action 或者 ActionForward来传递控制。这保证了工作流在Struts 配置的控制之下,在这里可以进行集中管理。 然而,许多页面并不需要特殊的预处理(至少还不需要)。如果为这些页面创建ActionMapping ,你可以使用ForwardAction ,来仅进行路由控制。如果以后,需求改变,又需要进行预处理,你可以改变mapping 来为那个页面引用到一个Action。因为链接是引用到mapping, 而不是Action类, 所以你可以修改Action类而不用改变链接。
标准的Base Action DispatchAction – 避免每个Action创建一个类 DispatchAction的配置方法 添加parameter属性到Action的配置中 unspecified方法 举例说明DispatchAction的使用
struts配置中的路径与模式匹配 struts配置中的action,有一个path属性,它表明请求的URI 一般情况下,我们需要在配置文件中明确指定某个特定的URI,如path=“/user/add” 在一些大型应用中,如果能够制定一套严格的路径及其操作规范的话,我们可以利用path的路径模式匹配功能,来简化struts配置文件繁琐的编写工作量 假设有如下规范:
路径匹配规范示例 对user对象的所有处理Action,均需要以如下的路径进行访问: /user/add.do – 处理用户添加的有关操作 /user/delete.do – 处理用户删除的有关操作 /user/update.do – 处理用户更新的有关操作 … 所有操作(Action)对应的JSP如下: 所有操作成功(失败)之后的转向页面,有如下命名规范: /user/add.do -> /user/add_success.jsp或/user/add_error.jsp /user/delete.do -> /user/delete_success.jsp或/user/delete_error.jsp 所有操作的输入界面有如下命名规范: 添加操作 -> /user/add_input.jsp 更新操作 -> /user/update_input.jsp
Action配置示例 <action path="/user/*" type="com.bjsxt.struts.web.actions.UserAction" name="userForm" parameter="method" > <forward name="index" path="/user/index.jsp"/> <forward name="success" path="/user/{1}_success.jsp"/> <forward name="error" path="/user/{1}_error.jsp"/> <forward name="input" path="/user/{1}_input.jsp"/> </action>
Action配置解释 所有的/user/*.do请求,都将由UserAction这个类来处理,UserAction类继承DispatchAction,它将根据传入的method参数的值,来分发到不同的方法来进行处理 在UserAction类中的任何一个方法,都可以返回index/success/error/input等名称的ActionForward 根据请求路径的不同,即使调用相同的返回代码,但其转向也将不同,如: /user/add.do?method=add请求,将被转发给UserAction类的add方法处理,假设它用mapping.findForward(“success”);来返回成功页面,这将转向的实际JSP页面是:/user/add_success.jsp 而/user/delete.do?method=delete请求,将被转发给UserAction类的delete方法处理,假设它用mapping.findForward(“success”);来返回到删除成功页面,这将转向的实际JSP页面是:/user/delete_success.jsp,所以,不同URI请求的相同名称的返回页面将是不同的。 而/user/index.do请求(或者任何一个其它请求,如/user/abcd.do或/user/test.do),都因为没有传递method参数,而触发调用UserAction的unspecified方法
Struts回顾 Struts tag lib的配置和使用 JSTL的配置和使用 错误消息的处理 ActionForm的多种用途 DynaActionForm ActionForward的主要作用,如何动态创建ActionForward Action的主要职责和处理过程 如何保证Action的线程安全性 国际化与资源文件的配置、使用 ForwardAction DispatchAction
Hibernate 开源O/R映射框架
课程目标 课程目标: 理解O/R Mapping原理 掌握Hibernate开发的相关知识 能使用Hibernate进行实际项目开发
Hibernate的对象关系映射 目录 什么是Hibernate 快速体验Hibernate的开发步骤 认识Hibernate基本核心接口 Hibernate查询语句(HQL)
Hibernate? 直接使用JDBC操作数据库的步骤很繁琐 JDBC操作的是关系型数据库 我们用JAVA开发程序,则使用面向对象的思想 Hibernate正是在这两种不同的模型之间建立关联,Hibernate给我们提供了利用面向对象的思想来操作关系型数据的接口
什么是关系模型(Relational Model)? 关系模型把世界看作是由实体(Entity)和联系(Relationship)构成的。 所谓实体就是指现实世界中具有区分与其它事物的特征或属性并与其它实体有联系的对象。在关系模型中实体通常是以表的形式来表现的。表的每一行描述实体的一个实例,表的每一列描述实体的一个特征或属性。 所谓联系就是指实体之间的关系,即实体之间的对应关系。 1:1 1:n m:n 关系数据库 表 字段 主键 外键
什么是面向对象? 面向对象三大特征:封装、继承(一般与特殊)、多态(覆盖与重载) 类 对象 属性 关系 一般与特殊关系(is a) 组成(has a) 关联及其多重性 1:1 1:n m:n 双向关联与单向关联
对象关系映射(Object Relational Mapping,简称ORM) ORM是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将java程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。 Why ORM? 面向对象的开发方法是当今企业级应用开发环境中的主流开发方法 关系数据库是企业级应用环境中永久存放数据的主流数据存储系统 字母O起源于“对象”(Object),而R则来自于“关系”(Relational)。几乎所有的程序里面,都存在对象和关系数据库。在业务逻辑层和呈现层中,我们是面向对象的。当对象信息发生变化的时候,我们需要把对象的信息保存在关系数据库中。 当你开发一个应用程序的时候(不使用O/R Mapping),你可能会写不少数据访问层的代码,用来从数据库保存,删除,读取对象信息,等等。而这些代码写起来总是重复的。
对象-关系映射模式 属性映射 类映射 关联映射 一对一 一对多 多对多
什么是Hibernate? 对象/关系映射一直都是数据库技术中的难点,尽管人们提出了许多方案解决这个问题,但都不能完全做到即便利又高效。EJB的推出让人们看到了希望,但实践证明实体Bean的效率并不高,并且还十分难于为初学者理解。由Gavin King创建的Hibernate框架,从某种程序上正在朝着正确的方向迈走,并且得到越来越多IT从业人员的认可。就像当年的Struts框架一样,Hibernate也已经在许多项目中得到广泛应用。Hibernate由于投注了更多的精力在提升效率上,使用起来又十分方便,新版的EJB规范正在向Hibernate方向靠拢。正是由于得到广泛的认可,Hibernate已经成为程序员必须掌握的技术之一。
Hibernate能做什么? - 理解O/R映射 第一:将对象数据保存到数据库 第二:将数据库数据读入对象中 基于B/S的典型三层架构 关于分层 × 业务逻辑层和持久化层绝对不能依赖于展现层
Hibernate与O、R之间的关系
快速体验Hibernate – 安装以及创建新的项目 使用Eclipse创建新的项目 引入Hibernate及其依赖库(jar包) 引入mysql数据库驱动包 打开mysql控制台,创建测试数据库”hibernate” Create database hibernate; Use hibernate
创建Hibernate配置文件 – hibernate.cfg.xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.url">jdbc:mysql://127.0.0.1/hibernate</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">mysql</property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> </session-factory> </hibernate-configuration>
创建持久化类 User.java public class User { private String id; private String name; private String password; private Date createTime; private Date expireTime; …..getters/setters }
创建类的映射文件 – User.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.bjsxt.hibernate.User"> <id name="id"> <generator class="uuid"/> </id> <property name="name"/> <property name="password"/> <property name="createTime"/> <property name="expireTime"/> </class> </hibernate-mapping>
将类的映射文件加入Hibernate 为了让Hibernate能够处理User对象的持久化,需要将它的映射信息加入到Hibernate中 <mapping resource="com/bjsxt/hibernate/User.hbm.xml“/> 即可 resource属性指定了映射文件的位置和名称
创建数据库表 – 利用SchemaExport工具类 利用Hibernate提供的工具类来创建数据库表 创建ExportToDB类 public class ExportToDB { public static void main(String[] args) throws Exception{ //读取配置文件 Configuration cfg = new Configuration().configure(); //创建SchemaExport对象 SchemaExport export = new SchemaExport(cfg); //创建数据库表 export.create(true,true); }
将对象保存到数据库 – UserTest1.java public static void main(String[] args) throws Exception{ Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session = factory.openSession(); session.beginTransaction(); User user = new User(); user.setName("管理员"); user.setPassword("admin"); user.setCreateTime(new Date()); user.setExpireTime(new Date()); session.save(user); session.getTransaction().commit(); if(session.isOpen()){ session.close(); }
实际操作体验Hibernate开发步骤 按照上面的步骤,先快速体验一下Hibernate实际所做的事情 Hibernate开发步骤 实体类(持久化类)的设计 实体类与关系数据库的映射 应用的开发
认识Hibernate的基本组件 实体类 实体类映射文件 重点学习的部分 Hibernate配置文件 辅助工具
Hibernate核心接口
概述:Configuration 类负责管理Hibernate 的配置信息。它包括如下内容: Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,数据库连接池等。 Hibernate映射文件(*.hbm.xml)。 Hibernate配置的两种方法: 属性文件(hibernate.properties)。 调用代码:Configuration cfg = new Configuration(); Xml文件(hibernate.cfg.xml)。 调用代码:Configuration cfg = new Configuration().configure();
SessionFactory 概述:应用程序从SessionFactory(会话工厂)里获得Session(会话)实例。它在多个应用线程间进行共享。通常情况下,整个应用只有唯一的一个会话工厂——例如在应用初始化时被创建。然而,如果你使用Hibernate访问多个数据库,你需要对每一个数据库使用一个会话工厂。 会话工厂缓存了生成的SQL语句和Hibernate在运行时使用的映射元数据。 调用代码: SessionFactory sessionFactory = cfg.buildSessionFactory(); 说明:SessionFactory由Configuration对象创建,所以每个Hibernate配置文件,实际上是对SessionFactory的配置
Session(会话) 概述: Session不是线程安全的,它代表与数据库之间的一次操作,它的概念介于Connection和Transaction之间。 Session也称为持久化管理器,因为它是与持久化有关的操作接口。 Session通过SessionFactory打开,在所有的工作完成后,需要关闭。 它与Web层的HttpSession没有任何关系。 调用代码 Session session = sessionFactory.openSession();
持久化对象的状态 瞬时对象(Transient Objects):使用new 操作符初始化的对象不是立刻就持久的。它们的状态是瞬时的,也就是说它们没有任何跟数据库表相关联的行为,只要应用不再引用这些对象(不再被任何其它对象所引用),它们的状态将会丢失,并由垃圾回收机制回收。 持久化对象(Persist Objects):持久实例是任何具有数据库标识的实例。它有持久化管理器Session统一管理,持久实例是在事务中进行操作的——它们的状态在事务结束时同数据库进行同步。当事务提交时,通过执行SQL的INSERT、UPDATE和DELETE语句把内存中的状态同步到数据库中。 离线对象(Detached Objects):Session关闭之后,持久化对象就变为离线对象。离线表示这个对象不能再与数据库保持同步,它们不再受Hibernate管理。
持久化对象的生命周期(lifecycle)
注:使用Hibernate进行操作时必须显式的调用Transaction(默认:autoCommit=false)。 概述: 它将应用代码从底层的事务实现中抽象出来——这可能是一个JDBC事务,一个JTA用户事务或者甚至是一个公共对象请求代理结构(CORBA)——允许应用通过一组一致的API控制事务边界。这有助于保持Hibernate应用在不同类型的执行环境或容器中的可移植性。 调用代码: Transaction tx = session.beginTransaction(); 注:使用Hibernate进行操作时必须显式的调用Transaction(默认:autoCommit=false)。
从代码中体会Session和Transaction Session session = factory.openSession(); session.beginTransaction(); User user = new User(); user.setName("管理员"); user.setPassword("admin"); user.setCreateTime(new Date()); user.setExpireTime(new Date()); session.save(user); session.getTransaction().commit(); User user1 = new User(); user1.setName("jjj"); session.save(user1); if(session.isOpen()){ session.close(); }
Query 概述: Query(查询)接口允许你在数据库上执行查询并控制查询如何执行。查询语句使用HQL或者本地数据库的SQL方言编写。 调用代码: Query query = session.createQuery(“from User”); 关于HQL,在后续的课程中,将会介绍
Query举例 Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session = factory.openSession(); session.beginTransaction(); Query query = session.createQuery("from User"); List users = query.list(); for (Iterator iter = users.iterator(); iter.hasNext();) { User user = (User) iter.next(); System.out.println("user name = "+user.getName()); } session.getTransaction().commit(); if(session.isOpen()){ session.close();
Hibernate的对象关系映射 映射文件的基本结构举例 <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.bjsxt.hibernate.User"> <id name="id"> ……… </id> <property name="name"/> ….. </class> </hibernate-mapping>
hibernate-mapping元素 可以包含的子元素 class – 描述被映射的类 subclass/joined-subclass – 在继承关系的映射中会用到 query – 将查询语句定义在配置文件中 ….
class元素 常用属性 name – 实体类的类名 table – 被映射到数据库表的名称 可以包含的常见子元素 id – 主键定义 property – 属性定义 关系映射定义(一对多、多对一等)
主键 - id 被映射的类必须要有一个id定义 通常使用逻辑主键 逻辑主键:没有意义的唯一标识符 业务主键:有意义的唯一标识符 Hibernate使用generator类来生成主键 Hibernate自带了很多generator(不同的主键生成策略) int/long – native String - uuid 我们也可以定义自己的generator 实现IdentifierGenerator接口 一般情况下不需要实现自己的generator
主键生成策略generator generator Hibernate内置的主键生成策略 主键生成器,每个主键都必须定义相应的主键生成策略。它用来为持久化类实例生成唯一的标识。 Hibernate内置的主键生成策略 数据库提供的主键生成机制。identity、sequence(序列) 。 外部程序提供的主键生成机制。increment (递增) ,hilo(高低位) ,seqhilo(使用序列的高低位 ),uuid.hex(使用了IP地址+JVM的启动时间(精确到1/4秒)+系统时间+一个计数器值(在JVM中唯一) ),uuid.string。 其它。native(本地),assigned(手工指定),foreign(外部引用)。
普通属性映射 - property <property name=“property_name”/> 可使用的常见属性如下: type – 指定属性的类型,一般情况下可以不用指定,由hibernate自动匹配(可参考文档中的有关说明) length – 指定长度 column – 指定属性所对应的数据库字段的名称,如果不指定,就是属性的名称
多对一关联映射 - many-to-one User-Group 多个用户属于某个组 从代码上体现为: public class Group { privte String id; private String name; ….. } public class User{ private String id; privte String name; …… private Group group; public Group getGroup(){return group;} public void setGroup(Group group){ this.group = group;
many-to-one映射的编写 many-to-one的映射最常用,也是最容易理解和编写的 <many-to-one name="group" column=“groupid”/> 生成的DDL语句如下 create table T_Group (id varchar(255) not null, name varchar(255), primary key (id)) create table User (id varchar(255) not null, name varchar(255), password varchar(255), createTime datetime, expireTime datetime, groupid varchar(255), primary key (id)) alter table User add index FK285FEBC3D18669 (groupid), add constraint FK285FEBC3D18669 foreign key (groupid) references T_Group (id) 从生成的DDL语句,我们可以知道,实际上是在User表上建立了一个指向Group表的外键关联
重要属性cascade 重要属性 - cascade(级联) 级联的意思是指定两个对象之间的操作联动关系,对一个对象执行了操作之后,对其指定的级联对象也需要执行相同的操作 总共可以取值为:all、none、save-update、delete all-代表在所有的情况下都执行级联操作 none-在所有情况下都不执行级联操作 save-update-在保存和更新的时候执行级联操作 delete-在删除的时候执行级联操作 如:<many-to-one name=“group” column=“groupid” cascade=“all”/> 编写实际例子测试many-to-one以及cascade属性的配置
cascade实际上意味着什么? 无cascade配置的User-Group执行代码 配置 java代码: 执行结果: <many-to-one name=“group” column=“groupid”/> java代码: Group group = new Group(); group.setName("jkjk"); User user = new User(); user.setName("管理员"); user.setGroup(group); session.save(user); 执行结果: 抛出org.hibernate.TransientObjectException异常,以上代码中,group对象是一个瞬时对象,user对象引用了一个瞬时对象,所以在保存的时候出现异常
无cascade配置时正确的java执行代码 为避免异常,我们可以需要将group对象保存 Group group = new Group(); group.setName("jkjk"); //执行save操作之后,group对象变成持久化对象的状态 session.save(group); User user = new User(); user.setName("管理员"); user.setGroup(group); session.save(user);
添加cascade配置 <many-to-one name="group" column="groupid" cascade="all"/> 下面的代码(最初的代码) Group group = new Group(); group.setName("jkjk"); User user = new User(); user.setName("管理员"); user.setGroup(group); session.save(user); 可正确执行 cascade配置,使得hibernate在管理对象的时候,对cascade对象执行了级联操作
一对一关联映射 (one-to-one) 两个对象之间是一对一的关系,如Person-IdCard 有两种策略可以实现一对一的关联映射 主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联 唯一外键关联:外键关联,本来是用于多对一的配置,但是如果加上唯一的限制之后,也可以用来表示一对一关联关系;
一对一 (主键关联映射) Person类 public class Person { private int id; 一对一 (主键关联映射) Person类 public class Person { private int id; private IdCard idCard; ….. } 映射文件 <class name="com.bjsxt.hibernate.Person"> <id name="id"> <generator class="foreign"> <param name="property">idCard</param> </generator> </id> …. <one-to-one name="idCard" constrained="true"/> </class> IdCard类 public class IdCard { private int id; private Person person; …… } 映射文件 <class name="com.bjsxt.hibernate.IdCard"> <id name="id"> <generator class=“native"/> </id> …. <one-to-one name=“person"></one-to-one> </class>
一对一 (唯一外键关联映射) Mankind类 public class Mankind { private String id; 一对一 (唯一外键关联映射) Mankind类 public class Mankind { private String id; private String name; private Nose nose; 关联映射 <class name="com.bjsxt.hibernate.Mankind"> <id name="id"> <generator class="uuid"/> </id> <property name="name"/> <many-to-one name="nose" unique="true" cascade="all"></many-to-one> </class> Nose类 public class Nose { private String id; private Mankind mankind; 关联映射 <class name="com.bjsxt.hibernate.Nose"> <id name="id"> <generator class="uuid"/> </id> <one-to-one name="mankind" property-ref="nose"></one-to-one> </class> property-ref:在这种情况下,必须指定此属性,它表示本类(Nose)的主键将会与关联类(Mankind)的此属性(nose)相对应
一对多关联映射 (one-to-many) 在对象模型中,一对多的关联关系,使用集合来表示 比如Classes(班级)和Student(学生)之间是一对多的关系 public class Classes { private String id; private String name; private Set students; …. public class Student { …..
一对多关联映射文件 Classes映射文件 <hibernate-mapping> <class name="com.bjsxt.hibernate.Classes"> <id name="id"> <generator class="uuid"/> </id> <property name="name"/> <set name="students"> <key column="classesid" ></key> <one-to-many class="com.bjsxt.hibernate.Student" /> </set> </class> </hibernate-mapping>
关于lazy属性 lazy – 延迟加载(懒加载),一般用于集合的抓取策略,也就是说只在需要用到的情况下,再发出select语句,将其相关的对象查询出来 set默认lazy属性的值是true,即hibernate会自动使用懒加载策略,以提高性能 举例说明 <set name="students“ lazy=“false”> <key column="classesid" ></key> <one-to-many class="com.bjsxt.hibernate.Student" /> </set>
关于inverse属性 inverse – 标记由哪一方来维护关联关系(双向关联中会用到) inverse默认值为false 如果inverse设置为true,表示将由对方维护两者之间的关联关系 举例说明 <set name="students“ lazy=“false” inverse=“true”> <key column="classesid" ></key> <one-to-many class="com.bjsxt.hibernate.Student" /> </set>
多对多关联映射 (many-to-many) 一般的设计中,多对多关联映射,需要一个中间表 Hibernate会自动生成中间表 Hibernate使用many-to-many标签来表示多对多的关联 多对多的关联映射,在实体类中,跟一对多一样,也是用集合来表示的
many-to-many <many-to-many column="column_name" (1) class="ClassName" (2) /> (1) column(必需): 中间映射表中,关联目标表的关联字段 (2) class (必需): 类名,关联目标类 <key column="column_name"/> (1) (1) column(必需):当前表的关联字段
例子(many to many):student-trainClass <class name="com.test.hibernate.Student"> <id name="id" column="userId"><generator class="native"/></id> <set name="trainClasses" lazy="true" cascade="save-update"> <key column="studentId"/> <many-to-many class="com.test.hibernate.TrainClass" column="trainClassId"/> </set> </class> <class name="com.test.hibernate.TrainClass" table="TBL_TRAIN_CLASS"> <id name="id" column="trainClassId"><generator class="native"/></id>
继承 继承实现的三种策略 单表继承。每棵类继承树使用一个表(table per class hierarchy) 具体表继承。每个子类一个表(table per subclass) 类表继承。每个具体类一个表(table per concrete class)(有一些限制)
每个类继承树对应一张表 因为类继承树肯定是对应多个类,要把多个类的信息存放在一张表中,必须有某种机制来区分哪些记录是属于哪个类的。这种机制就是,在表中添加一个字段,用这个字段的值来进行区分。用hibernate实现这种策略的时候,有如下步骤: 父类用普通的<class>标签定义 在父类中定义一个discriminator,即指定这个区分的字段的名称和类型 如:<discriminator column=”XXX” type=”string”/> 子类使用<subclass>标签定义,在定义subclass的时候,需要注意如下几点: Subclass标签的name属性是子类的全路径名 在Subclass标签中,用discriminator-value属性来标明本子类的discriminator字段(用来区分不同类的字段)的值 Subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标签平行。 当subclass标签的定义与class标签平行的时候,需要在subclass标签中,添加extends属性,里面的值是父类的全路径名称。 子类的其它属性,像普通类一样,定义在subclass标签的内部。
每个子类一张表(除非将父类定义成抽象的,否则父类也是一张表) 这种策略是使用joined-subclass标签来定义子类的。父类、子类,每个类都对应一张数据库表。在父类对应的数据库表中,实际上会存储所有的记录,包括父类和子类的记录;在子类对应的数据库表中,这个表只定义了子类中所特有的属性映射的字段。子类与父类,通过相同的主键值来关联。实现这种策略的时候,有如下步骤: 父类用普通的<class>标签定义即可 父类不再需要定义discriminator字段 子类用<joined-subclass>标签定义,在定义joined-subclass的时候,需要注意如下几点: Joined-subclass标签的name属性是子类的全路径名 Joined-subclass标签需要包含一个key标签,这个标签指定了子类和父类之间是通过哪个字段来关联的。如:<key column=”PARENT_KEY_ID”/>,这里的column,实际上就是父类的主键对应的映射字段名称。 Joined-subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标签平行。 当Joined-subclass标签的定义与class标签平行的时候,需要在Joined-subclass标签中,添加extends属性,里面的值是父类的全路径名称。 子类的其它属性,像普通类一样,定义在joined-subclass标签的内部。
每个具体子类一张表 这种策略是使用union-subclass标签来定义子类的。每个子类对应一张表,而且这个表的信息是完备的,即包含了所有从父类继承下来的属性映射的字段(这就是它跟joined-subclass的不同之处,joined-subclass定义的子类的表,只包含子类特有属性映射的字段)。实现这种策略的时候,有如下步骤: 父类用普通<class>标签定义即可 子类用<union-subclass>标签定义,在定义union-subclass的时候,需要注意如下几点: Union-subclass标签不再需要包含key标签(与joined-subclass不同) Union-subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标签平行。 当Union-subclass标签的定义与class标签平行的时候,需要在Union-subclass标签中,添加extends属性,里面的值是父类的全路径名称。 子类的其它属性,像普通类一样,定义在Union-subclass标签的内部。这个时候,虽然在union-subclass里面定义的只有子类的属性,但是因为它继承了父类,所以,不需要定义其它的属性,在映射到数据库表的时候,依然包含了父类的所有属性的映射字段。
概念:双向关联允许通过关联的任一端访问另外一端。在Hibernate中, 支持两种类型的双向关联。 一对多(one-to-many),Set或者bag值在一端, 单独值(非集合)在另外一端 。 多对多(many-to-many),两端都是set或bag值。
例子(双向关联):group-user <class name="com.test.hibernate.Group" table="TBL_GROUP"> <id name="id" column="groupId"><generator class="native“></id> <set name="users" lazy="true" cascade="save-update" inverse="true"> <key column="groupId"/> <one-to-many class="com.test.hibernate.User"/> </set> </class> <class name="com.test.hibernate.User" table="TBL_USER"> <id name="id" column="userId"><generator class="native"/></id> <many-to-one name="group" column="groupId" outer-join="false"/>
例子:从Java代码看group-user双向关联的inverse 概念:inverse用来标识双向关联的关联关系由哪一端维护。默认inverse的值为false,由主动方负责维护关联关系;如果设为true,则由反向一端维护关联关系。 用例:我们假设已经有一个Group类的实例:adminGroup,现在我们要新增一个用户,并且将用户分配到adminGroup中。 inverse=“false”,由主动方Group负责维护group-user的关联关系. User user = new User(“Jak”); adminGroup.getUsers.add(user); session.save(user); session.update(group); inverse=“true”,由Group的反向段User负责维护关联关系。 user .setGroup(adminGroup); session.save(user);
概述:数据查询与检索是Hibernate中的一个亮点。相对其他ORM实现而言,Hibernate提供了灵活多样的查询机制。 标准化对象查询(Criteria Query):以对象的方式进行查询,将查询语句封装为对象操作。优点:可读性好,符合Java 程序员的编码习惯。缺点:不够成熟,不支持投影(projection)或统计函数(aggregation) Hibernate语言查询(Hibernate Query Language,HQL):它是完全面向对象的查询语句,查询功能非常强大,具备多态、关联等特性 。Hibernate官方推荐使用HQL进行查询。 Native SQL Queries(原生SQL查询):直接使用标准SQL语言或跟特定数据库相关的SQL进行查询。
例子:标准化对象查询(Criteria Query) 简单例子:查询用户名以“J”开头的所有用户。 Criteria criteria = session.createCriteria(User.class); criteria.add(Expression.like("name","J%")); List users = criteria.list();
Hibernate语言查询(Hibernate Query Language,HQL) HQL用面向对象的方式生成SQL 以类和属性来代替表和数据列 支持多态 支持各种关联 减少了SQL的冗余 HQL支持所有的关系数据库操作 连接(joins,包括Inner/outer/full joins),笛卡尔积(cartesian products) 投影(projection) 聚合(Aggregation,max, avg)和分组(group) 排序(Ordering) 子查询(Subqueries) SQL函数(SQL function calls)
例子: Hibernate语言查询(Hibernate Query Language,HQL) 简单例子:查询用户名以“J”开头的所有用户。 Query query = session.createQuery( "from User user where user.name like 'J%'"); List users = query.list(); 复杂例子:从User和Group中查找属于“admin”组的所有用户。 “from User user where user.group.name=‘admin’”); 如果用传统的SQL则查询语句如下: select user.userId as userId, user.name as name, user.groupId as groupId, user.idCardId as idCardId from TBL_USER user, TBL_GROUP group where (group.groupName='admin' and user.groupId=group.groupId)
Hibernate性能优化策略 Hibernate高级话题:性能优化策略的配置与使用 本节包括如下主题: 一级缓存 二级缓存 查询缓存 批量抓取 批量更新
一级缓存 即session级别的缓存,随着session的关闭而消失,load/iterator操作,会从一级缓存中查找数据,如果找不到,再到数据库里面查找。Query.list操作,如果没有配置查询缓存,将直接从数据库中获取数据。
二级缓存(1) 首先要打开二级缓存 默认情况下,二级缓存是打开的,可以通过配置: <property name="hibernate.cache.use_second_level_cache">false</property> 来关闭二级缓存。 使用: <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> 来指定缓存提供商。即有谁来提供缓存的功能。 Ehcache配置 可以看例子,里面有非常清楚的说明。
二级缓存(2) 要在映射文件中指定缓存策略 在hbm文件中加入: <cache usage="read-only"/> 这个缓存策略的配置一定要加上,否则便不会有缓存的作用,Load/list/iterator等操作的结果将都不会缓存。 注意,在hbm的class配置中添加<cache>配置,表示的是类缓存,如果把这个配置删除,将只缓存ID,不缓存整个对象。(这个时候对list操作,也可能有n+1查询问题) 当然,也可以选择在<sessionFactory>标签里面嵌套定义这样的标签: <class-cache class="com.bjsxt.hibernate.User2" usage="read-only" /> 来代替直接将<cache>定义嵌套入<class>标签的内部。 缓存策略 缓存有几种形式,可以在映射文件中配置:read-only(只读,适用于很少变更的静态数据/历史数据),nonstrict-read-write(不严格读写缓存,如果基本不会发生有两个事务同时修改一个数据的时候,比read-write的性能要好),read-write(比较普遍的形式,效率一般),transactional(JTA中,且支持的缓存产品较少)
二级缓存(3) Session如何与二级缓存交互? Session接口通过CacheMode来定制与二级缓存之间的交互方法
查询缓存 默认情况下关闭,需要打开。查询缓存,对list/iterator这样的操作会起作用。。。。 可以使用: <property name="hibernate.cache.use_query_cache">true</property> 来打开查询缓存,默认的情况下是关闭的。 所谓查询缓存,即让hibernate缓存list、iterator、createQuery等方法的查询结果集。如果没有打开查询缓存,hibernate将只缓存load方法获得的单个持久化对象。 在打开了查询缓存之后,需要注意,调用query.list()操作之前,必须显式调用query.setCachable(true)来标识某个查询使用缓存。
单端代理的批量抓取 实例A引用实例B,B如果是代理的话(比如多对一关联中): 如果遍历A的查询结果集(假设有10条记录),在遍历A的时候,访问B变量,将会导致n次查询语句的发出!这个时候,如果在B一端的class上配置batch-size,Hibernate将会减少SQL语句的数量。 Hibernate可以充分有效的使用批量抓取,也就是说,如果仅一个访问代理(或集合),那么Hibernate将不载入其他未实例化的代理。 批量抓取是延迟查询抓取的优化方案,你可以在两种批量抓取方案之间进行选择:在类级别和集合级别。 类/实体级别的批量抓取很容易理解。假设你在运行时将需要面对下面的问题:你在一个Session中载入了25个 Cat实例,每个Cat实例都拥有一个引用成员owner, 其指向Person,而Person类是代理,同时lazy="true"。 如果你必须遍历整个cats集合,对每个元素调用getOwner()方法,Hibernate将会默认的执行25次SELECT查询, 得到其owner的代理对象。这时,你可以通过在映射文件的Person属性,显式声明batch-size,改变其行为: <class name="Person" batch-size="10">...</class>随之,Hibernate将只需要执行三次查询,分别为10、10、 5。
集合代理的批量抓取 你也可以在集合级别定义批量抓取。例如,如果每个Person都拥有一个延迟载入的Cats集合, 现在,Sesssion中载入了10个person对象,遍历person集合将会引起10次SELECT查询, 每次查询都会调用getCats()方法。如果你在Person的映射定义部分,允许对cats批量抓取, 那么,Hibernate将可以预先抓取整个集合。请看例子: <class name="Person"> <set name="cats" batch-size="3"> ... </set></class> 如果整个的batch-size是3,那么Hibernate将会分四次执行SELECT查询, 按照3、3、3、1的大小分别载入数据。
批量更新 Jdbc fetch size: 每次取多少条数据,需要JDBC和底层数据库的支持。不会一次性把全部数据读入内存,而是按照一定的数量来批量读取相应的数据。 Fetch size建议值是50 hibernate.jdbc.fetch_size Jdbc batch size 批量更新 建议取值30 hibernate.jdbc.batch_size
Hibernate最佳实践(Best Practices) 1、使用Configuration装载映射文件时,不要使用绝对路径装载。最好的方式是通过getResourceAsStream()装载映射文件,这样Hibernate会从classpath中寻找已配置的映射文件。 2、SessionFactory的创建非常消耗资源,整个应用一般只要一个SessionFactory就够了,只有多个数据库的时候才会使用多个SessionFactory。 3、在整个应用中,Session和事务应该能够统一管理。(Spring为Hibernate提供了非常好的支持) 4、将所有的集合属性配置设置为懒加载(lazy=”true”)。在hibernate2.x版本中,lazy默认值是“false”,但hibernate3.x已经将lazy的默认改为“true”了。
Hibernate最佳实践(Best Practices) 5、在定义关联关系时,集合首选Set,如果集合中的实体存在重复,则选择List(在定义配置文件时,可以将List定义为bag),数组的性能最差。 6、在一对多的双向关联中,一般将集合的inverse属性设置为true,让集合的对方维护关联关系。例如:Group-User,由User来维护Group和User的关联关系。 7、HQL子句本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。 8、在非分布式架构中,不需要使用DTO来向上层传输数据。直接使用POJO的Entity就可以了。 9、如果要精通Hibernate,熟练掌握关系数据库理论和SQL是前提条件。
Hibernate资源 官方网站:http://www.hibernate.org 国内网站:http://www.hibernate.org.cn Java新视线论坛:http://forum.hibernate.org 《Hibernate 中文开发指南》作者夏昕(http://www.redsaga.com/) 《深入浅出Hibernate》作者:夏昕 曹晓钢 唐勇 (http://www.china-pub.com/computers/common/info.asp?id=24500) 《Hibernate in Action》作者:Christian Bauer and Gavin King(http://www.javafan.net/可下载) 《Hibernate: A Developer's Notebook》作者:James Elliott
Spring 轻量级容器架构
Spring最常用的特性 利用Spring来创建对象(JavaBean工厂) 利用Spring构建业务逻辑层 管理依赖关系 适应需求变更 利用Spring创建数据访问对象(DAO) 利用Spring进行事务处理
Spring的安装 下载并解压 http://www.springframework.org/ 将相应的jar包加入类路径 ApplicationContext.xml 体验Spring的最基本特性-BeanFactory
创建Sample类及其Spring配置文件 Sample.java public class Sample { public int compute(int i,int j){ return i + j; } ApplicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id=“sample” class=“com.bjsxt.spring.Sample”/> </beans>
创建测试程序ClientTest.java public class ClientTest { /** * @param args */ public static void main(String[] args) { //获取BeanFactory BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器中获取Sample对象 Sample sample = (Sample)beanFactory.getBean("sample"); //调用Sample对象的方法 int result = sample.compute(3,4); System.out.println(result); }
重要的设计原则:针对接口编程,而不要针对实现编程 针对接口编程的目的:降低耦合度,增强应用程序的稳定性 让Sample类变成接口 针对接口编程的原则 重要的设计原则:针对接口编程,而不要针对实现编程 思考:compute方法功能需求变更 ? 针对接口编程的目的:降低耦合度,增强应用程序的稳定性 让Sample类变成接口
Sample接口及其实现 只需改动ApplicationContext.xml的配置,即可实现实际功能的切换 接口定义 public interface Sample{ public int compute(int i,int j); } 接口的实现一 public class SampleImpl1 implements Sample { public int compute(int i,int j){ return i + j; 接口的实现二 public class SampleImpl2 implements Sample { public int compute(int i,int j){ return i * j; } ClientTest.java无需改动 只需改动ApplicationContext.xml的配置,即可实现实际功能的切换
第二个例子 我们已经确定,基于接口编程,是OOD中的重要原则 一个层次的实现代码,不应该依赖于另外一个层次的实现代码 下面是第二个例子:BookLister,这个例子包括如下几个类: 实体类:Book,有名称、作者等属性 BookFinder接口,定义了findAll()方法 BookLister接口,定义了findBooks(String name)方法,以书名作为参数,并返回Book[]数组作为查找的结果 以及一个测试客户端BookClient,调用BookLister,来获取所需要的数据
Book.java public class Book { private String name; private String author; /** * @return Returns the author. */ public String getAuthor() { return author; } * @param author The author to set. public void setAuthor(String author) { this.author = author; //other getters/setters…
BookFinder BookFinder接口 public interface BookFinder { public List findAll(); } SimpleBookFinderImpl public class SimpleBookFinderImpl implements BookFinder{ /** * @see com.bjsxt.spring.BookFinder#findAll() */ public List findAll() { List books = new ArrayList(); for(int i=0; i<20; i++){ Book book = new Book(); book.setName("book"+i); book.setAuthor("author"+i); books.add(book); } return books;
BookLister BookList接口 public interface BookLister{ public Book[] findBooks(String name); } BookListerImpl实现代码 public class BookListerImpl implements BookLister { BookFinder finder; public BookListerImpl() { finder = new SimpleBookFinderImpl(); } public Book[] findBooks(String name){ List books = finder.findAll(); for (Iterator iter = books.iterator(); iter.hasNext();) { Book book = (Book) iter.next(); if(!book.getName().equals(name)){ iter.remove(); return (Book[])books.toArray(new Book[books.size()]);
Spring配置:ApplicationContext.xml及客户调用代码 <bean id="bookLister" class="com.bjsxt.spring.BookListerImpl“/> 客户调用代码 public static void main(String[] args) { BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml"); BookLister bl = (BookLister)beanFactory.getBean("bookLister"); Book[] books = bl.findBooks("book3"); if(books != null){ for(int i=0; i<books.length; i++){ System.out.println("书《"+books[i].getName()+"》的作者是:"+books[i].getAuthor()); }
同样的问题:需求变更? 现在,我们需要的不再是一个简单的BookFinder,我们需要的是一个能从本地文件系统中读取文件,并分析其中所包含的Book的内容,将其结果返回 因为我们遵守了面向接口编程,所以,我们只需要提供第二个实现即可
FileBookFinderImpl – 从文件系统读取Book的信息 public class FileBookFinderImpl implements BookFinder { public List findAll() { String file = “d:/test.txt”; List books = new ArrayList(); try { BufferedReader in = new BufferedReader(new FileReader(file)); String line = null; while( (line = in.readLine()) != null) { String[] sp = line.split(";"); if(sp.length == 2){ … Book book = new Book(); book.setName(sp[0]); book.setAuthor(sp[1]); books.add(book); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { return books;
现在需要做什么? 我们的客户代码调用BookLister BookLister如何调用BookFinder?下面是以前初始化BookFinder的代码: public BookListerImpl() { finder = new SimpleBookFinderImpl(); } 现在,我们必须改动这段代码: finder = new FileBookFinderImpl(); 看出问题来了吗?
严重违反面向对象的设计原则 BookLister接口的实现类严重依赖于BookFinder接口的实现类,这就是问题所在! 我们需实现以下目标: BookLister的实现类BookListerImpl不应该依赖于特定的BookFinder接口的实现类(比如SimpleBookFinderImpl或FileBookFinderImpl) BookLister的实现类,应该仅仅依赖于BookFinder接口,它不应该跟具体的功能需求实现相关(如,是否是从文件读取Book的信息) 总之,我们不应该让BookLister和BookFinder的实现类之间互相耦合!
Spring让一切变得轻而易举 在BookListerImpl中定义一个setter方法 public void setFinder(BookFinder finder) { this.finder = finder; } 把BookListerImpl构造器中的new语句注释掉 public BookListerImpl() { //finder = new FileBookFinderImpl(); 让Spring去关心那些烦人的依赖关系吧!
Spring配置文件:ApplicationContext.xml 添加BookFinder定义 <bean id="fileBookFinder" class="com.bjsxt.spring.impl.FileBookFinderImpl“/> 修改BookLister定义的配置 <bean id="bookLister" class="com.bjsxt.spring.BookListerImpl"> <property name="finder" ref=“fileBookFinder" /> </bean> 以上配置,即指定了BookListerImpl和FileBookFinderImpl之间的依赖关系,这种依赖关系,被放到了配置文件中,这样,只要需求有变更,只需要修改这种依赖关系即可,java代码本身不用任何变更
热门词汇:IOC/DI 控制反转(Inversion of Control,IoC)与依赖注入(Dependency Injection) 由容器来管理对象之间的依赖关系(而不是对象本身来管理),就叫“控制反转”或“依赖注入” 以上代码,已清楚阐述IOC/DI出现的原因,以及IOC的基本原理 Spring框架的基本思想就是IOC/DI Spring就是一个IOC容器 IOC与DI,说的是一回事,但DI这个名词更能表达这种设计模式的思想
依赖注入的类型 构造器注入 通过类的构造方法注入依赖关系 设值方法注入 通过类的setter方法注入依赖关系 第三种:接口注入(不常用) 定义一个注入接口,在需要注入的类中实现此接口,由于这种方法具有侵入性,所以不常用
Spring的依赖注入 Spring是一个Java Bean容器,它维护着一系列Java Bean的示例
更多的例子 我们可以通过构造器来将实现依赖进行注入 示例 我们可以通过setter方法,实现类的配置 将易于变动的部分转移到配置文件中 示例:给FileBookListerImpl加上path和name的配置
通过setter方法注入依赖(属性) 类代码示例 Spring配置示例
Spring架构
DAO设计模式 什么是DAO? Data Access Object(数据访问对象) 最重要的核心J2EE模式之一 为什么需要DAO? 屏蔽业务逻辑对数据持久化的依赖 一定需要DAO吗? 如果业务逻辑比较简单(比如只有简单的数据存取操作),完全可以取消DAO 是否需要DAO,完全取决于业务需求,没有固定的说法
Spring与Hibernate的集成 从例子入手 安装Hibernate 创建User实体类 创建User映射文件 创建DAO对象 创建Manager对象 SessionFactory配置 声明式事务配置 DAO定义 Manager定义
创建可以支持Hibernate的DAO对象 HibernateDaoSupport 这是Spring提供的集成Hibernate的基类,所有的DAO,均需要继承它 从这个类中,可以获取Hibernate的各种核心接口,如Session、SessionFactory等 HibernateTemplate HibernateTemplate是Spring封装的Hibernate操作接口,类似于Session接口 可以调用HibernateDaoSupport提供的getHibernateTemplate()方法获取HibernateTemplate对象
创建Manager对象 Manager对象没有任何特殊之处,它就是普通的接口和实现类
在Spring中声明SessionFactory 一种简单的配置方式,即指定Hibernate配置文件的路径所在即可 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation"><value>classpath:hibernate.cfg.xml</value> </property> </bean>
在Spring中配置事务管理 大多数的应用程序,事务管理被分配到业务逻辑方法上,即每个业务逻辑方法是一个事务 在Spring中,所有的业务逻辑对象,均是普通的POJO Spring最强大的功能在于,它可以在普通的POJO上面实现声明式的事务管理(它使用AOP来完成这样的任务) 可插入的事务策略 步骤如下: 定义一个事务管理器 定义一个事务代理基类
事务管理器的定义 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory"><ref local="sessionFactory"/></property> </bean>
事务管理的AOP配置 <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> <!-- AOP配置 --> <aop:config> <aop:pointcut id="managersMethod" expression="execution(* com.bjsxt.spring.manager.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="managersMethod"/> </aop:config>
正确的配置DAO Dao的配置示例 <bean id="baseDao" class="com.bjsxt.dao.BaseDaoImpl"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> 必须注入sessionFactory的定义
简单配置Manager <bean id="userManager" class="com.bjsxt.spring.manager.UserManagerImpl" > <property name=“baseDao” ref=“baseDao”/> </bean>
好了,让我们来测试一下 编写测试代码 测试事务特性
Spring与Struts的集成 修改web.xml 让Tomcat自动加载Spring,所以需要增加下面的配置 <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext-*.xml,classpath*:applicationContext-*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
修改Struts的Action类 我们在原来的Action配置中,type属性,直接指向我们的Action类(即Action类的全路径) 现在我们需要将这些Action类的创建,交给Spring去完成 所以,我们需要修改两个地方: Action配置中type属性,由原来的Action类,改为:org.springframework.web.struts.DelegatingActionProxy 在Spring配置文件中,添加对Action类的定义,如: <bean name="/trade" class="com.bjsxt.ccs.web.action.TradeAction" singleton="false" > <property name="tradeManager" ref="tradeManager"></property> <property name="userManager" ref="userManager"></property> </bean>
建立测试J2EE项目 建立一个web项目 在web项目中安装Struts 安装Spring 配置web.xml 配置Action
Struts+Hibernate+Spring Struts与Spring的集成 Struts+Hibernate+Spring的集成 没有更多的东西,前面已经分别将它们集成起来了 注意使用OpenSessionInView模式,这是使用Hibernate的web项目最需要注意的一个问题,Spring为此提供了专门的解决方案
OpenSessionInViewFilter 这是为了避免Hibernate的懒加载异常而创建的解决方案 它是一个过滤器,能够让Session在请求解释完成之后再关闭(所以才能够避免懒加载异常) 配置方式是,在web.xml中定义过滤器即可: <filter> <filter-name>hibernateFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <url-pattern>/*</url-pattern> </filter-mapping>
CharacterEncodingFilter 如何提交中文字符? 配置web.xml了,Spring提供了专门的针对Encoding的过滤器 配置方法如下: <filter> <filter-name>Spring character encoding filter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>GBK</param-value> </init-param> </filter> <filter-mapping> <url-pattern>/*</url-pattern> </filter-mapping>
更多的资料 Spring官方网站 http://www.springframework.org Spring中文网 http://spring.jactiongroup.net (有中文参考手册下载)