java学习笔记————SSH

i18n ... ****5.作用:指挥控制器的运作: C 6.默认需要在应用的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中找到 ——————> 找到 class="cn.it.bos.web.action.UserAction"(spring容器中略写为:class="userAction"),并实例化UserAction类 ——————> 调用方法 : login() (找到 method = "login") ——————> 返回值 return = "home" ——————> 找到结果集配置: /WEB-INF/pages/common/index.jsp ——————> 跳转到结果视图: /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.properties init_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.properties struts2-core-2.3.15.3.jar\org\apache\struts2\default.properties 参数配置 程序员不能修改 struts-default.xml struts2-core-2.3.15.3.jar\struts2-default.xml Struts2核心配置 程序员不能修改 struts.xml 在 classpath 中 用户配置 程序员使用 // ,以上配置文件在应用被启动的时候就完成了加载;而且有加载的顺序要求。后面的配置会覆盖掉前面配置文件中相同的信息。 2.1.default.properties 1、 参数的含义 // 做了很多 初始化过滤 设置 eg:过滤 URL后缀 、 编码方式 、 国际化 、 开发者模式... 2、 如何修改参数 // (struts.xml中修改) 后加载的覆盖先加载的 参数名称 默认值 说明 struts.i18n.encoding UTF-8 编码方式 struts.action.extension action,, 进入框架核心的url的后缀名 // xxx.action 或者 无后缀名 ———过滤———>进入到struts的核心内部 其他后缀,放行!!! // 无论什么 请求 都要进入 核心过滤器类, 经过过滤器过滤 , 根据后缀名 判断 是否放行!!! eg: .jsp / .html ... struts.devMode false 开发阶段设置为true // struts.custom.i18n.resources 关闭 国际化 //注册国际化: struts.serve.static.browserCache true 告诉客户端要不要缓存静态的资源 struts.configuration.xml.reload 被注释掉了:建议true 每当xml配置文件被修改后,不需要重新加载应用就能够使配置生效。适合开发阶段 3.如何修改他们的默认值: struts.xml:通过该文件来进行修改 (覆盖) 2.2.struts-default.xml简介 1、 2、 // 给定包名name abstract:抽象包T/F 只能被继承,不能直接使用 1)结果类型 // 应用在 中 // stream:文件上传和下载 chain:转发到一个动作———> 配置两个参数:setNamespace() 和 setActionName() // 结果类型 // 指定 dispatcher 为默认结果类型 ... 2)拦截器、拦截器栈(放入很多拦截器) ****C ***A 拦截器:  // 拦截器 : 定义拦截器 给定 name 和 class 拦截器都放入拦截器栈中!!! ... // n个拦截器   ***B 拦截器栈: // 拦截器栈 : 给拦截器栈 声明一个 name // 拦截器栈 就是 一个拦截器集合 , 里面通过 拦截器name 引用了很多 拦截器 // 既然 拦截器栈里面包含的就是多个拦截器 , 那么 拦截器栈 也可以 直接引用拦截器栈 ——————> 即:直接引用 拦截器集合 *A   // 拦截器引用 : 拦截器栈 中 全部是引入的上面定义的 拦截器(栈)———> 根据拦截器(栈)name 引入 *A/B ... // n个拦截器(栈)引用 // 和 两个拦截器栈 有被其他 拦截器栈引用 ... // n个拦截器栈 (每个栈里引用了 n个拦截器/栈) 3)默认引用 // 默认 拦截器栈 的引用 // 默认 类 的引用 2.3.配置文件package元素:// 作用:希望程序员能够 按照 分包的思想 , 管理你的动作配置 // 子包能够继承父包中的所有配置。自己写的包一般情况下要继承一个name=struts-default的包(它在struts-default.xml中) 1.Package:name 包的名称唯一 extends:extends="struts-default" // package 继承的是 内置配置文件 struts-default.xml 中的 不要搞错了! abstract="true" 抽象包概念: 没有任何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对比) // 必须出现 元素内部 name: 动作名称,就是url ——————对应servlet名 class: 动作类的全路径 没有定义,默认到struts-default.xml中找 method: 执行动作类中的方法 默认的是public String execute(); 3、生命周期: Action在用户访问action的时候创建的,是多例的(每次访问都会实例化) // Action ———————— 多例的!!! 诞生:每次用户访问时。 活着:动作没有响应结束。 死亡:响应结束。 多例的;没有线程安全问题。(与struts1所不同的)。 因为动作类是一个非常普通的Java类,创建它需要消耗的资源可以省略不计。 3、动作的访问 /** 通配符的使用 * 案例:增删改查 案例1)一个*号 /{1}.jsp http://localhost:8088/struts2_01/addUser * 通配符 代表addUser {1} 第一个*代表的值 案例2)两个* http://localhost:8088/struts2_01/add_User /{1}{2}.jsp 案例3) DMI:Dynamic Method Invocation: Struts2框架默认不允许动态方法调用的,不安全。 覆盖一个常量:struts.enable.DynamicMethodInvocation=true才能使用 !的使用 ——————此时不再是使用method去调用action的方法 http://localhost:8088/struts2_01!add_User 1) 配置文件 // 注册开启 使用!的方式 调用action中的方法 2) 不安全 、 */ 4、访问动作前的拦截器 // 动作执行前有很多拦截器在工作。 1、 Struts中有很多功能在拦截器中实现的 2、 Action执行之前 ,先要执行拦截器 3、 5、结果视图配置 name:字符串,逻辑视图名称, 默认值 Success 一、结果类型 dispatcher 转发到一个jsp视图 // 转发 到 : jsp chain 转发到一个动作Action // Action redirect 重定向到一个jsp视图 // 重定向 到 : jsp redirectAction 重定向到一个动作Action // Action 二、两个动作不在一个包中 6、结果的配置 struts.xml: 元素配置:配置结果视图。 属性: name:字符串。逻辑视图。对应当前动作的动作方法的返回值。默认值就是success字符串。 type:结果视图的类型 默认值是 dispatcher 请求转发到一个jsp页面。 // struts2中的结果类型: struts-default.xml中做出的定义。 7、局部逻辑视图和全局逻辑视图 局部逻辑视图:定义在 元素内部,为当前动作服务的。 全局逻辑视图:在某个动作中找不到对应的逻辑视图,框架会从全局视图中找。 7、配置多个struts配置文件 // 使用多个struts配置文件,方便模块开发。 // 在struts.xml中: 使用 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的拦截器给你注入进来的。 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 extends ActionSupport implements ModelDriven 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、显示错误消息 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 char Character int Integer long Long float Float double Double Date yyyy-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 中继承过来的 API a、针对动作类中的所有动作方法进行验证 // 所有 动作类继承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: regist xml的dtd声明在: xwork-core-2.3.15.3.jar\xwork-validator-1.0.3.dtd // tips:这里的xxx:是 不是动作类中方法名 // class-name-validation.xml所以: 根据method ——找到对应——>name a、针对动作类中的所有动作方法进行验证 // 所有 // 约定大于编码!!! 此种写法下:我们100%会保持method和name值一直的!!! 在动作类所在的包中,建立一个名称为“动作类名-validation.xml”的配置文件 b、针对动作类中的某个/些动作方法进行验证 // 部分 1.不需要验证的方法数量少: 在不需要验证的方法上面使用@SkipValidation注解 2.需要验证的方法数量少: // class-name-validation.xml 配置文件的命名:动作类名-动作名-validation.xml // 若仅有一个方法需要验证 ,还可以考虑考虑此方法 其他就没卵用╮(╯_╰)╭ 动作名:指struts.xml中<action元素的name值,不是动作类中的动作方法的名字。 // 有点蛋疼 eg;我想验证 UserAction 中的regist方法 // 正常情况下的命名规范 : ... ——————这才是实际应用中 // 要对add()方法实施验证,校验文件的取名为: UserAction-user_add-validation.xml // 要对update()方法实施验证,校验文件的取名为: UserAction-user_update-validation.xml // ...——————这时我们前面学的 最原始的的方式 // 此处: name是可以随便写的,并没有要求必须和 method一直, 所有我们要注意了: name值的对应关系为action——name 可不是 action——method // ...写了这么一大堆,都没啥卵用... 实际根本用不着 ╮(╯_╰)╭ 格式: // 根元素 ————> 要验证的字段 ————> 内置验证器类型的引用 ————> 传参(当然,部分类型是没有参数的) /** —————————— type 对应 default.xml 内置验证器配置文件中的 true 必须输入名字 您输入的用户名只能是字母和数字 ,且长度必须在4到25之间 true 必须输入密码 您输入的密码只能是字母和数字 ,且长度必须在4到25之间 1 150 年纪必须在1到150之间 1900-01-01 2050-02-21 生日必须在${min}到${max}之间 */ 2.3验证失败的处理 失败后会自动转向一个 name="input" 的逻辑视图; // error.jsp 错误消息的显示: 标签 2.4struts2提供的一些内置验证器 // default.xml 1、内置验证器在何处定义的 xwork-core-2.3.15.3.jar\com\opensymphony\xwork2\validator\validators\default.xml /** * validators : 根目录 * validator : 声明验证规则 ————————表明此标签代表的是:验证规则 * name : 验证规则的 唯一名称 * class : 验证规则 对应的完整路径 */ 系统提供的校验器如下: 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常量 赋值, 把资源文件 定义为 全局资源文件 // message为资源文件的基本名 message.properties —————— 后缀略 // msg_en_US.properties —————— 省略 _en_US.properties b、定义包范围的资源包 // 包级别 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页面中使用 // name为 message.properties 中的key loginError=\u7528\u6237\u540D ———————— 输出显示 c、在表单标签中,通过key属性指定资源文件中的key // 获取值 为表单 name字段 赋值! d、自由选择消息资源包——————————直接指定访问 哪个资源文件 // 直接访问 cn.it.action包下 基本 名为package 的资源文件 // key=welcome 小张 // 这里表示 我们可以在 资源包中 用通配符的方式 传参! eg:资源文件中的内容: welcome= {0},欢迎来到{1} 东京 // 在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); // 核心对象:FilterChain API(放行): chain.doFilter(request, response); intercept(ActionInvocation invocation) // 核心对象:ActionInvocation API(放行): 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了! // 全局 // 声明 自定义拦截器 // 声明 自定义拦截器栈 // 引用默认拦截器栈 // 引用自定义拦截器 c、使用已经声明的拦截器 d、调用过程 // 见 上2 4、执行动作前检查用户是否登陆的拦截器 5、MethodFilterInterceptor 是AbstractInterceptor的子类: setIncludeMethods(String includeMethods) : 设置 需要拦截 的方法, 多个方法用逗号分隔 setExcludeMethods(String excludeMethods) : 设置 不需要拦截 的方法, 多个方法用逗号分隔 6、配置拦截器的参数: 1.方式一: // 在自定义栈中 直接设置 同上 3.b、略 .... login .... 2.方式二: // 在 具体某个action内部 引用过来再设置 // 引用自定义拦截器栈 // 给栈中的 某个拦截器 设置参数 login 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-data c、表单提供 类型的输入域 struts2中如何实现文件上传的: a、借助一个fileUpload拦截器完成的; b、最底层的还是借助的commons-fileupload这个组件; 1.2单文件上传 a、编码步骤: 准备的表单页面:
描述:
// 此字段就是一个普通文本输入框 , 忽略即可 文件:
// 这才是 文件上传 功能框 tips:此处的 name是注入到Action中的 —————— 动态参数注入 // 所以此处 name 必须和 模型对象Action(动作类充当模型对象) 的 File/File[] 的字段名保持一致
struts.xml 动作类 //按照框架要求编写即可 public class UploadAction extends ActionSupport { private String name; // 普通文本字段,可不用管它 private File upload; //动作类上传的属性必须是File类型 字段名和 保持一致 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 中覆盖原始参数 并在src 下创建资源文件 : msg_zh_CN.properties // 内容: struts.messages.upload.error.SizeLimitExceededException=\u6587\u4EF6\u6700\u5927\u5141\u8BB8{0},\u60A8\u7684\u6587\u4EF6\u5927\u5C0F{1} /** // 注册国际化 资源文件 引用自定义文件, 覆盖部分原始参数的值 eg:下4.中文提示 // 覆盖 文件上传大小 原始参数设置 // 允许上传的文件的扩展名,多个扩展名用逗号分隔 ————————————给fileUpload拦截器 传入 文件扩展名参数 .doc,.txt // allowedTypes text/plain,application/msword //允许上传的文件的最大字节数据:此参数设置无效 5242880 —————————— 此处设置 文件大小参数 , 无效!!! 只能在上面设置 /upload1.jsp */ 3.遇到的问题及解决办法: 上传时遇到问题,fileUpload拦截器会自动转向一个name=input的逻辑视图 // 在 中配置一个结果集 uploadError.jsp jsp页面显示原始错误信息: 显示原始错误信息 // eg: 在 uploadError.jsp 页面 上写 展示 原始错误信息! // 一般我们会 采取自定义的 错误提示! 提高用户体验度! 真实错误信息,会打印到日志! 4.中文提示: // struts-message.properties struts2中的文字提示是基于国际化的。有内置的默认提示信息(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文件加载该资源文件 // 此处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}: 中name属性的值 {1}:上传文件的真实名称 {2}:上传文件保存到临时目录的名称 {3}:上传文件的类型(对struts.messages.error.file.too.large是上传文件的大小) */ /** 5.upload拦截器 FileUpload 拦截器负责处理文件的上传操作, 它是默认的 defaultStack 拦截器栈的一员. FileUpload 拦截器有 3 个属性可以设置. maximumSize: 上传文件的最大长度(以字节为单位), 默认值为 2 MB allowedTypes: 允许上传文件的类型, 各类型之间以逗号分隔 allowedExtensions: 允许上传文件扩展名, 各扩展名之间以逗号分隔 可以在 struts.xml 文件中覆盖这 3 个属性 若用户上传的文件大小大于给定的最大长度或其内容类型没有被列在 allowedTypes, allowedExtensions 参数里, 将会显示一条出错消息. 在struts-messages.properties 文件里预定义. (org.apache.struts2包下) 可以在文件上传 Action 相对应的资源文件中重新定义错误消息, 但需要在 struts.xml 文件中配置使用 action 的消息: */ 1.3多文件上传 // name 保持一致 数组接收——————File[] / String[] 表单:提供多个 类型的上传输入域, 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参数,该参数指定一个输入流,提供被下载文件的入口 // 最主要的参数 :InputStream inputStream输入流对象 // 通过 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 //我们尽量都把它需要的参数,手动设置进去 /** // 结果类型 type="stream" inputStream // 就是我们 Action类中 提供的 输入流对象 // 告诉浏览器 以下载的方式 打开 attachment;filename=${name} // attachment:附件 // 告诉浏览器, 我给你响应回来的是什么类型的文件 图片/html/json/二进制/... application/octet-stream // 指定MIME类型 八位字节的二进制 1024 //单位 自然是字节 ${filelength} //不设置 也可自动获取  没啥卵用   可忽略 */ *********************第三篇 数据篇************************************** 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结构 key value 说 明 root java.util.List contextMap中的根,是一个List结构 栈结构 request java.util.Map 对应的是请求范围中的所有属性(Attributes) session java.util.Map 对应的是会话范围中的所有属性(Attributes) application java.util.Map 对应的是应用范围中的所有属性(Attributes) parameters java.util.Map 所有的请求参数 action java.lang.Object 当前动作对象 attr java.util.Map 依次从页面、请求、会话、应用范围搜索 3.cotextMap何时被创建的 // 每次动作访问,都会创建一个新的contextMap,保持本次访问用到的数据。 当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。 注意: Struts2中,OGNL表达式需要配合Struts标签才可以使用。如: 4.ActionContext和ValueStack: ActionContext<---------- ----------="">ValueStack 创建ActionContext后,会绑定到当前线程上(ThreadLocal) 5.contextMap中的数据操作 // 我们可以把 contextMap 想象成一个数据库 它分为两张表:OGNL Context = ActionContext + ValueStack // ActionContext中存放的 HTTP请求信息 action对象存放在ValueStack 1.contextMap是动作访问的数据中心,本质是一个ActionContext类,这个类有一个Map.可以操作 _root 和 _values class ActionContext{ private Map 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 + Http ActionContext; } 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 CompoundRoot root; // 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 getContext(); // 获取 ValueStack的上下文(Map类型的context), 保存了ValueStack 所有的信息 和 环境 ———————— Map context // _root(action) + _values 只获取root: CompoundRoot getRoot(); // 获取 root ————————保存了 压入栈顶中的所有 Object findValue(String s); // 等同于 域对象的getAttribute() ————————————只不过此处是在 ValueStack中查找属性值 peek(); // 获取 栈顶对象 ——————仅获取,不做修改 pop(); // 获取 栈顶对象 ——————获取, 并从栈顶 remove push(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值即可 小问题:不建议这么做 :从root找对象的name属性,如果找不到,会作为key到contextMap中去找。 18、Struts2的标签库 1、通用标签库 // 在OGNL表达式中, 字符串要使用 "" 引起来 双重 ""时, 默认 外层单引,内层双引 test='#grade=="A"' 控制标签: /** if elseif else 优秀 // 在OGNL表达式中:字符串要使用双引号引起来 良好 尚需努力 */ /** iterator: // 作用:实现遍历迭代,类似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不指定,默认输出栈顶对象 /** // var 和 value 若指定了key, 均放入contextMap 取值就需要带# var若不指定,自动放入栈顶! // 用 奇偶设置 颜色 // 索引 // 序号 // 第一个 // 最后一个 // 奇数 // 偶数 // 姓名 // 性别 // 城市 // 生日 */ 数据标签: property: // 作用: 输出表达式的值到页面上 属性: value:是一个OGNL表达式.如果没有指定该属性,则默认输出ValueStack栈顶的值. default:指定一个默认值, 如果需要输出的属性值为null,则显示该属性指定的值. escapeHtml:是否忽略HTML标签 date: // 功能:日期类型格式化 属性: name : OGNL表达式 format : SimpleDateFormat action: // ——————————执行 ognlAction_test.action 通过指定命名空间和action名称,该标签允许在jsp页面直接调用Action 在页面显示一个动作的执行结果. a:超链接 子标签 param : 属性: name : 参数名 value :OGNL 点我 url:该标签用于创建url地址,可以通过"param"标签提供request参数. action:用来生成url的action动作名称,如果没有则使用value namespace :命名空间 var:起一个临时的名称 放在了contextMap . value:如果不提供就用当前action,使用value后缀必须加.action ">hello /** 表单标签 s:radio list OGNL:创建list和map对象 创建list:{元素1,元素2,....} 创建map:#{key:value,key:value,}; key:value(传递值,页面显示值) value:指定默认值:value(指定的是key的值) name:注意一定要有name属性 // 不然,你把值赋给谁 --> */ 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",...} 正确写法: requiredLabel属性: 显示*号 // 用在 必填项中 // 用户名 * : 布局模板:默认的模板xhtml;经常使用的模板: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.防止表单重复提交 ... struts.xml中配置自定义 name="token"拦截器 30.struts2中内置配置文件 default.properties 参数配置文件 struts.action.extension=action,, struts-default.xml 结果集 + 拦截器(栈) 、 + 两个默认引用 配置文件 default.xml 内置验证器配置文件 // 校验配置文件的DTD信息 ————> xwork-validator-1.0.3.dtd struts-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.一个通配符:(常用) // 在 spring 容器中 class简写. 2.两个以上通配符的使用: // 页面跳转用 // 需要进行权限控制的页面访问 执行默认的ActionSupport类的execute() ———— execute()方法 返回值为: return SUCCESS; /WEB-INF/pages/{1}/{2}.jsp 32.所谓栈:先进后出 33.struts.xml中的几个继承,到底继承的是谁? // 此处 继承自 struts-default.xml 中的 ———————————— // 注册自定义 拦截器栈 // 此处 继承自 struts-default.xml 中的 拦截器栈: 也是struts2的默认拦截器栈 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是一种数据层解决方案,用于进行数据层开发,制作DAO 4.常见的数据层解决方案 JDBC // sun 规范 最原始的的方式 , 也是最高效(运行高效——编程低效) 简单封装JDBCUtils————开源工具————DBCP、C3P0进一步优化改造JDBCUtils Hibernate 市场占有率: ORM:非ORM 9:1 ORM市场: 开源:收费 9:1 开源ORM市场: Hibernate:iBatis:其他 6:3:1 JPA(Java Persistence API ) iBatis/MyBatis Apache DBUtils // QueryRunner update(增删改) / query(查) Spring JDBCTemplate、HibernateTemplate、等等 2.Hibernate系统架构【理解】 图的分类:系统架构图、技术架构图 系统架构图:描述系统中的重要模块以及模块与模块之间的关系【上层依赖于下层】 Application ↓ persistent Objects(中间连接量) ↓ hibernate(hibernate.properties + XML Mapping) ↓ Database 1.包含内容:应用程序、Hibernate、数据库 2.Hibernate位于APP与DB之间 3.APP依赖Hibernate,Hibernate依赖DB 4.Hibernate中有两种配置文件 5.映射使用XML格式完成 6.持久化对象出现在APP与Hibernate间,起桥梁的作用,连接APP与Hibernate。Hibernate与APP间使用持久化对象进行数据交换 技术架构图: Application (Transient Object) ↓ persistent Objects(中间连接量) ↓ Session //持久化对象传递给hibernate的Session SessionFactory + JDBC + JNDI + JTA ↓ Database 1.Hibernate裂变成5块 JDBC JNDI JTA SessionFactory Session 2.APP需要操作的数据通过 持久化对象交给Hibernate的Session对象完成 3.APP中包含一种瞬时对象 4.SessionFactory用于创建Session 5.JDBC,JNDI,JTA作用未知 Application (Transient Object) ↓ persistent Objects(中间连接量) ↓ Session Transaction + SessionFactory + TransactionFactory + ConnectionProvider ↓ JDBC + JNDI + JTA ↓ Database 1.Hibernate依赖由第三方厂商实现 JDBC,JTA,JNDI技术完成任务,而非Hibernate自身实现 2.Hibernate存在有事务及事务工厂 3.Hibernate的连接需要由第三方提供 4.SessionFactory依赖 连接供应商 和 事务工厂 来工作 /**3.Hiberante资源下载与介绍【了解】【操作】 1.下载Hibernate Hibernate 最高版本4.3,本课程基于Hibernate3.6.10学习 下载地址:http://sourceforge.net/projects/hibernate/files/hibernate3/ 2.厂商介绍JBOSS JBOSS服务器 Web服务器:内置WEB容器(TOMCAT) 应用服务器:内置WEB容器+EJB容器(JBOSS、Weblogic) Hibernate JBPM 3.资源包目录层次 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.xml A.在创建的对象(Model)同层目录下创建与类名相同的xml文件,名称:UserModel.hbm.xml ———————— 放在 cn.it.hibernate.domain包下 B.从资源包中搜索*.hbm.xml(User.hbm.xml) // xxxModel.hbm.xml C.将资源包中查找的信息复制过来,描述具体的对应关系: 类与表的映射 // class —————————— 主键的映射 // id —————————— 属性与字段的映射 // property —————————— /** */ 6.制作系统配置文件,用于描述连接的数据库相关信息及Hibernate的基本配置信息 A. 在src目录中创建XML文件 // hibernate.hbm.xml B.从资源包中搜索hibernate.cfg.xml文件 C.将资源包中查找的信息复制过来,描述具体数据连接信息 数据库连接的配置 可选配置 // 方言 、 show_sql 资源注册 // xxxModel.hbm.xml /** com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/hibernate root root org.hibernate.dialect.MySQLDialect true */ 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保存数据 —————————————————————————————— 持久化 到DB session.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值相同 三种声明格式: 使用空标记(常用) 使用非空标记 使用子元素声明数据库字段名 6.范例 */ 7.OID——主键生成策略: // 持久化时,以配置文件 的主键生成策略为准!!! eg: user.setId("001"); save(user) // 此时设定的 OID="001" 无效, 系统会按配置的生成策略 自动生成32位随机字符串!!! 1.generator // 描述的是OID的生成策略 1.手工控制策略: assigned: 程序员手工控制生成策略 // 手动设置 OID 2.自动生成策略: // 自动生成 OID uuid (字符串类型:32位及以上) // 服务端 随机生成32位字符串 increment (整型数值类型) 通过获取max的值,执行SQL语句 // 服务端 SQL命令:max+1 identity (整型数值类型) 通过数据库端实现,要求设置自增 ———— MySQL可用 // 数据库自增 MySQL sequence (整型数值类型) 通过数据库端实现,要求设置自增 ———— Oracle可用 // 数据库自增 Oracle native (整型数值类型) 根据数据库的实现 自动选择identity/ sequence // 数据库自增 自动选择 /** 2.联合主键/复合主键 为兼容早期遗留系统而设计,目前几乎不采用该配置 【常见面试题】: 请列举Hibernate的主键生成策略 */ 8.系统配置文件: // hibernate.cfg.xml ————————> 代替了 早期的 hibernate.properties(此方式以被淘汰) /** 1.系统配置文件中包含内容: 数据库连接的配置 可选配置 资源注册 二级缓存(后期) 2.命名规范: hibernate.cfg.xml // Use the mappings and properties specified in an application resource named hibernate.cfg.xml. 可以写成任意的名称a.xml,此格式不满足规范,参看Configuration对象 // 源码:configure( "/hibernate.cfg.xml" ); 3.数据库连接的配置: hibernate.connection.driver_class JDBC驱动类类名 hibernate.connection.url JDBC连接字符串 hibernate.connection.username JDBC连接用户名 hibernate.connection.password JDBC连接用户名对应的密码 4.可选配置 1.必选:方言配置 用于设置生成的SQL语句的格式 org.hibernate.dialect.MySQLDialect 2.其他可选配置 hibernate.show_sql 设置是否将执行过程中运行的SQL语句输出到控制台 开发阶段开启此属性,上线阶段关闭此属性 hibernate.format_sql 设置show_sql属性打印出的SQL语句以分行的格式进行显示 hibernate.connection.pool_size 设置Hibernate模型连接池最大连接数量 使用c3p0连接池 步骤: 1.导入c3p0连接池jar包 lib/optional/c3p0/*.jar 2.cfg.xml中配置下列配置属性 开启任意一句与c3p0有关的配置,即开启使用c3p0连接池 21 721 3091 hibernate.hbm2ddl 设置是否根据hbm文件格式生成表结构,需要配合hbm.xml文件中对于属性类型与长度相关的配置使用 connection.autocommit 设置是否自动提交事务 5.资源注册 将Hibernate的映射文件加入到系统配置中,否则将无法操作该模型 注意:目录层次结构间使用/符号描述 */ 9.Configuration // 封装 Hibernate 系统配置信息 的对象 两种格式: 常用格式: Configuration conf = new Configuration().configure(); // 不写 默认读取 hibernate.cfg.xml Configuration 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.xml SessionFactory 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; //静态的SessionFactory private 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) ———————— 必须提供 OID 1.修改时提供的对象必须拥有OID,否则抛出异常 2.在一个Session内对同一个执行修改操作多次,只会执行对应SQL语句一次(一级缓存中详解) // 4.添加/修改数据 saveOrUpdate(Object) 该方法可以完成修改和添加操作,取决于是否提供OID 判断是否提供OID的策略 1.OID是否为null 2.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 UserModel HQL: 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 u HQL: 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 模型对象 select userName from UserModel (查询结果为单一项) // 单一 List 仅查询一个属性(只获取userName列数据) select userName,age from UserModel (查询结果为多项) // 多项 List [{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 = : userName q.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 返回的结果:Long max/min 返回的结果:原始类型 sum 返回的结果:Long或Double avg 返回的结果:Double 注意:由于返回结果通常为单一数据,常使用uniqueResult()获得结果 // 单一数据 q.uniqueResult(); 8.分组查询 // group by Query q = s.createQuery("select address,count(address) from UserModel group by address"); 与SQL基本相同 9.排序查询 // order by Query 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.and and 关系 Restrictions.or or关系 Restrictions.sqlRestriction Sql限定查询 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(..) 聚合函数的使用隶属于投影范畴,因此设置使用聚合函数需要设置投影 count Projection condition = Projections.rowCount(); Long max/min Projection conditionMax = Projections.max("age"); 原始类型 Projection conditionMin = Projections.min("age"); 原始类型 Sum Projection conditionSum = Projections.sum("age"); Long/Double Avg Projection conditionAvg = Projections.avg("age"); Double 8.分组查询 // 隶属于 投影 ———————— 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 queryList = c.list(); for(UserModel um:queryList){ System.out.println(um); } 问题:使三层架构变成了紧耦合状态 松耦合:可传Model 11.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 u SQLQuery对象要指定对应的别名包装的对象 sq.addEntity("别名",模型类); sq.addEntity("u",UserModel.class); 20.配置型SQL/HQL语句执行方式 // / 命名查询 1.Hibernate支持将查询语句配置在hbm.xml文件的hibernate-mapping元素内 2.配置的查询语句具有两种格式 HQL: from UserModel where uuid = :uuid SQL: select * from tbl_user 注意:配置SQL语句时,为避免语句中具有与XML语法格式冲突的符号,通常使用使用XML语法规则控制查询语句中出现的与XML冲突的错误符号, 例如 // XML语法冲突 —————— 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控制, 具有OID 3.三种状态间切换时机 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对象指定OID 4.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再次查询 , 从缓存获取 , 没有发送SQL D.重新开启全新Session,读取上一获取的数据,执行SQL // 使用不同 session 再次查询 , 重新发送了SQL , 不同session间 5.load与get的区别 区别一: get 是 立即加载 // get ——————立即加载出所有属性 load 是 延迟加载(OID之外的属性) // load——————延迟加载 (仅立即加载OID) 通过设置 xxxModel.hbm.xml 文件的class元素lazy属性,修改延迟加载策略,默认lazy="true" 区别二: get 获取不存在的OID数据, 返回null // null load 获取不存在的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————————>PO save操作是将TO转换为PO update : 1.update操作是将DO转换为PO 2.由于update操作将DO转化为PO后,快照区域并没有对应的比对数据,因此Hibernate强制此操作执行一次更新指令,可以通过配置,在update执行前,将对应的快照区数据加载,避免进行没有意义的更新操作 在hbm.xml文件class元素中添加配置,修改前进行查询,判断是否进行update操作 // 修改前,先select一份DB数据到快照,比对 缓存和快照 数据, 不同才执行更新 3.update操作是将DO转换为PO,如果该对象不具有OID,则为TO,此时将抛出异常 4.update操作将DO转换为PO时,如果一级缓存数据区已经存在使用了该OID对象,抛出异常 // Hibernate内部区分对象采用OID进行,如果存在两个相同的OID对象,Hibernate将无法管理 总结:一级缓存中,同一个时间点不能两个PO具有相同的OID merge: 当需要将DO对象放入Hibernate一级缓存时,为避免一级缓存中已经存在有该数据,可以使用合并操作,避免错误的发生 A.如果存在相同的OID对象,将当前操作对象的属性合并到一级缓存中已有的对象中(OID相同),使用当前对象的属性值覆盖原始对象 B.如果不存在相同的OID对象,将当前操作对象放入一级缓存 saveOrUpdate: 将一个应用程序提供的对象放入一级缓存时,要根据对象是否携带OID判断使用何种操作。 为了进一步的兼容该操作,使用saveOrUpdate操作,Hibernate可以根据对象的状态来判断进行何种操作 如果是TO,执行save 判断对象是否是TO的标准: 1.OID为null 2.OID值与 id元素配置unsave-value值相同 如果是PO,不执行任何操作 如果是DO,执行update delete: 如果OID不存在,抛出异常 24.Session对象管理 1.Session管理的三种形式 Session对象生命周期由开发者管理 Session对象生命周期与JTA事务绑定 // JTA: 事物处理构架(Java Transaction Architecture) Session对象生命周期由本地线程管理 2.Session对象由SessionFactory创建,一旦关闭,将不再执行任何与数据库相关的操作。但是当某个延迟加载的数据在关闭Session后,试图执行Session延迟加载操作时,会引发Session对象已经关闭异常。解决该问题,需要将Session对象的操作范围扩大。通过当前线程绑定Session来完成。 3.使用本地线程绑定Session A.在cfg.xml文件中设置线程管理Session thread 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_teacher CREATE 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=utf8 tbl_student CREATE 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=utf8 2.模型 TeahcerModel 包含学生对象的集合 public class TeacherModel { private Long uuid; private String teacherName; private String nick; //在一方添加多方的集合 private Set students; } private Set students = new HashSet (); // 注意:为了方便操作,通常为集合进行初始化,不初始化也可以 StudentModel 包含教师的对象 public class StudentModel { private Long uuid; private String studentName; private String className; //在多方添加一方的对象 private TeacherModel teacher ; } 3.配置 TeacherModel.hbm.xml(教师) // 注意:key元素必须出现在one-to-many元素的前面,否则报错 StudentModel.hbm.xml(学生) 4.资源注册: 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); 设置添加老师对象时,级联关联对象 // 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 ———————————update B.删除所有关联数据(执行delete语句,删除关联数据) // 先删关联对象 ———————————delete C.删除当前数据(执行delete语句,删除当前数据) // 最后删除自身 ———————————delete 在进行级联删除前首先将当前数据从数据库中查询出来,而不是采用DO的形式手工创建,否则无法加载关联关系。 读取数据与关联数据 TeacherModel tm = (TeacherModel)s.get(TeacherModel.class,2L); // 必须先从 数据库get到PO数据——————————获取关联对象信息 然后执行删除 s.delete(tm); 设置删除老师对象时,级联关联对象 // 执行 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); //此步骤可以省略 设置老师对象级联关联具有孤子删除效果 // 执行 save / delete / update 操作时, 级联增/删/改 / 孤子删除 解除一对多中的关联关系时,未被关联的数据将被删除掉 // 触发条件为: 解除关联操作——————————外键置为null 即可, 并不要求你做删除主表对象操作 7.级联操作的种类 all all-delete-orphan all,delete-orphan 8.关系维护能力 // inverse:反转 在进行关联关系对象操作时,关系维护发生在关联关系的双方对象,造成关系维护进行多次的现象发生。通过配置,设置一方失去关系维护权。 // 当前对象 放弃关系维护权!!! ————————> 交给 被关联 对象管理!!! 设置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_tea CREATE TABLE `tbl_tea` ( `uuid` bigint(20) NOT NULL auto_increment, `teaName` varchar(30) NOT NULL, PRIMARY KEY (`uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 tbl_stu CREATE TABLE `tbl_stu` ( `uuid` bigint(20) NOT NULL auto_increment, ` stuName` varchar(30) NOT NULL, PRIMARY KEY (`uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 tbl_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 students = new HashSet (); } StudentModel 包含教师对象的集合 public class StuModel { private Long uuid; private String stuName; //声明一个学生对应多个老师 private Set teachers = new HashSet (); } */ 3.配置 // 当前模型类(老师) 内部 // 当前模型类的 集合字段 与 字段的所在的关系表(此处双边都一样,因为是多对多的第三方关系表) // 外部 ———————— 被关联对象在 本对象中的映射 关系字段 // 当前模型类 在 第三方关系表 中的外键字段 两边的配置是相同的,差别在于不同模型对应的外键与模型类名不同 4.资源注册: // 在cfg.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.级联添加: 设置老师端级联添加,同时失去关系维护能力 //通常由 失去关系维护权的一方   发起级联 注意:通常由失去关系维护权的一方发起级联 // 则另一方 就不用做啥设置了 本来就 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.迫切内连接// 30 HQL: 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属性) // 主关联: 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语句S B:获取集合具体数据:按需执行一条子查询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属性) // 从关联: 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语句:可以用于控制加载主数据或从数据的SQL B: 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中添加使用二级缓存的配置 true 3.在cfg.xml中配置二级缓存供应商 org.hibernate.cache.EhCacheProvider 4.添加二级缓存配置信息,控制哪些数据进入二级缓存 格式一: 在Model.hbm.xml文件中添加配置 //在class元素中,作为子元素出现 //在set元素中,作为子元素出现 格式二: 在hibernate.cfg.xml文件中进行配置 5.配置ehcache自身设定值 在src目录下创建配置文件ehcache.xml */ 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对象A A读取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中管理对应使用的OID B.关联关系中不存储具体数据,具体数据在类对象缓存区域存储 说明:此种保存数据的格式可以有效的提高数据存储量,避免浪费大量的空间。每次获取集合数据时,首先从集合数据区取出所有的数据编号,然后再从类对象缓存区根据编号获取数据。 最后将取出的散装数据按照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文件中开启查询缓存功能 true 然后:针对某一次的查询设定是否使用查询缓存 Query q = s.createQuery(“hql”); q.setCacheable(true); 注意:q.setCacheable(true)将设置该查询数据与查询缓存同步。每次执行均要设置该操作。 数据存储格式: SQL (KEY) 数据(VALUE) select * from tbl_teacher 集合数据1 select * from tbl_teacher where uuid = 1 集合数据2 select * 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=utf8 CREATE 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=utf8 2.模型 主方:配置从方的对象 从方:配置主方的对象 3.配置 主方: 从方: 4.资源注册 */ /**补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=utf8 CREATE TABLE `tbl_wife` ( `uuid` bigint(10) NOT NULL, `name` varchar(30) NOT NULL, PRIMARY KEY (`uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 2.模型 主方:配置从方的对象 从方:配置主方的对象 3.配置 主方: 从方: 主键配置: husband */ 35.关于查询方式: get/load——————————OID Query ——————————HQL Criteria——————————QBC ————>自动生成最差性能SQL SQLQuery——————————使用原生 SQL 语句查询 36.关于内外连接: 内连接:只有关联的数据 才显示 外连接:没有关联的数据 也需要显示 /** * 迫切左外连接 ——————fetch * * 发现:左外连接的时候,加上distinct去重,居然也可以!!!(搞不懂!!!) * 发现:使用distinct,可使 内/外连接 转换为 迫切内/外连接!!! 不加distinct,分分钟给你报错!!! * * 综上,这里内外连接均可实现! * 推荐使用: 迫切内/外连接!!! */ @Override public List 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 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 1 + 174 // Function + Role + User 如果加了限制条件 ——————————eg: FROM Function f left outer join fetch f.roles r WHERE r.id = ? 那么此时返回的数据是: 仅为 Role_Function(主表_从表1关联表) 中关联的数据 !!! // Function + Role + User List 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 + F2 User1| | ————————> Role2 ————————> F1 + F2 + F3 <======> | ————————> Role1 ————————> | F1 | | User1 | ————————> Role2 ————————> | | ————————> Role1 ————————> | F2 | | User1 | ————————> Role2 ————————> | | | F3 |————————> Role1 ————————> | User1 | | ========================================== Spring ================================================================================================================================ 1.Spring是一个基于JAVA的轻量级的J2EE应用框架. IOC容器:控制反转——————管理资源 AOP:面向切面编程 DB:连接数据库(模板)————JDBC、ORM、transaction =================================================== //模板:给个sql,就能操作数据库.其他,不用我们管. // JDBCTemplate、HibernateTemplate、DBCP... JDBC、JDBCUtils、Hibernate、模板 1.spring简介 1.Spring是一个基于Java的轻量级的J2EE框架 基于Java: 底层Java实现 轻量级: 性能较高,运行速度较快,内存消耗较少 重量级组件:EJB、CGI J2EE应用框架: Spring可以在J2EE开发的三层架构的每一层为开发者带来帮助 表现层: Servlet/JSP,Struts2,SpringMVC 业务层: Bean管理、AOP、事务管理 数据层: JDBC,Hibernate,Spring JDBCTemplate 域模型层: 实体类+DAO 服务层: 远程调用(webservice)[类似技术RMI+JNDI] Spring提供的每层的服务技术 表现层: String MVC 业务逻辑层: Bean管理、AOP、事务管理 数据层: DAO支持抽象类,DAO模板技术JDBCTemplate 2.Spring的发展历程 /** 创始人:Rod Johnson Expert 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:远程 JDBC ORM Web Servlet OXM JMS portlet Struts Transaction AOP Aspect Instrumentation // aspect:方面;方向 instrumentation:仪器,仪表 Core Container Beans Core Context 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.zip Spring依赖资源包 spring-framework-3.0.2.RELEASE-dependencies.zip 4.资源包目录层次结构 docs:API帮助文档与官方手册 libs:开发使用的jar包 schema:开发使用的XML约束文档源文件 5.开发jar包 核心jar(4个) spring-beans-3.2.0.RELEASE.jar spring-context-3.2.0.RELEASE.jar spring-core-3.2.0.RELEASE.jar spring-expression-3.2.0.RELEASE.jar 日志相关jar(2个) commons-logging-1.1.1.jar commons-logging日志整合,与slf4j类似,由apache提供 log4j-1.2.15.jar */ *****************************Spring调用资源的方式: new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.getBean("id"); bean.C/R/U/D(); // 读取 // 获取 // 调用 3.IoC // 控制反转 Spring ——————> Bean 1.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() UserDAO B.工厂模式,使用面向接口编程设计,解决层与层之间的紧耦合 制作接口,制作实现类,制作对象工厂 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获取 D.将应用程序需要使用的资源配置成Spring的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类中不存在时,抛出异常 */ 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名称获取Bean UserService service = (UserService) bf.getBean("userService"); 3.AppplicationContext与BeanFactory的区别 A.加载方式不同 AppplicationContext: 立即加载,加载配置文件时即加载 BeanFactory: 延迟加载,获取Bean实例时才加载 B.AppplicationContext具有更多的功能 国际化处理 事件传递 Bean自动装配 各种不同应用层的Context实现 注意:实际开发中,优先选择ApplicationContext对象,避免使用BeanFactory 4..Schema风格离线约束配置方式 A.拷贝访问路径 B.打开MyEclipse设置中的XML catalog选项 C.创建新的映射 D.选择对应的schema风格的约束文件 E.将key type修改为schema location F.将要配置的路径复制到key中 */ 5.Bean配置(XML) 1.Bean对象初始化方式有三种 /** 构造器初始化: Bean对应的类必须提供一个默认无参可访问的构造方法 // Bean类 ——————> 无参构造 静态工厂初始化【了解】 提供一个工厂类,使用其中的静态方法获取对象 实例工厂初始化【了解】 提供一个实例工厂类,使用其中的实例方法获取对象。由于该工厂类本身需要创建对象,因此该对象必须受Spring控制,所以必须配置该工厂类为Bean */ /** 2.Bean的作用域 // 默认单例 ————————多例:Scope("prototype") Spring初始化的Bean默认为单例模式,如果想修改成非单例模式需要修改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的初始化与销毁操作时执行的动作 销毁操作只针对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=”值” 注入引用类型:对象 A.为某个Bean注入引用类型的值,首先在Bean对应的类中声明对应的属性 private TeacherDAO dao; B.为每个要注入的属性提供对应的标准封装setter方法 (访问器) public void setDao(TeacherDAO dao) { this.dao = dao; } C.必须保障引入的对象是Spring控制的Bean D.在Bean的属性注入中,使用ref引用对应的资源 ref=”beanId/beanName” */ /** 2.构造器注入【了解】 A.提供对应的构造方法 B.配置中设置构造方法的参数 注意:如果类型匹配不成功,可以为配置中指定index索引属性,对应构造器中参数的位置 说明:构造器传参受构造器参数位置和类型的限定,因此不常使用 */ /** 5.P命名空间的使用【了解】 Spring2.5提供了一种简化配置格式,P命名空间支持 A. xml头信息中引入对p命名空间的支持 xmlns:p="http://www.springframework.org/schema/p" B. 将所有的属性注入property子元素全部修改为bean的属性 原始格式: p空间格式: */ 6.SpEl Spring3.0引入全新的SpEL(Spring Expression Language)Spring表达式语言,支持各种格式语法格式,所有的属性注入全部使用value属性名 详情参考《Spring_表达式语言.pdf》 7.集合注入 /** Spring支持为Bean注入集合 // / 、 、 List集合与数组的配置格式通用 List Java Struts2 Spring3 Set 张三 张大三 张小三 Map 张三 中国 约翰 中国 吴莫愁 榜单No.1 Properties 百度 新浪 网秦 */ /**6.团队开发 1.团队开发时,每个独立的模块制作成一个独立的配置文件 命名格式:applicationContext-模块名.xml 模块名使用全小写字母 2.某个配置文件中,使用其他配置文件中的Bean,导入对应的配置文件 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.xml applicationContext-book.xml applicationContext-order.xml 6.多配置文件加载ApplicationContext对象 格式一: 在子配置文件中导入主配置文件 加载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提供使用注解的形式配置Bean 1.配置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空间的支持 b)开启自动扫描功能,并指定扫描的路径 // 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)在属性名上方声明该属性自动装配@Autowired b)在属性名上方声明该属性注入的值@Value(value) @Autowired @Value("it") private String msg; 注意:注解自动装配属性值无需提供对应属性的setter方法 5.为Bean注入引用类型属性 // @Qualifier("xx") ——————无需set() a)在属性名上方声明该属性自动装配@Autowired b)在属性名上方声明该属性注入的值@Qualifier(bean引用名称) @Autowired @Qualifier("anno2") private AnnotationOther other; 注意:被注入的Bean一定要存在,无需提供对应属性的setter方法 6.@Autowired注解属性required用于声明自动装配的数据是否是必须的 如果required=true,必须为其装配数据,如果没有值,抛出异常 如果required=false,自动装配数据失败,则注入值为null 7.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"}) //设定该方法的返回值是一个Bean public static BookService getInst(){ return new BookService(); } } // 以上操作必须基于自动扫描功能,如不开启自动扫描,Spring将无法查找到对应的注解配置 11.可以不使用自动扫描使用Bean AnnotationConfigApplicationContext对象可以手工加载基于注解配置的Bean //用于手工添加注解开发Bean的上下文对象 AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); //加载配置管理类 ctx.register(MyConfiguration.class); //刷新上下文对象 ctx.refresh(); 注意:注册完成后一定要进行刷新,否则加载的Bean将无法被使用 8.Spring整合JUnit Spring提供了对JUnit的整合支持 操作步骤 1.导入测试Jar包 spring-test-3.2.0.RELEASE.jar 2.设置类运行器(注解添加到运行程序类的上方) @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; @Test public 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.jar Spring整合AspectJ框架(3.2资源包) spring-aspects-3.2.0.RELEASE.jar AOP联盟规范(1个) (3.0.2依赖包) com.springsource.org.aopalliance-1.0.0.jar aspectJ支持(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 { //切面类  中的是 抽取出的通知(共性功能) //被抽取的共性功能1 public void functionOne(){ System.out.println("共性功能1"); } } 说明: 被抽取的共性功能制作成独立的类中方法。由于要将两个对象中的内容融合, Spring在控制其融合的过程必须控制两个对象,因此需要分别将两个类配置为Spring管理的Bean /** D.开启AOP空间的支持 */ E.配置AOP的切面 // 告知 切面对应的 通知类 // method: 通知类中的 具体哪一个通知(方法) // pointcut:被挖取了共性功能的 方法 ——————即告知 xx类的xx方法为 切入点 //上述整体表明:在cn.it.aop.one包下的 UserImpl类的add()执行前, 先执行 通知类:myAdvice中的 functionOne()方法. /** 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) 两个参数,第一个任意,第二个int add(..) 任意参数 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:before pointcut-ref="pt2" ….. 格式三:配置在AOP总配置中,在全部范围内共享 // 全局 声明在切面标签 外部 —————— 一个切入点变量引用(id) <aop:before pointcut-ref="pt1"…… 范例: // 全局 声明在 切面标签 外部   的一个 子切入点标签 引用(id) // "局部" 声明在 切面标签 内部 的一个 子切入点标签 引用(id) // ——————> 效果 = 全局 // 独立 配置在切入点标签内部 , 仅自己使用 // 这里 同样可以引用上面定义的 pt1 ——————"局部"=全局 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......"); } 通知的配置格式 5.通知顺序 在切入点之前运行的通知 执行顺序与配置顺序有关,在上面的先运行 在切入点之后运行的通知 在同一个切面中 执行顺序与配置顺序有关,在上面的先运行 在不同的切面中 执行顺序与配置顺序有关,与切面的配置顺序相反 // 不同切面, 与切面的配置顺序 相反 总结:不同通知类型执行的顺序以配置顺序为准 // 最终 以调试结果为准!o(╯□╰)o 6.获取通知参数 // 两类 : 环绕 / 非环绕 1.为环绕通知之外的通知方法定义形参JoinPoint,该参数必须是通知方法的第一个参数 获取参数:Obejct[] args = jp.getArgs(); 范例: public void before(JoinPoint jp){ //形参 JoinPoint Object[] 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声明保存返回值的变量名 8.获取通知异常对象 异常对象的获取方式与返回值很相似,声明变量,在配置中声明保存异常对象的变量名 public void afterThrowing (Throwable e){ System.out.println("afterThrowing......."+ e); } ********9.应用 AOP参数与返回值的应用 AOP编程中,由于可以拦截方法的参数与返回值,在编程时,可以根据业务需要对参数与返回值进行修改。 eg: -用户界面输入的用户名最后包含有空格,使用AOP可以将多余的空格处理掉后,再传入逻辑层或数据层 -用户界面收集的分页页码值并不是数据层最终的使用值,可以通过AOP将该值处理后再传入数据层 5.AOP(注解————AspectJ技术) // 开启 ————————————————————————> 配置Bean ————————————————————————> @Aspect ————————————————> @Before("execution(...)") // // /注解声明 // 切面类上 加注解 // 切面类中的 通知方法上 添加通知类型注解 1.AOP注解配置流程 A.开启AOP配置支持注解@aspectj // // @AspectJ提供使用注解开发SpringAOP @AspectJ 是对AOP标准的一套实现API 核心配置文件中添加以下配置,功能等同于注解配置Bean的自动扫描路径 B.将所有参与AOP配置的类声明为Spring控制的Bean 可以使用XML配置格式或注解格式 C.在切面类的类定义上方添加切面的声明 // 切面类 的声明: @Aspect @Aspect public 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 before before around after after afterReturning // 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支持模板类 JdbcTemplate HibernateTemplate 总结: queryForInt/queryForLong 可以查询返回一个整形的数据 queryForObject 查询一个字符串类型,一个对象 query() 查询集合对象 /** 常用的数据源: Spring数据源实现类 DriverManagerDataSource DBCP数据源 BasicDataSource C3P0数据源 ComboPooledDataSource */ 15.事务 状态信息 要从 定义信息 中获取! 事务定义 ————> 事务状态 ————> 操作事务 : 刷新/存储点/完成/新事物/回滚 /** Spring事务管理主要包括3个接口 PlatformTransactionManager 事务管理器 TransactionDefinition 事务定义信息 TransactionStatus 事务具体运行状态 */ DataSourceTransactionManager 使用Spring JDBC或iBatis 进行持久化数据时使用 HibernateTransactionManager 使用Hibernate3.0版本进行持久化数据时使用 1.开启注解式事务驱动管理 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.在服务器启动时加载资源 //使用监听器完成资源的初始化工作 // 在服务器启动时,监测当前有没有初始化,如果没有 // 加载对应位置的配置文件( contextConfigLocation // classpath:applicationContext.xml ) // 加载完毕后,创建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事务管理的对象 ——————>以基于注解的方式进行事务管理——即:开启事务注解 使用Hibernate3进行开发,需要开发者手工的加载配置信息,创建SessionFactory对象,手工开启Session对象,完成相应的数据层操作 使用Spring3整合Hibernate3,前期开发者手工控制的所有对象均交由Spring统一管理,因此需要对Hibernate3所管理的对象进行设定 SessionFactory作为Spring的Bean进行管理 整合方式: 引入式整合 独立整合 */ HibernateTemplate: Serializable save(Object entity) void delete(Object entity) void update(Object entity) T load(Class entityClass, Serializable id) // ID查询 ———— 立即加载 T get(Class entityClass, Serializable id) // ... ———— 懒加载 List find(String queryString) // HQL查询 List findByCriteria(DetachedCriteria criteria) // 离线(条件)查询 List findByNamedQuery(String queryName, Object... values) // 命名查询 Session获取 getSession() // 使用HibernateDaoSupport获取Session对象 SSH整合几个注意点: 1. 声明自动扫描路径,spring自动扫描此包下的所有java类,将其下全部java类纳入为spring下管理的 即:配置此项后,spring会将 cn.it.bos 包下的 所有java类 配置为spring的 ,且都以 注解的格式 初始化 ! 2. 开启spring注解 3. 开启事务注解 ————> 可在在service层直接使用@Transactional管理DAO层的事务(AOP) 4. 整合hibernate的事务管理器 100.重点: 1.Spring调用资源的方式: new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.getBean("id"); bean.C/R/U/D(); //读取 //获取 //调用 扫描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.1 url拦截进行权限控制 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 安全操作。 组件: // 注入 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, // 自带 Realm properties文件数据源的PropertiesRealm,等等。 我们也可以插入自己的 Realm实现来代表自定义的数据源。 像其他组件一样,Realms也是由SecurityManager控制 // 也可自定义 Realm // 统一由SecurityManager控制 2.简单配置(Spring整合配置) 1.url拦截进行权限控制 使用shiro框架进行项目的认证和授权操作 第一步:导入shiro的jar到项目中 第二步:在web.xml中配置一个过滤器代理对象,在项目启动时到spring工厂中加载一个和当前过滤器name同名的bean对象 // web.xml中配置shiro的过滤器 /** shiroFilter org.springframework.web.filter.DelegatingFilterProxy shiroFilter /* */ 第三步:在spring配置文件中配置一个名称为shiroFilter的bean // 在Spring的applicationContext.xml中添加shiro配置 /** /css/** = anon /images/** = anon /js/** = anon /validatecode.jsp* = anon /login.jsp = anon /userAction_login.action = anon /page_base_staff.action = perms["staff"] /** = authc */ /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请求,协议为https user: 例如/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);//调用安全管理器,安全管理器调用Realm User 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,进行认证和授权操作 /** */ 第六步:在spring配置文件中注册上面的realm,并注入给安全管理器 /** */ 第七步:在自定义的realm中进行授权操作 public class BosRealm extends AuthorizingRealm{ @Resource private IUserDao userDao; @Resource private IRoleDao roleDao; @Resource private 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 roles = roleDao.findAll(); for (Role role : roles) { authorizationInfo.addRole(role.getCode()); } List functions = functionDao.findAll(); for (Function function : functions) { authorizationInfo.addStringPermission(function.getCode()); } } else { // 普通用户 , 根据当前用户,查询具有角色,通过角色获取权限 List roles = roleDao.findRolesByUser(user); // 添加角色 for (Role role : roles) { authorizationInfo.addRole(role.getCode()); // 添加角色对应权限 Set 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配置文件中配置自动代理和切面 /** */ 第二步:在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 * 然后,赋值给 声明的实体model */ public BaseAction(){ // 获取 BaseAction 子类的Class对象 Class< extends BaseAction> clazz = this.getClass(); // 通过 clazz对象 获取到其父类(BaseAction) 的参数化类型 :BaseAction Type genericSuperclass = clazz.getGenericSuperclass(); // 声明一个 ParameterizedType类型变量 ParameterizedType pType = null; // 判断 得到的父类是否带有 泛型 (若子类采用代理对象(相当于 孙子类) 继承该 类 —————— 则此处得到的 父类其实就是 BaseAction的子类) if ( genericSuperclass instanceof ParameterizedType ) { // 带有泛型 可强转为 ParameterizedType类型 , 并赋给 pType pType = (ParameterizedType) genericSuperclass; }else { // 不带泛型 向上多获取一级 ———————— 相当于获取到其 爷爷辈 的参数化类型 Type genericSuperclass2 = this.getClass().getSuperclass().getGenericSuperclass(); // 再强转为 ParameterizedType类型 , 并赋给 pType pType = (ParameterizedType) genericSuperclass2; } // 获取 参数化类型 中的 实际类型参数数组 Type[] actualTypeArguments = pType.getActualTypeArguments(); // 获取到 T 并强转为 Class类型 Tips: Class implements java.lang.reflect.Type,... Class domainClass = (Class ) actualTypeArguments[0]; // 给条件 对象赋值 并将其封装到pegeBean对象中 detachedCriteria = DetachedCriteria.forClass( domainClass ) ; pageBean.setDetachedCriteria(detachedCriteria); // 将 T的实例 赋值给model try { model = domainClass.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } 3.配置缓存 /** */ 3.简单扩展 /** 1.自定义realm: //这是授权方法 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理解成权限对象。类似user Subject 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" %> 默认显示用户名称 标签条件(均是显示标签内容) 登录之后 不在登录状态时 用户在没有RememberMe时 用户在RememberMe时 在有abc或者123角色时 拥有角色abc 没有角色abc 拥有权限资源abc 没有abc权限资源 默认显示用户名称 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文件 创建核心对象 ————————流程引擎:processEngine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); ProcessEngine:activiti的核心类! 所有类都来自于它! API: .getRepositoryService() .createDeployment(); // 直接加载源文件 、 zip .deleteDeployment(); // 删除流程定义 .createDeploymentQuery(); // 流程定义对象信息的查询获取 ——————主要查的是图片,用于显示流程用 .getRuntimeService() .startProcessInstanceByKey(); // 开启流程 .deleteProcessInstance(); // 删除流程实例 .getActivityId() //获取当前活动节点 .getTaskService() .createTaskQuery(); // 查询任务信息... .taskCandidateUser(); // 组任务查询过程 .complete(taskId); // 执行任务 .claim() // 拾取任务 组任务——>个人 .setAssignee() // 退回组任务 个人——>组任务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值