java学习笔记————SSH

时间:2023-02-24 19:22:21
==========================================Struts2 ===========================================================================================================================1.Struts 2是一个MVC框架,以WebWork框架的设计思想为核心,吸收了Struts 1的部分优点// spring :容器————对象的构建 hibernate :Dao层struts2 = javaBean + jsp + struts.xml(servlet) M V C1.servlet和Filter:// Filter参数: request + response + FilterChain =====》 void doFilter(ServletRequest request, ServletResponse response,FilterChain chain)// 用 Filter 替代 servlet 的功能filter可用来进行字符编码的过滤,检测用户是否登陆的过滤,禁止页面缓存等.servlet能做到的,Filter一定能做到! Filter能做到的,Servlet不一定能做到! Filter比Servlet多了一个能否放行(过滤)的功能!Filter可过滤所有资源 之前拦截interceptorstruts2框架 仅针对action之前 + 之后(均可)2.配置核心过滤器 : 在web.xml中配置.3.Strust2核心功能:允许POJO(Plain Old Java Objects)对象 作为ActionAction的execute 方法不再与Servlet API耦合,更易测试支持更多视图技术(JSP、FreeMarker、Velocity)基于Spring AOP思想的拦截器机制,更易扩展更强大、更易用输入校验功能4.struts.xml 配置文件 :name 、 class 、 method 、 result <interceptors> <package name="default" extends="struts-default">i18n ... ****5.作用:指挥控制器的运作:C6.默认需要在应用的classpath中建立一个struts.xml的配置文件2.struts2的开发步骤.要点(图解理解): 1、创建流程:创建动作action、创建视图jsp、配置文件struts.xml 2、执行流程:看图一条线(生活案例的理解)请求action————>核心过滤器————判断————>执行哪个action,的哪个方法,return 到哪个jsp , ===>在struts.xml中配置 3、方法的要求public String 方法名(){} 3.配置文件没有提示问题:struts2-core-2.3.15.3.jar 中找到 struts-2.3.dtd 中,配置 // dtd ———— URI xsd ————— Schema location (最好新建一个java项目,全部放到其中) // 在 XML Catalog中配置4.具体执行过程:从两个角度考虑:1、启动服务器://tomcat容器加载项目————>加载web.xml————>实例化(web.xml中的配置类 eg;核心过滤器类)————>初始化(web.xml中的配置类的初始化参数...)————>在初始化过滤器时,加载了struts.xml文件2、执行请求:http://localhost:8080/zjs_bos/userAction_login.action ——————> 通过核心过滤器 获取到请求动作: userAction_login.action  // (请求信息封装到了核心过滤器中,由它负责 过滤 + 请求 + 响应)——————> 在已加载的struts.xml中找到 <action name="userAction_*"> ——————> 找到 class="cn.it.bos.web.action.UserAction"(spring容器中略写为:class="userAction"),并实例化UserAction类——————> 调用方法 : login() (找到 method = "login")——————> 返回值 return = "home"——————> 找到结果集配置: <result name="home">/WEB-INF/pages/common/index.jsp</result>——————> 跳转到结果视图: /WEB-INF/pages/common/index.jsp页面5.Struts2的配置文件简介:1.1)在核心控制器初始化的时候加载的// web.xml 初始化的时候2)加载是有顺序的,后面覆盖前面的 // default.properties > struts-default.xml > struts.xml 查看源码:StrutsPrepareAndExecuteFilter 核心类中的 init(FilterConfig filterConfig)方法中 ————调用了初始化方法—————> init.initDispatcher(config);————查看————> initDispatcher( HostConfig filterConfig )方法, 执行了初始化方法 dispatcher.init(); ————查看————> init() , 初始化顺序如下:init_FileManager();init_DefaultProperties(); // [1]——————> org/apache/struts2/default.propertiesinit_TraditionalXmlConfigurations(); // [2] ——————> DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";init_LegacyStrutsProperties(); // [3]init_CustomConfigurationProviders(); // [5]init_FilterInitParameters() ; // [6]init_AliasStandardObjects() ; // [7] 2.default.properties , struts-default.xml , ( struts-plugin.xml ), struts.xml 的三个配置文件: 配置文件的名称位置存储的内容说明default.propertiesstruts2-core-2.3.15.3.jar\org\apache\struts2\default.properties参数配置程序员不能修改struts-default.xmlstruts2-core-2.3.15.3.jar\struts2-default.xmlStruts2核心配置 程序员不能修改struts.xml在 classpath 中用户配置程序员使用// ,以上配置文件在应用被启动的时候就完成了加载;而且有加载的顺序要求。后面的配置会覆盖掉前面配置文件中相同的信息。2.1.default.properties1、参数的含义// 做了很多 初始化过滤 设置eg:过滤 URL后缀 、 编码方式 、 国际化 、 开发者模式...2、如何修改参数// (struts.xml中修改) 后加载的覆盖先加载的参数名称默认值说明struts.i18n.encodingUTF-8编码方式struts.action.extensionaction,,进入框架核心的url的后缀名 // xxx.action 或者 无后缀名 ———过滤———>进入到struts的核心内部其他后缀,放行!!!// 无论什么 请求 都要进入 核心过滤器类, 经过过滤器过滤 , 根据后缀名 判断 是否放行!!! eg: .jsp / .html ...struts.devMode false开发阶段设置为true // <constant name="struts.devMode" value="true">struts.custom.i18n.resources关闭国际化 //注册国际化:<constant name="struts.custom.i18n.resources" value="message(.properties可略)">struts.serve.static.browserCachetrue告诉客户端要不要缓存静态的资源struts.configuration.xml.reload 被注释掉了:建议true 每当xml配置文件被修改后,不需要重新加载应用就能够使配置生效。适合开发阶段3.如何修改他们的默认值:struts.xml:通过该文件来进行修改 (覆盖)2.2.struts-default.xml简介<struts>1、<bean>2、<package>//给定包名name <package name="struts-default" abstract="true">abstract:抽象包T/F 只能被继承,不能直接使用1)结果类型 // 应用在<result type="xxx"> 中// stream:文件上传和下载chain:转发到一个动作———> 配置两个参数:setNamespace() 和 setActionName()<result-types><result-type name="" class="">// 结果类型<result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"> // 指定 dispatcher 为默认结果类型...</result-type></result-type></result-types>2)拦截器、拦截器栈(放入很多拦截器)****C<interceptors>***A 拦截器: <interceptor name="" class=""> // 拦截器:定义拦截器 给定 name 和 class拦截器都放入拦截器栈中!!!... // n个拦截器  ***B 拦截器栈:<interceptor-stack name="basicStack">// 拦截器栈: 给拦截器栈 声明一个 name // 拦截器栈 就是 一个拦截器集合 , 里面通过 拦截器name 引用了很多 拦截器// 既然 拦截器栈里面包含的就是多个拦截器 , 那么 拦截器栈 也可以 直接引用拦截器栈 ——————> 即:直接引用 拦截器集合 *A<interceptor-ref name="exception"> // 拦截器引用: 拦截器栈 中 全部是引入的上面定义的 拦截器(栈)———> 根据拦截器(栈)name 引入 *A/B... // n个拦截器(栈)引用// <basicstack> 和<defaultstack>两个拦截器栈 有被其他 拦截器栈引用</defaultstack></basicstack></interceptor-ref></interceptor-stack>... // n个拦截器栈 (每个栈里引用了 n个拦截器/栈)</interceptor></interceptors>3)默认引用<default-interceptor-ref name="defaultStack">// 默认 拦截器栈 的引用<default-class-ref class="com.opensymphony.xwork2.ActionSupport">// 默认 类 的引用</default-class-ref></default-interceptor-ref></result></package></package></bean></struts>2.3.配置文件package元素:// 作用:希望程序员能够 按照 分包的思想 , 管理你的动作配置// 子包能够继承父包中的所有配置。自己写的包一般情况下要继承一个name=struts-default的包(它在struts-default.xml中)1.Package:name 包的名称唯一 extends:extends="struts-default"// package 继承的是 内置配置文件 struts-default.xml 中的<package name="struts-default" abstract="true"> 不要搞错了!abstract="true"抽象包概念:没有任何action的子元素的包就是抽象包// 包中不存在 <action> 即为抽象包  功能:这个包 不能直接使用,只能被继承   namespace:  命名空间 默认值是 ""// 访问路径:命名空间的名称+动作的名称 方便管理// 注意:There is no Action mapped for namespace [/] and action name [helloAction] /**2.namespace的搜索顺序例如:user/a/b 总结:1) 先找命名空间,再找action名称 2) 当前包找不到,向前找,最后找/,都找不到报错 案例:http://localhost:8088/struts2_04_namespace/user/a/b/helloAction http://localhost:8088/struts2_04_namespace/user/a1/b/helloAction http://localhost:8088/struts2_04_namespace/user1/a1/b/helloAction 3)如果包存在,找action,如果这个包中没有action到默认的包中找action(注意:默认包是namespace=””) 找不到报错namespace=””和namespace=”/”不同案例:localhost:8088/struts2_04_namespace/user/helloAction1*/6.动作类Action的编写和配置:1、实现action的三种方式/**方式一、动作类继承com.opensymphony.xwork2.ActionSupport(推荐)方式二、动作类 实现 com.opensymphony.xwork2.Action接口方法返回值的常量,返回值也可以自己定义 SUCCESS 一切成功 NONE/null 没有任何跳转 (eg:区域设置中的 导入 、 ajax请求响应回:json数据 、xml数据 、 自定义状态码...) INPUT 回显 LOGIN 用户登录 ERROR 报错public String execute() throws Exception; 动作方法 方式三、动作类就是POJO(Plain Old Java Object最为普通的JavaBean)。*/2、动作类Action的配置 (和Servlet对比) // <action>必须出现<package>元素内部 name: 动作名称,就是url ——————对应servlet名 class:动作类的全路径没有定义,默认到struts-default.xml中找<default-class-ref class="com.opensymphony.xwork2.ActionSupport"> method:执行动作类中的方法默认的是public String execute(); 3、生命周期: Action在用户访问action的时候创建的,是多例的(每次访问都会实例化) // Action ———————— 多例的!!!诞生:每次用户访问时。活着:动作没有响应结束。死亡:响应结束。多例的;没有线程安全问题。(与struts1所不同的)。因为动作类是一个非常普通的Java类,创建它需要消耗的资源可以省略不计。 3、动作的访问/**通配符的使用 * 案例:增删改查案例1)一个*号<action name="*" class="cn.it.action.UserAction" method="{1}"><result name="success">/{1}.jsp</result></action>http://localhost:8088/struts2_01/addUser* 通配符 代表addUser{1} 第一个*代表的值案例2)两个*http://localhost:8088/struts2_01/add_User<action name="*_*" class="cn.it.action.{2}Action" method="{1}"><result name="success">/{1}{2}.jsp</result></action>案例3)DMI:Dynamic Method Invocation:Struts2框架默认不允许动态方法调用的,不安全。覆盖一个常量:struts.enable.DynamicMethodInvocation=true才能使用!的使用 ——————此时不再是使用method去调用action的方法http://localhost:8088/struts2_01!add_User1)配置文件<constant name="struts.enable.DynamicMethodInvocation" value="true">// 注册开启 使用!的方式 调用action中的方法2)不安全、*/4、访问动作前的拦截器// 动作执行前有很多拦截器在工作。1、Struts中有很多功能在拦截器中实现的2、Action执行之前 ,先要执行拦截器3、<default-interceptor-ref name="defaultStack">5、结果视图配置<result name="success" type="redirectAction"> name:字符串,逻辑视图名称,默认值Success 一、结果类型dispatcher 转发到一个jsp视图// 转发 到 :jspchain 转发到一个动作Action//Actionredirect 重定向到一个jsp视图// 重定向 到 : jspredirectAction 重定向到一个动作Action// Action 二、两个动作不在一个包中6、结果的配置struts.xml:<result>元素配置:配置结果视图。属性:name:字符串。逻辑视图。对应当前动作的动作方法的返回值。默认值就是success字符串。type:结果视图的类型 默认值是 dispatcher请求转发到一个jsp页面。// <result-type name="dispatcher" class="...dispatcher.ServletDispatcherResult" default="true">struts2中的结果类型:struts-default.xml中做出的定义。7、局部逻辑视图和全局逻辑视图局部逻辑视图:定义在<action>元素内部,为当前动作服务的。全局逻辑视图:在某个动作中找不到对应的逻辑视图,框架会从全局视图中找。 7、配置多个struts配置文件// 使用多个struts配置文件,方便模块开发。// 在struts.xml中:使用 <include file="">9、动作类中获取Servlet的API // Struts2的优点:与Servlet API进行了解耦第一种方式:HttpServletRequest request = ServletActionContext.getRequest();// HttpSession session = request.getSession();HttpServletResponse response = ServletActionContext.getResponse();ServletContext servletContext = ServletActionContext.getServletContext(); // servletContext.getRealPath("");得到绝对路径第二种方式: // DemoAction extends ActionSupport implements ServletRequestAware , ServletResponseAware, ServletContextAware{ 构造...注入...}原理: // 由一个叫做servletConfig的拦截器给你注入进来的。 <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"> 10.封装请求参数:// 非常重要依靠struts2的拦截器完成参数封装:params(封装动态参数)、staticParams(静态参数封装)modelDrivern(模型驱动)1、封装静态参数运行期间一般不会发生变化的数据;通过配置文件设置的。 是由一个叫做 staticParams拦截器 负责注入的参数。2、封装动态参数运行期间由用户输入的数据;通过表单输入的。2.1.动作类充当模型对象// 由一个叫做 params的拦截器 完成的。 使用:模型对象xxx中没有的字段 , 我们一般会在 xxxAction中 声明该字段 , 并提供 set 方法 来获取到页面上 该字段的值!!!//同2.2.属性注入(动作类和模型对象分开):推荐// 根据 name="user.username" ——————> set数据到 user对象的username属性中细粒度开发:设计越精细,可重用性就越高。表单:name="user.usernmae" name="模型对象.属性"name="user.password"动作类:不需要提供 模型对象模型:2.3.模型驱动(动作类和模型对象分开):推荐 // 模型驱动是一个由叫做 modelDrivern的拦截器 负责的。表单:name="usernmae" name="属性"name="password"动作类:BaseAction<t> extends ActionSupport implements ModelDriven<t>1.实现 ModelDriven 接口2.定义模型对象的引用,一定要实例化 // 作用: 声明的 模型对象的 引用model(代理对象) , ModelDriven拦截器 会把参数自动注入到 model对象中 , 3.实现接口的getModel()// 而 model = User(真实的模型类)public T getModel() {return model;}12.类型转换:1、认知:用户的输入都是String类型;而JavaBean中的属性有可能是其他类型的。 ——————————类型转换读操作:查询; 其他类型---------------> java.lang.String写操作:添加、修改、删除; java.lang.String---------------> 其他类型2、Struts2的类型转换使用频率较高:java.lang.String<----------------->java.util.Date互转struts2默认使用本地(操作系统)默认的日期格式进行自动转换。yyyy-MM-dd ,默认使用该格式进行转换。// 默认支持 yyyy-MM-dd格式 , 其他格式,需手动提供转换方法 // 当然,你也可以修改 本地操作系统的日期格式. 额..这../**3、自定义类型转换器// 实际开发基本上 用不到自定义 类型转换器MM/dd/yyyy (String)<------------------>java.util.Date 互转a、自定义类型转换器 编写一个类, 继承StrutsTypeConverter, 实现两个抽象方法// MyDateConverter extends StrutsTypeConverter {convertFromString()convertToString() } b、注册类型转换器方式一:局部类型转换器服务于当前的动作类;按照属性进行注册的形式;// 模型类的某个属性在属性所在类的包中,建立名称为 “类名-conversion.properties” 的文件// User-conversion.properties——————>birthday=cn.it.struts.converter.MyDateConverter// 放在 cn.it.struts.domain 包下 ——————> 和 模型类User 放在一起 局部 仅UserAction可用方式二:全局类型转换器服务于整个所有动作类;按照类型进行注册的形式;// 按照java类中的 Type类型在构建路径中,建立一个固定名称为 “xwork-conversion.properties” 的文件// 固定名称: xwork-conversion.properties ————————>java.util.Date=cn.it.struts.converter.MyDateConverter// 放在 构建路径下: src下面———————> 全局所有Action可用c、转换出错时的问题解决a、类型转换失败后,会自动转向一个 name="input" 的逻辑视图b、显示错误消息<s:fielderror>c、把错误提示改为中文的// 由一个叫做 conversionError的拦截器 完成的在Action所在的包中,建立名称为 “类名xxxAction.properties” 的文件// UserAction.properties ———————> invalid.fieldvalue.birthday=\u751F\u65E5\u683C\u5F0F\u5FC5\u987B\u4E3Ayyyy-MM-dd// 放在 cn.it.struts.action 包下 ————————> 和 UserAction放在一起 数据回显使用 OGNL / EL*/ 4、总结Struts2的类型转换器 boolean Boolean charCharacter int Integer longLong floatFloat doubleDouble Dateyyyy-MM-dd(支持) 数组可将多个同名参数,转换到数组中 集合支持将数据保存到 List 或者 Map集合 13.用户输入验证1、概述// 实际开发:客户端验证+服务器端验证方式一:客户端验证。使用JavaScript// eg:EasyUI的 $(this).form('validate'); ...方式二:服务器端验证。使用服务器的java代码 // ..2、struts2中的服务器端验证// 前提:extends ActionSupport前提:动作类要继承ActionSupport类。2.1编程式验证:// 就是在你的 java代码中 编写验证的逻辑 1—核心API:addFieldError() // 从ActionSupport 中继承过来的 APIa、针对动作类中的所有动作方法进行验证 // 所有动作类继承ActionSupport,覆盖 public void validate(){addFieldError(fieldName, errorMessage)// 验证失败,封装信息到一个Map("错误字段","错误信息");} // 如果Map中没有任何元素,说明验证通过. 否则,不通过. b、针对动作类中的某个/些动作方法进行验证 // 部分1.不需要验证的方法数量少:在不需要验证的方法上面使用@SkipValidation注解2.需要验证的方法数量少:validate方法名()// validateRegist() 2.2声明式验证 // 通过 配置文件 制定验证规则——————全局: UserAction-validation.xml局部: UserAction-xxx-validation.xml --->仅对xxx方法验证 eg: registxml的dtd声明在:xwork-core-2.3.15.3.jar\xwork-validator-1.0.3.dtd// tips:这里的xxx:是<action name="xxx" mehtod="regist"> 不是动作类中方法名// class-name-validation.xml所以: 根据method ——找到对应——>namea、针对动作类中的所有动作方法进行验证// 所有// 约定大于编码!!! 此种写法下:我们100%会保持method和name值一直的!!!在动作类所在的包中,建立一个名称为“动作类名-validation.xml”的配置文件 b、针对动作类中的某个/些动作方法进行验证// 部分1.不需要验证的方法数量少:在不需要验证的方法上面使用@SkipValidation注解2.需要验证的方法数量少: // class-name-validation.xml配置文件的命名:动作类名-动作名-validation.xml // 若仅有一个方法需要验证 ,还可以考虑考虑此方法 其他就没卵用╮(╯_╰)╭动作名:指struts.xml中<action元素的name值,不是动作类中的动作方法的名字。// 有点蛋疼 eg;我想验证 UserAction 中的regist方法 //正常情况下的命名规范 :<action name="userAction_*" class="userAction" method="{1}">... ——————这才是实际应用中//要对add()方法实施验证,校验文件的取名为: UserAction-user_add-validation.xml//要对update()方法实施验证,校验文件的取名为: UserAction-user_update-validation.xml//<action name="regist" class="cn.it.struts.userAction" method="regist">...——————这时我们前面学的 最原始的的方式//此处:name是可以随便写的,并没有要求必须和 method一直, 所有我们要注意了:name值的对应关系为action——name可不是 action——method //...写了这么一大堆,都没啥卵用... 实际根本用不着 ╮(╯_╰)╭格式:// 根元素 ————> 要验证的字段 ————> 内置验证器类型的引用 ————> 传参(当然,部分类型是没有参数的)/**<!-- 指定校验配置文件的DTD信息 --><!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.3//EN""http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd"><!-- 校验文件的根元素 --><validators><!-- 校验Action的name属性 --><field name="name"><!-- 指定name属性必须满足必填规则 --><field-validator type="requiredstring">—————————— type 对应 default.xml 内置验证器配置文件中的 <validator name="requiredstring" class="..."><param name="trim" />true<message>必须输入名字</message></validator></field-validator><!-- 指定name属性必须匹配正则表达式 --><field-validator type="regex"><param name="expression" /><![CDATA[(\w{4,25})]]><message>您输入的用户名只能是字母和数字,且长度必须在4到25之间</message></field-validator></field><!-- 校验Action的pass属性 --><field name="pass"><!-- 指定pass属性必须满足必填规则 --><field-validator type="requiredstring"><param name="trim" />true<message>必须输入密码</message></field-validator><!-- 指定pass属性必须满足匹配指定的正则表达式 --><field-validator type="regex"><param name="expression" /><![CDATA[(\w{4,25})]]><message>您输入的密码只能是字母和数字,且长度必须在4到25之间</message></field-validator></field><!-- 指定age属性必须在指定范围内--><field name="age"><field-validator type="int"><param name="min" />1<param name="max" />150<message>年纪必须在1到150之间</message></field-validator></field><!-- 指定birth属性必须在指定范围内--><field name="birth"><field-validator type="date"><!-- 下面指定日期字符串时,必须使用本Locale的日期格式 --><param name="min" />1900-01-01<param name="max" />2050-02-21<message>生日必须在${min}到${max}之间</message></field-validator></field></validators>*/ 2.3验证失败的处理失败后会自动转向一个name="input"的逻辑视图;//<result name="input">error.jsp</result>错误消息的显示: <s:fielderror>标签2.4struts2提供的一些内置验证器// default.xml1、内置验证器在何处定义的xwork-core-2.3.15.3.jar\com\opensymphony\xwork2\validator\validators\default.xml/***validators : 根目录*validator : 声明验证规则————————表明此标签代表的是:验证规则*name : 验证规则的 唯一名称*class : 验证规则 对应的完整路径*/<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="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"><validator name="expression" class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"><validator name="fieldexpression" class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"><validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"><validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"><validator name="visitor" class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"><validator name="conversion" class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"><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"></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validator></validators> 系统提供的校验器如下:required (必填校验器,要求被校验的属性值不能为null)requiredstring (必填字符串校验器,要求被校验的属性值不能为null,并且长度大于0,默认情况下会对字符串去前后空格)stringlength(字符串长度校验器,要求被校验的属性值必须在指定的范围内,否则校验失败:minLength参数指定最小长度, maxLength参数指定最大长度, trim参数指定校验field之前是否去除字符串前后的空格)regex(正则表达式校验器,检查被校验的属性值是否匹配一个正则表达式,expression参数指定正则表达式,caseSensitive参数指定进行正则表达式匹配时,是否区分大小写,默认值为true)int(整数校验器,要求field的整数值必须在指定范围内,min指定最小值,max指定最大值)double(双精度浮点数校验器,要求field的双精度浮点数必须在指定范围内,min指定最小值,max指定最大值)fieldexpression(字段OGNL表达式校验器,要求field满足一个ognl表达式,expression参数指定ognl表达式,该逻辑表达式基于ValueStack进行求值,返回true时校验通过,否则不通过)email(邮件地址校验器,要求如果被校验的属性值非空,则必须是合法的邮件地址)url(网址校验器,要求如果被校验的属性值非空,则必须是合法的url地址)date(日期校验器,要求field的日期值必须在指定范围内,min指定最小值,max指定最大值)conversion(转换校验器,指定在类型转换失败时,提示的错误信息)visitor(用于校验action中复合类型的属性,它指定一个校验文件用于校验复合类型属性中的属性)expression(OGNL表达式校验器,它是一个非字段校验器, expression参数指定ognl表达式,该逻辑表达式基于ValueStack进行求值,返回true时校验通过,否则不通过,该校验器不可用在字段校验器风格的配置中) 14.struts2中对于国际化的支持// 大前提:动作类必须 继承ActionSupport !!!1、定义消息资源包——————————后面我们就可以在JSP页面或action类中访问国际化信息a、定义全局消息资源包// 项目级别// 在struts.xml中通过 为struts.custom.i18n.resources常量 赋值,把资源文件 定义为 全局资源文件<constant name="struts.custom.i18n.resources" value="message">// message为资源文件的基本名message.properties —————— 后缀略<constant name="struts.custom.i18n.resources" value="cn/it/resources/msg">// msg_en_US.properties—————— 省略_en_US.propertiesb、定义包范围的资源包// 包级别package开头———————— package_zh_CN.properties资源文件的开头,固定位 package本包中的动作类,及子包下的动作类都能访问包范围的消息资源文件。当查找指定key的消息时,系统会先从package资源文件查找,当找不到对应的key时,才会从常量struts.custom.i18n.resources指定的资源文件中寻找。 c、定义局部消息资源包// 类级别xxxAction开头 ———————— Demo1Action_zh_CN.properties在动作类所在的包中,建立固定名称"动作类名_语言代码_国家代码.properties" 2、如何获取资源包中的内容a、在动作类中使用// getText("key") 在Action类中,可以继承ActionSupport 使用其API: getText("key") getText("key")———————— 获取值b、在JSP页面中使用//<s:text name="loginError"> name为 message.properties 中的key loginError=\u7528\u6237\u540D<s:text name="key">———————— 输出显示c、在表单标签中,通过key属性指定资源文件中的key<s:textfield name=".." key="loginError">// 获取值 为表单 name字段 赋值!d、*选择消息资源包——————————直接指定访问 哪个资源文件<s:i18n name="cn/it/action/package">// 直接访问 cn.it.action包下 基本 名为package 的资源文件<s:text name="welcome">// key=welcome<s:param>小张</s:param>// 这里表示 我们可以在 资源包中 用通配符的方式 传参!eg:资源文件中的内容: welcome= {0},欢迎来到{1}<s:param>东京</s:param></s:text></s:i18n>// 在Action类中获取带占位符的国际化信息,可以使用getText(String key, String[] args)或getText(String aTextName, List args)方法。3、资源文件的搜索顺序有小到大 ..... 15.拦截器1、拦截器简介struts2中很多的核心功能都是拦截器完成的比如:params(封装动态参数)、staticParams(静态参数封装)、i18n(国际化)、modelDrivern(模型驱动)、servletConfig(注入servletAPI)等作用:在执行动作 前或后 进行拦截2、struts2的运作全图 结论:动作方法执行前:拦截器会按照顺序依次进行拦截执行动作方法返回结果视图拦截器会按照原来的顺序的相反顺序再次进行拦截执行过程中:ActionInvocation 保存了 需要用到的数据< 自定义拦截器A ><~B> <~C> ...// 放行的过程 —————就是————— 调用下一个方法的过程A———>B———>C———>D———>E ...———>...调用下一个拦截器(放行)...———>xxxAction //Action 执行完 return "success";Action永远都在 拦截器中间执行↓ jsp // 执行完Action 就会根据 结果集 ————>跳转到 jsp视图页面↓ A<———B<———C<———D<———E ...<———...传递返回值: success ... <——— // 然后 将Action的 结果集 继续逐层 往回传递到 第一个拦截器 ————————— 反向回传的过程// 拦截器 反向执行的过程// 拦截器 与 Filter 不同点:doFilter(ServletRequest request, ServletResponse response,FilterChain chain);//核心对象:FilterChainAPI(放行):chain.doFilter(request, response);intercept(ActionInvocation invocation)//核心对象:ActionInvocationAPI(放行):invocation.invoke();service(ServletRequest req, ServletResponse res);3、自定义拦截器熟悉拦截器的体系:所有的拦截器都直接或间接的实现了Interceptor接口interface Interceptor extends Serializable //init();intercept(ActionInvocation invocation)destroy();abstract class AbstractInterceptor implements Interceptor // 实现了 init()/destroy()————>为空方法 ,继承 抽象方法 intercept(ActionInvocation invocation);abstract class MethodFilterInterceptor extends AbstractInterceptor // 实现了 intercept(ActionInvocation invocation);调用了 抽象方法 doIntercept(invocation);// 其 子类 必须重写 doIntercept(invocation);a、编写一个类,继承AbstractInterceptor或实现Interceptor接口b、配置拦截器:声明拦截器// 格式:仿照 struts-default.xml 配置文件来就OK了!// 全局<interceptors>//声明 自定义拦截器<interceptor name="checkLogin" class="cn.it.struts.Interceptors.checkLoginInterceptor">//声明 自定义拦截器栈<interceptor-stack name="myInterceptorStack">// 引用默认拦截器栈<interceptor-ref name="defaultStack">// 引用自定义拦截器<interceptor-ref name="checkLogin"></interceptor-ref></interceptor-ref></interceptor-stack></interceptor></interceptors>c、使用已经声明的拦截器 d、调用过程//见 上2 4、执行动作前检查用户是否登陆的拦截器 5、MethodFilterInterceptor是AbstractInterceptor的子类:setIncludeMethods(String includeMethods) : 设置 需要拦截 的方法,多个方法用逗号分隔setExcludeMethods(String excludeMethods) : 设置 不需要拦截 的方法, 多个方法用逗号分隔 6、配置拦截器的参数:1.方式一:// 在自定义栈中 直接设置同上 3.b、略.... <interceptor-ref name="checkLogin"> <param name="excludeMethods" />login </interceptor-ref>.... 2.方式二:// 在 具体某个action内部 引用过来再设置<action>// 引用自定义拦截器栈<interceptor-ref name="myInterceptorStack">// 给栈中的 某个拦截器 设置参数<param name="checkLogin.excludeMethods" />login</interceptor-ref></action> 7、Interceptor接口:Struts 会依次调用程序员为某个 Action 而注册的每一个拦截器的 interecept 方法.每次调用 interecept 方法时, Struts 会传递一个 ActionInvocation 接口的实例. ActionInvocation: 代表一个给定动作的执行状态, 拦截器可以从该类的对象里获得与该动作相关联的 Action 对象和 Result 对象. 在完成拦截器自己的任务之后, 拦截器将调用 ActionInvocation 对象的 invoke 方法前进到 Action 处理流程的下一个环节. 还可以调用 ActionInvocation 对象的 addPreResultListener 方法给 ActionInvocation 对象 “挂” 上一个或多个 PreResultListener 监听器. 该监听器对象可以在动作执行完毕之后, 开始执行动作结果之前做些事情.AbstractInterceptor 类实现了 Interceptor 接口. 并为 init, destroy 提供了一个空白的实现 16、文件的上传和下载// fileUpload拦截器底层:fileupload组件1、文件的上传1.1文件上传简介前提:表单写法有要求:a、表单的method必须是POST方式提交b、表单的enctype属性必须是multipart/form-datac、表单提供<input type="file" name="name" />类型的输入域struts2中如何实现文件上传的:a、借助一个fileUpload拦截器完成的;b、最底层的还是借助的commons-fileupload这个组件;1.2单文件上传a、编码步骤:准备的表单页面:<form action="${pageContext.request.contextPath}/upload1" method="post" enctype="multipart/form-data">描述:<input type="text" name="name" /><br />// 此字段就是一个普通文本输入框 , 忽略即可文件:<input type="file" name="upload" /><br />// 这才是 文件上传 功能框tips:此处的 name是注入到Action中的 —————— 动态参数注入<input type="submit" value="保存" />// 所以此处 name 必须和 模型对象Action(动作类充当模型对象) 的 File/File[] 的字段名保持一致</form>struts.xml<action name="upload" class="com.it.action.UploadAction"></action>动作类//按照框架要求编写即可public class UploadAction extends ActionSupport {private String name;// 普通文本字段,可不用管它private File upload; //动作类上传的属性必须是File类型 字段名和<input type="file" name="upload" /> 保持一致private String uploadFileName;//上传的文件的文件名。 上传字段+FileName, FileName固定写法private String uploadContentType;//上传文件的MIME类型。 上传字段+ContentType, ContentType固定写法public String execute() throws Exception {System.out.println(name);//封装到upload字段中//服务器建立一个保存文件的目录:WEB-INF/files目录,存放文件的地方ServletContext sc = ServletActionContext.getServletContext();String realStorePath = sc.getRealPath("/WEB-INF/files");File rootDirectory = new File(realStorePath);if(!rootDirectory.exists()){rootDirectory.mkdirs();//如果目录不存在,创建目录}//实现上传:IO流InputStream in = new FileInputStream(upload);OutputStream out = new FileOutputStream(new File(rootDirectory, uploadFileName));int len = -1;byte[] b = new byte[1024];while((len=in.read(b))!=-1){out.write(b, 0, len);}in.close();out.close();return null;}public String getName() {return name;}public void setName(String name) {this.name = name;}public File getUpload() {return upload;}public void setUpload(File upload) {this.upload = upload;}public String getUploadFileName() {return uploadFileName;}public void setUploadFileName(String uploadFileName) {this.uploadFileName = uploadFileName;}public String getUploadContentType() {return uploadContentType;}public void setUploadContentType(String uploadContentType) {this.uploadContentType = uploadContentType;}} b、代码改进1.动作类实现上传部分的代码:// 对 流对接 做了一个简单封装//实现上传:IO流FileUtils.copyFile(upload, new File(rootDirectory, uploadFileName)); //拷贝文件FileUtils.moveFile(upload, new File(rootDirectory, uploadFileName)); //剪切文件return null; 2.文件上传的一些限制:文件的大小(总文件大小);文件的类型等默认值是2M, 在struts.xml 中覆盖原始参数<constant name="struts.multipart.maxSize" value="86170804">并在src 下创建资源文件 : msg_zh_CN.properties// 内容: struts.messages.upload.error.SizeLimitExceededException=\u6587\u4EF6\u6700\u5927\u5141\u8BB8{0},\u60A8\u7684\u6587\u4EF6\u5927\u5C0F{1}/**<struts><constant name="struts.devMode" value="true">// 注册国际化 资源文件 引用自定义文件, 覆盖部分原始参数的值eg:下4.中文提示<constant name="struts.custom.i18n.resources" value="msg">// 覆盖 文件上传大小 原始参数设置<constant name="struts.multipart.maxSize" value="5242880"><package name="p1" extends="struts-default"><action name="upload1" class="com.it.action.Upload1Action"><interceptor-ref name="defaultStack">//允许上传的文件的扩展名,多个扩展名用逗号分隔 ————————————给fileUpload拦截器 传入 文件扩展名参数<param name="fileUpload.allowedExtensions" />.doc,.txt// allowedTypes <param name="fileUpload.allowedTypes" />text/plain,application/msword //允许上传的文件的最大字节数据:此参数设置无效 <param name="fileUpload.maximumSize" />5242880 —————————— 此处设置 文件大小参数 , 无效!!! 只能在上面设置</interceptor-ref><result name="input">/upload1.jsp</result></action></package></constant></constant></constant></struts>*/3.遇到的问题及解决办法:上传时遇到问题,fileUpload拦截器会自动转向一个name=input的逻辑视图// 在<action>中配置一个结果集 <result name="input">uploadError.jsp</result>jsp页面显示原始错误信息:<s:fielderror> 显示原始错误信息// eg:在 uploadError.jsp 页面 上写 <s:fielderror> 展示 原始错误信息!// 一般我们会 采取自定义的 错误提示! 提高用户体验度!真实错误信息,会打印到日志!4.中文提示:// struts-message.propertiesstruts2中的文字提示是基于国际化的。有内置的默认提示信息(struts2-core.jar\org\apache\struts\struts-message.properties)覆盖其中有关文件上传的key的value即可// (注意:使用全局消息资源包的形式) /**具体步骤:// 覆盖步骤第一步:创建新的资源文件 例如 fileuploadmessage.properties, 放置在 src 下在该资源文件中增加如下信息:struts.messages.error.uploading=上传错误: {0}struts.messages.error.file.too.large=上传文件太大: {0} "{1}" "{2}" {3}struts.messages.error.content.type.not.allowed=上传文件的类型不允许: {0} "{1}" "{2}" {3}struts.messages.error.file.extension.not.allowed=上传文件的后缀名不允许: {0} "{1}" "{2}" {3}第二步:在struts.xml文件加载该资源文件 <!-- 配置上传文件的出错信息的资源文件 --> <constant name="struts.custom.i18n.resources" value="“cn….xxx.fileuploadmessage“/">// 此处value 一般要求配置全局的 直接src下 ,写文件名简称即可*//**struts-message.properties 配置如下:struts.messages.error.uploading=Error uploading: {0}struts.messages.error.file.too.large=File too large: {0} "{1}" "{2}" {3}struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}{0}:<input type="“file”" name="“uploadImage”" />中name属性的值{1}:上传文件的真实名称{2}:上传文件保存到临时目录的名称{3}:上传文件的类型(对struts.messages.error.file.too.large是上传文件的大小)*//**5.upload拦截器FileUpload 拦截器负责处理文件的上传操作, 它是默认的 defaultStack 拦截器栈的一员. FileUpload 拦截器有 3 个属性可以设置.maximumSize: 上传文件的最大长度(以字节为单位), 默认值为 2 MBallowedTypes: 允许上传文件的类型, 各类型之间以逗号分隔allowedExtensions: 允许上传文件扩展名, 各扩展名之间以逗号分隔可以在 struts.xml 文件中覆盖这 3 个属性若用户上传的文件大小大于给定的最大长度或其内容类型没有被列在 allowedTypes, allowedExtensions 参数里, 将会显示一条出错消息. 在struts-messages.properties 文件里预定义. (org.apache.struts2包下)可以在文件上传 Action 相对应的资源文件中重新定义错误消息, 但需要在 struts.xml 文件中配置使用 action 的消息: */ 1.3多文件上传// name 保持一致 数组接收——————File[] / String[]表单:提供多个<input type="file" name="保持一致" />类型的上传输入域, name保持一致 /**动作类:// 数组接收private File[] upload;//动作类上传的属性必须是File类型 .List或数组private String[] uploadFileName;//上传的文件的文件名。 上传字段+FileName,FileName固定写法private String[] uploadContentType;//上传文件的MIME类型。 上传字段+ContentType,ContentType固定写法*///实现上传:IO流if( upload!=null && upload.length!=0 ){for(int i=0;i<upload.length;i++){//FileUtils.copyFile(upload[i], new File(rootDirectory, uploadFileName[i]));//拷贝文件FileUtils.moveFile(upload[i], new File(rootDirectory, uploadFileName[i]));//剪切文件}}2、文件的下载// type="stream" —————————— 是一个结果类型:stream,实现文件的下载的下载交给它就OK了,我们提供一个 InputStream 参数即可!!!// 查看 此结果类型的源码 发现 其doExecute()方法, 封装了文件下载功能 只需要我们为其提供 该类中声明的一个 成员变量inputName 的参数值即可 : // protected String inputName = "inputStream";struts2提供了stream结果类型,该结果类型就是专门用于支持文件下载功能的指定stream结果类型 需要指定一个 inputName参数,该参数指定一个输入流,提供被下载文件的入口// 最主要的参数 :InputStreaminputStream输入流对象 // 通过 struts.xml 配置给 stream即可 动作类编写://写法也是固定的public class Download1Action extends ActionSupport {//提供一个InputStream类型的属性private InputStream inputStream;// 仅提供此 输入流对象 就可以实现下载 // 下面的是我们 配置的几个参数 是保证浏览器获取到 文件名 和 文件大小的private int filelength;//要下载的文件的大小private String name;//下载的文件的文件名public String execute() throws Exception {//把inputStream赋值即可//文件的真实路径ServletContext sc = ServletActionContext.getServletContext();String realPath = sc.getRealPath("/WEB-INF/files/美女.jpg");inputStream = new FileInputStream(realPath);//设定大小filelength = inputStream.available();//设置文件名name = FilenameUtils.getName(realPath);//要进行URL编码,火狐浏览器除外 ———————— 火狐 用Base64编码即可//在动作中进行编码name = URLEncoder.encode(name, "UTF-8");return SUCCESS;}public int getFilelength() {return filelength;}public void setFilelength(int filelength) {this.filelength = filelength;}public InputStream getInputStream() {return inputStream;}public void setInputStream(InputStream inputStream) {this.inputStream = inputStream;}public String getName() {return name;}public void setName(String name) {this.name = name;}} struts.xml配置:主要配置stream结果类型的参数// StreamResult类有 7 个set方法 setAllowCaching() 默认false:不使用缓存 setContentCharSet()默认系统本地utf-8 //我们尽量都把它需要的参数,手动设置进去/**<struts><constant name="struts.devMode" value="true"><package name="p1" extends="struts-default"><action name="download1" class="com.it.action.Download1Action"><result name="success" type="stream">// 结果类型 type="stream"<!-- 设置输入流的来源 --><param name="inputName" />inputStream// 就是我们 Action类中 提供的 输入流对象<!-- 相当于响应消息头:Content-Disposition:attachement;filename=0.jpg -->// 告诉浏览器 以下载的方式 打开<param name="contentDisposition" />attachment;filename=${name}// attachment:附件<!-- 保证能够使用下载的方式:contentType的响应消息头 -->// 告诉浏览器, 我给你响应回来的是什么类型的文件 图片/html/json/二进制/...<param name="contentType" />application/octet-stream// 指定MIME类型 八位字节的二进制<!-- 下载缓存: struts2框架默认就是1024--><param name="bufferSize" />1024//单位 自然是字节<!-- 设置下载的文件的大小 : ${filelength} OGNL表达式 ,相当于调用当前动作的getFilelength()--><param name="cotentLength" />${filelength}//不设置 也可自动获取  没啥卵用   可忽略</result></action></package></constant></struts>*/ *********************第三篇 数据篇**************************************17、OGNL表达式// Ognl 有一个上下文(Context)概念,说白了上下文就是一个MAP结构,它实现了java.utils.Map的接口 OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一个开源项目。 Struts2框架使用OGNL作为默认的表达式语言。能够:获取JavaBean的属性;List或数组的元素;Map根据key获取value的值;执行逻辑运算;OGNL其他功能:前提:在struts2中使用OGNL表达式,必须把表达式写到struts2的自定义的标签中。1、支持对象的普通方法调用 2、访问类的静态方法和静态变量 3、contextMap:(非常重要)1.contextMap:是整个动作访问的数据中心。即每一次动作请求所有的数据都放在这里。 2.contextMap就是一个Map结构keyvalue说明rootjava.util.ListcontextMap中的根,是一个List结构栈结构requestjava.util.Map对应的是请求范围中的所有属性(Attributes)sessionjava.util.Map对应的是会话范围中的所有属性(Attributes)applicationjava.util.Map对应的是应用范围中的所有属性(Attributes)parametersjava.util.Map所有的请求参数actionjava.lang.Object当前动作对象attrjava.util.Map依次从页面、请求、会话、应用范围搜索3.cotextMap何时被创建的// 每次动作访问,都会创建一个新的contextMap,保持本次访问用到的数据。当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。 注意: Struts2中,OGNL表达式需要配合Struts标签才可以使用。如:<s:property value="name">4.ActionContext和ValueStack: ActionContext<---------- ----------="">ValueStack创建ActionContext后,会绑定到当前线程上(ThreadLocal)5.contextMap中的数据操作// 我们可以把 contextMap 想象成一个数据库它分为两张表:OGNL Context = ActionContext + ValueStack// ActionContext中存放的 HTTP请求信息action对象存放在ValueStack1.contextMap是动作访问的数据中心,本质是一个ActionContext类,这个类有一个Map.可以操作 _root 和 _valuesclass ActionContext{private Map<string object=""> context;// 此 context —————— contextMap = _root(action) + _values(request、session、application、parameters、attr)}2.其子类:class ServletActionContext extends ActionContext implements StrutsStatics ValueStack(值栈): 贯穿整个 Action 的生命周期(每个 Action 类的对象实例都拥有一个 ValueStack 对象). 相当于一个数据的中转站. 在其中保存当前 Action 对象和其他相关对象. @scope("prototype") // 多例的class Action{ValueStack;// 数据 存取 中转站 ———————————— Action + HttpActionContext;} interface ValueStack {// 在 ValueStack 对象的内部有两个逻辑部分:List ObjectStack;// ObjectStack: Struts 把动作和相关对象 压入 ObjectStack 中-----List // 相关对象 :在Action中 声明过的 对象 Map ContextMap;// ContextMap: Struts 把各种各样的 映射关系(一些 Map 类型的对象) 压入 ContextMap 中 }// Http请求 :Request + Request + Application + Attribute + parameters class OgnlValueStack implements ValueStack{// Struts 框架把 ValueStack 对象保存在名为 “struts.valueStack” 的请求属性中,request中/**断点执行如下代码: Object ctx = ServletActionContext.getRequest().getAttribute("struts.valueStack");通过断点,我们发现 ctx = root + context ;context = _root + _values ;且对比发现:_root = root ;_values 中自然封装的 http对象的Map映射 */// ObjectStack:private CompoundRootroot;// CompoundRoot extends ArrayList// ObjectStack: Struts 把 动作和相关对象 压入 ObjectStack 中// ContextMap:private OgnlContext context;//ContextMap : Struts 把各种各样的 映射关系(一些 Map 类型的对象) 压入 ContextMap 中 root(Action);// root中保存着调用Action的实例}class OgnlContext implements Map {// ObjectStack:private Object _root;// OgnlContext 中默认的顶层对象 _root= OgnlValueStack.root(action)// ContextMap:private hashMap _values;// Http请求 :Request + Request + Application + Attribute + parameters ——————————————..._root = root; // Struts2将 OgnlValueStack的root对象 赋值给了 OgnlContext 中的_root对象 , // 在OgnlValueStack的root对象中,保存着调用Action的实例, // 因此,在页面上通过Struts2标签访问Action 的属性时,就不需要通过#号来引用}/**ognl Context包含 ObjectStack属性 和 ContextMap属性 ognl上下文 = root + context (context = _root + _values)*/1.存数据:(实际使用中由框架为我们存数据)ActionContext的API// ActionContext提供了对ognl上下文对象中数据操作的方法.get/setServletContext();get/setRequest(); get/setResponse();getPageContext();getContext();ServletActionContext.getContext().getValueStack();...ValueStack的API获取整个contextMap:Map<string object=""> getContext(); // 获取 ValueStack的上下文(Map类型的context), 保存了ValueStack 所有的信息 和 环境 ———————— Map context //_root(action) + _values只获取root:CompoundRoot getRoot(); // 获取 root ————————保存了 压入栈顶中的所有 ObjectfindValue(String s); // 等同于 域对象的getAttribute()————————————只不过此处是在 ValueStack中查找属性值peek(); // 获取 栈顶对象 ——————仅获取,不做修改pop(); // 获取 栈顶对象 ——————获取, 并从栈顶 removepush(Object o); // 将对象 压入栈顶set(String key, Object o); // 如果栈顶存在 此Map,存入. 不存在,创建再存入....2.取数据:OGNL表达式 为何使用EL表达式能够访问valueStack中对象的属性:原因是Struts2对HttpServletRequest作了进一步的封装。3.#号的用法# 相当于 ActionContext.getContext()// ActionContext ac = ActionContext.getContext(); 获取一个ActionContext实例// OGNL会设定一个根对象(root对象),在Struts2中根对象就是ValueStack(值栈) 。// 如果要访问根对象(即ValueStack)中对象的属性,则可以省略#命名对象,直接访问该对象的属性即可。在OgnlValueStack类里有一个List类型的root变量,他存放了一组对象,处于第一位的对象叫栈顶对象。通常我们在OGNL表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下访问,直到找到为止。 总结:访问contextMap中的数据,使用#访问root中的数据,直接写属性或者key值即可小问题:不建议这么做<s:property value="name">:从root找对象的name属性,如果找不到,会作为key到contextMap中去找。 18、Struts2的标签库1、通用标签库// 在OGNL表达式中, 字符串要使用 "" 引起来双重 ""时, 默认 外层单引,内层双引 test='#grade=="A"'控制标签:/**if elseif else <s:if test="#grade=='A'">优秀</s:if> // 在OGNL表达式中:字符串要使用双引号引起来 <s:elseif test="#grade=='B'">良好</s:elseif> <s:else>尚需努力</s:else>*//** iterator:// <s:iterator value="list" var="s" status="status"> 作用:实现遍历迭代,类似JSTL中的c:forEach value: OGNL表达式 var: 指定一个key值。遍历过程中,会把当前元素以var指定的key存放到contextMap中. 可以不指定。会把当前遍历的元素存放到root中。(ValueStack.push(元素)) status: 指定一个key值,引用一个对象,放在了contextMap中。该对象记录着当前遍历的元素的属性int getIndex():当前对象的索引。从0开始int getCount():当前对象的顺序。从1开始boolean isFirst():是否是第一个元素boolean isLast():是否是最后一个元素boolean isOdd():是否是奇数boolean isEven():是否是偶数 begin: 从哪个开始 end:结束索引 step:步长。默认1*/property:value不指定,默认输出栈顶对象/**<s:iterator value="list" var="s" status="status"> // var 和 value 若指定了key, 均放入contextMap取值就需要带#var若不指定,自动放入栈顶! <tr bgcolor="<s:property value='#status.odd" c3f3c3="" :="" f3c3f3="">// 用 奇偶设置 颜色 <td><s:property value="#status.index"></s:property></td>// 索引 <td><s:property value="#status.count"></s:property></td>// 序号 <td><s:property value="#status.first"></s:property></td>// 第一个 <td><s:property value="#status.last"></s:property></td>// 最后一个 <td><s:property value="#status.odd"></s:property></td>// 奇数 <td><s:property value="#status.even"></s:property></td>// 偶数 <td><s:property value="#s.name"></s:property></td>// 姓名 <td><s:property value="#s.gender=='1''男':'女'"></s:property></td>// 性别 <td><s:property value="#s.city"></s:property></td>// 城市 <td><s:property value="#s.birthday"></s:property></td>// 生日 </tr> </s:iterator>*/ 数据标签:property:// <s:property value="#name" default="a default value">作用: 输出表达式的值到页面上属性:value:是一个OGNL表达式.如果没有指定该属性,则默认输出ValueStack栈顶的值.default:指定一个默认值, 如果需要输出的属性值为null,则显示该属性指定的值.escapeHtml:是否忽略HTML标签 date:// <s:date name="studentList[0].birthday" format="yyyy-MM-dd">功能:日期类型格式化属性: name : OGNL表达式 format :SimpleDateFormat action:// <s:action name="ognlAction_test" namespace="/ognl" executeresult="true"> ——————————执行 ognlAction_test.action通过指定命名空间和action名称,该标签允许在jsp页面直接调用Action 在页面显示一个动作的执行结果.a:超链接子标签 param :属性: name : 参数名 value :OGNL<s:a action="actionDemo">点我<s:param name="username" value="'hello'"></s:param><s:param name="userpwd" value="'123'"></s:param></s:a>url:该标签用于创建url地址,可以通过"param"标签提供request参数. action:用来生成url的action动作名称,如果没有则使用valuenamespace :命名空间var:起一个临时的名称 放在了contextMap .value:如果不提供就用当前action,使用value后缀必须加.action<s:url action="actionDemo" var="action"></s:url><a target=_blank href="<s:property value='#action'/>">hello</a>/**表单标签<s:form action="" method="get"> <s:textfield name="username" label="用户名"></s:textfield> <s:password name="userpwd" label="密码"></s:password> s:radio list OGNL:创建list和map对象创建list:{元素1,元素2,....} 创建map:#{key:value,key:value,}; key:value(传递值,页面显示值) value:指定默认值:value(指定的是key的值) name:注意一定要有name属性 // 不然,你把值赋给谁 --> <s:radio list="#{'0':'man','1':'female'}" name="gender" label="性别" value="0"> </s:radio> <!-- list:集合 list='{"北京","上海","hello"}' name:注意一定要有name属性 headerKey:请选择的key值 headerValue="请选择" value='"hello"' 注意OGNL表达式 --> <s:select list="{'北京','上海','hello'}" label="城市" name="city" headerkey="-1" headervalue="请选择" value="'hello'"></s:select> <s:checkboxlist list="{'乒乓球','乒乓球1','乒乓球2'}" label="hobby" name="hobby" value="'乒乓球2'"></s:checkboxlist> <s:textarea cols="5" rows="5" label="简介"></s:textarea> <s:reset value="重置"></s:reset> <s:submit value="提交"></s:submit> </s:form><s:hidden name="" value="">*/2、Struts2中的EL表达式普通EL表达式:${name}:相当于调用pageContext.findAttribute("name"),从 页面、请求、会话、应用 范围查找key=name对应的value的值。Struts2中的EL表达式:// struts2框架对服务器原有的request对象的getAttribute()方法,进行了重写.———————————— 会在 root 、 contextMap中 也查找 顺序如下:${name}: 页面范围、请求范围、 root中对象的属性、contextMap中的key、 会话范围、应用范围3、struts2UI标签中的表单标签使用Struts2表单标签的优势:1、错误回显2、便于布局和排版OGNL操作集合:list:{a,b,c,...}map:#{"key1":"value1", "key2":"value2","key3":"value3",...}<s:form action="${pageContext.request.contextPath}/UserAction_regist.action"> 正确写法: <s:form action="UserAction_regist">requiredLabel属性: 显示*号 // 用在 必填项中<s:textfield name="name" label="用户名" requiredlabel="true"></s:textfield> // 用户名 * :布局模板:默认的模板xhtml;经常使用的模板:simple// <constant name="struts.ui.theme" value="simple">统一更改表单标签的模板// 覆盖 default.properties参数配置文件中 常量的值 4.OGNL其他细节不使用任何符号:比如:name,从root中取对象的属性。如果没有任何的属性,变为key到contextMap中找使用#: 1、#name:从contextMap中找key=name对应的value的值2、#{key1:value1,key2:value2,...}:创建一个Map对象字符串直接量:使用双引号引起来:比如,"abcdef"使用%:通用标签:value属性一般取值是一个 OGNL表达式表单标签:value属性一般取值是一个 普通字符串%可以强制变为OGNL表达式使用$符号的使用:在struts.xml或*.properties配置文件中使用OGNL表达式,使用${OGNL}5.防止表单重复提交<s:form><s:token>...</s:token></s:form> struts.xml中配置自定义 name="token"拦截器30.struts2中内置配置文件default.properties 参数配置文件struts.action.extension=action,,struts-default.xml结果集 + 拦截器(栈) <result-type name="chain" class="..."> 、 <interceptor-stack name="defaultStack"> + 两个默认引用<default-interceptor-ref name="defaultStack">配置文件<default-class-ref class="com.opensymphony.xwork2.ActionSupport"> default.xml 内置验证器配置文件<validator name="requiredstring" class="...">// 校验配置文件的DTD信息 ————> xwork-validator-1.0.3.dtdstruts-messages.properties文件上传有关的出错消息31.所谓通配符访问action// userAction_save.action name = " userAction_save "method = "save"class = userAction就是把 同一个Action类 中的 method 集中处理.方式为:将 name 以统一的xxxAction开头, 再拼接上 通配符 _*——————> name = " xxxAction_* " method 也统一以 占位符{0} 、 {1} ...获取 * 的值——————> method = {0}1.一个通配符:(常用)<action name="xxxAction_*" class="xxxAction" method="{0}">// 在 spring 容器中 class简写. 2.两个以上通配符的使用:// 页面跳转用// 需要进行权限控制的页面访问 执行默认的ActionSupport类的execute() ———— execute()方法 返回值为:return SUCCESS;<action name="page_*_*"><result type="dispatcher">/WEB-INF/pages/{1}/{2}.jsp</result></action>32.所谓栈:先进后出33.struts.xml中的几个继承,到底继承的是谁?<package name="my-default" extends="struts-default">// 此处 继承自struts-default.xml 中的 <package> ————————————<package name="struts-default" abstract="true"><interceptor-stack name="bos">// 注册自定义 拦截器栈 <interceptor-ref name="bosLogin"><interceptor-ref name="defaultStack">// 此处 继承自 struts-default.xml 中的 拦截器栈:<interceptor-stack name="defaultStack">也是struts2的默认拦截器栈</interceptor-stack>34.namespace="/"的名称空间 和 默认的名称空间(namespace不写或者值为"")的区别到底在哪???35注解.@Controller@Scope("prototype")@Resource@Service@Transactional@Repository==========================================Hibernate ===========================================================================================================================1.Hibernate框架简介【了解】1.定义:Hibernate是一个基于Java的轻量级的ORM框架基于Java:底层是Java语言实现的轻量级:内存消耗比较少,运行速度比较快,性能稍高ORM(Object Relation Mapping):Object: 对象类属性数据类型对象Relation: 关系型数据库表表名字段数据类型表Mapping: 映射将具有对应关系的模型关联起来,操作对象,即操作数据库表2.Hibernate实质:自动完成JDBC + 自动生成SQL语句说明:自动生成的SQL语句是一种匹配所有格式的语句,因此该语句不能根据用户需要进行优化,因此生成的SQL语句是一种最差实践,性能较低3.Hibernate是一种数据层解决方案,用于进行数据层开发,制作DAO4.常见的数据层解决方案JDBC// sun 规范 最原始的的方式 , 也是最高效(运行高效——编程低效)简单封装JDBCUtils————开源工具————DBCP、C3P0进一步优化改造JDBCUtilsHibernate市场占有率:ORM:非ORM9:1ORM市场:开源:收费9:1开源ORM市场:Hibernate:iBatis:其他6:3:1JPA(Java Persistence API )iBatis/MyBatisApache DBUtils// QueryRunner update(增删改) / query(查)Spring JDBCTemplate、HibernateTemplate、等等2.Hibernate系统架构【理解】图的分类:系统架构图、技术架构图系统架构图:描述系统中的重要模块以及模块与模块之间的关系【上层依赖于下层】Application↓persistent Objects(中间连接量)↓hibernate(hibernate.properties + XML Mapping)↓Database1.包含内容:应用程序、Hibernate、数据库2.Hibernate位于APP与DB之间3.APP依赖Hibernate,Hibernate依赖DB4.Hibernate中有两种配置文件5.映射使用XML格式完成6.持久化对象出现在APP与Hibernate间,起桥梁的作用,连接APP与Hibernate。Hibernate与APP间使用持久化对象进行数据交换技术架构图:Application(Transient Object)↓persistent Objects(中间连接量)↓ Session //持久化对象传递给hibernate的SessionSessionFactory + JDBC + JNDI + JTA ↓Database1.Hibernate裂变成5块 JDBC JNDI JTA SessionFactory Session2.APP需要操作的数据通过持久化对象交给Hibernate的Session对象完成3.APP中包含一种瞬时对象4.SessionFactory用于创建Session5.JDBC,JNDI,JTA作用未知Application(Transient Object)↓persistent Objects(中间连接量)↓ SessionTransaction +SessionFactory + TransactionFactory+ ConnectionProvider↓JDBC + JNDI + JTA ↓Database1.Hibernate依赖由第三方厂商实现JDBC,JTA,JNDI技术完成任务,而非Hibernate自身实现2.Hibernate存在有事务及事务工厂3.Hibernate的连接需要由第三方提供4.SessionFactory依赖 连接供应商 和 事务工厂 来工作/**3.Hiberante资源下载与介绍【了解】【操作】1.下载HibernateHibernate 最高版本4.3,本课程基于Hibernate3.6.10学习下载地址:http://sourceforge.net/projects/hibernate/files/hibernate3/2.厂商介绍JBOSSJBOSS服务器Web服务器:内置WEB容器(TOMCAT)应用服务器:内置WEB容器+EJB容器(JBOSS、Weblogic)HibernateJBPM3.资源包目录层次documentation:帮助文档lib:开发使用jar包bytecode:字节码实现(代理对象相关)jpa:JPA规范包optional:可选包,包含开发Hibernate常用可选技术jar包required:必选包,包含开发Hibernate必须使用的jar包project:源码hibernate3.jar:核心jar包hibernate-testing.jar:测试包4.开发所使用的jar包:必选:(9个)1.核心jar包:hibernate3.jar(1个)2.lib//required/*.jar (6个)3.lib/jpa/hibernate-jpa-2.0-api-1.0.1.Final.jar (1个)4.DB驱动包:mysql的驱动jar包 (1个)可选:(2个)如果不使用,则无法查看Hibernate的运行日志5.log4j的jar包:log4j-1.2.16.jar(1个)6.slf4j整合log4j的jar包:slf4j-log4j12-1.7.2.jar(1个)5.【扩展】slf4j(简单日志门面技术)不同的日志记录技术,全部是自己的记录方式,规范不统一,slf4j提供一种标准的记录日志方式,整合不同的日志技术,slf4j本身不提供具体日志记录实现5.log4j技术————————————————slf4j的实现之一参看《log4j快速入门手册》*/4.Hibernate基础实例1.基础实例制作分析:O:Object对象(开发者完成)R:Relation关系型数据库表(开发者完成)M:Mapping对象与表间的对应关系(开发者完成)JDBC:Hibernate自动完成SQL: Hibernate自动完成2.步骤:1.创建工程(标准java工程)2.导入jar包(9个)/**3.制作R,创建表结构CREATE TABLE `tbl_user` (`uuid` varchar(32) NOT NULL,`userName` varchar(30) NOT NULL,`age` int(3) NOT NULL,`address` varchar(30) NOT NULL,PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8*/4.制作O,创建Java中的对象/**提供一个无参的构造方法(可以使用默认无参构造方法)提供一个主键属性(uuid)私有化其他的属性(userName,age,addres…)封装所有属性(提供getter与setter方法)*不要声明为final类(与后期优化有关)*使当前类实现序列化接口(与后期服务器数据检测有关,目前可忽略)*/5.制作M,创建表与对象间的映射关系// xxxModel.hbm.xmlA.在创建的对象(Model)同层目录下创建与类名相同的xml文件,名称:UserModel.hbm.xml———————— 放在 cn.it.hibernate.domain包下B.从资源包中搜索*.hbm.xml(User.hbm.xml)// xxxModel.hbm.xmlC.将资源包中查找的信息复制过来,描述具体的对应关系:类与表的映射//class ——————————<class name=".." column="..">主键的映射//id ——————————<id name=".." column="..">属性与字段的映射//property ——————————<property name=".." column="..">/**<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><!-- 类与表的映射制作在class元素上 --><!-- name:全路径类名 --><!-- table:表名 --><class name="cn.it.h3.helloworld.UserModel" table="tbl_user"><!-- 主键的映射制作在id元素上 --><!-- name:对象中用于作为主键的属性名 --><!-- colomn:表中主键字段名 --><!-- 如果name与column值相同,可以省略column --><id name="uuid" column="uuid"><!-- 将generator元素class属性设置为"assigned" --><generator class="assigned"></generator></id><!-- 属性与字段的映射制作在property元素上 --><!-- name:类中的属性名 --><!-- column:表中的字段名 --><!-- 如果name与column值相同,可以省略column --><property name="userName" column="userName"><property name="age"><property name="address"></property></property></property></class></hibernate-mapping>*/6.制作系统配置文件,用于描述连接的数据库相关信息及Hibernate的基本配置信息A. 在src目录中创建XML文件// hibernate.hbm.xmlB.从资源包中搜索hibernate.cfg.xml文件C.将资源包中查找的信息复制过来,描述具体数据连接信息数据库连接的配置可选配置// 方言 、 show_sql资源注册// xxxModel.hbm.xml/**<xml version="1.0" encoding="utf-8"><!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="connection.driver_class">com.mysql.jdbc.Driver</property><property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property><property name="connection.username">root</property><property name="connection.password">root</property><!-- 可选配置 --><!-- 1.方言配置 --><property name="dialect">org.hibernate.dialect.MySQLDialect</property><!-- 2.是否输出执行的SQL语句 --><property name="show_sql">true</property><!-- 资源注册 --><!-- 将对应的映射文件的全访问名称配置到resource中,目录间使用/符号分隔 --><mapping resource="cn/it/h3/helloworld/UserModel.hbm.xml"></mapping></session-factory></hibernate-configuration>*/7.制作客户端程序,运行Hibernate范例如下:/**UserModel um = new UserModel();//1.创建一个要保存的数据 —————— 瞬时um.setUuid(1L);um.setUserName("传智");um.setAge(8);um.setAddress("双元课堂");//2.加载配置信息Configuration conf = new Configuration().configure();//3.基于配置信息,创建SessionFactory对象SessionFactory sf = conf.buildSessionFactory();//4.打开一个与数据库相关的Session对象Session session = sf.openSession();//5.开启事务,基于Session得到Transaction t = session.beginTransaction();//6.通过session保存数据—————————————————————————————— 持久化 到DBsession.save(um);//7.提交事务t.commit();//8.操作完毕后,关闭session连接对象session.close();*/5.映射模型类制作规范:// 对象id OID ———————— hibernate 据此 区分不同的 对象1.提供可访问的公共无参构造方法(可使用自动生成的)// DB ——————> Object 构造Hibernate在读取数据时,将读取的数据转换成一个对象,该对象的创建需要使用该构造方法2.提供一个标识属性,作为对象的主键,映射数据库表主键,通常使用uuid属性作为主键// 主键该属性用于Hibernate内部区分对象使用,Hibernate无法使用内存地址比对对象是否相同,Hibernate依赖该属性对对象进行区分,如果该属性值相同,Hibernate则认为是同一个对象3.对所有属性进行私有化声明,并进行标准封装// 私有化 Integer:√int:×注意:属性声明时使用封装类模型,避免使用基本数据类型4.不要使用final修饰符(否则将无法生成代理对象进行优化,一级缓存中学习)// 对象id OID ———————— hibernate 据此 区分不同的 对象OID:Hibernate对于对象的管理标识,用于区分对象,对应数据库中的主键OID选取规则:优先考虑代理主键属性:身份证号具有业务含义,称为自然主键属性:uuid不具有任何业务含义,称为代理主键对象的属性选择:优先使用封装类,避免使用基本数据类型A.使用基本数据类型,对于没有输入值的数据,采用实际数据作为默认值B.使用封装类类型,对于米有输入值的数据,采用null作为默认值基本数据类型无法区分默认值是用户输入的还是没有输入产生的默认值,具有一定的安全隐患,因此放弃使用,改由封装类类型6.映射配置文件//xxxModel.hbm.xml详解/**1.映射文件中包含内容:类与表的映射// class主键的映射// id属性与字段的映射// property关系的映射(后期)// many-to-one 、 many-to-many ...2.命名规范:模型类名.hbm.xml可以写成任意的名称a.xml,此格式不满足规范,但是可以使用(Hibernate低版本格式严格)3.class元素用于指定对象关系映射中的类与表的关系name:模型类的全路径名格式table:数据库表名schema:数据库名4.id元素用于声明主键映射name:数据模型中的OID,通常使用uuid属性(必须)column:数据库表的主键,通常使用uuid字段(可选)如省略column配置,则默认column值与name值相同id元素的选择字段一定是作为对象的OID的字段5.property元素用于声明属性与字段的对应关系name:数据模型中的属性名,也就是Java字段名(必须)column:数据库表的字段名(可选)如省略column配置,则默认column值与name值相同三种声明格式:使用空标记(常用)<property name="userName">使用非空标记<property name="age"></property>使用子元素声明数据库字段名<property name="address"><column name="address"></column></property>6.范例<hibernate-mapping><!-- 类与表的映射 --><!-- class:描述类与表的映射 --><!-- class:name:模型类全路径名 --><!-- class:table:映射的表名 --><class name="cn.it.h3.user.UserModel" table="tbl_user" schema="h3db"><!-- 主键的映射 --><!-- id:描述类的OID与表的主键映射关系 --><!-- id:name:配置模型类OID属性名 --><!-- id:column:数据库表中主键字段名,如该值与name属性值相同,可省略 --><id name="uuid" column="uuid"><!-- generator:用于指定当前OID值的生成策略 --><generator class="assigned"></generator></id><!-- 属性与字段的映射 --><!-- property:描述类中的属性与表中的字段之间的映射关系 --><!-- property:name:类中的属性名 --><!-- property:colun:表中的字段名,如该值与name属性值相同,可省略--><property name="age"><property name="usreName" column="userName"></property><property name="address"><column name="address"></column></property></property></class></hibernate-mapping>*/7.OID——主键生成策略:// 持久化时,以配置文件 的主键生成策略为准!!!eg:<generator class="uuid"> user.setId("001");save(user) //此时设定的 OID="001" 无效,系统会按配置的生成策略 自动生成32位随机字符串!!!1.generator//描述的是OID的生成策略1.手工控制策略:assigned:程序员手工控制生成策略// 手动设置 OID2.自动生成策略:// 自动生成 OIDuuid(字符串类型:32位及以上)// 服务端随机生成32位字符串increment(整型数值类型)通过获取max的值,执行SQL语句//服务端 SQL命令:max+1identity (整型数值类型)通过数据库端实现,要求设置自增 ———— MySQL可用//数据库自增MySQLsequence (整型数值类型) 通过数据库端实现,要求设置自增 ———— Oracle可用//数据库自增Oraclenative(整型数值类型)根据数据库的实现 自动选择identity/ sequence//数据库自增自动选择/**2.联合主键/复合主键为兼容早期遗留系统而设计,目前几乎不采用该配置<composite-id><key-property name="uuid"><key-property name="userName"></key-property></key-property></composite-id>【常见面试题】:请列举Hibernate的主键生成策略*/8.系统配置文件://hibernate.cfg.xml————————> 代替了早期的 hibernate.properties(此方式以被淘汰)/**1.系统配置文件中包含内容:数据库连接的配置可选配置资源注册二级缓存(后期)2.命名规范:hibernate.cfg.xml//Use the mappings and properties specified in an application resource named <tt>hibernate.cfg.xml</tt>.可以写成任意的名称a.xml,此格式不满足规范,参看Configuration对象//源码:configure( "/hibernate.cfg.xml" );3.数据库连接的配置:hibernate.connection.driver_classJDBC驱动类类名hibernate.connection.urlJDBC连接字符串hibernate.connection.usernameJDBC连接用户名hibernate.connection.passwordJDBC连接用户名对应的密码4.可选配置1.必选:方言配置用于设置生成的SQL语句的格式<property name="dialect">org.hibernate.dialect.MySQLDialect</property>2.其他可选配置hibernate.show_sql设置是否将执行过程中运行的SQL语句输出到控制台开发阶段开启此属性,上线阶段关闭此属性hibernate.format_sql设置show_sql属性打印出的SQL语句以分行的格式进行显示hibernate.connection.pool_size设置Hibernate模型连接池最大连接数量使用c3p0连接池步骤:1.导入c3p0连接池jar包lib/optional/c3p0/*.jar2.cfg.xml中配置下列配置属性开启任意一句与c3p0有关的配置,即开启使用c3p0连接池<property name="hibernate.c3p0.min_size">21</property><property name="hibernate.c3p0.max_size">721</property><property name="hibernate.c3p0.timeout">3091</property>hibernate.hbm2ddl设置是否根据hbm文件格式生成表结构,需要配合hbm.xml文件中对于属性类型与长度相关的配置使用connection.autocommit设置是否自动提交事务5.资源注册将Hibernate的映射文件加入到系统配置中,否则将无法操作该模型注意:目录层次结构间使用/符号描述<mapping resource="cn/it/h3/vo/UserModel.hbm.xml">*/9.Configuration// 封装 Hibernate系统配置信息的对象两种格式:常用格式:Configuration conf = new Configuration().configure();// 不写 默认读取 hibernate.cfg.xmlConfiguration conf = new Configuration().configure(“配置文件名”);// 读取指定的配置文件/**早期格式介绍:早期格式:Configuration conf = new Configuration();//读取 hibernate.properties早期格式读取properties格式配置文件,无法进行资源注册的信息配置,需要使用下列格式进行资源注册:Configuration conf = new Configuration();//格式一:手动添加映射配置文件,操作量较大conf.addResource("cn/it/api/UserModel.hbm.xml");//格式二:手动添加映射类,由类名自动查找该类同包结构下的类名.hbm.xml文件conf.addClass(UserModel.class);*/ 10.SessionFactory// 线程安全创建Session对象的工厂类,通过加载Configuration对象中封装的配置信息得到Configuration conf = new Configuration().configure();// 加载 hibernate.cfg.xmlSessionFactory sf = conf.buildSessionFactory();// 用获取到的 配置对象创建SessionFactory注意:1.由于每次加载配置信息,均要读取XML文件,需要浪费大量时间,因此同一项目中,Configuration对象只会加载一次,SessionFactory对象也只加载一次2.对于同一个工程,连接数据库的信息唯一,基于加载的配置信息Configuration对象所创建的SessionFactory对象用于创建连接数据库的对象,不同的连接对象使用的连接信息是相同的,因此SessionFactory对象是线程安全的11.Session:// 不安全同 Connection与DB交互数据功能更强大!!!1.描述单次应用程序与数据库间进行数据交换的对象,作用相当于Connection,但是其功能远远超过普通的JDBC连接对象2.Session对象基于SessionFactory对象获取Session s = sf.openSession();3.Session对象操作完成后要进行关闭,否则将一直占用该连接对象s.close();说明:Session对象由SessionFactory创建,每次创建并不是new出一个全新的Session对象,而是从数据库连接池中获取一个闲置的连接对象,使用完毕后,关闭Session对象也不是将该对象销毁,而是清空本次操作过程中所有的数据痕迹后,将该对象交还给连接池。如果使用同一个Session连续执行若干次任务,前一次执行任务所操作的数据痕迹没有及时清除,会影响下一次的操作,因此Session是线程不安全的。13.工具类H3Util制作:public class H3Util {private static Configuration conf = null;//静态的SessionFactoryprivate static SessionFactory sf = null;//使用静态代码块初始化对象static{conf = new Configuration().configure();sf = conf.buildSessionFactory();}public static SessionFactory getSf(){return sf;}/** * 获取Session对象 * @return 全新的Session对象 */public static Session getSession(){return sf.openSession();}} 14.Transaction:1.描述Hibernate操作过程中,绑定当前Session对象的事务控制对象2.获取事务对象:通过使用Session中的beginTransaction()方法获取Transaction t = session.beginTransaction();3.事务回滚:t.rollback();4.事务提交:t.commit();15.Session API:1.插入数据save(Object)//Serializable oid = s.save(user);1.如果配置信息提供了OID生成策略,那么对象携带的OID将失效2.该操作返回的是添加的对象对应的OID注意:如果对象保存时,不能获取到OID的值,将抛出异常persist(Object)1.该操作可以保存对象// 仅保存 无返回值!2.如果手工给出了OID,并且又使用了自动生成OID,该操作将抛出异常// OID自动生成 和 手动 不能同时使用!!!2.删除数据delete(Object);1.删除时提供的对象必须拥有OID,否则不做任何操作 //s.delete(user)———————— user中必须封装 有OID, 否则等于没操作!2.删除一个不存在的OID对象时,抛出异常// 且提供的 OID有误时, 报错!3.修改数据update(Object) //s.update(user)———————— 必须提供 OID1.修改时提供的对象必须拥有OID,否则抛出异常2.在一个Session内对同一个执行修改操作多次,只会执行对应SQL语句一次(一级缓存中详解)//4.添加/修改数据saveOrUpdate(Object)该方法可以完成修改和添加操作,取决于是否提供OID判断是否提供OID的策略1.OID是否为null2.OID的值是否与配置中 unsaved-value 是否冲突5.查询单条数据get(Class, Serializable) load(Class, Serializable)1.查询时只能查询1条数据,并且需要提供OID,根据OID进行查询2.查询时必须指定查询数据模型,Hibernate根据模型查找对应映射文件,找到查询数据对应的表,查询完毕后将数据按照模型格式进行封装// xxxModel——————> xxxModel.hbm.xml ——————>表↓// xxxModel(查询到的数据封装到模型类中)16.HQL语句书写规范:1.HQL(Hibernate Query Language)语言:Hibernate专用的查询语言2.HQL与SQL语言对比:SQL语法中使用表与字段描述查询//表 ———— 字段HQL语法中使用类与属性描述查询,由SQL衍生而来,具有SQL语言不具有的特殊语法//类 ———— 属性例一:SQL:select * from tbl_user HQL:from UserModelHQL:select um from UserModel um例二:SQL:select userName,age from tbl_user//表的 字段HQL:select userName,age from UserModel //类的 属性例三:SQL:select u.userName,u.age from tbl_user uHQL:select um.userName,um.age from UserModel um例四:HQL:select um.userName from UserModel 错误HQL:select userName from UserModel um 正确HQL:select userName from UserModel as um 正确 17.Query://HQL查询—————————— 自定义HQL语句————————————session.createQuery("手写HQL语句");1.Query对象用于进行查询操作,使用HQL语法完成2.Query对象由Session对象创建,传入字符串HQL参数Query q = session.createQuery("from UserModel");3.获取查询结果信息,如果查询的是映射的模型对象,根据配置信息封装为对应对象使用list()获取结果为列表信息// List list = q.list();0——N将查询结果包装成对象,并将对象组装成一个List集合可以用于获取0到多条数据,如果是0条数据,返回的集合中没有数据使用uniqueResut()获取结果为单一信息 //Object obj = q.uniqueResult();0——1若结果超过1条,报错!将查询结果包装成对象,直接返回用于获取0到1条数据,如果是0条数据,返回null注意:uniqueResut()返回结果不能超过1条,否则抛出异常4.投影查询// 查询 部分属性(字段) 或 单个对象 通过设置HQL语句中select的内容进行的查询即为投影查询例如:select um from UserModel um(查询结果为单一项)// 单一 List<usermodel>模型对象select userName from UserModel(查询结果为单一项)// 单一 List<username>仅查询一个属性(只获取userName列数据)select userName,age from UserModel (查询结果为多项)// 多项 List<object obj="" obj="" obj=""> [{obj1,obj2},{obj_1,obj_2},{obj5,obj6}...] A.如果查询项为单一项且是一个对象,返回数据模型为查询的模型B.如果查询项为单一项且是一个属性,返回数据模型为对应的查询结果模型C.如果查询项为多项,返回的数据模型为对象数组Object[],该对象数组中封装了若干个对象,内容为查询项目,顺序与HQL语句中的定义顺序一一对应例如:Select userName,age,address from UserModel查询结果中每条数据为Object[],其中包含三个数据Object,第一个数据为userName的值,第二个数据为age的值,第三个数据为address的值5.分页查询HQL查询使用面向对象的格式进行分页查询的设置,Hibernate会根据系统配置中设置的dialect(方言)生成对应数据库使用的分页查询语句1.设定查询的第一条数据编号,该值为索引值,从0开始//q.setFirstResult(index);—————— 从第21条记录开始查2.设置查询结果数据总量//q.setMaxResults(index);—————— 查询30条显示:21——50的数据6.按条件查询1.传入可变参数查询(常用)格式一:HQL:from UserModel where userName = q.setParameter(0,”value”);弊端:索引传递参数,必须位置全完匹配,如果不匹配,错误格式二:HQL:from UserModel where userName = : userNameq.setParameter(“userName”,”value”);弊端:由于当前传递的参数是没有类型限定的,因此需要开发者传递参数时明确数据类型格式三:HQL:from UserModel where userName = and age = q.setString(0,”value”);// 指定 类型 + 索引传参q.setInteger(1,34);格式四:HQL:from UserModel where userName = :userName and age = : age// 语句指定了别名q.setString(“userName”,”value”);q.setInteger(“age”,8);// 指定类型+ 引用别名传参格式四较为严谨,不会因为查询语句的条件位置发生变化而产生错误无参数条件查询(少用)固定值查询(不推荐使用)6.链式结构【了解】7.聚合函数Hibernate进行聚合函数的使用与SQL查询完全相同,但是查询结果类型为Java数据类型count返回的结果:Longmax/min 返回的结果:原始类型sum返回的结果:Long或Doubleavg返回的结果:Double注意:由于返回结果通常为单一数据,常使用uniqueResult()获得结果// 单一数据q.uniqueResult();8.分组查询// group byQuery q = s.createQuery("select address,count(address) from UserModel group by address");与SQL基本相同9.排序查询// order byQuery q = s.createQuery("from UserModel order by age desc");与SQL基本相同10.投影数据封装【了解】// 按需自定义有参构造+提供无参构造对查询结果为投影格式的数据进行封装,包装为对象模型A.在模型类中提供对应的构造方法(注意无参构造方法的提供)B.书写HQL语句时,使用定义的构造方法Query q = s.createQuery("select new UserModel(age,userName,address) from UserModel");改造HQL语句:select new UserModel(userName,age) from UserModel注意:构造方法的参数顺序一定要和HQL中的查询投影顺序相同18.Criteria://QBC查询———————————— 面向对象,自动生成SQL语句 —————————————— session.createCriteria("UserModel.class");1.Criteria查询又名QBC查询,以面向对象格式完成查询操作,实现真正的自动生成SQL语句// Criteria:条件2.Criteria对象由Session对象获取,需要传入要查询的模型类session.createCriteria(UserModel.class);3.获取查询结果信息与Query对象完全相同list()获取多条数据uniqueResult()获取单条数据4.投影查询//projections(投影) ———————— 投影对象A.QBC查询的默认投影格式为查询模型的全部属性B.设置查询单一属性,查询结果为该属性类型I.声明属性Property用于封装查询属性Property pro = Property.forName("userName");//Property.forName("属性");————————添加属性名,反射获取实体II.设置查询内容为该属性c.setProjection(pro);//criteria.setProjection(pro);————————属性作为 查询参数C.设置查询多属性,查询结果为对象数组I.声明用于封装查询属性的列表对象ProjectionList plist = Projections.projectionList();//Projections.projectionList(); —————————— 投影集合对象II.声明要查询的每个属性Property pro1 = Property.forName("userName");// 获取属性Property pro2 = Property.forName("age");III.将待查询属性添加到属性列表中plist.add(pro1);// 加入投影集合plist.add(pro2);IV.设置查询内容为该属性列表c.setProjection(plist);// 投影集合作为 查询参数5.按条件查询//Restrictions(条件) ———————— 条件对象A.此功能是QBC查询中最强大的功能,基于面向对象的格式,描述查询条件短语含义// 条件——————————Restrictions.eq(...)Restrictions.eq等于=Restrictions.allEq使用Map,使用key/value进行多个等于的判断Restrictions.gt大于>Restrictions.ge大于等于>=Restrictions.lt小于<Restrictions.le小于等于<=Restrictions.between对应sql的between子句Restrictions.like对应sql的like子句Restrictions.in对应sql的in子句Restrictions.andand 关系Restrictions.oror关系Restrictions.sqlRestrictionSql限定查询B.添加条件时候,使用add方法添加多个条件。// add ———————— 添加条件c.add(条件);c.add(条件)C.默认添加的条件间是并且(and)关系与关系 可以不使用and(条件,条件),默认写出的条件就是and连接// c.add( Restrictions.and(a,b,c) )c.add( Restrictions.and( Restrictions.like("userName", "%J%"), Restrictions.gt("age", 30) ) );或关系 必须使用or(条件,条件)操作c.add( Restrictions.or( Restrictions.like("userName", "%o%"), Restrictions.gt("age", 30) ) );非关系使用not方法完成,但是个别特殊的操作使用方法名完成c.add( Restrictions.not( Restrictions.like("userName", "%o%") ) );D.示例默认and关系c.add(Restrictions.like("userName", "%传智%"));c.add(Restrictions.eq("age", 34));c.add(Restrictions.le("age", 30));c.add(Restrictions.eq("userName", "传智"));// 上述添加的 4个条件 默认是 and 关系!!! /** 可直接写成 c.add(Restrictions.and(Restrictions.like("userName", "%传智%"),Restrictions.eq("age", 34),Restrictions.le("age", 30),Restrictions.eq("userName", "传智")));*/非关系c.add(Restrictions.not(Restrictions.eq("userName", "传智")));// !(userName="传智") ———————— userName!="传智"与关系c.add(Restrictions.and(Restrictions.eq("age", 33), Restrictions.eq("userName", "传智")));或关系c.add(Restrictions.or(Restrictions.eq("age", 33), Restrictions.eq("userName", "传智")));6.链式结构【了解】7.聚合函数// 隶属于 投影———————— Projections.函数(); 函数Projections.max(..)聚合函数的使用隶属于投影范畴,因此设置使用聚合函数需要设置投影countProjection condition= Projections.rowCount();Longmax/minProjection conditionMax = Projections.max("age");原始类型Projection conditionMin = Projections.min("age");原始类型SumProjection conditionSum = Projections.sum("age");Long/DoubleAvgProjection conditionAvg = Projections.avg("age");Double8.分组查询 // 隶属于 投影———————— Projections.groupProperty("属性");分组查询隶属于投影范畴,使用投影的格式添加设置Projections.groupProperty("address")9.排序QBC查询的排序使用独立的格式完成:// c.addOrder(Order);排序规则由Order对象的静态方法设置升序:Order.asc("排序字段名");降序:Order.desc("排序字段名");10.离线查询// 间接获取 Criteria对象:DetachedCriteria(离线条件) ——————————Session————————————>Criteria离线查询的目的是在不获取Criteria对象的情况下,创建一个用于封装查询条件的对象,该对象可以通过关联Session对象,转化为一个可执行的Criteria对象/**步骤:1.在表现层将查询数据封装到DetachedCriteria对象中2.将DetachedCriteria对象传入业务层,业务层传入数据层3.使用Session对象将DetachedCriteria对象关联成一个可运行的Criteria对象Criteria c = dc.getExecutableCriteria(session);4.执行Criteria对象的查询方法*/表现层/业务层DetachedCriteria dc = DetachedCriteria.forClass(UserModel.class);dc.add(Restrictions.like("address", "%"+address+"%"));dc.add(Restrictions.between("age", age1, age2));数据层Session s = HibernateUtil.getSession();Criteria c = dc.getExecutableCriteria(s);// 使用Session对象将DetachedCriteria对象关联成一个可运行的Criteria对象List<usermodel> queryList = c.list();for(UserModel um:queryList){System.out.println(um);}问题:使三层架构变成了紧耦合状态松耦合:可传Model11.Query(HQL)与Criteria(QBC)区别HQL:书写HQL语句,所有的查询与投影的设计均使用HQL语句完成//手动书写HQL语句QBC:没有任何查询语句, 所有的查询与投影的设计使用面向对象格式完成//传入对象参数 或 投影对象 ,自动生成SQL语句选择时机:当业务十分简单时,推荐使用Query查询//手动编写HQL命令当进行复杂条件查询时,推荐使用Criteria查询// 设置好 投影对象之间的关系 即可,面向对象 自动生成复杂的 HQL命令19.SQLQuery//使用原生 SQL 语句查询1.SQLQuery用于在Hibernate环境中执行SQL语句2.SQLQuery对象由Session对象获取,参数传入SQL语句s.createSQLQuery(SQL);3.获取数据使用list()或uniqueResult()进行查询,查询结果为Object[]4.可以通过别名为其查询的模型数据进行封装SQL:select u.* from tbl_user uSQLQuery对象要指定对应的别名包装的对象sq.addEntity("别名",模型类);sq.addEntity("u",UserModel.class);20.配置型SQL/HQL语句执行方式//<query> / <sql-query> 命名查询1.Hibernate支持将查询语句配置在hbm.xml文件的hibernate-mapping元素内2.配置的查询语句具有两种格式HQL: <query name="getAll">from UserModel where uuid = :uuid</query>SQL: <sql-query name="getAll">select * from tbl_user</sql-query>注意:配置SQL语句时,为避免语句中具有与XML语法格式冲突的符号,通常使用使用XML语法规则<![CDATA[*****]]>控制查询语句中出现的与XML冲突的错误符号,例如<![CDATA[from TeacherModel where teacherName = :name]]>//XML语法冲突 —————— <![CDATA[...]]>3.执行查询使用Query对象完成Query q = session.getNamedQuery("配置中的查询name");// 命名查询 执行方法session.getNamedQuery("getAll");21.Hibernate对象状态1.Hibernate对象状态描述了对象在不同阶段的状态信息 ,而不是多个对象2.对象的状态共有三种瞬时状态:瞬时对象 (TO)不受Hibernate控制, 不具有OID//注意:TO对象不具有OID,一旦为TO赋值OID,那么此时就不是TO ——————>DO(托管)持久化状态:持久化对象(PO)受Hibernate控制,具有OID托管状态:托管对象 (DO)不受Hibernate控制, 具有OID3.三种状态间切换时机TO:new创建的对象,并且不携带OID// NEW USER()USER.ID=NULL为DO对象设置OID为null// setDO(OID = NULL)PO:// CRUD 操作后 获取到的对象save后的对象update后的对象saveOrUpdate后的对象merge后的对象// merge:合并delete后的对象load/get 等通过查询方法获取到的对象Query 、 Criteria 读取的数据对应的对象DO:Session关闭后,在此Session操作过程中所有的PO对象手工清除session中的某个PO(特殊)为TO对象指定OID4.TO,PO,DO的区别A.是否受控(Session)//PO(受控) ————DO(不受控)B.是否具有OID//TO(无OID)————DO(有OID) 22.Hibernate一级缓存:1.什么是缓存缓存是临时存放数据的空间,并不是真实数据的存放位置,但是与真实数据拥有一一对应的关系。为了加速查询,才有了缓存,如果从缓存中能够获取到数据,就不从真实存放数据的地方获取.2.Hibernate支持的缓存一级缓存(Hibernate自身携带)// 自带二级缓存(使用外部技术)// 外部3.一级缓存的位置// 绑定session 一对一: 一级缓存——————session一级缓存是绑定Session对象的,每个Session对象会对应一个自己的一级缓存空间,// session间 不共享 缓存多个Session不共享该区域,不同的Session拥有不同的一级缓存区域.4.一级缓存存在性验证A.开启全新的Session,此时一级缓存中没有数据B.加载一个数据(查询),由于一级缓存中不存在该数据,所以要查询数据库,执行SQL,此时一级缓存存有该数据C.使用上述Session对象再次读取该数据(查询),由于一级缓存中存在该数据,不查询数据库// 使用同一个session再次查询 , 从缓存获取 , 没有发送SQLD.重新开启全新Session,读取上一获取的数据,执行SQL// 使用不同 session 再次查询 , 重新发送了SQL , 不同session间5.load与get的区别区别一:get 是立即加载// get ——————立即加载出所有属性load是延迟加载(OID之外的属性)// load——————延迟加载 (仅立即加载OID)通过设置 xxxModel.hbm.xml 文件的class元素lazy属性,修改延迟加载策略,默认lazy="true"<class name="cn.it.h3.cache.UserModel" table="tbl_user" lazy="true">区别二:get 获取不存在的OID数据,返回null// nullload 获取不存在的OID数据,抛出异常// 异常区别三:get 查询的数据是 模型数据对象// User模型对象load查询的数据是 模型数据代理对象//User_$$_Proxy代理对象// 注意:load方法获取的代理对象,最初只有OID数据, 当访问OID之外的任何属性时,执行SQL, 并对该对象的所有属性赋值 6.影响一级缓存的操作Hibernate使用OID进行数据读取操作如果一级缓存中存在该数据,直接取出使用如果一级缓存中不存在,执行SQL语句从数据库中获取,并加载入一级缓存Hibernate进行数据增删改操作将待操作的数据及操作模式放入一级缓存Hibernate使用SQL语句相关操作读取数据无论是Query还是Criteria对象执行的 查询结果 都进入一级缓存(写入)// Query / Criteria只放无论是Query还是Criteria对象执行的 查询 均不检测 一级缓存(不读取)// 不取 每次查询都重新发送SQL 7.刷新一级缓存刷新一级缓存操作即将一级缓存中的数据与数据库同步//与数据库同步 执行SQL条件:数据区与快照区的数据是否相同三种方式:1.提交事务刷新2.手工刷新(强制)3.关闭Session刷新(基于自动提交事务)// JDBC 仅 连接数据库 + 发送SQL到数据库端8.查询快照 // 执行SQL —————— 数据库A.快照区保存了一份与一级缓存相对应的数据,但是并不是真正的对象B.刷新一级缓存时,执行SQL语句的判定标准为数据区与快照区的数据是否相同,// 如果不同,生成SQL语句,如果相同则不做任何操作//注意:生成SQL语句后,并不是立即执行该SQL语句,还要基于事务是否提交。此操作仅仅是将SQL语句发送到数据库端,并存在于执行序列中,而不是真正的执行。//补充:JDBC技术完成了连接数据库,并将执行的SQL语句发送到数据库端的操作,JDBC本身不具有执行解析SQL语句的能力,完成执行SQL解析SQL任务的是数据库。C.执行任意查询操作时,将查询的数据封装为对象,保存到一级缓存数据区中,Hibernate使用OID管理数据区中的数据,同时将查询数据的快照信息保存到快照区D.执行增删改操作,将数据放入数据区,快照区不保存对应的数据。Update操作仅仅是将DO转换为PO,具有更新能力的不是该操作,是快照区的数据比对。// 增删改 数据是放在数据区 , 真正具有更新能力的是 快照区的数据比对9.一级缓存操作API:s.flush();刷新一级缓存(手工强制刷新:与快照数据进行比对后,生成SQL语句)// s.flush();s.clear();清除一级缓存// s.clear();s.evict(obj);// s.evict(obj);清除一级缓存中指定对象s.refresh(obj);按照OID重新加载一级缓存中的PO(使用数据库中数据覆盖一级缓存数据及快照数据)10.一级缓存的刷新时机一级缓存的刷新时机可以通过设置,进行修改s.setFlushModel(FlushModel.常量);ALWAYS:任意操作导致刷新(效率过低)AUTO:根据操作功能区别是否刷新(默认)COMMIT:提交事务时刷新MANUAL:手动执行刷新时进行刷新(提交事务不会触发) 23.基本操作对一级缓存的影响save :// save() : TO————————>POsave操作是将TO转换为POupdate :1.update操作是将DO转换为PO2.由于update操作将DO转化为PO后,快照区域并没有对应的比对数据,因此Hibernate强制此操作执行一次更新指令,可以通过配置,在update执行前,将对应的快照区数据加载,避免进行没有意义的更新操作在hbm.xml文件class元素中添加配置,修改前进行查询,判断是否进行update操作<class select-before-update="true">//修改前,先select一份DB数据到快照,比对 缓存和快照 数据, 不同才执行更新3.update操作是将DO转换为PO,如果该对象不具有OID,则为TO,此时将抛出异常4.update操作将DO转换为PO时,如果一级缓存数据区已经存在使用了该OID对象,抛出异常//Hibernate内部区分对象采用OID进行,如果存在两个相同的OID对象,Hibernate将无法管理总结:一级缓存中,同一个时间点不能两个PO具有相同的OIDmerge:当需要将DO对象放入Hibernate一级缓存时,为避免一级缓存中已经存在有该数据,可以使用合并操作,避免错误的发生A.如果存在相同的OID对象,将当前操作对象的属性合并到一级缓存中已有的对象中(OID相同),使用当前对象的属性值覆盖原始对象B.如果不存在相同的OID对象,将当前操作对象放入一级缓存saveOrUpdate:将一个应用程序提供的对象放入一级缓存时,要根据对象是否携带OID判断使用何种操作。为了进一步的兼容该操作,使用saveOrUpdate操作,Hibernate可以根据对象的状态来判断进行何种操作如果是TO,执行save判断对象是否是TO的标准:1.OID为null2.OID值与 id元素配置unsave-value值相同<id unsave-value="”44”">如果是PO,不执行任何操作如果是DO,执行updatedelete:如果OID不存在,抛出异常 24.Session对象管理1.Session管理的三种形式Session对象生命周期由开发者管理Session对象生命周期与JTA事务绑定//JTA: 事物处理构架(Java Transaction Architecture)Session对象生命周期由本地线程管理2.Session对象由SessionFactory创建,一旦关闭,将不再执行任何与数据库相关的操作。但是当某个延迟加载的数据在关闭Session后,试图执行Session延迟加载操作时,会引发Session对象已经关闭异常。解决该问题,需要将Session对象的操作范围扩大。通过当前线程绑定Session来完成。3.使用本地线程绑定SessionA.在cfg.xml文件中设置线程管理Session<property name="current_session_context_class">thread</property>B.在任意位置如果要使用Session,使用下列方式获取// sessionFactory.getCurrentSession();获取SessionFactory sf;通过SessionFactory对象获取当前线程绑定的Session对象sf.getCurrentSession();C.优缺点:优点:可以在任意层获取Session对象,主要用于在业务逻辑层开启事务缺点:紧耦合 25.关联关系介绍一对一A中包含B的对象,B中包含A的对象一对多A中包含B的集合,B中包含A的对象多对多A中包含B的集合,B中包含A的集合 /**一对多配置一名老师对应多名学生1.表结构tbl_teacherCREATE TABLE `tbl_teacher` ( `uuid` bigint(10) NOT NULL auto_increment, `teacherName` varchar(30) NOT NULL, `nick` varchar(30) default NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8tbl_studentCREATE TABLE `tbl_student` ( `uuid` bigint(10) NOT NULL auto_increment, `studentName` varchar(30) NOT NULL, `className` varchar(30) NOT NULL, `teacherUuid` bigint(10) default NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf82.模型TeahcerModel 包含学生对象的集合public class TeacherModel {private Long uuid;private String teacherName;private String nick;//在一方添加多方的集合private Set<studentmodel> students;}private Set<studentmodel> students = new HashSet<studentmodel>();// 注意:为了方便操作,通常为集合进行初始化,不初始化也可以StudentModel 包含教师的对象public class StudentModel {private Long uuid;private String studentName;private String className;//在多方添加一方的对象private TeacherModel teacher ;}3.配置TeacherModel.hbm.xml(教师)<hibernate-mapping package="cn.it.h3.realtion.one2many.vo"><class name="TeacherModel" table="tbl_teacher"><id name="uuid"><generator class="native"></generator></id><property name="teacherName"><property name="nick"><!-- name:集合对象名 --><set name="students"><!-- 使用key描述对应的关系表中的外键字段 --><!-- column:外键字段名(student表中) --><key column="teacherUuid">// 注意:key元素必须出现在one-to-many元素的前面,否则报错<!-- 使用one-to-many配置关联关系 --><!-- class:配置对应的关系模型类 --><one-to-many class="cn.it.h3.one2many.StudentModel"></one-to-many></key></set></property></property></class></hibernate-mapping>StudentModel.hbm.xml(学生)<hibernate-mapping package="cn.it.h3.realtion.one2many.vo"><class name="StudentModel" table="tbl_student"><id name="uuid"><generator class="native"></generator></id><property name="studentName"><property name="className"><!-- 配置多对一关系中的一方对象 --><!-- many-to-one描述多对一关系 --><!-- name:对象名 --><!-- column:外键字段名(student表中) --><!-- class:配置对应的关系模型类 --><many-to-one name="teacher" column="teacherUuid" class="cn.it.h3.one2many.TeacherModel"></many-to-one></property></property></class></hibernate-mapping>4.资源注册:<mapping resource="cn/it/h3/one2many/vo/StudentModel.hbm.xml"><mapping resource="cn/it/h3/one2many/vo/TeacherModel.hbm.xml">5.补充两边的配置如果只配置一方,也可以使用如果仅配置老师对象:可以通过老师找到学生,但是不能通过学生找到老师如果仅配置学生对象:可以通过学生找到老师,但是不能通过老师找到学生*/26.一对多应用操作1.单独添加与单表操作完全相同说明:由于单独添加学生时,没有设定与老师的关系,故外键字段为空,如果数据库表中设置不允许为空,将与数据库表约束冲突,抛出异常2.同时添加在进行具有关联关系的对象同时添加时首先绑定对象间的关系将多方对象添加到一方对象的集合中tm.getStudents().add(sm1);tm.getStudents().add(sm2);设置多方的关联对象为一方对象sm1.setTeacher(tm);sm2.setTeacher(tm);然后全部添加s.save(tm);// 保存老师的同时,将老师关联下的学生的s.save(sm1);s.save(sm2);说明: // 关于在多的一方(学生)中设置的老师外键:A.保存老师对象时,由于设置了学生集合,在学生数据添加完毕后,将每个学生的外键字段设置为老师对象的OID,伴随执行N条update语句// 老师维护一次B.保存学生对象时,由于设置了老师对象,在学生数据添加的SQL语句中,自动添加了外键// 学生又维护一次C.此处外键被添加了一次,又被修改了一次,从执行最终结果看,没有差别,但是效率方面多执行了N条update语句// 效率低下3.级联添加// cascade :级联级联操作指操作当前数据时,将关联数据也进行操作。例如级联添加指,保存当前数据的同时,保存/修改关联数据。首先绑定对象间的关系将多方对象添加到一方对象的集合中tm.getStudents().add(sm1);tm.getStudents().add(sm2);设置多方的关联对象为一方对象sm1.setTeacher(tm);sm2.setTeacher(tm);然后添加老师对象s.save(tm);设置添加老师对象时,级联关联对象<set name="students" cascade="save-update">// save / update 时, 做级联操作!!!说明:设置了set元素中的cascade="save-update"属性后,当保存对应的老师对象时,会检测变量为student的集合中是否保存有数据,如果有数据,将其中的对象进行级联操作。如果被级联的对象是TO,执行save操作,如果被级联的对象是PO,执行update操作。注意:A.当前仅仅设置了老师对学生的级联,因此保存老师时,可以级联学生对象,但是保存学生时不能级联老师对象。可以通过设置many-to-one元素的cascade属性完成学生到老师的级联B.如果未设置级联操作,而保存具有关联关系的老师对象,会关联瞬时对象抛出异常【难点】 // 上述说的都是关联 TO对象,若老师或者学生本身就是PO,虽然是save(),但是实际肯定执行update更新了啊!!!A.如果当前老师对象不是TO,而是PO,也可以进行上述操作,但是由于PO对象一级缓存中已经存在有数据,因此执行的是update语句。B.如果老师对象关联的学生对象不是TO,而是PO,被级联后,执行的不是save操作,而是update操作。C.如果老师对象在数据库中已经存在有关联的学生数据,而操作时老师数据使用DO数据,将会产生关系的变化(inverse属性中详细讲解)应用:级联操作只能从设置了级联关系的一方发起,也可以双方都设置级联,实际开发中通常将级联操作设置在一方对象上,多方对象通常不设置级联。// 在 一方设置(多方的)级联4.单独删除单独删除与单表操作完全相同// update ——————外键————————null说明:单独删除老师对象时,由于老师对象被删除后,对应的学生对象无法关联到相应的数据,因此执行update语句,将所有被删除老师的学生数据外键设置为null的操作5.级联删除级联删除指删除当前数据的同时,将具有关联关系的数据也一删除。如果不存在关联关系数据,则跳过删除关联数据的操作。级联删除的过程如下:A.断开所有关联关系(执行update语句,将外键设置为null)// 外键置为null ———————————updateB.删除所有关联数据(执行delete语句,删除关联数据)// 先删关联对象 ———————————deleteC.删除当前数据(执行delete语句,删除当前数据)// 最后删除自身 ———————————delete在进行级联删除前首先将当前数据从数据库中查询出来,而不是采用DO的形式手工创建,否则无法加载关联关系。读取数据与关联数据TeacherModel tm = (TeacherModel)s.get(TeacherModel.class,2L);// 必须先从 数据库get到PO数据——————————获取关联对象信息然后执行删除s.delete(tm);设置删除老师对象时,级联关联对象<set name="students" cascade="delete,save-update">// 执行 save / delete / update 操作时, 级联增/删/改说明:cascade属性可以设置多个,中间使用逗号分隔// 可设置多个 cascade="xx,yy,zz..."注意:如果未设置级联操作,而删除老师对象时,仅仅将关联关系外键设置为null,将老师数据删除,学生数据保留。// 未设置 级联 ——————依然会将 外键置为null // (不然也没法删除啊 , 不解除关系的话...)【难点】如果老师对象在数据库中已经存在有关联的学生数据,而操作时老师数据使用DO数据,将会产生关系的变化(inverse属性中详细讲解)应用:级联删除操作只能从设置了级联操作的一方发起,也可以双方都设置级联操作,实际开发中通常将级联操作设置在一方对象上,多方对象通常不设置级联。也就是说可以删除部门时,将部门员工都删除,但是删除员工时,很少出现将部门删除的情况。6.孤子删除// orphan:孤儿 触发条件为: 解除关联操作——————————外键置为null当数据已经存在有关联关系时,如果解除关联关系,会出现多方数据无法找到对应的关联数据的现象发生,此数据将无法被正常关联到,造成数据库中出现垃圾数据。为了避免此种数据的出现,设置当某个数据无法被关联时,直接将该数据删除。此种删除操作称为孤儿删除/孤子删除。读取数据与关联数据TeacherModel tm = (TeacherModel)s.get(TeacherModel.class,2L);StudentModel sm = (StudentModel)s.get(StudentModel.class,4L);然后解除关系tm.getStudents.remove(sm);// 触发条件为: 解除关联操作——————————外键置为null并 不要求你做删除主表对象操作sm.setTeacher(null);//此步骤可以省略设置老师对象级联关联具有孤子删除效果<set name="students" cascade="delete-orphan,delete,save-update">// 执行 save / delete / update 操作时, 级联增/删/改 / 孤子删除解除一对多中的关联关系时,未被关联的数据将被删除掉// 触发条件为: 解除关联操作——————————外键置为null 即可, 并不要求你做删除主表对象操作7.级联操作的种类allall-delete-orphanall,delete-orphan8.关系维护能力// inverse:反转在进行关联关系对象操作时,关系维护发生在关联关系的双方对象,造成关系维护进行多次的现象发生。通过配置,设置一方失去关系维护权。<set name="”students”" inverse="”true”">// 当前对象 放弃关系维护权!!! ————————> 交给 被关联 对象管理!!!设置inverse=”true”后,当前对象在进行任何操作时,将不再对关系进行维护。交由被关联对象进行关系维护。说明:在一对多关系中,将一方对象设置为放弃关系维护。由于外键属性在多方对象对应的数据库表中,因此多方对象无法设置放弃关系维护。// 一对多中, 只能一方 inverse=”true” ———————————— 外键在 多方对象中 , 其无法放弃!!!9.inverse与cascade区别cascade:用于设置级联操作能力,操作的内容为关联对象// cascade —————— 级联 —————————— 操作—————> 关联对象inverse:用于设置关系维护能力,操作的内容为外键字段// inverse —————— 关系维护 ——————— 操作—————> 外键注意:由于inverse=”false”是默认值,容易造成cascade属性即设置对象操作又维护关系操作的假象10.开发案例详解场景:一个部门中具有20名员工业务:修改部门名称操作:操作由页面发起,页面填写部门数据,struts2的Action得到页面封装的数据。问题:此时如果直接对收集到的对象进行update,那么当前对象是一个DO对象(具有OID),在进行更新操作时,由于未从页面上收集到对应的20名员工信息,此时员工信息集合中没有数据。如果没有设置部门对员工的集合失去关系维护,那么在进行更新操作时,Hibernate会认为该对象不关联任何数据,执行将关联数据表中所有外键为当前部门OID的数据的外键设置为null的操作。一旦更新完毕,就断开了20名员工与部门间的关系。解决方案:一.设置部门失去关系维护能力(常见)// 部门 inverse=”false”不维护外键二.在业务层使用传递的OID重新加载部门数据,操作后将获取到对应的员工信息集合数据。此时将外部收集的数据set到部门对象中,使用快照进行更新(针对某些业务较为实用)// get到部门数据 快照更新:set部门名称 即可, 自动更新名称,其他数据不动! 27.多对多配置一名老师对应多名学生,一名学生对应多名老师,形成多对多关系,通过中间关系表完成/** 1.表结构tbl_teaCREATE TABLE `tbl_tea` ( `uuid` bigint(20) NOT NULL auto_increment, `teaName` varchar(30) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8tbl_stuCREATE TABLE `tbl_stu` ( `uuid` bigint(20) NOT NULL auto_increment, ` stuName` varchar(30) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8tbl_tea_stu(关系表)CREATE TABLE `tbl_tea_stu` ( `uuid` bigint(20) NOT NULL auto_increment, `teaUuid` bigint(20) default NULL, `stuUuid` bigint(20) default NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8*//**2.模型TeahcerModel 包含学生对象的集合public class TeaModel {private Long uuid;private String teaName;//声明一个老师对应多个学生private Set<stumodel> students = new HashSet<stumodel>();}StudentModel 包含教师对象的集合public class StuModel {private Long uuid;private String stuName;//声明一个学生对应多个老师private Set<teamodel> teachers = new HashSet<teamodel>();}*/3.配置<!-- 配置多对多的关系 --><!-- set:描述当前模型对应多个数据的关系模型 --><!-- set:name:当前模型的集合属性名 -->// 当前模型类(老师) 内部<!-- set:table:多对多关联关系中的关系表名 --><set name="students" table="tbl_tea_stu">// 当前模型类的 集合字段与 字段的所在的关系表(此处双边都一样,因为是多对多的第三方关系表)<!-- key:描述映射关系 -->// 外部 ———————— 被关联对象在 本对象中的映射 关系字段<!-- key:column:当前模型在关系表中对应的外键字段 --><key column="teaUuid"> // 当前模型类 在 第三方关系表 中的外键字段<!-- many-to-many:描述多对多关系 --><!-- many-to-many:class:多对多关系中被关联模型类名 --><!-- many-to-many:column:当前模型被关联模型在关系表中外键字段 --><many-to-many column="stuUuid" class="cn.it.h3.many2many.StuModel"></many-to-many></key></set>两边的配置是相同的,差别在于不同模型对应的外键与模型类名不同4.资源注册:// 在cfg.xml中注册 上面配置的 映射资源<mapping resource="cn/it/h3/many2many/TeaModel.hbm.xml"><mapping resource="cn/it/h3/many2many/StuModel.hbm.xml">5.补充/**两边的配置如果只配置一方,也可以使用如果仅配置老师对象:可以通过老师找到学生,但是不能通过学生找到老师如果仅配置学生对象:可以通过学生找到老师,但是不能通过老师找到学生*/ 28.多对多应用操作:1.单独添加与单表操作完全相同说明:单独添加任何数据时,由于不存在关联关系,中间关系表不产生任何数据2.同时添加:添加学生对象到老师的集合关系tm.getStudents.add(sm1);tm.getStudents.add(sm2);添加老师对象到学生的集合关系sm1.getTeachers.add(tm);sm2.getTeachers.add(tm);由于保存老师对象时,需要对关系进行维护(默认的inverse=”false”),所以在关系表中添加了数据由于保存学生对象时,需要对关系进行维护(默认的inverse=”false”),所以在关系表中添加了数据结论:关系维护进行了两次,数据出现了错误解决方案1:只添加老师或者学生结论:保存的对象关联了TO,抛出异常解决方案2:配置级联添加结论:由于老师对象save时维护关系,进行了关系表中数据的添加;保存老师对象时,级联到学生对象也进行保存。而学生对象也要进行了关系维护。所以关系维护仍然进行两次//解决方案3:将其中一方设置为失去关系维护权(参看级联添加)3.级联添加: 设置老师端级联添加,同时失去关系维护能力<set name="”students”" cascade="”save-update”" inverse="”true”">//通常由 失去关系维护权的一方   发起级联注意:通常由失去关系维护权的一方发起级联// 则另一方 就不用做啥设置了 本来就 inverse=”false”说明:此处也可以设置学生端失去维护权,在实际的开发过程中,需要根据需求进行设定// 而我们 又不需要它来做级联 so,不用任何设置! 例如:部门与员工间的关系,往往设置部门失去关系维护能力 例如:主订单与子订单,往往设置主订单级联子订单,主订单失去关系维护能力4.添加关系添加关系指将没有关系的两条数据建立关系1.首先读取已存在数据(如果使用DO对象会引发丢失关联数据的现象:见一对多知识点10)TeaModel tm = (TeaModel)s.get(TeaModel.class,3L);StuModel sm = (StuModel)s.get(StuModel.class,5L);2.相互绑定关系(当前配置:TeaModel中的students集合失去关系维护权)// 此处 只需对 有关系维护权的一方设置 关联对象即可tm.getStudents.add(sm);A// 另一方 又没有关系维护权, 你设置了 又有啥卵用!eg:此处A就可略!sm.getTeachers.add(tm);B// 由于两个数据都是PO,使用快照的工作原理,无需执行update操作,即可进行更新。注意:上述操作中A的工作实际上是无效的,因为教师端不维护关系,即便是发生了变化也不产生任何SQL语句,只需要对能够进行关系维护的对象进行关联关系操作即可。5.删除关系删除关系指将存在关系的两条数据断开关系// 先读取get到PO 再解除remove// PO对象 自动快照更新, 我们remove————外键置为null即可, 1.首先读取已存在数据(如果使用DO对象会引发丢失关联数据的现象:见一对多知识点10)// hibernate会自动更新外键值到数据库端!TeaModel tm = (TeaModel)s.get(TeaModel.class,3L);StuModel sm = (StuModel)s.get(StuModel.class,5L);2.相互解除关系(当前配置:TeaModel中的students集合失去关系维护权)tm.getStudents.remove(sm);A// 同上, TeaModel 根本无 关系维护权 , 无法操作外键!!! 此处,属无效操作!sm.getTeachers.remove(tm);B由于两个数据都是PO,使用快照的工作原理,无需执行update操作,即可进行更新。注意:上述操作中A的工作实际上是无效的,因为教师端不维护关系,即便是发生了变化也不产生任何SQL语句,只需要对能够进行关系维护的对象进行关联关系操作即可。6.修改关系// 重新定义关系 ————本质上是:修改外键的值删除 旧关系——————————再建立全新的关系// s多对多关联关系中的修改关系不要简单的看成是对关系表中数据的更新,此处的修改关系应该理解为重新定义关系,即删除之前所有的关系,建立全新的关系。案例分析:一名老师对应3名学生ABC,如果将C换成D的话,关系维护做修改可以完成,但是如果将C换成DE,关系维护做修改是无法完成的。所以需要删除之前的C数据,添加DE数据。针对Hibernate如果只对CDE进行维护,就意味着需要在另一个区域保存哪些数据发生了变化,哪些数据没有发生变化。Hibernate内部实现采用将原始关系全部删除,重新建立所有关系的策略完成修改关系分为两个步骤(当前配置:TeaModel中的students集合失去关系维护权)TeaModel tm = (TeaModel)s.get(TeaModel.class,3L);StuModel sm1 = (StuModel)s.get(StuModel.class,5L);StuModel sm2 = (StuModel)s.get(StuModel.class,6L);1.断开关系(同删除关系)// 当然 先get到PO时前提tm.getStudents().remove(sm1);A// 無法操縱外鍵,屬無效操縱!sm1.getTeachers().remove(tm);B// B/D才是维护关系的一方!仅此remove外键为null即可 此处为第一步 删除旧关系2.建立全新的关系(同添加关系)tm.getStudents().add(sm2);C// 同上,無關係維護權!sm2.getTeachers().add(tm);D// 此处是 再重新为 外键赋新值 即建立新的映射关系注意:上述操作中AC的工作实际上是无效的,因为教师端不维护关系,即便是发生了变化也不产生任何SQL语句,只需要对能够进行关系维护的对象进行关联关系操作即可。7.单独删除: // 前提: get到PO关系维护方发起删除 :先解除关系,再单独删除要删除的数据!进行删除数据时,必须先从数据库中获取数据,而不能使用DO数据,DO数据中不具有关联关系数据,所以无法在删除数据时完成有效的关系维护。删除操作只能从具有关系维护权的一方发起,不具有关系维护权的一方如果发起删除操作将无法对关系表中的数据进行删除,出现数据错误。8.级联删除级联删除操作在实际业务中几乎不会出现在多对多的场景中// 实际开发,不会用级联删除的. 安全起见!29.实体参数:1.HQL查询设置实体参数格式一:q.setEntity(“参数名称”,实体对象);格式一:q.setParameter(“参数名称”,实体对象);(通用格式)eg:String hql = "from StudentModel where teacher = :teacher";Query q = s.createQuery(hql);TeacherModel tm = new TeacherModel();tm.setUuid(2L);q.setEntity("teacher", tm);2.QBC查询设置实体参数范例:Criteria c = s.createCriteria(StudentModel.class);TeacherModel tm = new TeacherModel();tm.setUuid(3L);c.add(Restrictions.eq("teacher", tm));3.使用关联数据OID之外的属性作为HQL查询条件(非实体参数格式)String hql = "from StudentModel where teacher.teacherName = ";4.使用关联数据OID之外的属性作为QBC查询条件(非实体参数格式)Criteria c = s.createCriteria(StudentModel.class);//设置别名可以对QBC查询 设置查询条件中的属性名为表名,而不是属性名c.createAlias("teacher", "t");// Alias:别名c.add(Restrictions.eq("t.teacherName", "张三丰"));5.多态查询Criteria c = s.createCriteria(Object.class);查询所有是Object类型的子类类型的数据,实用性不强。=============================================================================================================Tips:下面我们做的测试:都是没有加限制条件的!!!=======================================即:加载主从表的All数据(符合条件的)====================如果加了限制条件,eg:id=xx 那么结果就会只显示关联上的数据!!!30.多表关联查询// 表(类)级别的查询主表(类A)————从表(类B)—————————————— 是整张表(类)信息的查询//主:4 从:40外键关联数据:3——30// HQL多表关联查询 主数据/从数据 有7种语法格式,此处不讨论交叉连接格式// 交叉连接 :笛卡尔积4*40 = 160条记录1.内连接// 30———————— 交集部分数据总量 :有外键关联的数据条数HQL: from TeacherModel tm inner join tm.students数据总量:关联数据总量// 30//关联数据总量=主∩从数据模型:主数据(延迟加载关联数据)+关联数据多条SQL语句// 延迟加载Object[]———————— T + S(lazy)2.迫切内连接// 30HQL:from TeacherModel tm inner join fetch tm.students // ——————————关联学生集合fetch:取来数据总量:关联数据总量// 30数据模型:主数据(含关联数据)不执行SQL语句// 立即加载Object ———————— T(S)3.隐式内连接// 4 ————————— 主表的总记录数(总共4名老师)HQL:from TeacherModel数据总量:主表数据总量数据模型:主数据(延迟加载关联数据)多条SQL完成// 延迟加载Object ———————— T//内连接//数据总量:30条(关联的数据总量)//数据格式:对象数组 ———————————— 数据分开存放————> Object[0]:老师 + Object[1]:学生//数据内容:老师数据(不包含学生数据,延迟加载) 学生数据// 既然都分开存放了, Object[0]:老师中 自然就没有存放学生信息了这里说的不包含,专业术语就叫:延迟加载!你使用的时候,我再重发SQL查询!//迫切内连接//数据总量:30条(关联的数据总量)//数据格式:对象———————————— 数据合并存放 ————> Object:老师 //数据内容:老师数据(包含学生数据,立即加载) ——————————查询学生,不再发送SQL// 合并存放:学生数据一并加载倒了老师对象中//隐式内连接//数据总量:4条(主表数据总量)//数据格式:对象//数据内容:老师数据(不包含学生数据,延迟加载)4.左外连接 // 31 ———————— 30(关) + 1(一名未被关联的老师)HQL:from TeacherModel tm left outer join tm.students数据总量:关联数据总量+主表未关联数据总量数据模型:主数据(延迟加载关联数据)+关联数据多条SQL完成  // 延迟加载5.迫切左外连接// 31 ——————— 30(关) + 1(一名没有外键关系的老师:未被分配学生的老师)HQL:from TeacherModel tm left outer join fetch tm.students数据总量:关联数据总量+主表未关联数据总量数据模型:主数据(含关联数据)不执行SQL // 立即加载6.右外连接 // 40 ——————— 总共40名学生 HQL:from TeacherModel tm right outer join tm.students数据总量:从表数据总量数据模型:主数据(延迟加载关联数据)+关联数据多条SQL语句 // 延迟加载//左外连接//数据总量:31条(关联的数据总量+主表未关联数据总量)//数据格式:对象数组//数据内容:老师数据(不包含学生数据,延迟加载) 学生数据//迫切左外连接//数据总量:31条(关联的数据总量+主表未关联数据总量)//数据格式:对象//数据内容:老师数据(包含学生数据,立即加载) //右外连接//数据总量:40条(关联的数据总量+从表未关联数据总量)/从表数据总量//数据格式:对象数组//数据内容:老师数据(不包含学生数据,延迟加载) 学生数据7.迫切连接与非迫切连接区别A.迫切连接一次性取出主数据和关联数据 非迫切连接不是一次性将所有主数据与关联数据全部取出B.迫切连接查询结果只包含主数据 非迫切连接查询结果包含主数据和从数据C.两种方式获取的数据总量相同8.Criteria进行关联查询仅有两种格式// QBC格式:c.setFetchModel("关联的对象名",关联模式)隐式内连接 FetchModel.SELECT=FetchModel.DEFAULT// 相当于单表操作迫切左外连接 FetchModel.JOIN// 主从数据一次性全部加载 保存到主表对象中(包含从表数据,直接get,不用重发SQL)9.关于两种查询,到底查询的什么,要知道使用它们查询的目的何在???1.多表关联查询:// 表(类) 级别的查询 ———————————— 主表(类A)——————从表(类B)—————————————— 是整张表(类)信息 的查询2.主、从关联数据加载:// 字段(属性) 级别的查询 ———————————— 主表外键字段———从表外键字段—————————————— 表内部 单(多)个关联字段 的查询 31.数据抓取策略// 字段(属性)级别 查询 ———————————— 主表外键字段———从表外键字段————————————表内部 单(多)个关联字段 的查询1.※主关联数据加载策略(设置set元素的fetch与lazy属性)// 主关联: <set>1.fetch:关联数据的获取SQL语句格式select:普通查询subselect:子查询join:OID查询/QBC查询 生成左外连接查询,HQL查询同select相同2.lazy:控制的是关联数据的加载策略false:关联数据立即加载true:关联数据延迟加载,使用时查询3.extra:关联数据超级延迟加载,如果只使用集合大小,那么进行集合数量的SQL查询。如果使用集合中的数据,那么进行集合数据的SQL查询// extra:额外的// 超级懒加载// 集合大小:数量 集合数据:全部信息1.fetch=select lazy=false查询时执行的SQL语句读取主数据:一条SQL语句读取主数据集合数据:N条SQL语句A:获取集合数据总量:不执行SQL语句B:获取集合具体数据:不执行SQL语句2.fetch=select lazy= true查询时执行的SQL语句读取主数据:一条SQL语句A:获取集合数据总量:按需执行N条SQL语句S//直接查询 集合所有信息B:获取集合具体数据:按需执行N条SQL语句S// 同上...//A与B操作,无论谁先执行,另一个都将不执行3.fetch=select lazy= extra查询时执行的SQL语句读取主数据:一条SQL语句A:获取集合数据总量:按需执行N条SQL语句S1(count)// 仅查询 数量B:获取集合具体数据:按需执行N条SQL语句S2//查询 全部信息//如果A先执行,那么B仍然执行,如果B先执行,A就不执行4.fetch=subselect lazy=false查询时执行的SQL语句读取主数据:一条SQL语句读取主数据集合数据:一条子查询SQL语句A:获取集合数据总量:不执行SQL语句B:获取集合具体数据:不执行SQL语句5.fetch= subselect lazy= true查询时执行的SQL语句读取主数据:一条SQL语句A:获取集合数据总量:按需执行一条子查询SQL语句SB:获取集合具体数据:按需执行一条子查询SQL语句S//A与B操作,无论谁先执行,另一个都将不执行6.fetch= subselect lazy= extra查询时执行的SQL语句读取主数据:一条SQL语句A:获取集合数据总量:按需执行N条SQL语句S1(count)// 仅查询 数量B:获取集合具体数据:执行一条子查询SQL语句S2// 查询 所有信息// 如果A先执行,那么B仍然执行,如果B先执行,A就不执行7.fetch= join 相当于fetch=select (使用HQL查询————情况1,2,3)8.fetch= join 此时lazy失效(使用QBC) // QBC ——————OID————————get/load查询时执行的SQL语句读取主数据:一条左外连接查询SQL语句A:获取集合数据总量:不执行SQL语句B:获取集合具体数据:不执行SQL语句9.fetch= join 此时lazy失效(使用OID get/load查询,效果同QBC查询————情况8)在进行查询时,如果执行的查询语句具有多条相同的操作,可以进行优化2.※从关联数据加载策略(设置many-to-one元素的fetch与lazy属性)//从关联: <many-to-one>fetch:关联数据的获取SQL语句格式select:普通查询语句join:Query查询 join=select OID查询 生成左外连接查询lazy:控制的是关联数据的加载策略false: 直接加载proxy:交由关联的对象自己本身的lazy属性(被关联数据的class元素lazy属性)进行控制// = 代理对象 的lazy属性no-proxy(Hibernate拦截器功能支持)1.fetch=”select” lazy=”false”执行查询获取从数据:执行一条SQL语句获取从关联数据:执行N条SQL语句读取从关联数据:不执行SQL语句2.fetch=”select” lazy=”proxy”执行查询获取从数据:执行一条SQL语句被关联对象lazy=false:获取从关联数据:执行N条SQL语句读取从关联数据:被关联对象lazy=true:按需要执行对应的SQL语句(如果内存中存在就不执行)3.使用HQL查询时,fetch=”join”等同于fetch=”select”4.使用QBC查询时,fetch=”join”时,lazy失效执行查询获取从数据:执行一条左外连接查询SQL语句读取从关联数据:不执行SQL语句3.※关联数据加载策略优化当使用关联数据时,被关联的数据如果加载时执行多条SQL语句,会对数据库服务器造成巨大的压力设置batch-size属性,控制获取数据时,每次获取的数据总条目数set batch-size="10" 一次获取10条信息方案缺点:如果设置batch-size值过大,那么每次获取的数据量过大,内存消耗严重********容易混淆:A:6条SQL语句:可以用于控制加载主数据或从数据的SQLB:3*3和2*2配置:用于控制加载主关联数据或从关联数据的SQL和加载策略如果A策略已经将关联数据加载完毕,那么B策略失效如果A策略是延迟加载,此时B策略才生效 32.二级缓存简介1.缓存的作用缓存是为了加快查询速度,当进行查询时,如果缓存中存在有需要查询的数据,直接使用,如果缓存中不存在要查询的数据,从数据库端获取后,提供给用户,并加入缓存,提高下一次查询的速度。缓存的目的是为了加快查询速度,但是缓存并不是最终保存数据的区域。缓存数据可以看做真实数据的一份克隆,将要查询的数据的克隆放置在一个临时存储空间内,查询时访问这个临时空间,而不去访问原始数据存放的位置。注意:A.缓存的数据必须与数据库同步,如果不同步,缓存将是灾难性的设计。// 同步B.如果数据库中的数据发生了修改,对应的缓存数据也要进行同步更新。// 同步修改2.二级缓存:二级缓存是在一级缓存的技术之上增设的一级缓存,目的是弥补一级缓存的不足。Hibernate缓存的优缺点:一级缓存缺点:Session范围的缓存数据,不同的Session不共享数据// Session 级别缓存session间不共享二级缓存优点:有效弥补一级缓存间数据不共享的缺点,在Session间进行数据共享,该级别缓存也称为SessionFactory级别的缓存。// SessionFactory 级别缓存session间共享即同一个SessionFactory开启的Session间共享数据的缓存空间3.二级缓存中存放的数据要求适合加入二级缓存的数据很少被修改的数据(相对而言)不是很重要的数据,允许出现偶尔并发的数据不会被并发访问的数据参考数据不适合加入二级缓存的数据//钱有关经常被修改的数据财务数据,绝对不允许出现并发与其他应用共享的数据 33.二级缓存配置/**1.导入要配置的二级缓存对应jar包(ehcache)导入ehcache对应3个jar包2.在cfg.xml中添加使用二级缓存的配置<!-- 配置当前环境使用二级缓存 --><property name="cache.use_second_level_cache">true</property>3.在cfg.xml中配置二级缓存供应商<!-- 配置二级缓存供应商 --><property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>4.添加二级缓存配置信息,控制哪些数据进入二级缓存格式一:在Model.hbm.xml文件中添加配置<class name="TeacherModel" table="tbl_teacher"><!-- 配置当前模型加入二级缓存 --><cache usage="read-write">//在class元素中,作为子元素出现<id name="uuid"><generator class="native"></generator></id><property name="teacherName"><property name="nick"><set name="students"><!-- 配置当期模型的关联集合数据加入二级缓存 --><cache usage="read-write">//在set元素中,作为子元素出现<key column="teacherUuid"><one-to-many class="StudentModel"></one-to-many></key></cache></set></property></property></cache></class>格式二:在hibernate.cfg.xml文件中进行配置<!-- 配置一个类对象进入二级缓存 --><!-- usage配置的是缓存的级别,通常配置只读缓存read-only和读写缓存read-write --><!-- class配置的是加入缓存的类模型 --><class-cache class="cn.it.h3.cache2.vo.TeacherModel" usage="read-write"><class-cache class="cn.it.h3.cache2.vo.StudentModel" usage="read-write"><!-- 配置一个集合进入二级缓存 --><!-- usage配置的是缓存的级别,通常配置只读缓存read-only和读写缓存read-write --><!-- collection配置的是加入缓存的某个类中的集合对象 --><collection-cache usage="read-write" collection="cn.it.h3.cache2.vo.TeacherModel.students">5.配置ehcache自身设定值在src目录下创建配置文件ehcache.xml<diskstore path="“java.io.tmpdir”/"><!--指定策略二级缓存配置 --><cache name="”my”" maxelementsinmemory="10" eternal="false" timetoidleseconds="120" timetoliveseconds="1200" overflowtodisk="true" maxelementsondisk="10000000" diskpersistent="false" diskexpirythreadintervalseconds="“60'" memorystoreevictionpolicy="LRU" --="" --="" defaultcache="" maxelementsinmemory="10" eternal="false" timetoidleseconds="120" timetoliveseconds="1200" overflowtodisk="true" maxelementsondisk="10000000" diskpersistent="false" diskexpirythreadintervalseconds="“60'" memorystoreevictionpolicy="LRU">*/34.二级缓存操作// 二级缓存对数据的识别采用OID的形式进行,只有仅按照OID的形式获取数据,才能从二级缓存中获取1.验证二级缓存的存在性使用同一个SessionFactory开启两个不同的Session对象AB。使用A读取数据X执行SQL语句,使用B读取数据X不执行SQL语句。说明:二级缓存的开启会导致SessionFactory开启Session时为Session对象附加二级缓存数据索引。该操作是一个动态过程。开启Session的同时会出现二级缓存数据索引的加载。案例一:开启Session对象A开启Session对象B此时加载的二级缓存索引中没有X数据的索引A读取X数据B读取X数据关闭A关闭B以上操作执行两次SQL语句案例二:开启Session对象AA读取X数据此时X数据进入二级缓存,同步更新二级缓存索引开启Session对象B此时加载的二级缓存索引中有X数据的索引B读取X数据关闭A关闭B以上操作执行一次SQL语句2.基本操作对缓存数据影响A.load/get查找一级缓存,如果存在返回,如果不存在向下查找二级缓存,如果存在,将数据读取到一级缓存,由一级缓存将数据返回,如果不存在向下查找数据库,执行SQL,将返回数据存储一级缓存,一级缓存将数据存储二级缓存B.SQL查询查找数据库,执行SQL,将返回数据存储一级缓存,一级缓存将数据存储二级缓存注意:SQL类别的查询不读取二级缓存数据。因为Hibernate仅仅完成连接数据库,将执行的SQL序列发送到数据库的任务,Hibernate本身不具有解析执行SQL语句的能力,所以无法分辨哪些数据符合要求,因此SQL类别的查询无法从二级缓存中直接获取数据查询操作对二级缓存数据影响总结:1.自定义生成SQL语句查询内容不从二级缓存获取数据2.二级缓存获取数据按OID获取3.从数据库查询的信息全部进入二级缓存C.添加数据更新所在一级缓存的数据更新数据库对应数据// 注意:添加操作不影响二级缓存,添加操作不能保障数据的执行确定性。如果加入二级缓存会造成缓存与数据库数据不同步现象的发生save的是TO对象 不影响二级D.删除、修改数据更新所在一级缓存的数据更新所在二级缓存的数据更新数据库对应数据// 说明:删除与修改数据时,操作数据均携带有OID值,该值即Hibernate区分对象所使用的唯一标识。此类数据影响二级缓存删改的是PO对象 影响二级3.二级缓存存储区域共划分为4个区域1.类对象缓存区域存储内容:普通对象(单表)一对多主数据一对多主关联数据(单一对象数据,非集合数据)一对多从数据一对多从关联数据(单一对象数据,非集合数据)多对多主数据多对多主关联数据(单一对象数据,非集合数据)多对多从数据多对多从关联数据(单一对象数据,非集合数据)一对一主数据一对一从数据一对一主关联数据一对一从关联数据存储格式:散装数据:将一个数据拆分开,然后分别存储独立的数据。散装数据的存储格式不是Java对象,该格式有二级缓存供应商决定。就像是数据库表中的数据存储具有自身特定格式一样注意:一级缓存从二级缓存中获取的数据不是对象,而是将一些散装数据重新组装成对象后加入一级缓存// 上述数据均是单一对象的格式// 二级缓存中存储的数据不是Java对象的格式,而是原始数据的散装数据,简言之就是将原始对象的数据分散存储,便于管理,获取时重新组装成对象2.集合对象缓存区域A.集合数据区存储的不是具体的数据,而是每一个数据对应的编号,该编号服务于特定的二级缓存,其值可以理解为Hibernate中管理对应使用的OIDB.关联关系中不存储具体数据,具体数据在类对象缓存区域存储说明:此种保存数据的格式可以有效的提高数据存储量,避免浪费大量的空间。每次获取集合数据时,首先从集合数据区取出所有的数据编号,然后再从类对象缓存区根据编号获取数据。 最后将取出的散装数据按照Java的格式进行组装,最后放入一级缓存。3.更新标识时间戳区域保存数据是否需要更新的标识信息。当执行DML格式更新数据库,而不是使用常规的更新时,由于无法预计该操作都对哪些数据进行了影响,因此需要将此类数据进行标识,防止数据不同步现象的发生。因此读取二级缓存数据时,如果发现该数据具有更新标识,马上进行二次加载,避免数据不同步。Query q = s.createQuery("update TeacherModel set teacherName=:teacherName where uuid = :uuid");q.setString("teacherName", "测试更新1");q.setLong("uuid", 4L);q.executeUpdate();4.查询缓存区域// 查询缓存使用查询的最终SQL语句作为Key查询的结果作为Value弥补二级缓存只能依赖仅使用OID进行查询的缺陷!A.用于保存使用非OID读取数据格式的查询结果。B.查询缓存是将每次查询的SQL语句与查询的结果全部保存起来,数据存储量较大。//注意:select * from tbl_user where uuid = 1 与 select * from tbl_user where 1= uuid是两种不同查询C.查询缓存开启方式首先:在hibernate.cfg.xml文件中开启查询缓存功能<property name="cache.use_query_cache">true</property>然后:针对某一次的查询设定是否使用查询缓存Query q = s.createQuery(“hql”);q.setCacheable(true);注意:q.setCacheable(true)将设置该查询数据与查询缓存同步。每次执行均要设置该操作。数据存储格式: SQL(KEY)数据(VALUE)select * from tbl_teacher 集合数据1select * from tbl_teacher where uuid = 1集合数据2select * from tbl_teacher where age>10集合数据3总结:查询缓存使用时,一定要小心,不要将所有的操作都加入查询缓存,加入查询缓存的操作一定是频度非常高的查询操作(由OID获取数据)类对象缓存区 ← ← ← ← ← ← ← ← 集合对象缓存区↑↑(确认是否二次加载)↑更新时间戳区 查询缓存区 /**补1:一对一外键约束配置1.表结构CREATE TABLE `tbl_hus` ( `uuid` bigint(10) NOT NULL auto_increment, `name` varchar(30) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8CREATE TABLE `tbl_wife` ( `uuid` bigint(10) NOT NULL auto_increment, `name` varchar(30) NOT NULL, `husUuid` bigint(10) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf82.模型主方:配置从方的对象从方:配置主方的对象3.配置主方:<!-- 不具有外键的一方(主方)模型中配置one-to-one --> <!-- name:配置的是模型中的关联对象名 --> <!-- class:配置的是模型中的关联对象类名 --> <!-- property-ref:一对一关系中对方对象中的关系对象名 --> <one-to-one name="wife" class="WifeModel" property-ref="husband">从方:<!-- 具有外键的一方(从方)模型中配置many-to-one --> <!-- name:配置的是模型中的关联对象名 --> <!-- class:配置的是模型中的关联对象类名 --> <!-- column:配置的是模型中的外键字段名 --> <!-- unique:配置many-to-one的关系为唯一 --> <many-to-one name="husband" class="HusbandModel" column="husbandUuid" unique="true">4.资源注册 <mapping resource="cn/it/h3/one2one/vo/HusbandModel.hbm.xml"> <mapping resource="cn/it/h3/one2one/vo/WifeModel.hbm.xml">*//**补2:一对一主外键约束配置1.表CREATE TABLE `tbl_hus` ( `uuid` bigint(10) NOT NULL auto_increment, `name` varchar(30) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8CREATE TABLE `tbl_wife` (`uuid` bigint(10) NOT NULL, `name` varchar(30) NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf82.模型主方:配置从方的对象从方:配置主方的对象3.配置主方: <!-- 一对一主外键约束配置方式 --> <!--name:模型中对应的关系对象名--><!--class:模型中对应的关系对象类名--> <one-to-one name="wife" class="WifeModel">从方: <!-- 一对一主外键约束配置方式one-to-one --> <!-- constrained:设置当前对应模型主键是采用对方的外键形式进行约束 --> <one-to-one name="husband" class="HusbandModel" constrained="true">主键配置:<!-- 主键生成策略要根据一对一的主外键格式进行修改 --> <!-- 主键约束方式修改:由外键提供 class="foreign" --> <!-- 为主键生成从外部提供设置由谁提供 --> <!-- param为由参数提供 --> <!-- param:name 表示提供方式属性提供 --> <!-- 提供数据的属性名设置为param的值 --> <id name="uuid"> <generator class="foreign"> <param name="property" />husband </generator> </id>*/35.关于查询方式:get/load——————————OIDQuery ——————————HQLCriteria——————————QBC ————>自动生成最差性能SQLSQLQuery——————————使用原生 SQL 语句查询36.关于内外连接:内连接:只有关联的数据才显示外连接:没有关联的数据也需要显示/** * 迫切左外连接 ——————fetch * * 发现:左外连接的时候,加上distinct去重,居然也可以!!!(搞不懂!!!) * 发现:使用distinct,可使 内/外连接 转换为 迫切内/外连接!!!不加distinct,分分钟给你报错!!! * * 综上,这里内外连接均可实现! * 推荐使用: 迫切内/外连接!!! */@Overridepublic List<function> findMenuByUserId() {// 此时返回的是 role_function第三方关系表的数量总和 + auth_function中未关联的数据总量String hql = " "+ " select distinct f "//手动去重+ " FROM Function f left outer join fetch f.roles r " // 即: 主表有且只有一个 ————>那就是FROM xxx————Function 其下的所有从关联数据都是从表1、从表2...//+ " left outer join fetch r.users "// 那么最终显示的都是:主表(Function)与从表1(role)的关联数据 + 主表Function未关联数据 的总和// Tips: 此处测试的是 加载所有数据!!! ——————没有加条件的查询 eg: id=? 左 + 关联//+ " WHERE r.id = ? "// Tips2: 对于加了限制条件的查询 , 则只会加载 主从表相关联的数据 关联//+ " FROM Function f left outer join f.roles r " // 非迫切 ——————>必须加distinct才行!!!否则,报错!!!//+ " left outer join r.users u WHERE u.id = ? "+ " left outer join fetch r.users u WHERE u.id = ? "// Tips2: 对于加了限制条件的查询 , 则只会加载 主从表相关联的数据 关联+ " AND f.generatemenu = '1' order by f.zindex ";List<function> list = this.getHibernateTemplate().find(hql,"1");return list;}===========================================================使用distinct,可使 内/外连接 转换为 迫切内/外连接!!! ===========================================================这里以迫切左外连接为例:(详情见上例)// 这里关联表上是会有很多重复数据的,我们需手动去重:distinct(去重) select distinct f FROM Function f...如果没加限制条件 ——————————eg:FROM Function f left outer join fetch f.roles r 那么此时返回的数据是:Function(未关联总量) + Role_Function(有关联的数据总量)List<function>1 +174// Function + Role + User如果加了限制条件 ——————————eg:FROM Function f left outer join fetch f.roles r WHERE r.id = ? 那么此时返回的数据是:仅为 Role_Function(主表_从表1关联表) 中关联的数据 !!!// Function + Role + UserList<function>5// 另关于3个表以上的关联查询:主表为:Function从表1:Role从表2:User从表3:.........// 主从关联表仅为:Function_Role(主表_从表1)对于3个表以上的 条件关联查询————————获取的就是此表中关联的数据!!!// 从表2、3、...只能作为Function_Role 主从关联表的 限制(剔除不符条件的数据)条件而已!!!// 手动去重—————————select distinct f FROM Function f...// 去重过程:Function[Function [name=f1, roles=[Role [name=r1, users=[User [username=岳不群]]], Role [name=r2, users=[User [username=岳不群]]]]] , Function [name=f1, roles=[Role [name=r1, users=[User [username=岳不群]]], Role [name=r2, users=[User [username=岳不群]]]]] , //distinct(去重)Function [name=f2, roles=[Role [name=r1, users=[User [username=岳不群]]], Role [name=r2, users=[User [username=岳不群]]]]] , Function [name=f2, roles=[Role [name=r1, users=[User [username=岳不群]]], Role [name=r2, users=[User [username=岳不群]]]]] ,//distinct(去重)Function [name=f3, roles=[Role [name=r2, users=[User [username=岳不群]]]]] ] | ————————>Role1 ————————>F1 + F2User1| | ————————>Role2 ————————>F1 + F2 + F3<======>| ————————>Role1 ————————>|F1||User1| ————————>Role2 ————————> || ————————>Role1 ————————>|F2||User1| ————————>Role2 ————————>|| |F3|————————>Role1 ————————>|User1| |==========================================Spring ================================================================================================================================1.Spring是一个基于JAVA的轻量级的J2EE应用框架.IOC容器:控制反转——————管理资源<bean>AOP:面向切面编程 DB:连接数据库(模板)————JDBC、ORM、transaction =================================================== //模板:给个sql,就能操作数据库.其他,不用我们管.// JDBCTemplate、HibernateTemplate、DBCP...JDBC、JDBCUtils、Hibernate、模板1.spring简介1.Spring是一个基于Java的轻量级的J2EE框架基于Java: 底层Java实现轻量级:性能较高,运行速度较快,内存消耗较少重量级组件:EJB、CGIJ2EE应用框架:Spring可以在J2EE开发的三层架构的每一层为开发者带来帮助表现层:Servlet/JSP,Struts2,SpringMVC业务层: Bean管理、AOP、事务管理数据层:JDBC,Hibernate,Spring JDBCTemplate域模型层:实体类+DAO服务层:远程调用(webservice)[类似技术RMI+JNDI]Spring提供的每层的服务技术表现层:String MVC业务逻辑层:Bean管理、AOP、事务管理数据层:DAO支持抽象类,DAO模板技术JDBCTemplate2.Spring的发展历程/**创始人:Rod JohnsonExpert One-to-One J2EE Design and Development(2002)阐述了J2EE使用EJB开发设计的优点及解决方案Expert One-to-One J2EE Development without EJB(2004)阐述了J2EE开发过程中不使用EJB的解决方式(Spring雏形)*/3.Spring核心技术 IOC:控制反转AOP:面向切面编程/面向方面编程Data Access/Integration Web( MVC / Remoting )// Remote:远程JDBCORMWebServletOXMJMSportletStrutsTransactionAOPAspectInstrumentation// aspect:方面;方向instrumentation:仪器,仪表Core ContainerBeansCoreContext EL(Expression Language)4.Spring是一个超级的“黏合平台”,将很多技术黏合在一起,形成一个整体,使每个组件发挥其最大功效 /**2.资源包整理1.本课程基于Spring3.2.0进行讲解2.下载地址:http://maven.springframework.org/release/org/springframework/spring/说明:通过Maven部署资源从仓库获取(后期课程)3.资源文件Spring资源包下spring-framework-3.2.0.RELEASE-dist.zipSpring依赖资源包spring-framework-3.0.2.RELEASE-dependencies.zip4.资源包目录层次结构docs:API帮助文档与官方手册libs:开发使用的jar包schema:开发使用的XML约束文档源文件5.开发jar包核心jar(4个)spring-beans-3.2.0.RELEASE.jarspring-context-3.2.0.RELEASE.jarspring-core-3.2.0.RELEASE.jarspring-expression-3.2.0.RELEASE.jar日志相关jar(2个)commons-logging-1.1.1.jarcommons-logging日志整合,与slf4j类似,由apache提供log4j-1.2.15.jar*/*****************************Spring调用资源的方式: new ClassPathXmlApplicationContext("applicationContext.xml");ctx.getBean("id");bean.C/R/U/D();// 读取 // 获取 // 调用3.IoC// 控制反转Spring ——————> Bean1.IoC(Inversion of Control)控制反转A:控制:控制的双方是什么?谁控制谁?主控方:Spring被控方:应用程序所使用的资源// Bean //(原始)应用程序控制自己执行操作 需要使用的外部资源//(Spring)Spring控制整个程序中所 需要使用的外部资源B.反转:什么是反转?反转什么?// 自身控制资源 ——————————> Spring控制资源正向:应用程序直接调用资源反向:应用程序依赖Spring为其提供资源//反转的是资源的控制权//应用程序由主动调用资源,变为被动的等待Spring提供资源C.有什么好处?正向缺点:应用程序控制的资源分布在程序的每一个地方反向的优点:所有的资源都在Spring中,便于管理// 总结:Spring反向控制应用程序所需要使用的外部资源。资源受Spring管理,利用率最大化。2.Spring模型演化过程A.基于分层设计UserAction使用 UserService,new UserService()// 紧耦合UserService使用 UserDAO,new UserDAO()UserDAOB.工厂模式,使用面向接口编程设计,解决层与层之间的紧耦合制作接口,制作实现类,制作对象工厂C.工厂模式+配置制作XML文件,将实现类配置到XML文件中读取XML文件中的配置信息,得到实现类的类名使用反射思想,获取实现类对象 class.newInstance();// 反射获取D.Spring设计雏形 自动的工厂+配置工厂由Spring提供,实现类使用XML格式配置/**3.IoC基础实例Spring调用资源的方式: new ClassPathXmlApplicationContext("applicationContext.xml");ctx.getBean("id");A.导入jar包(6个)B.制作log4j对应的配置文件C.在src目录下创建核心配置文件applicationContext.xml说明:格式可以从xsd-config.html获取<xml version="1.0" encoding="UTF-8"><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>D.将应用程序需要使用的资源配置成Spring的Bean<!-- Spring管理的资源全部声明到配置文件中 --><!-- 所有资源以Bean的形式存在 --><!-- bean:声明一个资源Bean --><!-- bean:id:用于区分bean的标识 --><!-- bean:class:bean对应的模型类名 --><bean id="userDao" class="cn.it.spring.ioc.UserDao"></bean>E.创建ApplicationContext对象ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件名");ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");F.获取Bean对象使用应用上下文对象获取ctx.getBean("bean的id属性值");ctx.getBean("userService");补充:使用文件系统获取配置信息,要求配置文件在工程路径下(少用)ApplicationContext ctx = new FileSystemXmlApplicationContext("applicationContext.xml");*/3.DI// 依赖注入APP而言1.DI依赖注入:指应用程序依赖Spring为其提供运行时所需的资源// (Dependency Injection)2.DI实例/**A.在应用程序中声明要使用的资源// 私有化声明private String msg;B.为其提供setter访问器//set方法public void setMsg(String msg) {this.msg = msg;}C. 在Bean配置中配置该资源对象具有某个属性,属性可以配置多个// bean中配置该字段(属性)<bean id="userService" class="cn.it.ioc.helloworld.UserService"><!-- 在配置中,为该Bean需要注入的资源进行初始化 --><!-- property:为该Bean注入属性 --><!-- property:name:注入的属性名称(变量名) --><property name="msg" value="it"></property></bean>注意:当配置中出现的属性在Bean类中不存在时,抛出异常*/3.IoC与DI区别//同一件事,站在不同角度上看待问题的不同描述IoC:Spring立场DI:应用程序立场4.BeanFactory// AppplicationContext:立即加载,加载配置文件时即加载(实际开发用它)BeanFactory:延迟加载,获取Bean实例时才加载/**1.BeanFactory是ApplicationContext的顶层父接口,使用BeanFactory接口也可以完成获取Bean的操作2.操作步骤:A.初始化Resource对象Resource res = new ClassPathResource("applicationContext.xml");B.初始化BeanFactory对象BeanFactory bf = new XmlBeanFactory(res);C.根据id名称获取BeanUserService service = (UserService) bf.getBean("userService");3.AppplicationContext与BeanFactory的区别A.加载方式不同AppplicationContext:立即加载,加载配置文件时即加载BeanFactory:延迟加载,获取Bean实例时才加载B.AppplicationContext具有更多的功能国际化处理事件传递Bean自动装配各种不同应用层的Context实现注意:实际开发中,优先选择ApplicationContext对象,避免使用BeanFactory4..Schema风格离线约束配置方式A.拷贝访问路径B.打开MyEclipse设置中的XML catalog选项C.创建新的映射D.选择对应的schema风格的约束文件E.将key type修改为schema locationF.将要配置的路径复制到key中*/5.Bean配置(XML)1.Bean对象初始化方式有三种/**构造器初始化:Bean对应的类必须提供一个默认无参可访问的构造方法// Bean类 ——————> 无参构造静态工厂初始化【了解】提供一个工厂类,使用其中的静态方法获取对象<bean id="userService2" class="cn.it.bean.UserStataicFactory" class="" factory-method="getInst"></bean>实例工厂初始化【了解】提供一个实例工厂类,使用其中的实例方法获取对象。由于该工厂类本身需要创建对象,因此该对象必须受Spring控制,所以必须配置该工厂类为Bean<!—实例化工厂Bean --><bean id="uf" class="cn.it.bean.UserFactory"></bean><!--使用实例工厂创建Bean --><!--factory-bean: 配置实例工厂在Spring范围内对应的Bean的id名称--><!--factory-method:配置工厂类中的创建实例的实例方法--><bean id="userService3" factory-bean="uf" factory-method="getInst2"></bean>*//**2.Bean的作用域// 默认单例 ————————多例:Scope("prototype")Spring初始化的Bean默认为单例模式,如果想修改成非单例模式需要修改Bean的作用范围<bean id="”beanId”" class="”BeanClassName”" scope="”prototype”"></bean>scope属性:singleton:单例prototype:非单例request:请求对象范围request.setAttribute("beanId",obj);session:会话Session范围request.getSession().setAttribute("beanId",obj);globalSession:全局会话分布式服务器*//**Bean的5种作用域:singleton(默认)(常用)创建出的实例为单例模式,在IoC容器中唯一prototype(常用)创建出的实例为非单例模式,每次获取bean得到新对象request(用于web开发)创建的实例绑定request对象,获取的bean作为request的属性session (用于web开发)创建的实例绑定session对象,获取的bean作为session的属性globalSession (用于分布式web开发)创建的实例绑定全局session对象,用于多个服务器间进行数据共享,获取的bean作为globalSession的属性*/3.Bean的生命周期定义Bean初始化与销毁时的动作,属于回调方法配置定义bean时指定两个回调方法,控制bean的初始化与销毁操作时执行的动作<!-- init-method:初始化回调方法名,配置的方法必须由bean对象提供--><!-- destroy-method:销毁回调方法名,配置的方法必须由bean对象提供--><bean id="user" class="cn.it.lifecycle.User" init-method="init" destroy-method="destroy" scope="prototype"></bean>销毁操作只针对scope="singletion"的对象,对于非单例对象无效单例对象的销毁是在IoC容器关闭时发生,使用ClassPathXmlApplicationContext对象close方法完成// 单例 关闭: close(); ————人为可控!非单例Bean对象的内存回收交由JVM完成——GC// 非单例 关闭: JVM——————系统!4.Bean属性注入注入方式有两种/**1.Setter注入【重点】// 前提: 提供无参构造前提:setter注入要求Bean必须提供无参可访问构造方法注入简单类型:String,基本数据类型封装类A.提供对应要注入的属性B.为每个要注入的属性提供对应的标准封装setter方法C.在配置中为Bean指定要注入的属性,使用property元素 name=“属性名” value=”值”<property name="属性名" value="值">注入引用类型:对象A.为某个Bean注入引用类型的值,首先在Bean对应的类中声明对应的属性private TeacherDAO dao;B.为每个要注入的属性提供对应的标准封装setter方法 (访问器)public void setDao(TeacherDAO dao) {this.dao = dao;}C.必须保障引入的对象是Spring控制的Bean<!-- 声明引用类型的资源为Bean --><bean id="teacherDao" class="cn.it.di.setter.TeacherDAO"></bean>D.在Bean的属性注入中,使用ref引用对应的资源 ref=”beanId/beanName”<!-- setter注入 --><!-- name:属性名 --><!-- value:简单类型的值 --><!-- ref:引用别的Bean,beanId/beanName --><bean id="bean7" class="cn.it.bean.xml.Bean7"><property name="name" value="it"><property name="age" value="8"><property name="dao" ref="teacherDao"></property></property></property></bean>*//**2.构造器注入【了解】A.提供对应的构造方法B.配置中设置构造方法的参数<!-- constructor-arg:使用构造器传递参数 --><!-- value:赋值 --><bean id="bean6" class="cn.it.bean.xml.Bean6"><constructor-arg value="传智播客"><constructor-arg value="2014"></constructor-arg></constructor-arg></bean>注意:如果类型匹配不成功,可以为配置中指定index索引属性,对应构造器中参数的位置<bean id="bean6" class="cn.it.bean.xml.Bean6"><constructor-arg index="0" value="2014" type="java.lang.Integer"></constructor-arg><constructor-arg index="1" value="it" type="java.lang.String"></constructor-arg></bean>说明:构造器传参受构造器参数位置和类型的限定,因此不常使用*//**5.P命名空间的使用【了解】Spring2.5提供了一种简化配置格式,P命名空间支持A. xml头信息中引入对p命名空间的支持xmlns:p="http://www.springframework.org/schema/p"B. 将所有的属性注入property子元素全部修改为bean的属性原始格式:<bean><property name="propName" value="propValue"><property name="propName" ref="beanId"></property></property></bean>p空间格式:<bean p:propname="propValue" p:propname-ref="beanId"></bean>*/6.SpElSpring3.0引入全新的SpEL(Spring Expression Language)Spring表达式语言,支持各种格式语法格式,所有的属性注入全部使用value属性名详情参考《Spring_表达式语言.pdf》7.集合注入/**Spring支持为Bean注入集合// <list>/<array> 、<set>、<map>、<props>List集合与数组的配置格式通用List<property name="list"><list><value>Java</value><value>Struts2</value><value>Spring3</value></list></property>Set<property name="setCol"><set><value>张三</value><value>张大三</value><value>张小三</value></set></property>Map<property name="map"><map><entry><key><value>张三</value></key><value>中国</value></entry><entry><key><value>约翰</value></key><value>中国</value></entry><entry><key><value>吴莫愁</value></key><value>榜单No.1</value></entry><entry key="小沈阳" value="榜单No.3"></entry></map></property>Properties<property name="props"><props><prop key="BIDU">百度</prop><prop key="SINA">新浪</prop><prop key="NQ">网秦</prop></props></property>*/ /**6.团队开发1.团队开发时,每个独立的模块制作成一个独立的配置文件命名格式:applicationContext-模块名.xml 模块名使用全小写字母2.某个配置文件中,使用其他配置文件中的Bean,导入对应的配置文件<import resource="applicationContext.xml">3.Bean的冲突问题A.当多个配置中具有相同id的Bean时,后加载的替换先加载的(由配置文件的加载顺序决定)B.当多个配置中具有相同id的Bean时,import导入相当于拷贝(由配置文件的Bean定义顺序决定)C.同一个配置文件中id不能重复,不同配置文件,同id依赖上述规则相互覆盖4.当id名称比较复杂,或者定义名称过长时,可以为bean追加别名,使用name属性完成name=”aa,bb,cc”5.同一个工程中,仅配置一个主配置文件applicationContext.xml,其中添加公用信息对应的Bean,每个开发人员根据自己的模块需求,制作独立的配置文件applicationContext-user.xmlapplicationContext-book.xmlapplicationContext-order.xml6.多配置文件加载ApplicationContext对象格式一:在子配置文件中导入主配置文件<import resource="applicationContext.xml">加载ApplicatioContext对象时需要加载子配置文件即可ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-user.xml");格式二:一次性加载多个配置文件(可变参数对象格式)ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");格式三: 一次性加载多个配置文件(数组对象格式)ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"bean1.xml","bean2.xml"});*/ 7.Bean配置(注解)Spring2.5提供使用注解的形式配置Bean1.配置Bean使用@Component注解// Component:组件如需为Bean定义名称,在参数中添加Bean名称@Component("beanName")//bean的名称可以定义多个,如@Component(“u1,u2,u3”)@Component功能:指定对应的类为Spring控制的bean格式:定义在类的上方,可以为类指定bean名称//@Component("userAction")定义UserAction类为Spring控制的bean,名称"userAction"2.设定Spring的自动扫描路径,用于检测对应的Bean是否配置了注解,并加载配置了注解的类a)开启context空间的支持<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context // 开启 context空间的支持http://www.springframework.org/schema/context/spring-context.xsd">b)开启自动扫描功能,并指定扫描的路径<context:component-scan base-package="cn.it.annotation"></context:component-scan>// component-scan:组件扫描//说明:扫描路径可以设置多个,中间使用,隔开,扫描路径包含指定包和子包内所有的类3.Spring2.5提供了3个注解开发Bean的兼容格式,用于后期分层设计扩展// 目前同@Component ———————定义 xxx类为Spring控制的bean@Controller 用于控制层实现类进行标注@Scope("prototype")@Service 用于业务逻辑层实现类标注@Transactional@Repository 用于数据层实现类标注// Spring2.5还定义了3个@Component的衍生注解,用于在后期版本中对其功能进行深层次的扩展// 目前上述三种注解与@Component功能完全相同,仅仅是名称上的区别4.为Bean注入简单类型属性//@Value("xx")——————无需set()a)在属性名上方声明该属性自动装配@Autowiredb)在属性名上方声明该属性注入的值@Value(value)@Autowired@Value("it")private String msg;注意:注解自动装配属性值无需提供对应属性的setter方法5.为Bean注入引用类型属性// @Qualifier("xx")——————无需set()a)在属性名上方声明该属性自动装配@Autowiredb)在属性名上方声明该属性注入的值@Qualifier(bean引用名称)@Autowired@Qualifier("anno2")private AnnotationOther other;注意:被注入的Bean一定要存在,无需提供对应属性的setter方法6.@Autowired注解属性required用于声明自动装配的数据是否是必须的如果required=true,必须为其装配数据,如果没有值,抛出异常如果required=false,自动装配数据失败,则注入值为null7.Spring支持JSR-250规范提供的@Resource注解的支持格式一:@Resource(name="myImpl")格式二:@Autowired(required=false)@Qualifier("myImpl")以上两种格式效果相同8.Bean生命周期注解@PostConstruct 功能:为当前Bean指定init-method参数格式:定义在成员方法的上方,兼容静态方法@PreDestroy 功能:为当前Bean指定destory-method参数格式:定义在成员方法的上方,兼容静态方法注意:要求当前类被注册为Bean,否则无效果9.Bean作用域注解在类的定义上方添加@Scope指定Bean的作用域常用:@Scope("prototype")默认:@Scope("singleton")10.Spring3.0注解支持// 前提:开启自动扫描@Configuration配置当前类为配置类,用于加载其中配置的Bean,与静态工厂初始化Bean很相似@Bean(name="b1")将配置类中的某个方法的返回值声明为Bean,该方法必须返回一个对象@Configuration//设定该类参与自动扫描public class MyBeanFactory {@Bean(name={"aa","bb","cc"})//设定该方法的返回值是一个Beanpublic static BookService getInst(){return new BookService();}}// 以上操作必须基于自动扫描功能,如不开启自动扫描,Spring将无法查找到对应的注解配置11.可以不使用自动扫描使用BeanAnnotationConfigApplicationContext对象可以手工加载基于注解配置的Bean//用于手工添加注解开发Bean的上下文对象AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();//加载配置管理类ctx.register(MyConfiguration.class);//刷新上下文对象ctx.refresh();注意:注册完成后一定要进行刷新,否则加载的Bean将无法被使用8.Spring整合JUnitSpring提供了对JUnit的整合支持操作步骤1.导入测试Jar包spring-test-3.2.0.RELEASE.jar2.设置类运行器(注解添加到运行程序类的上方)@RunWith(SpringJUnit4ClassRunner.class)3.设置读取Spring的配置文件路径@ContextConfiguration(locations="classpath:/applicationContext.xml")/**范例:@RunWith(SpringJUnit4ClassRunner.class)//设置JUnit运行器为Spring@ContextConfiguration(locations={"classpath:/applicationContext-junit.xml"})//加载配置public class App {//要测试的Bean必须称为测试类的属性注入进来,然后对其进行测试@Autowired@Qualifier("testBean")private Bean1 testBean;@Testpublic void testJunit(){testBean.fn();}}注意:使用junit整合spring时,必须保障运行类中注入要测试的Bean。整合完毕后,当前的测试类将作为Spring的Bean出现,而测试的Bean对象作为测试类的注入资源进行运行。*/ 9.AOP// 面向切面编程1.AOP(Aspect Oriented Programing)面向切面/方面编程2.AOP隶属软件工程的范畴,指导开发人员如何制作开发软件,进行结构设计3.AOP联盟:掌握着AOP的发展方向4.AOP开发只关注共性功能5.主思想:在不惊动原始设计的基础之上,为原始设计追加功能*插拔式组件体系结构*OOP与AOP区别OOP开发,关注的是层与层之间的关系,及其每层中各个实现类如何制作 // 层与层关系 ———————— 分层设计AOP开发,关注的是共性功能,无论该共性功能是否服务于同一层面的类将共性功能抽取出来,制作成独立的模块,在程序运行时,动态的为其追加该功能 // 共性功能抽取 ———————— 抽取成独立模块,动态追加6.AOP核心思想AOP关注的是程序中的共性功能,开发时,将共性功能抽取出来制作成独立的功能模块,此时原始功能中将不具有这些被抽取出的共性功能代码。在当具有被抽取的共性功能的模块运行时候,将共性功能,模块进行运行,即可完成原始的功能。优点:加强代码的复用性,同时程序开发时可以只考虑个性化的功能,不需要考虑共性功能2.AOP基本概念连接点(Joinpoint):类中的任意方法的运行时表示,可以简单理解为类中的方法// 任意类中的方法连接点 = 切入点 + 通知(此处 连接点 = 原始方法)切入点(Pointcut) :具有共性功能的方法的运行时表示,可以简单理解为具有共性功能的方法// 被抽取了 共性功能后的方法注意:切入点对应的是被挖去了共性功能后的方法执行时匹配断言(格式)//切入点一定是连接点,连接点不一定是切入点通知(Advice) :共性功能模块化,可以简单理解为将共性功能抽取出来制作成独立的方法//被抽取出的共性功能 组成的独立方法// 通知中 还包含 通知类别——————被抽取的代码,在切入点中的具体位置.切面(Aspect) :切入点与通知的对应关系,可以简单理解为被抽取的共性功能与共性功能被抽取位置对应的方法之间的关系//相当于一个 Map盒子,里面装着 Map(切入点,通知)//切入点 ———— 通知对应关系目标对象(Target Object) :包含切入点的运行时对象,开发阶段制作的是目标对象对应的类// 即被代理的对象 —————— 原始类———>切入点所在的类 //(的对象) AOP代理(AOP Proxy) :使用AOP的代理机制创建 目标对象运行时代理对象,完成原始的功能//完成切入点 与 通知 的融合注意:原始目标对象已经被挖去了共性功能,此时使用目标对象执行任务无法完成原始任务, //————>创建代理对象,并加入通知 使用AOP代理机制,创建一个代理对象,来完成 原始目标对象的功能. // 组成"原始"的完整方法 并执行此方法织入(Weaving) :是一个将通知功能加入原始字节码的动态过程,// 将 通知 加入到 切入点 对应位置的 动作——————织入共有三种方式:编译时/类加载时/运行时,Spring使用的是运行时织入机制 // Spring————运行时织入引入(Introduction) :一种特殊的机制,可以为一个类的字节码动态的加入变量或方法// 加入 成员变量/成员方法(了解)对于切入点所在的类,如果存在有共性的成员变量或者成员方法,通知将无法进行描述。AOP提供引入机制,将共性功能的成员进行加入。引入机制可以为类添加额外的成员变量或者成员方法 , 引入机制是在编译期或类加载期完成的.3.AOP工作流程:开发阶段(开发者完成)将共性功能独立开发出来,制作成通知将非共性功能开发到对应的目标对象类中,并制作成切入点方法在配置文件中,声明切入点与通知间的关系,即切面运行阶段(AOP完成)JVM读取配置文件中的信息,监控切入点方法的执行一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。 4.AOP(XML配置开发AOP)1.AOP基础实例/**A.导入jar包核心包(4个)日志(2个)AOP(4个)Spring进行AOP开发(1个)(3.2资源包)spring-aop-3.2.0.RELEASE.jarSpring整合AspectJ框架(3.2资源包)spring-aspects-3.2.0.RELEASE.jarAOP联盟规范(1个) (3.0.2依赖包)com.springsource.org.aopalliance-1.0.0.jaraspectJ支持(1个) (3.0.2依赖包)com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar*/B.制作目标对象类(配置成Bean)public class UserImpl {//当前类的三个方法中具有共性功能System.out.println("共性功能1"); 抽取出来public void add(){//共性功能1被抽取走了,放入到了MyAdvice类中的functionOne方法里System.out.println("共性功能2");System.out.println("user add....");}}注意:共性功能被抽取后,不是在原始位置进行调用,而是将共性功能删除C.将抽取的功能制作成通知(配置成Bean)public class MyAdvice {//切面类 中的是 抽取出的通知(共性功能)//被抽取的共性功能1public void functionOne(){System.out.println("共性功能1");}}说明:被抽取的共性功能制作成独立的类中方法。由于要将两个对象中的内容融合,Spring在控制其融合的过程必须控制两个对象,因此需要分别将两个类配置为Spring管理的Bean/**D.开启AOP空间的支持<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" aop="" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aop // AOPhttp://www.springframework.org/schema/aop/spring-aop.xsd// AOP">*/E.配置AOP的切面<!-- 配置AOP --><aop:config><!-- 配置切面,指定切面类的Bean:配置切入点与通知之间的关系 --><!-- ref:配置切面对应的Bean --><aop:aspect ref="myAdvice">// 告知切面对应的 通知类<!-- 声明通知的类别是何种类型 前置通知--><!-- 配置前置通知 --><!-- aop:before表示在原始方法执行前追加功能 --><!-- pointcut:配置Spring监控的方法切入点 --><!-- method:配置的切面类中对应的共性功能-方法名称 --><aop:before pointcut="execution(* cn.it.aop.one.UserImpl.add())" method="functionOne">// method:通知类中的具体哪一个通知(方法)// pointcut:被挖取了共性功能的 方法 ——————即告知 xx类的xx方法为 切入点</aop:before></aop:aspect>//上述整体表明:在cn.it.aop.one包下的UserImpl类的add()执行前, 先执行 通知类:myAdvice中的 functionOne()方法.</aop:config>/**F.制作Spring格式的客户端程序测试ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);UserDao dao = (UserDao)ctx.getBean(“userDao”);dao.add();*/2.切入点表达式格式:execution(切入点表达式)execution([方法的访问控制修饰符] 方法的返回值 包名.类名/接口名.方法名(参数)) // 权限 可略// 注意:方法的访问控制修饰符可以省略范例:public void cn.it.UserDAO.add()公共的cn.itcat包中的UserDAO类的无参数无返回值的add方法 void cn.it.UserDAO.add()公共的cn.itcat包中的UserDAO类的无参数无返回值的add方法方法的返回值可以写具体的返回值,或者写*表示任意返回值void cn.it.UserDAO.add()公共的cn.itcat包中的UserDAO类的无参数 无返回值 的add方法 * cn.it.UserDAO.add()公共的cn.itcat包中的UserDAO类的无参数 不限定返回值 的add方法// * ————> 任意返回值包名,方法名* cn.it.*.dao.UserDAO.add()cn包下的it包下的 任意包 下的dao包下的…..* cn.it..dao.UserDAO.add()cn包下的it包下的 任意层包 下的dao包下的…..* *..*.*()任意包下的任意类中的任意方法参数add()无参数add(*)一个参数add(int)一个int型参数add(*,*)两个参数add(*,int)两个参数,第一个任意,第二个intadd(..)任意参数add(*,..)至少一个参数特殊格式:* cn.it.UserDAO.add(..) && args(int,int)错误×* cn.it.UserDAO.add(..) && args(int,int)正确√* cn.it.UserDAO.add(int,int) && args(a,b)不常用,正确√* cn.it.UserDAO.add(int,int) && args(b,a)不常用,正确√3.切入点配置方式切入点配置时,可以设置切面间共享切入点,也可以设置切面内共享切入点,还可以设置局部切入点格式一:配置在通知类别后面<aop:before pointcut="execution(public void *..*.*Impl.a*())" ….//独立配置在 通知类别标签中格式二:配置在某个切面中,在当前切面范围内共享<aop:aspect ref="myAdvice">// 声明在切面标签 内部 ———————— 看上去是在 切面内共享 —————— 实际 也等同于全局<!-- 配置同一个切面中使用的公共切入点 --><aop:pointcut expression="execution(public void *..*.*Impl.a*())" id="pt2">// 即 实际上另一个<aop:aspect ref="..">可也以用<aop:before pointcut-ref="pt2" …..格式三:配置在AOP总配置中,在全部范围内共享<aop:config>//全局声明在切面标签 外部—————— 一个切入点变量引用(id)<aop:pointcut expression="execution(public void *..*.*Impl.a*())" id="pt1"><aop:aspect ref="myAdvice"><aop:before pointcut-ref="pt1"……</aop:aspect></aop:pointcut></aop:config>范例:<aop:config><!-- 所有切面共享的切入点 --><aop:pointcut expression="execution(* cn.it..*Tar*.*())" id="pt">// 全局声明在 切面标签 外部   的一个 子切入点标签 引用(id)<aop:aspect ref="myAdvice"><!-- 在一个切面内共享的切入点:配置独立的切入点表达式对象 --><aop:pointcut expression="execution(* cn.it..*Tar*.*(..))" id="pt1">// "局部"声明在 切面标签 内部 的一个 子切入点标签 引用(id)// ——————> 效果 = 全局<aop:before pointcut-ref="pt1" method="fn"><aop:before pointcut="execution(* *..*.*(..))" method="fn"></aop:before></aop:before></aop:pointcut></aop:aspect><aop:aspect ref="myAdvice"><!-- 配置独立的切入点表达式对象 --><aop:pointcut expression="execution(* cn.it..*Tar*.*(..))" id="pt2">// 独立配置在切入点标签内部 , 仅自己使用<aop:before pointcut-ref="pt2" method="fn"><aop:before pointcut-ref="pt" method="fn"><aop:before pointcut-ref="pt1" method="fn">// 这里 同样可以引用上面定义的 pt1 ——————"局部"=全局</aop:before></aop:before></aop:before></aop:pointcut></aop:aspect></aop:pointcut></aop:config>4.通知类别通知共有五种类别before:在原始操作前运行//各种校验after:在原始操作后运行,无论方法是否抛出异常//清理afterReturning:在原始操作后运行,只有方法正常结束才运行,抛出异常则不运行//常规数据处理afterThrowing:在原始操作中如果抛出异常,运行//包装异常信息around: 在原始操作前后运行,通过ProceedingJoinPoint对象调用procee()方法完成对原始操作的调用//可以做任何事情// proce:流程pjp.procee()————>指定原始方法在 around通知类别中的 位置around可以在原始方法的前后 任意位置出现,导致 切入点位置 不唯一! 此时 AOP无法定位 切入点! 便无法 排布原始方法的位置! 所以 AOP直接不调用原始方法! 我们会看到只运行了 around通知, 原始方法没有运行!解决办法(around通知类别格式):around通知类别对应的 通知方法中传入参数 ProceedingJoinPoint pjp ——————————> ...around(ProceedingJoinPoint pjp) throws Throwable{......pjp.proceed();// 指定原始方法的位置......};eg://环绕通知public void around(ProceedingJoinPoint pjp) throws Throwable{ //不可预知,调用的原始方法是否报错,故强制抛一个异常再说o(╯□╰)o!System.out.println("around before......");//调用原始方法pjp.proceed();// 指定原始方法 的调用位置你不指定,AOP将不运行原始方法!!!System.out.println("around after......");}通知的配置格式<aop:before pointcut-ref="pt2" method="before"> <aop:after pointcut-ref="pt2" method="after"> <aop:after-returning pointcut-ref="pt2" method="afterReturning"> <aop:after-throwing pointcut-ref="pt2" method="afterThrowing"> <aop:around pointcut-ref="pt2" method="around">5.通知顺序在切入点之前运行的通知执行顺序与配置顺序有关,在上面的先运行在切入点之后运行的通知在同一个切面中执行顺序与配置顺序有关,在上面的先运行在不同的切面中执行顺序与配置顺序有关,与切面的配置顺序相反//不同切面,与切面的配置顺序相反总结:不同通知类型执行的顺序以配置顺序为准 // 最终 以调试结果为准!o(╯□╰)o6.获取通知参数 // 两类 : 环绕 / 非环绕1.为环绕通知之外的通知方法定义形参JoinPoint,该参数必须是通知方法的第一个参数获取参数:Obejct[] args = jp.getArgs();范例:public void before(JoinPoint jp){//形参 JoinPointObject[] objs = jp.getArgs();System.out.println("before......"+objs[0]+","+objs[1]);}2.为环绕通知方法定义形参ProceedingJoinPoint对象//形参  ProceedingJoinPoint获取参数:Obejct[] args = pjp.getArgs();7.获取通知返回值afterReturning 与 around可以获取方法的返回值A.around通知获取返回值ProceedingJoinPoint对象执行调用原始操作的返回值就是原始方法的运行返回值Object res = pt.proceed(args);注意:如果原始方法返回值为void类型,则around方法返回值设置为Object如果原始方法返回值为非void类型,则around方法内必须将原始方法调用的结果返回原始方法返回值为void类型的,通知内获取的返回值统一为null // void ——————> null (可知java中 void实际是返回的一个null)public Object around(ProceedingJoinPoint pjp) throws Throwable{Object res = pjp.proceed(args);return res;}B.afterReturning通知获取返回值在通知方法的参数中,声明一个Object类型的参数,用于保存方法的返回值public void afterReturning(JoinPoint jp,Object abc){System.out.println("afterReturning......"+ abc);}在配置文件中,为afterReturning声明保存返回值的变量名<aop:after-returning method="afterReturning" returning="abc">8.获取通知异常对象异常对象的获取方式与返回值很相似,声明变量,在配置中声明保存异常对象的变量名<aop:after-throwing pointcut-ref="pt" method="afterThrowing" throwing="e">public void afterThrowing (Throwable e){System.out.println("afterThrowing......."+ e);}********9.应用AOP参数与返回值的应用AOP编程中,由于可以拦截方法的参数与返回值,在编程时,可以根据业务需要对参数与返回值进行修改。eg:-用户界面输入的用户名最后包含有空格,使用AOP可以将多余的空格处理掉后,再传入逻辑层或数据层-用户界面收集的分页页码值并不是数据层最终的使用值,可以通过AOP将该值处理后再传入数据层5.AOP(注解————AspectJ技术)// 开启————————————————————————> 配置Bean ————————————————————————> @Aspect ————————————————> @Before("execution(...)")// <aop:aspectj-autoproxy>//<bean id="..">/注解声明// 切面类上加注解// 切面类中的 通知方法上 添加通知类型注解1.AOP注解配置流程A.开启AOP配置支持注解@aspectj// // @AspectJ提供使用注解开发SpringAOP @AspectJ 是对AOP标准的一套实现API核心配置文件中添加以下配置,功能等同于注解配置Bean的自动扫描路径<aop:aspectj-autoproxy>B.将所有参与AOP配置的类声明为Spring控制的Bean可以使用XML配置格式或注解格式C.在切面类的类定义上方添加切面的声明// 切面类 的声明: @Aspect@Aspectpublic class MyAdvice {…}// 切面类 ——————包含 通知的类D.将切面类中的方法配置为指定类型的通知,配置时指定其切入点@Before("execution(* cn.it.aop.annotation.UserImpl.add())")public void before(JoinPoint jp) {System.out.println("before");}2.配置公共的切入点A.在切面类中声明一个方法(私有的),将该方法配置为切入点@Pointcut("execution(* cn.it.aop.annotation.UserImpl.add())")private void pt(){}B.使用配置的切入点@Before("引用切入点")格式:切面类名.方法名()范例:@Before("MyAdvice. pt ()")3.注解开发通知的通知类别前置通知@Before(value="execution(* *..*.*(..))")后置通知@After(value="execution(* *..*.*(..))")抛出异常通知@AfterThrowing(value="execution(* *..*.*(..))",throwing="ex")返回后通知@AfterReturning(value="execution(* *..*.*(..))",returning="ret")环绕通知@Around(value="execution(* *..*.*(..))")4.注解格式AOP顺序总体顺序由上到下为下列描述顺序// 注解方式 : 执行顺序固定!around beforebeforearound afterafterafterReturning// BUT 实际开发以最终运行顺序为准5.返回值与异常对象的获取方式@AfterReturning(value="MyAdvice.pt()",returning="aa")public void afterReturning(JoinPoint jp,Object aa){System.out.println("afterReturning......."+aa);}10.JDK动态代理:核心:Proxy.newProxyInstance(); +invoke();// 1.为该方法初始化参数: Proxy.newProxyInstance(loader, interfaces, h); 2. invoke(Object proxy, Method method, Object[] args) 中调用method.invoke(xxxProxy,args);/**public class JDKProxy implements InvocationHandler{private UserDao userDao;//定义一个方法用于创建JDK代理对象//JDK代理是对对象做代理public UserDao createProxyObject(UserDao userDao){//被代理对象作为参数传入进来this.userDao = userDao;//获取类加载器:对谁做代理,使用谁的类加载器ClassLoader loader = userDao.getClass().getClassLoader();//获取被代理对象的所有实现接口Class<?>[] interfaces = userDao.getClass().getInterfaces();//创建Handler对象InvocationHandler h = this;Object proxyObject = Proxy.newProxyInstance(loader, interfaces, h);return (UserDao) proxyObject;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("连接数据库");//对原始操作进行调用(反射)method.invoke(userDao, args);System.out.println("关闭数据库");return null;}public static void main(String[] args) {//1.创建一个对象UserDao dao = new UserImpl();//2.为原始对象创建代理对象UserDao daoProxy = new JDKProxy().createProxyObject(dao);//3.使用代理对象运行操作daoProxy.add();}}*/11.CGLIB// 核心对象 —————— Enhance (加强)核心思想:走父类,调子类. 走父类的运行,走子类的实现. 多态思想!反射 + 代理 + 子父类关系(多态特性)java代理: JDK代理 + cglib代理JDK代理是对 对象做代理,这个对象的类必须实现接口.若果没有实现接口,则只能使用cglib代理.cglib不是对 对象做代理,是对类做代理. 代理类 是由 被代理的类 造出来的.由原始的类在内存中创建一个新类,继承原始的类.新类里面有代理方法,是对原始类方法做的一个代理. 得到原始类的method的 方法代理对象——————methodProxy, 由它来完成的功能!/**public class CglibProxy {//JDK代理是对对象做代理,cglib代理是对类做代理public Cat createProxyObject(Class clazz){//1.创建内存中的动态类 Enhance//内存中造出一个没有名称的动态类Enhancer enhancer = new Enhancer();//2.现在的类最终完成原始类的功能,同时对其进行功能的增强,必须先具有原始类对应的功能————继承enhancer.setSuperclass(clazz);//3.进行功能的增强//设置了方法的调用拦截//设置具体的回调操作Callback callback = new MethodInterceptor() {//proxy:代理对象//method:被拦截的方法对象//args:调用参数//methodProxy:public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {//做增强System.out.println("小猫钓鱼");//调用原始的操作//method.invoke(proxy, args);//走子类的方法运行还是父类的方法运行?走父类Object ret = methodProxy.invokeSuper(proxy, args);//Object ret = methodProxy.invoke(proxy, args);return ret;}};enhancer.setCallback(callback);//4.创建内存中的全新的类的对象Object proxyObj = enhancer.create();return (Cat) proxyObj;}public static void main(String[] args) {Cat c = new CglibProxy().createProxyObject(Cat.class);c.eat();}*//**1.AOP内部工作原理是通过代理对象实现织入功能对于有接口实现的对象使用JDK动态代理// 实现接口 —————————— JDK动态代理对于无接口实现的对象使用Cglib动态代理// 无接口实现 —————————— Cglib动态代理2.CGLIB(code generator library)代码生成库—————第三方技术作用:动态的生成字节码CGLIB中的核心对象是Enhance对象,它的作用是创建一段动态的类字节码。// Enhance:加强CGLIB可以对任意的类进行代理,JDK的代理只能对实现接口的类进行代理public class MyCglibProxyObject implements MethodInterceptor {//提供一个方法用于创建Animal类的代理对象public Animal createCglibProxyObject(){//1.在内存中创建一个动态的类的字节码Enhancer enhancer = new Enhancer();//此时并没有做继承//2.为其指定父类//除了完成继承关系外,还将父类所有的方法名反射过来,并在自己的类中创建了这些方法enhancer.setSuperclass(Animal.class);//3.指定其回调操作enhancer.setCallback(this);//4.使用该类创建对象return (Animal) enhancer.create();}public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {if(method.getName().equals("eat")){System.out.println("吃前来个开胃菜");}return methodProxy.invokeSupper(proxy, args);}}*/14.模板:Spring提供有DAO支持模板类JdbcTemplateHibernateTemplate总结:queryForInt/queryForLong 可以查询返回一个整形的数据queryForObject 查询一个字符串类型,一个对象query() 查询集合对象/**常用的数据源:Spring数据源实现类 DriverManagerDataSourceDBCP数据源 BasicDataSource C3P0数据源 ComboPooledDataSource*/15.事务状态信息 要从 定义信息 中获取!事务定义 ————> 事务状态 ————> 操作事务 : 刷新/存储点/完成/新事物/回滚 /**Spring事务管理主要包括3个接口PlatformTransactionManager事务管理器TransactionDefinition事务定义信息TransactionStatus事务具体运行状态*/DataSourceTransactionManager使用Spring JDBC或iBatis 进行持久化数据时使用HibernateTransactionManager使用Hibernate3.0版本进行持久化数据时使用1.开启注解式事务驱动管理 <tx:annotation-driven transaction-manager="transactionManager">2.业务层接口上加@Transactional注解关于编程式事故管理的原理:无非就是 将归属于同一事务的两个(多个)操作 放到同一个方法中! 这里spring为我们提供的有————事务管理模板方法! 我们就是将操作全部写在模板方法内部!声明式事务管理:通过上面的编程式事务管理,我们不难发现,它其实就是在业务层中加入了一段事务管理的逻辑代码————事务管理模板!但是其中就将我们DAO层的代码嵌套在了service层,高耦合,这样肯定不好!那我们就的解决方式是什么呢?对了!当然就是AOP了!将 公共的事务逻辑代码 动态加入到service层后,我们就可以在业务层去完成事务管理!这就是声明式事务管理的核心!依靠的是 tx和aop 两项技术 来完成AOP动态切入的过程!16.整合:/**//1.直接获取spring的资源//优点:与前期学习格式完全相同,相对来说掌握简单//缺点:每次加载配置都要读取配置文件,文件IO过程速度比较慢,因此不推荐使用这种资源初始化方式//问题:资源加载的次数太多,速度较慢,降低资源加载次数//解决方案:1次加载以后直接使用,在服务器启动时加载ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");UserEbi userEbi = (UserEbi) ctx.getBean("userEbi");/*//2.在服务器启动时加载资源//使用监听器完成资源的初始化工作//在服务器启动时,监测当前有没有初始化,如果没有//加载对应位置的配置文件(<param-name>contextConfigLocation</param-name>//<param-value>classpath:applicationContext.xml</param-value>)//加载完毕后,创建ApplicationContext对象,将该对象放置在了一个所有请求都可以获取到的地方(ServletContext)//以后使用,直接获取//优点:加载的次数减少,整体性能提高//缺点:当前创建的struts对应的Action类的对象是由struts2控制,并且资源使用时,不是采用注入形式,对象管理松散//解决方案:最好资源从spring的环境中获取,而不是手工的方法获取ServletContext sc = ServletActionContext.getServletContext();WebApplicationContext wctx = WebApplicationContextUtils.getWebApplicationContext(sc);UserEbi userEbi = (UserEbi) wctx.getBean("userEbi");*//*//3.采用注入的形式完成资源的加载//struts-spring-plugin 插件开启了struts使用spring作为对象工厂的设置//当开启了该设置,struts2默认按照名称为其action对象注入资源,只要提供对应名称的bean,该对象就会自动装配进去//优点:格式简化了,不用初始化ApplicationContext对象,然后getBean....//缺点:当前的struts的action对象让然是struts自己创建//问题:资源的控制权有点松散//解决方案:将struts的action对象配置为spring的bean*///4.采用spring控制action类的创建//将struts的action类配置成spring的bean,同时注入业务层接口//在struts.xml中使用伪类名(spring的beanid)定义action对象//优点:对象的控制更加集中在spring端//缺点:没有缺点/**Struts2整合Spring3:1、Struts2运行及开发过程分析:客户通过页面提交请求(url或表单提交格式)制作jsp页面(参看备注)Web容器拦截到请求操作,由过滤器将操作转发至Struts2核心控制器参看资源包web.xml过滤器Struts2将拦截的请求通过action名称转发到对应的处理器制作Action类,提供对应的方法,默认execute方法制作struts.xml文件,并进行相应的名称配置与结果配置Struts2执行Action中对应的方法,返回字符串,并根据核心配置文件中的配置,转发对应的jsp页面根据struts.xml的配置,跳转页面2、在Action中使用Spring管理的Bean有如下4种方法:直接通过ApplicationContext对象加载使用WebApplicationContext对象加载使用自动装配加载Spring的Bean(Struts2提供支持)将Action交由Spring作为Bean进行管理(Spring提供)*//**Hibernate3整合Spring3:配置事务处理器为 HibernateTransactionManager ————————实现了hibernate事务管理的对象 ——————>以基于注解的方式进行事务管理——即:开启事务注解 <tx:annotation-driven transaction-manager="txManager">使用Hibernate3进行开发,需要开发者手工的加载配置信息,创建SessionFactory对象,手工开启Session对象,完成相应的数据层操作使用Spring3整合Hibernate3,前期开发者手工控制的所有对象均交由Spring统一管理,因此需要对Hibernate3所管理的对象进行设定SessionFactory作为Spring的Bean进行管理整合方式:引入式整合独立整合<tx:annotation-driven transaction-manager="txManager"><bean id="txManager">*/HibernateTemplate:Serializable save(Object entity) void delete(Object entity) void update(Object entity) <t> T load(Class<t> entityClass, Serializable id)// ID查询 ———— 立即加载<t> T get(Class<t> entityClass, Serializable id) //... ———— 懒加载List find(String queryString) // HQL查询List findByCriteria(DetachedCriteria criteria) // 离线(条件)查询List findByNamedQuery(String queryName, Object... values) // 命名查询Session获取getSession()// 使用HibernateDaoSupport获取Session对象SSH整合几个注意点:1.<context:component-scan base-package="cn.it.bos">声明自动扫描路径,spring自动扫描此包下的所有java类,将其下全部java类纳入为spring下管理的<bean>即:配置此项后,spring会将 cn.it.bos 包下的所有java类 配置为spring的<bean> ,且都以 注解的格式 初始化<bean>!2.<context:annotation-config>开启spring注解3.<tx:annotation-driven transaction-manager="transactionManager">开启事务注解————> 可在在service层直接使用@Transactional管理DAO层的事务(AOP)4.<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">整合hibernate的事务管理器<property name="sessionFactory" ref="sessionFactory"> </property></bean>100.重点:1.Spring调用资源的方式: new ClassPathXmlApplicationContext("applicationContext.xml");ctx.getBean("id");bean.C/R/U/D();//读取//获取//调用<context:property-placeholder> 扫描DateSource资源时,${username}作为key取值,配置明明正确,却一直报错!观察发现,用户名获取的并不是配置文件中的值,而是为当前计算机系统的用户名!查看相关资料,发现 username 默认被系统占用,所以用 key———>username ,默认加载的是计算机用户名. 不用username作为配置key即可!关于 Dao接口 DataSource的注入 , 其实是注入到 Dao extends 的 JDBCTemplate/hibernateTemplate 模板类中.JdbcDaoSupport内置属性:JdbcTemplate依赖:DataSource(注入它就可,不用注入JdbcTemplate)HibernateDaoSupport 内部属性:HibernateTemplate依赖:DataSource(...)为什么事务管理器对象中要注入DataSource数据源对象???这个过程,其实是我们将数据库的维护权交给了事务管理对象transactionManager!由它来管理 当前操作是否有效, 我是否需要更新数据库,又或者当前操作异常,我给你回滚到原始状态。等等...说简单点,就是: 它起了一个 java——————数据库 之间的一个过滤作用!你符合定义规则的,我让你通过!不符合的,对不起,操作无效!事务的本质是什么?就是在归属于同一个事务中的操作,你要么同时成功,要么同时失败!不能A操作成功,B操作失败了,然后你还让A的数据更新成功,那就乱套了!!!此时,必须让A操作也失效!这就是事务要做的事情!表现层 = Controller = 控制层 (struts2 、SpringMVC)Spring是怎么支持(融合)那么多技术的???Spring每支持一个技术(eg:A)——————>就会提供一个工厂类(AFactory)——————>工厂类(AFactory)中提供了可扩展属性(AProperty)——————>我们只需为该属性赋值即可// 关于为属性 赋值——————其实就是告诉Spring我选用的是哪个厂家的技术(实现类)//该可扩展属性(AProperty)就相当于一个技术接口,可以选用不同厂家的实现!!! so,我们可以自己选择用哪个实现类!!!==========================================shiro (Filter) =========================================================================================================================================权限控制=================1.权限概述系统中提供了很多功能,并不是每个人登录系统之后,可以操作的功能是完全相同的。需要对系统的功能的访问进行控制。在我们的系统中,通过系统的菜单和按钮展示系统的功能,菜单和按钮就是某个功能的访问入口。认证:系统提供的用于识别用户身份的功能(通常就是登录功能)-----你是谁????授权:系统中提供很多功能,需要为不同的用户根据其对应的权限,授予用户操作某些功能的能力------你能做什么???2.常见的权限控制的方式2.1url拦截进行权限控制 2.2方法注解方式权限控制 3.权限数据模型权限表角色表用户表角色权限关系表用户角色关系表 根据pdm文件导出sql文件执行sql文件,创建数据表使用myeclipse反转引擎插件实现反转生成 实体类和hbm =============== Apache shiro框架 =================1.shiro框架简介:Apache Shiro是一个强大而灵活的开源安全框架,它能够干净利落地 处理身份认证,授权,企业会话管理和加密。以下是你可以用 Apache Shiro所做的事情: 验证用户 对用户执行访问控制,如: 判断用户是否拥有角色admin。判断用户是否拥有访问的权限 在任何环境下使用 Session API。例如CS程序。 可以使用多个用户数据源。例如一个是oracle用户库,另外一个是mysql用户库。 单点登录(SSO)功能。 “Remember Me”服务 ,类似购物车的功能,shiro官方建议开启。 1.Shiro框架提供的权限控制的方式:url拦截进行权限控制 -----框架提供了很多过滤器进行url拦截方法注解进行权限控制 -----框架提供了在方法上使用的注解,为Action创建代理对象,进行权限控制页面标签权限控制 -----框架提供一套标签,用于根据权限展示或者隐藏按钮或者菜单代码级别权限控制 -----框架提供API,使用编程方式进行权限控制(不建议使用)Shiro框架下载:Shiro.apache.org地址下载 2.Shiro框架执行流程: Application Code ——————> Subject(当前用户)↓↓SecurityManager(安全管理器)↓↓ Realm(Dao——与权限DB交互数据)Application Code:应用程序代码,由开发人员负责开发Subject:当前用户Subject 是与程序进行交互的对象,可以是人也可以是服务或者其他,通常就理解为用户。所有Subject 实例都必须绑定到一个SecurityManager上。我们与一个 Subject 交互,运行时shiro会自动转化为与 SecurityManager交互的特定 subject的交互。SecurityManager:安全管理器,管理所有的用户,认证、授权等。// Shiro的核心协调shiro的各个组件SecurityManager 是 Shiro的核心,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager 。 但是我们得知道,当我们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操作。组件: // <bean> 注入 class属性不能注入接口,必须是一个实现类!!!1.Authenticator(org.apache.shiro.authc.Authenticator): 登录控制2.Authorizer(org.apache.shiro.authz.Authorizer) :决定subject能拥有什么样角色或者权限。3.SessionManager(org.apache.shiro.session.SessionManager) :创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。4.CacheManager(org.apache.shiro.cache.ehcache.EhCacheManager) :缓存管理器,可以减少不必要的后台访问。提高应用效率,增加用户体验。5.Cryptography(org.apache.shiro.crypto.*) :Shiro的api大幅度简化java api中繁琐的密码加密6.Realms(org.apache.shiro.realm.Realm) :程序与安全数据的桥梁Realm:安全数据桥,类似于Dao,负责访问安全数据(领域)Realms在 Shiro中作为应用程序和安全数据之间的“桥梁”或“连接器”。他获取安全数据来判断subject是否能够登录,subject拥有什么权限。他有点类似DAO。在配置realms时,需要至少一个realm。而且Shiro提供了一些常用的 Realms来连接数据源,如LDAP数据源的JndiLdapRealm,JDBC数据源的JdbcRealm,ini文件数据源的IniRealm,// 自带 Realmproperties文件数据源的PropertiesRealm,等等。我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制// 也可自定义 Realm// 统一由SecurityManager控制2.简单配置(Spring整合配置)1.url拦截进行权限控制使用shiro框架进行项目的认证和授权操作第一步:导入shiro的jar到项目中第二步:在web.xml中配置一个过滤器代理对象,在项目启动时到spring工厂中加载一个和当前过滤器name同名的bean对象// web.xml中配置shiro的过滤器/**<!-- 加入spring框架提供的过滤器代理对象 必须放在第一位,否则UnavailableSecurityManagerException!!!--><filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>*/ 第三步:在spring配置文件中配置一个名称为shiroFilter的bean// 在Spring的applicationContext.xml中添加shiro配置/**<!-- 配置shiro的bean, 使用当前工厂bean对象创建过滤器用于进行权限控制--><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!-- 注入安全管理器对象 --><property name="securityManager" ref="securityManager"></property><!-- 登录页面url地址 --><property name="loginUrl" value="/login.jsp"></property><!-- 成功页面 --><property name="successUrl" value="/index.jsp"></property><!-- 权限不足提示页面 --><property name="unauthorizedUrl" value="/unauthorized.jsp"></property><!-- 基于url拦截,使用过滤器进行拦截 --><property name="filterChainDefinitions"><value>/css/** = anon/images/** = anon/js/** = anon/validatecode.jsp* = anon/login.jsp = anon/userAction_login.action = anon/page_base_staff.action = perms["staff"] <!-- staff ——————— 对应 Function表的 code字段 只有将该字段添加到 授权对象AuthorizationInfo中,才能访问 对应的 /page_base_staff.action URL路径 -->/** = authc</value></property></bean><!-- 配置安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><!-- 注入 Realm--><property name="realm" ref="bosRealm"></property><!-- 注入缓存 --><property name="cacheManager" ref="cacheManager"></property></bean><!-- 缓存管理器 --><bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"><property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property></bean> <!-- 配置自定义的realm --><bean id="bosRealm" class="cn.it.bos.shiro.BosRealm"><!-- 开启shiro的注解 权限控制 --><!-- 自动代理 --><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"><!-- 强制使用cglib创建Action的代理对象 --><property name="proxyTargetClass" value="true"></property></bean><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">*//page_base_staff.action = perms["staff"] // staff ——————— 对应 Function表的 code字段 只有将该字段添加到 授权对象AuthorizationInfo中,才能访问 对应的 /page_base_staff.action URL路径 // 所以,授权步骤为:1.获取到当前登录User的——————角色[]———————— 权限[]2.遍历 权限[] , 获取到 所有code字段:function.getCode();3.将所有的code字段添加到 授权对象AuthorizationInfo中 info.addStringPermission( function.getCode() ); //这里以权限的code字段为 权限标记 1.2.Shiro框架提供的过滤器:注:anon,authcBasic,auchc,user是 认证过滤器,perms,roles,ssl,rest,port是 授权过滤器./**anno:匿名过滤器 /css/**=anon表示匿名用户可以访问css下的文件夹及其子文件夹authc:认证通过的过滤器 /**=authc 所有资源都需要认证通过才能访问,这样写了后若访问登录页面,所有请求都被shiro拦截,而使用authc表示要认证通过才能访问对应的资源perms[‘name’]:表示名为name权限的过滤器anon:例子/admins/**=anon 没有参数,表示可以匿名使用。authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为httpsuser:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查*/第四步:修改UserAction的login登录访问,使用shiro框架提供的方式进行认证操作 else {//使用shiro提供的方式进行权限认证//获得当前用户对象,现在状态为“未认证”Subject subject = SecurityUtils.getSubject();String username = model.getUsername();String password = model.getPassword();password = MD5Utils.md5(password);AuthenticationToken token = new UsernamePasswordToken(username,password);try{subject.login(token);//调用安全管理器,安全管理器调用RealmUser user = (User) subject.getPrincipal();// 登录成功,将user放入session,跳转到系统首页ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);}catch (UnknownAccountException e) {e.printStackTrace();//用户名不存在,跳转到登录页面this.addActionError("用户名不存在!");return "login";}catch (IncorrectCredentialsException e) {// 密码错误,跳转到登录页面this.addActionError("密码错误!");e.printStackTrace();return "login";}return "home"; }第五步:自定义一个Realm,进行认证和授权操作/**<!--自定义的bsoRealm 继承自AuthorizingRealm,也可以选择shiro提供的 --><bean id="bosRealm" class="cn.it.bos.shiro.BOSRealm">*/ 第六步:在spring配置文件中注册上面的realm,并注入给安全管理器/**<!-- 配置安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="bosRealm"></property></bean>*/第七步:在自定义的realm中进行授权操作public class BosRealm extends AuthorizingRealm{@Resourceprivate IUserDao userDao;@Resourceprivate IRoleDao roleDao;@Resourceprivate IFunctionDao functionDao;//认证方法protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("认证方法。。。");UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;String username = usernamePasswordToken.getUsername();//1 根据用户名查询密码User user = userDao.findUserByUserName(username);if(user == null){// 用户名不存在return null;}//2 返回AuthenticationInfo对象Object principal = user;//将当前查询到的用户对象放入SimpleAuthenticationInfo中,可以通过subject获得Object credentials = user.getPassword();//密码,shiro负责比较查询到的密码和用户输入的密码是否一致String realmName = super.getName();AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, credentials, realmName);return authenticationInfo;}//授权方法protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {System.out.println("授权...");// 获取当前登陆用户 ,根据当前登陆用户,查询对应角色信息Subject subject = SecurityUtils.getSubject();User user = (User) subject.getPrincipal();SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();if (user.getUsername().equals("admin")) {// 如果admin ,查询所有角色和所有权限List<role> roles = roleDao.findAll();for (Role role : roles) {authorizationInfo.addRole(role.getCode());}List<function> functions = functionDao.findAll();for (Function function : functions) {authorizationInfo.addStringPermission(function.getCode());}} else {// 普通用户 , 根据当前用户,查询具有角色,通过角色获取权限List<role> roles = roleDao.findRolesByUser(user);// 添加角色for (Role role : roles) {authorizationInfo.addRole(role.getCode());// 添加角色对应权限Set<function> functions = role.getFunctions();for (Function function : functions) {authorizationInfo.addStringPermission(function.getCode());}}}return authorizationInfo;}protected AuthorizationInfo doGetAuthorizationInfo_bak(PrincipalCollection principals) {System.out.println("授权方法。。。");User user = (User) principals.getPrimaryPrincipal();System.out.println(user);//根据当前登录用户查询对应的权限和角色SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();authorizationInfo.addStringPermission("staff:query");authorizationInfo.addStringPermission("abc");authorizationInfo.addRole("admin");return authorizationInfo;}}2.Shiro提供的注解方式权限控制第一步:在spring配置文件中配置自动代理和切面/**<!-- 自动代理 --><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"><!-- 指定使用cglib创建代理 --><property name="proxyTargetClass" value="true"></property></bean><!-- 切面 --><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>*/ 第二步:在Action方法上使用注解@RequiresPermissions("staff-delete")public String delete(){...}/**@RequiresAuthentication验证用户是否登录,等同于方法subject.isAuthenticated()结果为true时。@ RequiresUser验证用户是否被记忆,user有两种含义:一种是成功登录的(subject.isAuthenticated()结果为true);另外一种是被记忆的 ( subject.isRemembered() 结果为true)。@ RequiresGuest验证是否是一个guest的请求,与@ RequiresUser完全相反。换言之,RequiresUser == ! RequiresGuest 。此时subject.getPrincipal() 结果为null.@ RequiresRoles例如:@RequiresRoles("aRoleName");void someMethod();如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。@RequiresPermissions例如: @RequiresPermissions( {"file:read", "write:aFile.txt"} )void someMethod();要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException。*/ 第三步:修改BaseAction的构造方法 * 构造通过反射获取到 通用类 BaseAction<t> 的泛型 T * 然后,赋值给 声明的实体model */public BaseAction(){// 获取 BaseAction<t> 子类的Class对象Class< extends BaseAction> clazz = this.getClass();// 通过 clazz对象 获取到其父类(BaseAction) 的参数化类型 :BaseAction<t>Type genericSuperclass = clazz.getGenericSuperclass();// 声明一个 ParameterizedType类型变量ParameterizedType pType = null;// 判断 得到的父类是否带有 泛型 (若子类采用代理对象(相当于 孙子类) 继承该 类 —————— 则此处得到的 父类其实就是 BaseAction的子类)if ( genericSuperclass instanceof ParameterizedType ) {// 带有泛型 可强转为 ParameterizedType类型 , 并赋给 pTypepType = (ParameterizedType) genericSuperclass;}else {// 不带泛型 向上多获取一级 ———————— 相当于获取到其 爷爷辈 的参数化类型Type genericSuperclass2 = this.getClass().getSuperclass().getGenericSuperclass();// 再强转为 ParameterizedType类型 , 并赋给 pTypepType = (ParameterizedType) genericSuperclass2;}// 获取 参数化类型 中的 实际类型参数数组<t>Type[] actualTypeArguments = pType.getActualTypeArguments();// 获取到 T 并强转为 Class类型 Tips: Class<t> implements java.lang.reflect.Type,...Class<t> domainClass = (Class<t>) actualTypeArguments[0];// 给条件 对象赋值 并将其封装到pegeBean对象中detachedCriteria = DetachedCriteria.forClass( domainClass ) ;pageBean.setDetachedCriteria(detachedCriteria);// 将 T的实例 赋值给modeltry {model = domainClass.newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}3.配置缓存/**<!-- 缓存管理器 --><bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"><property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property></bean>*/3.简单扩展/**1.自定义realm:<!--自定义的myRealm 继承自AuthorizingRealm,也可以选择shiro提供的 --><bean id="myRealm" class="com.yada.shiro.MyReam"></bean>//这是授权方法protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String userName = (String) getAvailablePrincipal(principals);//TODO 通过用户名获得用户的所有资源,并把资源存入info中…………………….SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.setStringPermissions(set集合);info.setRoles(set集合);info.setObjectPermissions(set集合);return info;}//这是认证方法protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//token中储存着输入的用户名和密码UsernamePasswordToken upToken = (UsernamePasswordToken)token;//获得用户名与密码String username = upToken.getUsername();String password = String.valueOf(upToken.getPassword());//TODO 与数据库中用户名和密码进行比对。比对成功则返回info,比对失败则抛出对应信息的异常AuthenticationException…………………..SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password .toCharArray(),getName());return info;}2.自定义登录://创建用户名和密码的令牌UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),user.getPassWord());//记录该令牌,如果不记录则类似购物车功能不能使用。token.setRememberMe(true);//subject理解成权限对象。类似userSubject subject = SecurityUtils.getSubject();try {subject.login(token);} catch (UnknownAccountException ex) {//用户名没有找到。} catch (IncorrectCredentialsException ex) {//用户名密码不匹配。}catch (AuthenticationException e) {//其他的登录错误}//验证是否成功登录的方法if (subject.isAuthenticated()) {}3.自定义注销:Subject subject = SecurityUtils.getSubject();subject.logout();4.基于编码的角色授权实现:if判断方式控制:SubjectcurrentUser=SecurityUtils.getSubject();if(currentUser.hasRole("administrator")){//拥有角色administrator}else{//没有角色处理}断言方式控制SubjectcurrentUser=SecurityUtils.getSubject();//如果没有角色admin,则会抛出异常,someMethod()也不会被执行currentUser.checkRole(“admin");someMethod();5.基于编码的资源授权实现:if判断方式控制:SubjectcurrentUser=SecurityUtils.getSubject();if(currentUser.isPermitted("permssion:look")){//有资源权限}else{//没有权限}断言方式控制:SubjectcurrentUser=SecurityUtils.getSubject();//如果没有资源权限则会抛出异常。currentUser.checkPermission("permssion:look");someMethod();6.在JSP上的TAG实现:// <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %> 默认显示用户名称标签条件(均是显示标签内容)<shiro:authenticated>登录之后<shiro:notauthenticated>不在登录状态时<shiro:guest>用户在没有RememberMe时<shiro:user>用户在RememberMe时<shiro:hasanyroles name="abc,123">在有abc或者123角色时<shiro:hasrole name="abc">拥有角色abc<shiro:lacksrole name="abc">没有角色abc<shiro:haspermission name="abc">拥有权限资源abc<shiro:lackspermission name="abc">没有abc权限资源<shiro:principal>默认显示用户名称7.默认,添加或删除用户的角色 或资源 ,系统不需要重启,但是需要用户重新登录。即用户的授权是首次登录后第一次访问需要权限页面时进行加载。但是需要进行控制的权限资源,是在启动时就进行加载,如果要新增一个权限资源需要重启系统。8.控制精度:注解方式控制权限只能是在方法上控制,无法控制类级别访问。过滤器方式控制是根据访问的URL进行控制。允许使用*匹配URL,所以既可以进行粗粒度,也可以进行细粒度控制。9.Spring security 与apache shiro 差别:shiro配置更加容易理解,容易上手;security配置相对比较难懂。在spring的环境下,security整合性更好。Shiro对很多其他的框架兼容性更好,号称是无缝集成。shiro 不仅仅可以使用在web中,它可以工作在任何应用环境中。在集群会话时Shiro最重要的一个好处或许就是它的会话是独立于容器的。Shiro提供的密码加密使用起来非常方便。*/==========================================activiti(工作流) ======================================================================================================================================流程控制=================用activiti插件制作 业务流程图(process.bpmn 和 process.png) ——————————> 流程定义 ——————> 部署流程定义 (部署流程定义也可以认为是 增加流程定义——————往数据库中存储流程定义的过程)———————> 启动流程实例 (一般在这一步设置 流程变量————即传入 执行流程对象 eg: 快递单 .. 这一步是 将设计的流程图 与 实际执行的对象绑定,然后 对象跟随着 设计的流程图走!)// 流程变量的作用域范围是 流程实例 ——————可以通过runTimeService的方法来获取流程变量Tips: 流程变量 并不是唯一的! 实际应用中,我们中途会 设置条件判断,更改、甚至终止流程变量对象!当然 还会根据需求,添加一到多个流程变量 到当前绑定的流程实例中!还有一种流程变量,是我们在从前台动态获取到值,然后作为流程变量添加到流程实例中! ———————— 作为 排他网关 的判断条件,决定activiti的流程走向!这种流程变量,其实就相当于 流程执行过程中的判断(执行)条件的值!!!这里所说的操作,就属于TaskService()下的操作了!//流程变量:对象/条件———————> 查询/分配/办理任务 ———————> 当流程结束后,流程实例将会被删除// 自动加载 activiti.cfg.xml文件 创建核心对象 ————————流程引擎:processEngineProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();ProcessEngine:activiti的核心类! 所有类都来自于它! API: .getRepositoryService().createDeployment();// 直接加载源文件 、 zip.deleteDeployment();// 删除流程定义.createDeploymentQuery();// 流程定义对象信息的查询获取 ——————主要查的是图片,用于显示流程用.getRuntimeService().startProcessInstanceByKey();// 开启流程.deleteProcessInstance();// 删除流程实例.getActivityId()//获取当前活动节点.getTaskService().createTaskQuery();// 查询任务信息....taskCandidateUser();// 组任务查询过程.complete(taskId);// 执行任务.claim()// 拾取任务 组任务——>个人.setAssignee()// 退回组任务 个人——>组任务</shiro:principal></shiro:lackspermission></shiro:haspermission></shiro:lacksrole></shiro:hasrole></shiro:hasanyroles></shiro:user></shiro:guest></shiro:notauthenticated></shiro:authenticated></t></t></t></t></t></t></t></function></role></function></role></bean></bean></bean></bean></context:property-placeholder></tx:annotation-driven></context:annotation-config></bean></bean></bean></context:component-scan></t></t></t></t></bean></tx:annotation-driven></tx:annotation-driven></tx:annotation-driven></aop:aspectj-autoproxy></bean></aop:aspectj-autoproxy></aop:after-throwing></aop:after-returning></aop:around></aop:after-throwing></aop:after-returning></aop:after></aop:before></aop:aspect></aop:pointcut></aop:aspect></beans></beans></import></import></props></map></set></array></list></property></xml></bean></function></function></function></function></one-to-one></one-to-one></mapping></mapping></many-to-one></one-to-one></cache></diskstore></collection-cache></class-cache></class-cache></many-to-one></set></set></mapping></mapping></teamodel></teamodel></stumodel></stumodel></set></set></set></set></mapping></mapping></studentmodel></studentmodel></studentmodel></id></class></class></sql-query></query></usermodel></object></username></usermodel></mapping></generator></property></xml></xml></property></id></class></interceptor-ref></interceptor-ref></interceptor-stack></package></package></package></action></validator></default-class-ref></default-interceptor-ref></interceptor-stack></result-type></constant></s:form></s:form></s:hidden></s:action></s:date></s:property></s:iterator></s:property></string></string></----------></s:property></constant></s:fielderror></s:fielderror></action></constant></s:textfield></s:text></s:text></constant></constant></s:fielderror></action></action></action></s:fielderror></------------------></-----------------></t></t></interceptor></include></action></result-type></result></result></default-interceptor-ref></constant></default-class-ref></package></action></action></package></constant></constant></action></package></interceptors>