一、 JavaWeb基础
第一天:
1.Eclipse详解:
(1).Bad versionnumber in .class file:编译器版本和运行(JRE)版本不符合。高的JRE版本兼容低版本的编译器版本。
(2).当程序有错误的时候,使用Debug as 运行程序。双击语句设置断点。程序运行到此处停止。点击跳入方法的内部代码。点击跳过,执行下一条代码,点击跳出,跳出方法。观察变量的值,选中变量右击 选择watch. 跳入下一个断点。查看断点,调试完后一定要清除断点。结束运行断点的jvm.
2.HashSet和hashCode和equals方法
java系统首先调用对象的hashCode()方法获得该对象的哈希吗,然后根据哈希吗找到相应的存储区域,最后取出该存储区域内的每个元素与该 元素进行比较.两个equals相等,hashCode()相等。需要重写equals,hashCode()方法.更改数据的值,hashCode() 的值也更改了,并未删除.内存泄露.有个东西不在被用,但是未被删除,导致内存泄露.
3.Junit测试框架
(1).在测试类,方法前加注解:@Test,不然出现初始化异常。
(2).方法before,after前加@Before,@After注解。在测试方法之前和之后运行方法。
(3).静态方法beforeClass,afterClass方法前加上注解@BeforeClass,@AfterClass,类加载的时候运行
(4).Assert断言。判断两个对象是否相等。期望值和实际值相等。
4.得到配置文件的路径
通过类加载器 reflect.class.getClassLoader.getResourceAsStream();在class指定目录下查找指定的类文件进行 加载.编译器把src中的.java文件编译成class文件,所有非.java文件,原封不动的搬过去.但是这种方法是只读的.
通过类的信息reflect.class.getResourceAsStream();相对路径
一般用绝对路径,使用配置文件告诉用户路径.
一定要记住要用完整的路径,但是完整的路径不是硬编码的,是计算出来的.
5.反射
(1).反射主要用于框架开发
(2).一个类有多个组成部分,例如:成员变量,方法,构造方法等,反射就是加载类,并解析类的各个组成部分。
(3).加载类使用Class.forName()静态方法,给类的完整名称,包名和类名。
(4).Class提供了解析public的构造方法,方法,字段等方法以及private。字段封装数据,方法执行功能
(5).静态方法无需传递对象,method.invoke()
(6).升级时保持兼容性,main函数的解析有点麻烦,反射解析数组参变量的时候的问题。启动Java程序的main方法的参数是一个字符串数 组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按照jdk1.5的语法,整个数组是一个参数,而按照 jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理 呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成若干个单独的参数。所以,在给main方法传递参数时, 不能使用代码mainMethod.invoke(null,newString[]{"xxx"}),javac只把它当做jdk1.4的语法进行理 解,而不把它当做jdk1.5的语法解释,因此会出现参数类型不对的问题。解决的方法:
mainMethod.invoke(null,newObject[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)newString[]{"xxx"});编译器会做特殊处理,编译时不把参数当做数组看待,也就不会把数组打散成若干个参数了.
(7).对数组进行反射:相同的维数(不是数组元素的个数,例如都是一维数组,不关心数组的大小),相同的8种基本数据类型时数组有相同的字节码.
6. 泛型
(1).泛型是对象类型,不能是基本类型,泛型存在源代码级别上,给编译器看的,生成class文件就不存在泛型了
(2).参数类型变量,实际类型变量,泛型类型,参数化的类型
(3).自定义泛型方法:public void method(T args){};public<T,K,V> void method(T a,K b,V c){};
(4).静态方法public static void method(T t){};泛型类不作用静态方法
7.可变参数
(1).可变参数就看成数组,可以使用增强for循环
(2).可变参数列表为最后一个参数
(3).可以给可变参数传递一个数组
(4).可变参数的类型是基本类型还是对象类型
8.课程介绍
(1).在谷歌心目中,“云”必须具备以下条件:数据都存在网上,而非终端里,软件最终消失,只要你的“云”设备拥有浏览器就可以运行现在的一切, “云”时代的互联网终端设备将不仅仅是PC,手机,汽车,甚至手表,只要有简单的操作系统加个浏览器就完全可以实现,由于数据都在“云”端,企业的IT管 理越来越简单,企业和个人用户也不同在担心病毒,数据丢失等问题。
(2).李开复描述了这样一个场景,只要你的PC或手机等终端里安装了一个简单的操作系统和完整功能的浏览器,开机后输入自己的用户名和密码,你存在“云”中的应用软件和数据就会同步到终端里。
9.快捷键
(1).配置快捷键:window->preferences->key
(2).Alt+/ :内容提示
Ctrl+1 :快速修复
Ctrl+Shift+O :快速导入包
Ctrl+Shift+F :格式化代码
Alt+方向键 :跟踪代码
Ctrl+Shift+/ :添加注释
Ctrl+Shift+\ :取消注释
Ctrl+Shift+X :更改为大写
Ctrl+Shift+Y :更改为小写
Ctrl+Shift+向下键 :复制代码
Ctrl+Shift+向上,向下 :改变代码行的顺序
Ctrl+T :查看继承关系
Ctrl+Shift+T :查看源代码
Ctrl+Shift+L :查看所有的快捷键
10.类加载器及其委托机制的深入分析
(1).Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
(2).类加载器也是Java类,因为其他java类的加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap(内嵌到JVM的内核中,使用C++语言编写的)
(3).Java虚拟机中的所有类装载器采用具有父子关系的属性结构进行组织,在实例化每个类转载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载器
(4).
public class ClassLoaderTest{
public static void main(String[] args){
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//输出为sun.misc.Lanuncher$AppClassLoader;
System.out.println(System.class.getClassLoader());
//输出为null,因为类System是由BootStrap加载的;
}
}
(5).BootStrap->ExtClassLoader->AppClassLoader
ClassLoader loader= ClassLoaderTest.class.getClassLoader();
while(loader!=null){
System.out.println(loader.getClass().getName());
loader = loader.getParent();//往上顺序打印
}
System.out.println(loader);//最后打印老祖宗
(6).
BootStrap------>JRE/lib/rt.jar
ExtClassLoader----->JRE/lib/ext/*.jar
AppClassLoader------>ClassPath指定的所有jar或目录
用Eclipse的打包工具将ClassLoaderTest打包成itcast.jar,然后放在jre/lib/ext目录下,在 eclipse中运行这个类,运行结果显示为ExtClassLoader,此时的环境状态是classpath目录有 ClassLoaderTest.class,ext/itcast.jar包中也有ClassLoaderTest.class,这时我们在打印 ClassLoaderTest类的类加载名称,发现是ExtClassLoader,而不是AppClassLoader.
(7).类加载的委托机制:
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢:
首先当前线程的类加载器去加载线程中的第一个类
如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
每个类加载器加载类时,又先委托给其上级类加载器
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那么多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理
(8).Thread类有一个方法setContextClassLoader(ClassLoader classLoader)//加载当前的类.
(9).每个类加载都首先会委托送到BootStrap,那么BootStrap很累,这样,那为什么不多搞几个BootStrap呢,之所以不这 样做,是因为,便于统一管理,因为所有的类都会找BootStrap,可能这时有几个相同的类进行加载,那么BootStrap,不会多次将他们的 class文件加载内存中,只会加载一份即可.这样效率就高了.
(10).
public class MyClassLoader{
public static void main(String[]args){
String srcPath=args[0];
String destDir=args[1];//得到目录
String destFileName =srcPath.substring(srcPath.lastIndexOf('/')+1);//得到文件名
String destFilePath=destDir+"\"+destFileName;
FileInputStream fis = newFileInputStream(srcPath);
FileOutputStream fos=new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
private static void cyp(InputStreamips,OutputStream ops){
int b =-1;
while((b=ips.read())!=-1){
ops.write(b^0xff);//对内容进行异或处理
}
}
}
class ClassLoader extends Date{
public String toString(){
return "hello,itcast";
}
}
args[0]:ClassLoader.class的绝对路径
args[1]:itcastlib
有包名的类不能调用无包名的类.
(11).编写自己的类加载器:
知识讲解:
自定义的类加载器必须继承ClassLoader(抽象类)
覆盖findClass方法
defineClass方法:得到class文件转换成字节码
编程步棸:
编写一个文件内容进行简单加密的程序
编写了一个自己的类加载器,可实现对加密过的类进行装载和解密
编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中可以出了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName
(12).
模板设计模式:
父类--->loadClass(相同的)
子类1(自己干的)
子类2(自己干的)
覆盖findClass方法(自己干)
(13).
public class MyClassLoader extendsClassLoader{
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
@Override
protected Class<?> findClass(Stringname){
String classFileName = classDir +"\" + name + ".class";
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = newByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes = bos.toByteArray();
defineClass(bytes,0,bytes.length);
return super.findClass(name);
}
public static void main(String[] args){
Class clazz = newMyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
ClassLoaderAttachment d1 =clazz.newInstance();
}
}
(14).windows->showview->problem查看错误.
(15).可以查看Servlet的类加载委托继承树
11. 枚举
(1).枚举的作用限定指定的值,没有枚举前设计一个类,将构造函数设置成私有的,变量设置成静态常量
(2).枚举可以有构造函数(私有的),字段,方法
(3).可以定义set,get方法,获取变量的值。
(4).带有抽象方法的枚举,不能new出新的对象了。在new对象的时候就重写抽象方法,使用匿名内部类
(5).枚举中的每个枚举值代表枚举类的一个对象。
(6).枚举也可以实现接口,或继承抽象类
(7).JDK5中的switch拓展为除了接受int,short,char,byte外,也可以接受枚举类型。
(8).枚举的方法,name,ordial,valueOf,将字符串转换成枚举值。表单提交数据的时候。values返回枚举的所有的枚举值
12.内省
(1).内省:Introspector,专门操作Bean的属性。
(2).Bean的属性:只有字段提供了set/get方法,就是属性。只要有get/set的方法,就有一个属性,所以属性是有get/set方法决定的
(3).任何类都继承了Object类,又因为Object中有一个class属性
(4).内省的入口类:Introspector,方法getPropertyDescriptors()获取Bean的属性
(5).操作Bean的指定属性
(6).BeanUtils框架操作Bean
(7).在工程中新建一个文件夹,将架包复制到文件夹中,还需另外的架包loging,选中架包,右击build path加入到工程的环境中
(8).BeanUtils使用方便,类型自动转化,只支持8中基本数据类型
(9).给BeanUtils注册类型转换器,ConvertUtils.register()
(10).将Map中的数据整合到对象中,BeanUtils.populate()方法
13.配置Java模板代码
window->preferences->java->Editor->Template:编辑模板代码:line_selection,光标cursor,右击选择source with
14.享元模式
相同的对象只实例化一个,实例:桌面上的图标,word中字符,有很多小的对象,有很多相同的属性,不同的属性叫做外部行为,相同的属性叫做内部行为integer的缓冲池
15.注解
(1).@SuppressWarning("deprecation")过时注解
(2).@Deprecated注解,表示该方法是否过时,架包升级时添加的注解
(3).注解相当于一种标记,通过反射了解你的类及各种元素上有无何种标记
(4).注解相当于一个类:@interface Annotation{};注解类,应用注解类的类,对应用了注解类的类进行反射操作的类
(5).AnnotationTest.class.getAnnotation(ItcastAnnotation.class)得到类AnnotationTest上的注解ItcastAnnotation,注解上使用注解叫做元注解,元数据,元信息
(6).@Retention(RetentionPolicy.RUNTIME)(保持到运行阶段),@Retention(RetentionPolicy.SOURCE)(保持在源文件阶 段),@Retention(RetentionPolicy.CLASS)(保持在class文件中):源代码->class文件->(类加载)内存中的文件(字节码)
(7).@Override注解保持到SOURCE,@SuppressWarning注解保持到SOURCE,@Deprecated注解保持到RUNTIME(只有将该类调到内存中才知道该类中的方法是否过时了)
(8).@Target({ElementType.METHOD,ElementType.TYPE})注解只能标记到方法上或类,接口等类型上 (9).注解的属性:String color();类有个color属性,还有一个特殊的属性value,属性的默认值default,数组的属性值,枚举的属性值,注解的属性值
第二天:
1.dom4j解析XML文档
(1).Dom4j是一个简单、灵活的开放源代码的库,Dom4j是由早期开发JDOM的人分离出来而后独立开发的,与JDOM不同的是,dom4j使用接口和抽象基类,虽然Dom4j的api相对要复杂一些,但是他提供了比JDOM更好的灵活性
(2).Dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极易使用的特点,现在很多软件采用的Dom4j,例如hibernate,包括sun公司自己的JAXM也使用了Dom4j
(3).使用Dom4j开发,需要下载dom4j相应的jar文件
- XML语法
(1).编写XML文档时,需要先使用文档声明,声明XML文档的类型,使用IE校验XML文档的正确性.
(2).XML文档中的"中国"保存为本地的码表的"中国"
(3).在XML文档中,空格和换行都作为原始内容被处理
(4).XML区分大小写,一个标签可以有多个属性,每个属性都有它自己的名称和取值,在XML技术中,
标签属性所代表的信息也可以被表示子标签表示
(5).XML文件中的注释采用:"
在Eclipse中jsp是用UTF-8编码存放的,当读取jsp的内容时,是用本地字符编码的,可能出现乱码,所以要设置读取文件的编码集.
(6).浏览器重定向的JSP不能放在web-inf目录中,而请求转发的JSP可以放在web-inf目录中
(7).struts2中的全局视图:
<resultname="index.jsp">
和struts1中的全局视图是很相似的
我们可以定义一个包,然后将全局视图的配置放到这个包中
5.struts2常用标签
(1).property标签用于输出指定值:
<s:setname="name" value="kk"/>
<s:propertyvalue ="#name"/>
default:可选属性,如果需要输出的属性值为null,则显示该属性指定的值
escape:可选属性,指定是否格式化HTML代码
value:可选属性,指定需要输出的属性值,如果没有指定该属性,则默认输出ValueStack栈顶的值
id:可选属性,指定该元素的标识
(2).iterator标签用于对集合进行迭代,这里的集合包含List、Set和数组
redblue>
value:可选属性,指定被迭代的集合,如果没有设置该属性,则使用ValueStack栈顶的集合,
id:可选属性,指定集合里元素的id(已被标注为过时)
status:可选属性,该属性指定迭代时的iteratorStatus实例,该实例包含如下几个方法:
int getCount(),返回当前迭代了几个元素
int getIndex(),返回当前迭代元素的索引
boolean isEven(),返回当前被迭代元素的索引是否为偶数
boolean isOdd(),返回当前被迭代元素的索引是否是奇数
boolean isFirst(),返回当前被迭代元素是否是第一个元素
boolean isLast(),返回当前被迭代元素是否是最后一个元素
(3).<s:setname="age" value="21" scope="request"/>
<s:iftest="#request.age==23">
23
21
都不等
(4).url标签:
生成类似如下路径:
/struts/test/helloworld_add.action?persionid=3
当标签的属性值作为字符串类型处理时,"%"符号的用途是计算OGNL表达式的值
输出结果:
myurl
http://www.foshanshop.net
(5).表单标签checkboxlist复选框
如果集合为list
Java
Net
Java
Java
如果集合为MAP
生成如下html代码:
aa
bb
当然集合里面存放的也可以是对象类型
(6).单选框
6.struts2的处理流程与Action的管理方式
(1).用户请求->(查看web.xml文 件)StrutsPrepareAndExecuteFilter->Interceptor(struts2内置的一些拦截器或用户自定义拦截 器)->Action(用户编写的Action类,类似Struts1中的action,针对每一次请求,都创建一个 Action)->Result(类似struts1中的forward)->Jsp/html(响应)
(2).StrutsPrepareAndExecuteFilter是struts2框架的核心控制器,它负责拦截由/"指定的所有用户请求,当用户请求到达时,该fileter会过滤用户的请求,默认情 况下,如果用户请求的路径不带后缀或者后缀以.action结尾,这时请求将被转入到struts2框架处理,否则struts2框架将略过该请求的处 理,当请求转入struts2框架处理时会先经过一系列的拦截器,然后再到Action,与struts1不同,struts2对用户的每一次请求都会创 建一个Action,所以struts2中的action是线程安全的.
7.XML配置方式实现对action的所有方法进行校验
(1).基于XML配置方式实现对action的所有方法进行输入校验:
使用基于XML配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和action类放在同一 个包下,文件的取名格式为:ActionClassName-validation.xml,其中,ActionClassName为action的简单 类名,-validation为固定写法,如果Action类为cn.itcast.UserAction,那么该文件的取名应 为:UserAction-validation.xml,下面是校验文件的模板:
<field-validatortype="requiredstring">
<paramname="trim">true
用户名不能为空
指定action中要校验的属性,指定校验器,上面指定的校验器 requirestring是由系统提供的,系统提供了能满足大部分验证需求的校验器,这些校验器的定义可以在xwork-2.x.jar中的 com.opensymphony.xwork2.validator.validators下的default.xml中找 到,为校验失败后的提示信息,如果需要国际化,可以为message指定key属性,key的值为资源文件中的key,在 这个校验文件中,对action中字符串类型的username属性进行验证,首先要求调用trim()方法去掉空格,然后判断用户名是否为空.
(2).struts2提供的校验器列表:
required(必填校验器,要求field的值不能为Null)
requiredstring(必填字符串校验器,要求field的值不能为null,并且长度大于0,默认情况下会对字符串取钱后空格)
stringlength(字符串长度校验器,要求field的值必须在指定的范围内,否则校验失败,minLength参数指定最小长度,maxLength参数指定最大长度,trim参数指定校验field之前是否去除字符串前后的空格)
regex(正则表达式校验器,检查被校验的field是否匹配一个正则表达式,expression参数指定正则表达式,caseSensitive参数指定进行正则表达式匹配时,是否区分大小写,默认值为true)
int(整数校验器,要求field的整数值必须在指定范围内,min指定最小值,max指定最大值)
double(双精度浮点数校验器,要求field的双精度浮点数必须在指定范围内,min指定最小值,max指定最大值)
fieldexpression(字段OGNL表达式校验器,要求field满足一个OGNL表达式,expression参数指定OGNL表达式,该逻辑表达式基于ValueStack进行求值,返回true时校验通过,否则不通过)
email(邮件地址校验器,要求如果field的值非空,则必须是合法的邮件地址)
URL(网址校验器,要求如果field的值非空,则必须是合法的URL地址)
date(日期校验器,要求field的日期值必须在指定范围内,min指定最小值,max指定最大值)
conversion(转换校验器,指定在类型转换失败时,提示的错误信息)
visitor(用于校验action中的符合属性,它指定一个校验文件用于校验符合属性中的属性)
expression(OGNL表达式校验器,expression参数指定ognl表达式,该逻辑表达式基于ValueStack进行求值,返回true时校验通过,否则不通过,该校验器不可用在字段校验器风格的配置中)
(3).[CDATA[文本内容]]:文本内容不会被解析,只会原封不动的当做文本处理
(4).编写校验文件时,不能出现帮助信息:
在编写ActionClassName-validation.xml校验文件时,如果出现不了帮助信息,可以按照下面方式解决:
windows->preferences->myeclipse->filesand editors->xml->xmlcatalog:点击add,在出现的窗口中的location中选"file system"然后再xwork-2.1.2戒烟目录的src\java目录中选择xwork-validator-1.0.3.dtd,回到设置窗口的 时候,不要急着关闭窗口,应把窗口中的Key Type改为URI,Key改为http://www.opensymphoney.com/xwork/xwork-validaor-1.0.3.dtd
8.XML配置方式实现对action的指定方法校验
(1).基于XML配置方式对指定action方法实现输入校验:
当校验文件的取名为ActionClassName-validation.xml时,会对action中的所有处理方法实施输入校验,如果你只需 要对action中的某个action方法实施校验,那么校验文件的取名应为:ActionClassName-ActionName- validation.xml,其中ActionName为struts.xml中的action的名称,例如:在实际应用中,常有以下配置:
<resultname="success">/WEB-INF/page/message.jsp
<resultname="input">/WEB-INF/page/addUser.jsp
UserAction中有以下两个处理方法:
public String add() throws Exception{
}
public String update() throws Exception{
}
要对add()方法实施验证,校验文件的取名为:UserAction-user_add-validation.xml
要对update()方法实施验证,校验文件的取名为:UserAction-user_update-validation.xml
(2).基于XML校验的一些特点:
当为某个action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统按下面顺序寻找校验文件:
ActionClassName-validation.xml
ActionClassName-ActionName-validation.xml
系统寻找到第一个校验文件时还会继续搜索后面的校验文件,当搜索到所有校验文件时,会把校验文件里的所有校验规则汇总,然后全部应用于action方法的校验,如果两个校验文件中指定的校验规则冲突,则只使用后面文件中的规则。
当action继承了另一个action,父类action的校验文件会先被搜索到
假设UserAction继承BaseAction
<actionname="user" class="cn.itcast.action.UserAction"method="{1}">
访问上面的action,系统先搜索父类的校验文件:BaseAction-validation.xml,BaseAction-user- validation.xml,接着搜索子类的校验文件:UserAction-validation.xml,UserAction-user- validation.xml,应用于上面action的校验规则为这四个文件的总和
9.动态方法调用和使用通配符定义action
(1).在struts1中实现方法的动态调用:
<actionpath="/control/employee/manage" type="....DispatchAction"parameter="method"/>
/control/employee/manage?method=addUI
但是Action必须继承DispatchAction
(2).在struts2中有两种方式:
第一种:(struts2.1版本后就不建议使用了)是动态方法调用:如果Action中存在多个方法时,我们可以使用!+方法名调用指定方法,如下:
public class HelloWorldAction{
private String message;
....
public String execute()throws Exception{
this.message="我的第一个struts2应用";
}
public String other() throws Exception{
this.message="第二个方法";
return "success";
}
}
假设访问上面的action的URL路径为:/struts/test/helloworld.action,要访问action的other方 法,我们就可以这样调用:/struts/test/helloworld!other.action,如果不想使用动态方法调用,我们可以通过常量 struts.enable.DynamicMethodInvocation关闭动态方法调用:
<constantname="struts.enable.DynamicMethodInvocation"value="false"/>
第二种:使用通配符定义action(推荐使用的)
<action name="helloworld_*"class="cn.itcast.action.HelloWorldAction" method="{1}>
<resultname="success">/WEB-INF/page/hello.jsp
public class HelloWorldAction{
private String message;
....
public String execute()throws Exception{
this.message="我的第一个struts2应用";
}
public String other() throws Exception{
this.message="第二个方法";
return "success";
}
}
要访问other()方法,可以通过这样的URL访问:/test/helloworld_other.action
name="helloworld_"后可根据多个,method={1},'1'表示匹配*的位置
name="helloworld_*_*",method={2}:要访问other()方法,可以通过这样的URL访问:/test/helloworld_xxx_other.action
10.对action指定的方法进行校验
手工编写代码实现对action指定方法输入校验:
通过validateXxx()方法实现,validateXxx()只会校验action中方法名为Xxx的方法,其中Xxx的第一个字母要大 写,当某个数据校验失败时,我们应该调用addFieldError()方法往系统的fieldErrors添加校验失败信息,(为了使用 addFieldError()方法,action可以继承ActionSupport(),如果系统的fieldErrors包含失败信 息,struts2会将请求转发到名为input的result,在input视图中可以通过显示失败信息.
validateXxx()方法使用例子:
public Stringadd() throws Exception{return "success";}
public voidvalidateAdd() {
if(username==null&&"".equals(username.trim()))this.addFieldError("username","用户名不能为空");
}
验证失败后,请求转发至input视图:<resultname="input">/WEB-INF/page/addUser.jsp
在addUser.jsp页面中使用显示失败信息
11.对Action中所有方法进行输入校验
(1).在struts2中,我们可以实现对action的所有方法进行校验或者对action的指定方法进行校验
(2).对于输入校验struts2提供了两种实现方法:一种是采用手工编写代码实现,另一种是基于XML配置方式实现
(3).手工编写代码实现对action中所有方法输入校验:通过重写validate()方法实现,validate()方法会校验action 中所有与execute方法签名相同的方法,当某个数据校验失败时,我们应该调用addFieldError()方法往系统的fieldErrors添加 校验失败信息(为了使用addFieldError()方法,action可以继承ActionSupport(),如果系统的fieldErrors包 含失败信息,struts2会将请求转发到名为input的result,在input视图中可以通过显示失 败信息.
validate()使用例子:
public voidvalidate(){
if(this.mobile==null||"".equals(this.mobile.trim())) {this.addFieldError("username","手机号不能为 空")}else{if(Pattern.compile("^1[358]\d{9}").matcher(this.mobile.trim()).matchers()) {this.addFieldError("mobile","手机号的格式不正确");}}
}
验证失败后,请求转发至input视图:
<resultname="input">/WEB-INF/page/addUser.jsp
在addUser.jsp页面中使用显示失败信息
12.多文件上传
(1).多文件上传,就是在一个文件上传的基础上,将属性File变成数组类型File[]类型即可,同时该字段的名称必须要和上传页面中属性name的名称一样.
然后进行一次迭代,就可以得到所有的文件
13.防止表单重复提交
标签防止表单重复提交
第一步:在表单中加入
<s:textfieldname="person.name"/>
第二步:
<interceptor-refname="defaultStack"/>
<interceptor-refname="token"/>
<resultname="invalid.token">/WEB-INF/page/message.jsp
/WEB-INF/page/result.jsp
以上配置加入了"token"拦截器和"invalid.token"结果,因为"token"拦截器在会话的tlent与请求的token不一致时,将会直接返回"invalid.token"结果
在debug状态,控制台出现下面信息,是因为Action中并没有struts.token和struts.token.name属性,我们不用关心这个错误
使用了
标签可以不指定action的上下文标签路径,可以通过命名空间实现.和前面的原理是一样的,在路径后面添加上sessionid号,只是这步操作不需要我们自己得到sessionid号,struts帮我们操作.在值栈中的对象,访问无需添加'#'
14.访问或添加几个属性
(1).访问或添加request/session/application属性,在struts2中的Action中的execute方法中没有Servlet api(没有响应的参数);
public String scope() throws Exception{
ActionContextctx=ActionContext.getContext();
ctx.getApplication().put("app","应用范围");
ctx.getSession().put("ses","session范围");
ctx.put("request","request范围");
return "scope";
}
${applicationScope.app} ${sessionScope.ses} ${requestScope.req}(2).获取HttpServletRequest/HttpSession/ServletContext/HttpServletResponse对象:
方法一:通过ServletActionContext类直接获取
public String rsa() throws Exception{
HttpServletRequest request =ServletActionContext.getRequest();
ServletContextservletContext=ServletContext.getServletContext();
request.getSession();
HttpServletResponseresponse=ServletActionContext.getResponse();
return "scope";
}
方法二:实现指定接口,由struts框架运行时注入:
public class HelloWorldAction implementsServletRequestAware,ServletResponseAware,ServletContextAware{
private HttpServletRequest request;
private ServletContext servletContext;
private HttpServletResponse response;
public voidsetServletRequest(HttpServletRequest req){
this.request.req;
}
public voidsetServletResponse(HttpServletResponse res){
this.response=res;
}
public voidsetServletContext(ServletContext ser){
this.servletContext=ser;
}
注意1和2的不同,一个不需要得到对象,一个需要得到对象,所以要区分两个的应用场景
}
15.解决struts配置文件无提示问题
找到struts2.0.dtd文件即可,windows->preferences->MyEclipse->XML->XMLCatalog,点击添加strut2.dtd
16.介绍struts2及struts2开发环境的搭建
(1).struts2是在webwork2基础发展而来的,和struts一样,struts2也属于MVC框架,不过有一点大家需要注意的是: 尽管struts2和struts1在名字上的差别不是很大,但是struts2和struts1在代码编写分割上几乎是不一样的,那么既然有了 struts1,为何还要推出struts2,主要是因为有一下有点:
第一:在软件设计上struts2没有像struts1那样跟Servlet api和struts api有着紧密的耦合,struts2的应用可以不依赖于servlet api 和struts api,struts2的这种设计属于无侵入式的设计,而struts1却属于侵入式设计,因为其的
execute()方法中的参数为ActionMapping,ActionForm,HttpServletRequest,HttpServletResponse
第二:struts2提供了拦截器,利用拦截器可以进行AOP编程,实现如权限拦截等功能
第三:struts2提供了类型转换器,我们可以把特殊的请求参数转换成需要的类型,在struts1中,如果我们要实现同样的功能,就必须向struts1的底层实现BeanUtils注册类型转换器才行
第四:struts2提供支持多种表现层技术,如:JSP,freeMarker,Velocity等
第五:struts2的输入校验可以对指定方法进行校验,解决了struts1长久之痛,struts1中的validate方法对所有的方法进行校验
第六:提供了全局范围、包范围、和Action范围的国际化资源文件管理实现.
(2).搭建struts2的环境和struts1是相同的,第一步导入相关包,第二步建立struts2的配置文件,第三步在web.xml中注册struts2框架的配置
(3).所需的包:struts2-core-2.x.x.jar,xwork-2.x.x.jar(webwork的核心架包),ognl-2.6.x.jar
(4).struts2默认的配置文件为struts.xml,该文件需要放在/web-inf/classes目录下
(5).在struts1中,struts框架是通过servlet启动的,在struts2中,struts框架是通过Filter启动的,它在 web.xml中的配置如下所示:可以参照struts文件夹下的例子中拷贝,在strutsperpareExecuteFilter的init()方 法中将会读取类路径下默认的配置文件struts.xml完成初始化操作,注意:struts2读取到struts.xml的内容后,以javabean 形式存放在内存中,以后struts2对用户的每次请求处理将使用内存中的数据,而不是每次都读取struts.xml文件
(6).自从struts2.1.3以后,下面的FilterDispatcher已经标注为过时了,struts2.1.3后期版本为StrutsPrepareAndExecuteFilter类
17.开发第一个应用
(1).在struts.xml中的配置:
<actionname="helloworld" class="cn.itcast.action.HelloWorldAction"method="execute">
<resultname="success">/WEB-INF/page/hello.jsp
在struts2框架中使用包来管理Action,包的作用和Java中的类包是非常类似的,它主要用于管理一组业务功能相关的action,在实际应用中,我们应该吧一组业务功能相关的Action放在同一个包下
配置包时必须指定name属性,该name属性可以任意取名,但必须唯一,它不对应java的类包,如果其他包要继承该包,必须通过该属性进行引 用,包的namespace属性用于定义该报的命名空间,命名空间作为访问该包下Action的路径的一部分,如访问上面例子的Action,访问路径 为:/test/helloworld.action,namespace属性可以不配置,对本例而言,如果不指定该属性,默认的命名空间为" "(空字符串).当然配置可以减少重复的代码,struts1中的重复代码就可以使用命名空间来解决
通常每个包都应该继承struts-default包,因为struts2很多核心的功能都是拦截器来实现的,如:从请求中把请求参数封装转到 action、文件上传和数据验证等都是通过拦截器实现的,struts-defaul定义了这些拦截器和Result类型,可以这么说:当包继承了 struts-default才能使用struts2提供的核心功能,struts-default包是在struts2-core-2.x.x.jar 文件中的struts-default.xml中定义,struts-default.xml也是struts2默认配置文件,struts2每次都会自 动加载struts-default.xml文件,包还可以通过abstract="true"定义为抽象包,抽象包中不能包含action,可以查看 struts-default.xml文件中,就可以看到定义了很多拦截器
和struts1中的forward很相似,定义视图
(2).public Stringexecute(){return 视图的名称;}注意到这个方法和struts1不同,没有参数,返回类型也不同,这就降低了耦合性,非侵入式的编程了.
(3).在jsp中使用el表达式即可${message},message是Action中的一个方法getMessage()方法,而不是根据Action中的成员变量message
18.配置Action范围国际化资源文件
(1).我们也可以为某个action单独制定资源文件,方法如下:在Action类所在的路径,放置 ActionClassName_language_country.properties资源文件,ActionClassName为Action类的 简单名称当查找指定key的消息时,系统会先从ActionClassName_language_country.properties资源文件查找, 如果没有找到对应的key,然后沿着当前包往上查找基本名为package的资源文件,一直找到最顶层包,乳沟还没有找到对应的key,最后会从常量 struts.custom.i18n.resources指定的资源文件中查找
(2).JSP中直接访问某个资源文件
struts2为我们提供了标签,使用标签我们可以在类路径下直接从某个资源文件中获取国际化数据,而无需任何配置:
itcast为类路径下资源文件的基本名
如果要访问的资源文件在类路径的某个包下,可以这样访问:
<s:i18nname="cn/itcast/action/package">
上面访问cn.itcast.action包下基本名为package的资源文件
19.配置包范围的国际化资源文件
(1).在一个大型应用中,整个应用有大量的内容需要实现国际化,如果我们把国际化的内容都放置在全局资源属性文件中,显然会导致资源文件变得过于庞大、臃肿,不便于维护,这个时候我们可以针对不同模块,使用包范围来组织国际化文件
方法如下:在java的包下放置package_language_country.properties资源文件,package为固定写法,处 于该包及子包下的action都可以访问该资源,当查找指定key的消息时,系统会先从package资源文件中查找,当找不到对应的key时,才会从常 量struts.custom.i18n.resources指定的资源文件中寻找.
20.配置国际化全局资源文件、输出国际化信息
(1).准备资源文件,资源文件的命名格式如下:
baseName_language_country.properties
baseName_language.properties
baseName.properties
其中baseName是资源文件的基本名,我们可以自定义,但是language和country必须是java支持的语言和国家。如:
中国大陆:baseName_zh_CN.properties
美国:baseName_en_US.properties
(2).现在为应用添加两个资源文件:
第一个存放中文:itcast_zh_CN.properties
内容为:welcom=欢迎来到传智播客
第二个存放英语(美国):itcast_en_US.properties
内容为:welcome=welcom to itcast
(3).对于中文的属性文件,我们编写好后,应该使用JDK提供的native2ascii命令把文件转换为unicode编码的文件,命令的使用方式如下:
native2ascii 源文件.properties 目标文件.properties,在MyEclipse6.6版本以及后面的版本会自动转换.
(4).struts2有:全局范围,包范围,action范围的资源文件
(5).配置全局资源与输出国际化信息:
当准备号资源文件之后,我们可以在struts.xml中通过:struts.custom.i18n.resources常量把资源文件定义为全局资源文件,如下:
<constantname="struts.custom.i18n.resources" vlaue="itcast"/>
itcast为资源文件的基本名
后面我们就可以在页面或在action中访问国际化信息:
在JSP页面中使用标签输出国际化信息:
<s:textname="user"/>,name为资源文件中的key
在Action类中,可以继承ActionSupport,使用getText()方法得到国际化信息,该该方法的第一个参数用于指定资源文件中的key,
在表单标签中,通过key属性指定资源文件中的key,如:
<s:textfieldname="realname" key="user"/>
21.请求参数接受
(1).struts1中是使用ActionForm接受用户的请求参数
(2).采用基本类型接受请求参数(get/post):
在Action类中定义与请求参数同名的属性,struts2便能自动接受请求参数并赋予给同名属性:请求路径:http://localhost:8080/test/view.action?id=78
public classProductAction{
private Integerid;
public voidsetId(Integer id){//struts2通过反射技术调用与请求参数同名的属性的setter方法获取请求参数值
this.id=id;
}
public IntegergetId(){return id;}
}
(3).采用复合类型接受请求参数
请求路径:http://localhost:8080/test/view.action?product_id=78
public class ProductAction{
private Product product;
public void setProduct(Product product){htis.product=product;}
public Product getProduct(){returnproduct;}
}
struts2首先通过反射技术调用Product的默认构造器创建product对象,然后再通过反射技术调用product中与请求参数同名的属性的setter方法来获取请求参数值
(4).关于struts2.1.6版本中存在一个Bug,及接受到的中文请求参数为乱码(以post方式提交),原因是struts2.1.6在 获取并使用了请求参数后才调用HttpServletRequest的setCharacterEncoding()方法进行编码设置,导致应用使用的就 是乱码请求参数,这个Bug在struts2.1.8中已经解决,如果你使用的是struts2.1.6,要解决这个问题,你可以这样做:新建一个 Filter,把这个Filter放置在Struts2的Filter之前,然后再doFilter()方法中添加以下代码:
public void doFilter(..){
HttpServletRequest req=(HttpServletRequest)request;
req.setCharacterEncoding("UTF-8");
filterchain.doFilter(request,response);
}
22.全局类型转换器
自定义全局类型转换器:将上面的类型转换器注册为全局类型转换器:在WEB-INF/classes下放置xword-conversion.properties文件,在properties文件中的内容为:待转换的类型=类型转换器的全类名
对于本例而言,xwork-conversion.properties文件中的内容为:
java.util.Date=cn.itcast.conversion.DateConverter
23.输出带有占位符的国际化信息
(1).资源文件中的内容如下:
welcom={0}欢迎来到传智播客{1}
在jsp页面中输出带占位符的国际化信息
在Action类中获取带占位符的国际化信息,可以使用getText(String key,String[] args)或getText(StringaTextName,List args)方法.
(2).占位符就当是一个变量参数,可以传递给定的参数值.
24.为Action属性注入值
struts2为Action中的属性提供了依赖注入功能,在struts2的配置文件中,我们可以很方便的为Action中的属性注入值,注意:属性必须提供setter方法,
public class HelloWorldAction{
private String savePah;
public String getSavePath(){
return savePath;
}
public void setSavePath(String savePath){
this.savePath=savePath;
}
}
<paramname="savePath">/images
<resultname="success">/WEB-INF/page/hello.jsp
上面通过节点为action的savePath属性注入"/images";Action的变量的值,不能写死,经常变换,需要通过配置来设置参数
25.为应用指定多个配置文件
(1).在大部分应用中,随着应用规模的增加,系统中的Action的数量也会大量增加,导致struts.xml配置文件变得非常臃肿,为了避免 struts.xml文件过于庞大、臃肿,提高struts.xml文件的可读性,我们可以讲一个struts.xml配置文件分解成多个配置文件,然后 再struts.xml文件中包含其他配置文件,下面的struts.xml通过元素指定多个配置文件:
<includefile="struts-user.xml"/>
<includefile="struts-order.xml"/>
通过这种方式,我们就可以将struts2的Action按模块添加在多个配置文件中
26.文件上传
第一步:在WEB-INF/lib下加入commons-fileupload-1.2.1.jar、commons-io-1.3.2.jar, 这两个文件可以从http://commons.apache.org下载,在struts2.1以前的版本需要添加,以后的版本就不需要添加
第二步:把form表的enctype设置为:"multipart/form.data",如下:
这个属性的name必须要和类中File名称一样第三步:在Action类中添加以下属性
public class HelloWorldAction{
private File uploadImage;//得到上传文件;
private String uploadImageContentType;//得到文件的类型
private String uploadImageFileName;//得到文件的名称
//这里省略了属性的get/set方法(但是要注意get/set方法是必须的)
public String upload() throws Exception{
String realpath =ServletActionContext.getServletContext().getRealPath("/images");
File file=new File(realpath);
if(file.getParentFile().exists())file.getParentFile().mkdirs();//目录是否存在,不存在就创建
FileUtils.copyFile(uploadImage,newFile(file,uploadImageFileName));
return "success";
}
}
(1).如果文件不保存,struts2会把文件保存到自己的目录中,但是当这个Action执行完后,该文件就会被删除,所以我们要将上传的文件保存到硬盘上
(2).最好还要判断以下,文件uploadImage是否为空
(3).如果上传大的文件,web都会失败,像一些门户网站上传视频,都是通过一个插件,可以把这个插件看成一个程序,只是这个程序是通过Socket变成的,针对一个端口进行传输数据
27.指定struts2处理的请求后缀
(1).前面我们都是默认使用.action后缀访问Action,其实默认后缀是可以通过常量"struts.action.extension"进行修改的,例如:我们可以配置struts2只处理以.do为后缀的请求路径
<constantname="struts.action.extendsion" value="do"/>
如果用户需要制定多个请求后缀,则多个后缀之间以英文逗号","隔开,如:
<constantname="struts.action.extendsion" value="do,go"/>
(2).常量可以在struts.xml或struts.properties中配置,建议在struts.xml中配置,两种配置方式如下:
在struts.xml文件中配置常量:
<constantname="struts.action.extendsion" value="do"/>
在struts.properties中配置常量:
struts.action.extension=do
因为常量可以在下面多个配置文件中进行定义,所以我们需要了解struts2加载常量的搜索顺序:
struts-default.xml
struts-plugin.xml
struts.xml
struts.properties
web.xml
如果在多个文件中配置了同一个常量,则后一个文件中配置的常量值会覆盖前面文件中配置的常量值
(3).
第一:默认编码集,作用于HttpServletRequest的setCharacterEncoding方法和freemarker、velocity的输出:
<constantname="struts.i18n.encoding" value="UTF-8"/>
第二:该属性指定需要struts2处理的请求后缀,该属性的默认值是action,即所有匹配*.action的请求都由struts2处理,如果用户需要指定多个请求后缀,则多个后缀之间以英文逗号(,)隔开
第三:设置浏览器是否缓存静态内容默认值为true(生产环境下使用)开发阶段最好关闭,不然看不到修改后的数据
<constantname="struts.serve.static.browserCache" value="false"/>
第四:当struts的配置文件修改后系统是否自动重新加载该文件默认值为false(生产环境下使用),开发阶段最好打开
<constantname="struts.configuration.xml.reload" value="true"/>
第五:开发模式下使用,这样可以打印出更详细的错误信息
第六:默认的视图主题
<constantname="struts.ui.theme" value="simple"/>
第七:与spring集成时,指定由spring负责action对象的创建
<constantname="struts.objectFactory" value="spring"/>
第七:该属性设置struts2是否支持动态方法调用,该属性的默认值是true,如果需要关闭动态方法调用,则可设置该属性为false
<constantname="struts.enable.DynamicMethodInvocation"value="false"/>
第八:上传所有文件的总大小限制
constantname="struts.mulitipart.maxSize" value="10701096"/>
28.自定义拦截器
(1).如果用户登录后可以访问action中的所有方法,如果用户没有登录不允许访问action中的方法,并且提示"你没有权限执行该操作"
(2).
<interceptorname="permission"class="cn.itcast.interceptor.PermissionInterceptor"/>
<actionname="list_*" class="cn.itcast.action.HelloWorldAction"method="{1}">
<interceptor-refname="permission"/>
如果为某一个Action定义一个拦截器,struts2中对Action的默认的很多拦截器都失去功能,所以要想做到两全其美,需要定义一个拦截器栈:
<interceptorname="permission"class="cn.itcast.interceptor.PermissionInterceptor"/>
<interceptor-stackname="permissionStack">
<interceptor-refname="defaultStack"/>
<interceptor-refname="permission"/>
因为struts2中如文件上传,数据验证,封装请求参数到action等功能都是由系统默认的defaultStack中的拦截器实现的,所以我 们定义的拦截器需要引用系统默认的defaultStack,这样应用才可以使用struts2框架提供的众多功能,如果希望包下的所有action都使 用自定义的拦截器,可以通过<default-interceptor-refname="permissionStack"/>把拦截器定 义为默认拦截器,注意:每个包只能指定一个默认拦截器,另外,一旦我们为该包中的某个action显示指定了某个拦截器,则默认拦截器不会起作用.
(3).系统默认的拦截器可以到struts-default.xml中查看,很多功能.系统拦截器放在最前面,自定义的拦截器放在后面.
29.自定义类型转换器
(1).struts2中提供了两种类型转换器:局部类型转换器(只对某一个action起作用),全局类型转换器(所有的action起作用)
(2).类型转换器必须继承DefaultTypeConverter最好用xwork2.jar中的,重写converValue(Map<String,Object>context,Objectvalue,Class toType){
returnsuper.convertValue(context,value,toType);
}
其中第一个参数和ognl表达式,第二个参数是需要转换类型的内容(是String数组,因为可能有多个值),第三个参数是需要转换成什么类型,要实现双向转换
(3).将上面的类型转换器注册为局部类型转换器:
在Action类所在的包下放置ActionClassName-conversion.properties文 件,ActionClassName是Action的类名,后面的-conversion.properties是固定写法,对于本例而言,文件的名称应 为HelloWorldAction-conversion.properties.在properties文件中的内容为:
需要转换的属性名称=类型转换器的全类名
对于本例而言,HelloWorldAction-conversion.properties文件中的内容为:
createtime=cn.itcast.conversion.DateConverter
四、 Spring
@Autowired注解与自动装配
@Autowired
private PersonDaopersonDao;
拿PersonDao与<bean id=""..../>中的id值进行比较,相同就找到了,即进行类型注解,当然也可以过@Qualifier注解进行名称进行注解.
自动装配:
对于自动装配,大家了解一下就可以了,实在不推荐大家使用,例子:
autowire属性取值如下:
byType:按类型装配,可以根据属性的类型,在容器中寻找根该类型匹配的bean,如果发现多个,那么将会抛出异常,如果没有找到,即属性值为null
byName:按名称装配,可以根据属性的名称,在容器中寻找跟该属性名相同的bean,如果没有找到,即属性值为null
construtor与byType的方式类似,不同之处在于它应用于构造器参数,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常.
autodetect:通过bean类的自省机制,来决定是使用constructor还是byType方式进行自动装配,如果发现默认的构造器,那么将使用byType方式.
@Resource注解完成属性装配
(1).前面讲到了使用构造器注入,属性的setter方法注入,这里还可以使用注解的方式对Field进行注入
(2).注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动转配会产生未知情况,开发人员无法预见最终的装配结果
(3).手工装配依赖对象,在这种方式中又有两种编程方式
方式一:在XML配置文件中,通过在Bean节点下配置,如:
构造器注入
属性的setter方法注入
在XML中注入属性,会给XML文件变得很臃肿.特别是对集合类型进行注入时,变得很臃肿.
方式二:
在java代码中使用@Autowire或@Resoruce注解方式进行装配,但我们需要在XML配置文件中配置以下信息:
<beansxmlns="http://www.springframe.....
....
....
这些配置项隐式注册了多个对注释进行解析处理的处理 器:AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor,RequireAnnotationBeanPostProcessor, 每个注解都有一个注解处理器,注解本身不干活,是相对应的注解处理器在干活
注:@Resource注解在spring安装目录的lib\j2ee\common-annotations.jar
(4).在java代码中使用@Autowired或@Resource注解方式进行装配,这两个注解的区别是:
@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配.
@Autowired
private PersonDaopersonDao;//用于字段上
@Autowired
public voidsetOrderDao(OrderDao orderDao){//用于setter方法上
this.orderDao=orerDao;
}
@Autowired注解是按类型装配依赖对象,默认情况下,它要求依赖对象必须存在,如果允许null值,可以设置它required的属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用,如下:
@Autowired@Qualifier("personDaoBean")
private PersonDaopersonDao;
@Resource注解和@Autowired一样,也可以标注在字段或属性的setter方法上,但他默认按名称装配,名称可以通过 @Resource的name属性执行,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在 属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象
@Resource(name="personDaoBean")
private PersonDaopersonDao;//用于字段上
注意:如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时,@Resoruce注解会回退到按类型装配,但一旦指定了name属 性,就只能按名称装配了.拿personDao与<bean id=" ".../>中的id是否相同,相同就找到,属性的setter方法也不用写,既方便,又优雅.
同时@Resource是j2ee提供的注解(建议使用),@Autowired是spring框架提供的.
Spring的三种实例化Bean的方式
(1).三种实例化bean的方式:
第一种:<bean id="orderService"class="cn.itcast.OrderServiceBean/>
第二种:使用静态工厂方法实例化:
public class OrderFactory{
public static OrderServiceBeancreateOrder(){
return new OrderServiceBean();
}
}
第三种:使用实例工厂方法实例化:
<beanid="personServiceFactory" class="cn.itcast.service.OrderFactory"/>
public class OrderFactory{
public OrderServiceBean createOrder(){
return new OrderServiceBean();
}
}
Spring管理的Bean的生命周期
(1).Bean实例化是在Spring容器实例化时进行的,但是这是Singleton作用域中,实例化的时机是可以更改的,lazy- init="true"延迟初始化,即更改为调用getBean()方法时进行初始化.同时也可以在配置文件中设置所有的bean延迟初始化,其实这个标 签是不建议使用的.
(2).当把作用域改成Prototype时,Bean实例化是在调用getBean()方法进行的
(3).可以指定一个初始化方法:init-method="";即在bean实例化后执行的初始化方法.如数据库的连接.容器通过反射技术调用的,同时还需要进行资源的释放.destory-method="";即在bean被销毁时执行的方法.
(4).关闭Spring容器:ctx.close()方法,bean此时被销毁了
5.Spring自动扫描和管理bean
通过在classpath自动扫描方式把组件纳入spring容器中管理,前面的例子我们都是使用XML的bean定义来配置组件,在一个稍大的项 目中,通常会有上百个组件,如果这些组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找及维护起来也不太方便,spring2.5为我 们引入了组件自动扫描机制,它可以在类路径底下寻找标注了@Componet、@Service、@Controller、@Reponsitory注解 的类,并把这些类纳入进spring容器中管理,它的作用和在XML文件中使用bean节点配置组件是一样的,要使用自动扫描机制,我们需要打开以下配置 信息:
<beansxmln="http://www.springframework.org/schema/beans"
....
....
其中base-package为需要扫描的包(含子包)
@Service用于标注业务层组件、@Controller用于标注控制层组件(如struts中的action)、@Repository用于 标注数据访问组件,即Dao组件,而@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注.同时也可以通过注解 @Scope("prototype")修改bean的作用域.@Service("personService")中的名称必须和bean的名称相同, 只是开头字母变成小写了.当然这是默认设置,可以修改的.
可以使用注解的方式指定初始化方法,在初始化方法init()上添加注解@PostConstruct,同样可以指定销毁方法destroy(),注解为:@PreDestroy
这种扫描方式是很方便的,很多人都采用
即前面所说的功能都使用注解进行操作
6. SSH整合开发
hibernate核心安装包下的:
hibernate3.jar
lib\required*.jar
lib\optional\ehcache-1.2.3.jar
hibernate注解安装包下的
lib\test\slf4j-log4j12.jar
Spring安装包下的
dist\spring.jar
dist\modules\spring-webmvc-struts.jar
lib\jakarta-commons\commons-logging.jar、commons-dbcp.jar、commons-pool.jar
lib\aspectj\aspectjweaver.jar、aspectjrt.jar
lib\cglib\cglib-nodep-2.1.3.jar
lib\j2ee\common-annotations.jar
lib\log4j-1.2.14.jar
Struts:下载struts-1.3.8-lib.zip,需要使用到解压目录下的所有jar,建议把jstl-1.0.2.jar和 standard-1.0.2.jar更换为1.1版本,Spring中已经存在一个antlr-2.7.6.jar,所以把struts中的 antlr-2.7.2.jar删除,避免jar冲突
数据库驱动jar
首先整合struts和hibernate然后再整合spring
7.编码解析@Resource注解的实现原理
(1).新建一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.Field,ElementType.METHOD})
public @interface ItcastResource{
String name() default "";
}
当然还要编写一个注解处理器.
private voidannotationInject(){
首先循环所有的bean对象,判断其是否使用了注解
如果使用了注解,就进行属性注入.
}
8.编码解析Spring依赖注入的原理
(1).基本类型对象注入:
构造器注入
//属性setter方法注入
(2).注入其他bean:
方式一:
ref是指被注入的对象personDao
方式二:(使用内部bean,但该bean不能被其他bean使用)
<beanclass="cn.itcast.service.OrderDaoBean"/>
(3).依赖注入的内部原理
9.编码解析Spring装配基本属性原理
也可以对基本类型进行注入,类型转换.
10.编码剖析Spring管理bean的原理
使用dom4j读取spring配置文件,使用反射技术即可
11.搭建和配置Spring与JDBC整合的环境
(1).配置数据源:
apache的数据源:BasicDataSource
<propertyname="driverClassName" value="org.gjt.mm.mysql.Driver"/>
连接池启动时的初始值
连接池的最大值
最大空闲值
最小空闲值
配置事务:
采用注解的方式:
<ts:annotation-driventransaction-manager="txManage"/>
12.搭建与测试spring的开发环境
(1).dist\spring.jar;
lib\jakarta-commons\commons-logging.jar
以上这两个包是必须的
lib\aspectj\aspectjweaver.jar和aspectjrt.jar
lib\cglib\cglib-nodep-2.1.3.jar
以上这两个包是用于切面编程(AOP)
lib\j2ee\common-annotations.jar
以上的这个包是JSR-250中的注解
(2).Spring项目既可以在j2se中也可以在j2ee中
(3).spring的配置文件模板可以从spring的参考手册或spring的例子中得到,配置文件的取名可以任意,文件可以存放在任何目录下,但考虑到通用性,一般放在类路径下
(4).实例化spring容器常用方式:
第一种方式:在类路径下寻找配置文件来实例化容器
ApplicationContextctx=new ClassPathXmlApplicationContext(new String[]{"beans.xml"});
第二种方式:在文件系统路径下寻找配置文件来实例化容器
ApplicationContextctx=new FileSystemXmlApplicationContext(newString[]{"d:\beans.xml"});//将路径写死了,通用性不好,不建议使用.
spring的配置文件可以指定多个,可以通过String数组传入
(5).IOC:控制反转:
public class PersonServiceBean{
private PersonDao personDao = newPersonDaoBean();
public void save(Person person){
personDao.save(person);
}
}
PersonDaoBean是在应用内部创建及维护的,所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的,这样控制权就由应用转移到外部容器,控制权的转移就是所谓反转.
(6).建立一个业务bean为PersonServiceBean,放在cn.itcast.service.impl包中.面向接口编程,进行 解耦,怎么将业务bean交给spring管理,需要在beans.xml配置文件中:,其中id,name都是bean的名称,但是id不能使用特殊字符,id本身就属于 XML属性的,如:"/sfs/"就会出错,但是name不会出错,class属性是bean类的路径,需要操纵bean的时候,只需到spring容器 中取出bean进行操作,而不需要实例化一个bean了:
ApplicationContext ctx=newClassPathXmlApplicationContext(new String[]{"beans.xml"});
PersonServiceBeanpersonService=(PersonServiceBean)ctx.getBean("personService");
personService.save();
(7).当在配置文件中没有标签的提示信息,需要手动添加schema文件,方法如 下:windows->preferences->myeclipse->filesand editors->xml->xmlcatalog,点击添加,在出现的窗口中的Key Type中选择URL,在location中选"File System",然后在spring解压目录的dist/resources目录中选择spring-beans-2.5.xsd,回到设置窗口的时候不 要急着关闭窗口,应该把窗口的Key Type改为Schema location,Key改为http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
13.配置Spring管理的bean的作用域
(1).Singleton:默认情况下,bean是单例模式的,在每个springIoc容器中一个bean定义只有一个对象实例,默认情况下会 在容器启动时初始化bean,但是我们可以指定Bean节点的lazy-init="true"来延迟初始化bean,这时候,只有第一次获取bean会 才初始化bean,如:
如果想对所有bean都应用延迟初始化,可以在根节点beans设置default-lazy-init="true",如下:
Prototype:每次从容器获取bean都是新的对象
Request:在request的域中
Session:在Session的域中
Global Session:在全局的Session的域中
14.全面阐释Spring及其各项功能
(1).Spring是一个开源的控制反转(Inversion of control,IoC)和面向切面(AOP)的容器框架,它的主要目的是简化企业开发
(2).IOC:控制反转:
public class PersonServiceBean{
private PersonDao personDao = newPersonDaoBean();
public void save(Person person){
personDao.save(person);
}
}
PersonDaoBean是在应用内部创建及维护的,所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负 责的,这样控制权就由应用转移到外部容器,控制权的转移就是所谓反转.PersonServiceBean是业务逻辑层类,PersonDaoBean是 Dao层类,在业务逻辑层类中控制和创建PersonDaoBean,这就是应用本身创建和维护了,但是有了spring容器 后,PersonDaoBean的创建和维护是在容器中进行的,不需要PersonServiceBean进行管理了,控制权进行的反转
(3).依赖注入:当我们把依赖对象交给外部容器负责创建,那么PersonServiceBean类可以改成如下:
public class PersonServiceBean{
private PersonDao personDao;
//通过构造器参数,让容器把创建好的依赖对象注入进PersonServiceBean,当然也可以使用setter方法进行注入
public PersonServiceBean(PersonDaopersonDao){
this.personDao=personDao;
}
public void save(Person person){
personDao.save(person);
}
}
所谓依赖注入就是指:在运行期,由外部容器动态的将依赖对象注入到组件中.
(4).为何要使用spring:
第一:降低组件之间的耦合度,实现软件各层之间的解耦:
Controller->Service->Dao
第二:可以使用容器提供的众多服务,如:事务管理服务,消息服务等,当我们使用容器管理事务时,开发人员就不再需要手工控制事务,也不需要处理复杂的事务传播
第三:容器提供单例模式支持,开发人员不在需要自己编写实现代码
第四:容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能
第五:容器提供的众多辅助类,使用这些类能够加快应用的开发,如:JdbcTemplate,HibernateTemplate
第六:Spring对于主流的应用框架提供了集成技术,如集成Hibernate、JPA、Struts等,这样更便于应用的开发.
(5).轻量级和重量级概念的划分,其实划分一个应用是否属于轻量级还是重量级,主要看他使用了多少服务,使用的服务越多,容器要为普通的java 对象的工作就越多,必然会影响到应用的发布时间或者是运行性能,对于spring容器,它提供了很多服务,但是这些服务并不是默认为应用打开的,应用需要 某种服务,还需要指明使用该服务,如果应用使用的服务很少,如:只是用了spring的核心服务,那么我们就可以认为此时应用属于轻量级的,如果应用使用 了spring提供的大部分服务,这时应用就属于重量级,目前EJB容器就因为他默认为应用提供了EJB规范中所有的功能,所以他属于重量级
15.使用CGLIB实现AOP功能与AOP概念详解
(1).使用cglib架包,构建代理,不需要被代理的对象需要实现接口
public class CGlibProxyFactory implementsMethodInterceptor{
private Object targetObject;
public Object createProxyIntance(ObjecttargetObject){
this.targetObject=targetObject;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.targetObject.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercep(Object proxy,Method method,Object[]args,MethodProxy methodProxy)throws Throwable{
returnmethodProxy.invoke(this.targetObject,args);
}
}
CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法
16.使用JDK中的Proxy技术实现AOP功能
(1).使用在权限拦截上.
(2).被代理对象必须实现接口
public class JDKProxyFactory implementsInvocationHandler{
private Object targetObject;
public Object createProxyIntance(ObjecttargetObject){
returnProxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),this.targetObject.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy,Methodmethod,Object[]args) throws Throwable{//环绕通知
PersonServiceBean bean=(PersonServiceBean)this.targetObject;
Object result=null;
if(bean.getUser()!=null){
//....advice(),调用方法前处理,叫做前置通知
try{
result=method.invoke(targetObject,args);
//....afteradvice();调用方法后处理,叫做后置通知
}catch(RuntimeException e){
//....exceptionadvice();调用方法出现例外后处理,叫做例外通知
}finally{
//....finallyadvice();调用方法最终都会处理,叫做最终通知
}
}
return result;
}
}
整个invoke方法叫做环绕通知.
(3).AOP中的概念:
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。
JoinPoint(连接点):所谓连接点是指那些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器
Pointcut:切入点:所谓切入点是指我们要对那些joinpoint进行拦截的定义
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Target(目标对象):代理的目标对象
Weave(织入):指将aspect应用到target对象并导致proxy对象创建的过程称之为织入
Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field
17.使用Spring的注解方式实现AOP
(1).要进行AOP编程,首先我们要在Spring的配置文件中引入AOP命名空间:
<beansxmlns="http://www.springframework.org/schema/beans"
Spring提供了两种切面使用方式,实际工作中我们可以选用其中一种:一种是基于XML配置方式进行AOP开发,另外一种是基于注解方式进行AOP开发
(2).基于注解方式声明切面:首先启动对@AspectJ注解的支持:
定义一个切面类(MyInterceptor):
@Aspect
public class MyInterceptor{
@Pointcut("execution(cn.itcast.service...*(..))
//定义切入点,通配符:指的是任何返回类型,..是指子包下也进行拦截,:指的是拦截所有类,*:指定的是所有方法,..是指任意参数
private void anyMethod(){}//声明一个切入点
@Before("anyMethod()")//定义一个前置通知,名称为切入点名称
public void doAccessCheck(){
System.out.println("前置通知");
}
@AfterReturning("anyMethod()")//后置通知
public void doAfterReturning(){
System.out.println("后置通知");
}
@After("anyMethod()")//最终通知
public void doAfter(){
System.out.println("最终通知");
}
@AfterThrowing("anyMethod()");//例外通知
public void doAfterThrowing(){
System.out.println("例外通知");
}
@Around("anyMethod()")//环绕通知
public ObjectdoBasicProfiling(ProceedingJoinPoint pjp)throws Throwable{
System.out.println("进入方法");
if(){//判断用户是否有权限,有权限就执行该方法.
Object result = pjp.proceed();
System.out.println("退出方法");
}else{
}
return result;
}
}
所以当出现例外通知时,后置通知是不执行的,即它们两者肯定有一个不执行,一个执行.
需要将切面类交给spring管理:基于XML配置管理或者自动扫描管理
(3).想得到参数:
@Before(anyMethod() &&args(userName)")//添加参数,只会拦截到对应的方法,即一个参数的方法.
public void doAccessCheck(String name){
System.out.println("前置通知");
}
(4).想得到返回结果:
@AfterReturning("anyMethod()",returning="result")//后置通知
public void doAfterReturning(String result){
System.out.println("后置通知");
System.out.println(result);//打印返回结果
}
(5).得到异常(例外):
@AfterThrowing("anyMethod()",throwing="e");//例外通知
public void doAfterThrowing(Exception e){
System.out.println("例外通知");
System.out.println(e);//打印例外
}
18.使用Spring配置文件实现AOP
(1).使用配置文件实现AOP,切面类只是个普通的类,其内部没有任何注解
public class MyInteceptor{
public void doAccessCheck(){
System.out.println("前置通知");
}
public void doAfterReturning(){
System.out.println("后置通知");
}
public void doAfter(){
System.out.println("最终通知");
}
public void doAfterThrowing(){
System.out.println("例外通知");
}
public ObjectdoBasicProfiling(ProceedingJoinPoint pjp)throws Throwable{
System.out.println("进入方法");
Object result=pjp.proceed();
System.out.println("退出方法");
return result;
}
}
(2).基于XML配置方式声明切面
<aop:beforepointcut-ref="mycut" method="doAccessCheck"/>
<aop:after-returningpointcut-ref="mycut" method="doReturnCheck"/>
<aop:after-throwingpointcut-ref="mycut" method="doExceptionAction"/>
<aop:afterpointcut-ref="mycut" method="doReleaseAction"/>
<aop:aroundpointcut-ref="mycut" method="doBasicProfiling"/>
(3).对于表达式expression的细节:
返回值类型为String:execution(java.lang.Stringcn.itcast.service...(..))
第一个参数为String:execution(java.lang.Stringcn.itcast.service...(java.lang.String..))
返回值类型为非void:execution(!void cn.itcast.service...(..))
19.使用Spring配置文件实现事务管理
(1).
<aop:pointcutid="transactionPointcut" expression="execution(cn.itcast.service..*(..))"/>对指定的方法进行拦截
<aop:advisoradvice-ref="txAdvice"pointcut-ref="transactionPointcut"/>
<tx:adviceid="txAdvice" transaction-manager="txManager">
<tx:methodname="*"/>
20.使用Spring注解方式管理事务与传播行为详解
(1).只有当遇到RuntimeException时,事务进行回滚.Spring开启的事务管理,当遇到运行期例外(unchecked),而(checked异常)是不进行事务的回滚的.
(2).当然也可修改这种情况.把unckecked异常改成不会进行回滚了:@Transactional(noRollbackFor=RuntimeException.class),
(3).@Transactional(propagation=Propagation.NOT_SUPPORTED);关闭事务,不开启事务的.spring容器默认是打开事务的.当然还有其他一些值:(事务的传播行为)
REQUIRED:业务方法需要在一个事务中运行,如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务(容器的默认值)
NOT_SUPPORTED:声明方法不需要事务,如果方法没有关联到一个事务,容器不会为他开启事务,如果方法再一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行
REQUIRESNEW:属性声明不管是否存在事务,业务方法总会为自己发起一个新的事务,如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行
MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务,如果业务方法再没有事务的环境下调用,容器就会抛出异常
SUPPORTS:这一事务属性声明,如果业务方法再某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法再事务范围外被调用,则方法再没有事务的环境下执行
Never:指定业务方法绝对不能在事务范围内执行,如果业务方法再某个事务中执行,容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行.
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动事务,则按REQUIRED属性执行,它使用了一个单独的事务,这 个事务拥有多个可以回滚的保存点,内部事务的回滚不会对外部事务造成影响,它只对DataSourceTransactionManager事务管理器起 效.Savepoint savepoint=conn.setSavepoint();conn.rollback(savepoint);
(4).readOnly值为事务不能修改了.timeout是事务的超时时间,isolation数据库中的隔离级别.
(5).数据库系统提供了四种事务隔离级别供用户选择,不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离 级别最高,Read Uncommited的隔离级别最低,大多数据库默认的隔离级别为Read Commited,如SQLServer,当然也有少部分数据库默认的隔离级别为Repeatable Read,如MySql
Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)
Read Commited:读已提交数据(会出现不可重复读和幻读)
Repeatable Read:可重复读(会出现幻读)
Serializable:串行化
脏读:一个事务读取到另一个事务未提交的更新数据
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同,换句话说就是,后续读取可以读到另一事务已经提交的更新数据,相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据.
幻读:一个事务读取到另一个事务已提交的insert数据.
五、 Hibernate
1. Criteria查询方式
(1).Criteria查询方式(条件查询):
Criteriac=s.createCriteria(User.class);
c.add(Restrictions.eq("name",name));//添加查询条件,User中的name属性的值是否等于"name"
Listlist=c.list();
Useru=(User)c.uniqueResult();
2. hibernate的二级缓存配置与分析
(1).二级缓存:SessionFactory级共享:
实现为可插拔,通过修改cache.provider_class参数来改变;hibernate内置了对 EhCache.OSCache,TreeCaceh,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入 Hibernate不支持的缓存实现
在hibernate.cfg.xml中加入:
<class-cacheclass="className" usage="read-only"/>或在映射文件的class元素加入子元素:
其中usage:read-only,read-write,nonstrict-read-write,transactional
Session的save(这个方法不适合native生成方式的主 键),update.saveOrUpdate,list,iterator,get,load,以及Query,Criteria都会填充二级缓存,但 只有(没有打开查询缓存时)Session的iterator,get,load会从二级缓存中取数据(iterator可能存在N+1次查询)
Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;修改cache,use_query_cache为 true打开对查询的缓存,并且调用query.setCaheable(true)或criteria.setCacheable(true)
SessionFactory中提供了evictXXX()方法用来清除缓存中的内容
统计消息打开generate_statistics,用sessionFactory.getSatistics()获取统计信息,获取统计信息成本是很高的,消耗资源.对程序的调试是很有帮助的,可以看到session的初始化时间,打开多少次,关闭多少次等信息.
(2).相对user对象进行缓存:
<class-cacheclass="cn.itcast.hibernate.domain.User"usage="read-only"/>只读方式,效率高,User类不会再改变了.能够保证并发.
(3).先到一级缓存中查找,找不到在到二级缓存中查找
3.Hibernate的拦截器和监听器
(1).拦截器和事件
拦截器与事件都是hibernate的扩展机制,Interceptor接口是老的实现机制,现在改成事件监听机制,他们都是hibernate的回调接口,hibernate在save,delete,update等会回调这些查询
(2).拦截保存的的事件:
实现SaveOrUpdateEventListener接口
public classSaveListener implements SaveOrUpdateEventListener{
public voidonSaveOrUpdate(SaveOrUpdateEvent event){
if(event.getObject()instantce of cn.itcast.hibernate.domain.User){
User user =(User)event.getObject();
System.out.println(user.getName().getFirstName());
}
}
}
配置文件中:
<eventtype="save">
<listenerclass="cn.itcast.hibernate.SaveListener"/>自己定义的监听器,不同监听器的注册顺序,输出的结果也是不同的.
<listenerclass="org.hibernate.evetn.def.DefaultSaveOrUpdateEventListenter"/& gt;hibernate缺省的监听器,自己定义的监听器会覆盖缺省的,所以在这里还要把缺省的监听器注册一下.
当保存user时,会监听到.
4.hibernate的内部缓存分析
(1).第一级缓存是在session中,第二缓存是在sessionFactory
(2).Useruser=(User)s.get(userClass,id);
System.out.println(user.getClass());
user=(User)s.get(userClass,id);
只有一条select语句
(3).当session关闭时,缓存也就没有数据了.
(4).缓存的作用主要用来提高性能,可以简单的理解成一个Map,使用缓存涉及到三个操作:把数据放入缓存、从缓存中获取数据、删除缓存中的无效数据
(5).一级缓存,Session级共 享,save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放在一级缓存中,一级 缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出,可以用evict,clear方法清除缓存的内容.
(6).只要有sql语句,就不会去缓存中拿数据,直接到数据库中拿数据
(7).手工的对缓存中的数据进行清除.清除一条记录:s.evict(user);清除所有的记录s.clear();定时的清除可以降低内存溢出的可能性.
(8).session的生命周期很短,只在一个web请求内
5.hibernate介绍与动手入门体验
(1).模型不匹配:Java面向对象语言,对象模型,其主要概念有:继承,关联,多态等,数据库是关系模型,其主要概念有:表,主键,外键等
(2).解决方法:第一种:使用JDBC手工转换,第二种使用ORM框架来解决,主流的ORM框架有Hibernate、TopLink、OJB
(3).下载hibernate,将下载目录/hibernate3.jar和/lib下的hibernate运行时必须的包
(4).配置文件hibernate.cfg.xml和hibernate.properties,XML和properties两种,这两个文件的作用一样,提供一个即可,推荐XML格式,下载目录/etc下是示例配置文件
可以在配置文件制定:
数据库的URL,用户名,密码,JDBC驱动类,方言等,启动时Hibernate会在CLASSPATH里找这个配置文件.映射文件(hbm.xml,对象模型和关系模型的映射),在/eg目录下有完整的hibernate示例
(5).首先建立一个对象:
public class User{
private int id;
private String name;
private Date birthday;
//省略了get/set方法
}
编写映射文件:User.hbm.xml
<hibernate-mappingpackage="cn.itcast.hibernate.domain">
在配置文件中hibernate.cfg.xml:
<propertyname="connection.url">jdbc:mysql://localhost:3306/jdbc
<propertyname="connection.username">root
<propertyname="connection.password">
org.hibernate.dialect.MySQLDialect
<propertyname="hbm2ddl.auto">
<mappingresource="org/hibernate/test/legacy/User.hbm.xml"/>
<class-cacheclass="org.hibernate.test.legacy.Simple" region="Simple"usage="read-write"/>
方言dialect就是哪种数据库.hibernate自己可以建立表(hbm2ddl.auto)
(6).初始化:
Configuration cfg = new Configuration();
cfg.configure();
SessionFactory sf=cfg.buildSessionFactory();//SessionFactory相当于JDBC中DriverManager
Session s=sf.openSession();//工厂模式,生产connection
Transaction tx=s.beginTransaction();
User user = new User();
user.setBirthday(new Date());
user.setName("name");
s.save(user);
ts.commit();
s.close();
(7).hibernate默认会把事务自动提交功能关闭了,所有自己要手动打开,查看表的结构命令:show create table user,看表的引擎是否支持事务,查看引擎命令:show engines
(8).开发流程:
方式一:由Domain object->mapping->db
方式二:由DB开始,用工具生成mapping和Domain object
方式三:由映射文件
(9).hibernate管理的Domain对象类定义必须要符合JavaBean的定义规则:默认的构造方法(必须的),有无意义的标示符id(主键),非final的,对懒加载有影响
public class User{
private int id;
private String name;
private Date birthDay;
//get/set方法
}
10.编写一个工具类进行初始化Hibernate
public final class HibernateUtil{
private static SessionFactorysessionFactory;
private HibernateUtil(){
}
static{//静态代码块
Configuration cfg = new Configuration();
cfg.configure();//默认的传入是hibernate.cfg.xml文件
sessionFactory = cfg.buildSessionFactory();
}
public static SessionFactorygetSessionFactory(){
return sessionFactory;
}
}
11.static void addUser(User user){//标准规范代码
Session s=null;
Transaction tx=null;
try{
s=HibernateUtil.getSession();
tx.s.beginTransaction();
s.save(user);
tx.commit();
}catch(HibernateException e){
if(tx!=null)
tx.rollback();//不仅要回滚,还有抛出异常
throw e;
}finally{
if(s!=null)
s.close();
}
}
6.hibernate配置文件中的配置项
(1).hibernate.cfg.xml和hbm.xml内容解释:
第一:数据类型: type可以是hibernate、java类型或者你自己的类型(需要实现hibernate的一个接口)
第二:基本类型一般不需要在映射文件中说明,只有在一个java类型和多个数据库数据类型相对应时并且你想要的和hiberante缺省映射不一致 时,需要在映射文件中指明类型(如:java.util.Date,数据库 DATE,TIME,DATATIME,TIMESTAMP,hibernate缺省会把java.util.Date映射成DATATIME型),而如 果你想映射成TIME,则你必须在映射文件中指定类型
第三:数据类型的对应关系
(2).Session是非线程安全的,生命周期短,代表一个和数据库的连接,在B/S系统中一般不会超过一个请求;内部维护以及缓存和数据库连接,如果session长时间打开,会长时间占用内存和数据库连接
(3).SessionFactory是线程安全的,一个数据库对应一个SessionFactory,生命周期长,一般在整个系统生命周期内有 效;SessionFactory保存着和数据库连接的相关信息(user,password,url)和映射信息,以及Hibernate运行时要用到 的一些信息.
7. Hibernate映射类型
serializable:序列化到数据库中.
8.Hibernate中使用的集合类型
(1).集合映射(set,list,array,bag,map)
Listemps = new ArrayList();
映射文件中:
<listname="emps">
配置和set标签是相同的,只是区分List,Set的区别
<list-indexcolumn="order_col"/>这一列是给hibernate使用的,需要记录该员工是第几个加进来的,即加入的顺序.
(2).由于懒加载的问题,Hibernate重写了java中的集合类,使其具有懒加载的功能.所以在定义的时候,必须要定义成接口类型即List,Set,Map
9.hql的命名参数与Query接口的分页查询
(1).匿名参数:不使用占位符了
String hql ="from User as user where user.name=:n";
query.setString("n",name);
不会依赖参数的位置
(2).Query接口中的方法
query.setFirstResult(0);
第一条记录从哪开始,参数为开始的位置
query.setMaxResult(10);
实现分页功能
10.Hql与Criteria查询的补充知识
HQL:查询多个对象select art,user from Article art,User user where art.author.id=user.idand art.id=:id这种方式返回的是Object[],Object[0]:article,Object[1]:user;
11.Iterate查询与N+1次查询
(1).假设已经加入到了10个用户
static void iterator(){
Session s=HibernateUtils.getSession();
Query q=s.createQuery("fromUser");
Iterator users =q.iterate();
while(users.hasNext()){
System.out.println(users.next().getName().getFirstName());
}
}
首先把10个用户的id都查询出来,然后按照id去查询详细信息,这是会到一级缓存中查找,找不到在到二级缓存,找不到在到数据库中查找.假设都到 数据库中查询,那么就进行了11次查询,第一次把所有的id都查询,然后再逐一按照id查询进行10次,总共进行了11次,所以在使用时一定要小心,是否 确定一级缓存和二级缓存中有我们想要查询的数据,不然的话,性能就下降了
(2).在懒加载的情况下,就会出现N+1次查询,比如一对一:
首先查询IdCard得到id,然后再去访问Person
Session s=HibernateUtil.getSession();
Query q=s.createQuery("fromIdCard");
List ics=q.list();
for(IdCard> ic:ics){
System.out.println(ic.getPerson().getName());
}
因为懒加载,每次访问数据的时候,都进行查询数据库.
12.load方法的懒加载及原理分析
(1).Useruser=(User)s.load(userClass,id);
System.out.println(user.getClass());
就是说s.load(userClass,id)返回的是User的一个代理对象.即是User的子类.在session没有关闭前,去访问数据库 user.getName();但是这种方式不好,最好使用Hibernate.initialize(user);初始化懒加载.
(2).懒加载是将与数据库的交互延迟,提高性能.load()方法,不会到数据库查询,只会返回一个User的一个子类.
(3).asm.jar,cglib.jar这两个包实现懒加载,能够动态的修改内存中的字节码.即动态的生成一个User的子类.
(4).employee.setUser(user);这是就可以使用懒加载,建立employee和user之间个关联,但是不需要去访问数据库的时候
(5).通过asm和cglib两个包实现的,Domain是非final的,session.load懒加载
one-to-one懒加载:必须满足三个条件才能实现懒加载:第一:主表不能有constrained=true,所以主表没有懒加载,第二:lazy!=false,第三:fetch=select;
one-to-many懒加载:第一:lazy!=false,第二:fetch=select
many-to-one:第一:lazy!=false,第二:fetch=select
many-to-many:第一:lazy!=false,第二:fetch=select
(6).能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和 getClass除外),hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象,当相关联 的session关闭后,再访问懒加载的对象将出现异常.
(7).方法getId和getClass不需要访问数据库也是知道的,所以不是出现懒加载的初始化异常.
(8).表中的属性也可以使用懒加载的,只是需要在编译后的内容进行处理,这种用途主要在字段是大文本类型时需要.
13.OpenSessionInView模式的代码分析
(1).ThreadLocal类
private static ThreadLocal session=newThreadLocal();
线程级变量,作用域在一个线程内.
Session s=(Session)session.get();
if(s==null)}
s=getSession();
session.set(s);
}
当有一个web请求来时,服务器创建一个线程进行服务,将创建一个session,所以在这个线程内可以访问到session
(2).sessioncontext和事务边界
用current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:
第一:Thread:ThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边 界,此时session不能调用close,当commit或rollback的时候session会自动关闭 (connection.realease_mode:after_transaction).Opensession in view:在生成(渲染)页面时保持session打开,前面所说的懒加载时,可以保证session没有关闭,可以访问到数据.
第二:由JTA事务管理器来管理事务(connection.release_mode:after_statement)
(3).用户发送请求->web容器->doFilter(过滤器)->OpenSessionView->打开 session,事务->ActionServlet(struts)的service方法->根据配置文件找到 ->Action(execute方法)->业务逻辑层(register方法)->Dao层(addUser方法)->返回, 直到doFilter的commit,提交事务.在这个过程中session都没有关闭,可以解决事务的边界问题,解决懒加载的问题(即什么时候使用懒加 载).缺点:延长事务,session的生命周期,session延迟关闭,那么一级缓存不会释放,长时间占用内存.客户端的网速比较慢,导致事务和 session长时间不能关闭.即延迟关闭.会给服务器端造成很大的负载.
14.Session接口及getloadpersist方法
(1).由于Session可以管理多个数据库表对应的多个实体对象,如果要查询id为1的实体对象,Session.get方法需要知道去哪个数 据库表中查询id为1的记录,所以,除了给get方法传递所要查询的实体对象的id值外,还必须给get方法传递实体对象的类型,get方法才能知道去哪 个数据库表中进行查询
(2).通过类的类型可以去hibernate.cfg.xml文件中查找到对应的表
(3).在配置文件中添加标签<propertyname="show_sql">true//可以打印sql语句
(4).Useruser=(User)s.get(userClass,id);与User user=(User)s.load(userClass,id);的区别,load不会去访问数据库,只有第一次访问时,才会访问数据库.增加一条打印 出user1的类名的代码,就可以看到load方法所返回的User子类的名称了,该语句如下:
System.out.println(user1.getClass().getName());
(5).s.save(user)和s.persist(user);都是存储数据,persist方法没有sql语句,没有开启事务,save会回滚,persist不会回滚
15.Session与SessionFactory的多线程问题
Session内部封装了一个connection对象,尽量迟的创建连接,尽量早的释放连接
16.本地sql查询与命名查询
(1).使用Query接口
static list sql(){
Session s=HibernateUtil.getSession();
Query q = s.createSQLQuery("select * fromuser").addEntity(User.class);//查询的结果是User对象
List rs=q.list();
for(User r:rs){
System.out.println(r.getName());
}
}
(2).不同的数据库,本地的查询语句是不同的,所以这种本地的查询语句最好不要使用,兼容性和移植性不好.
(3).命名查询:将查询语句放在配置文件中,以后修改查询语句只修改配置文件中的查询语句就可以了.
<queryname="getUserByBirthday">
<[CDATA[from User wherebirthday=:birthday]]>
这个定义可以放到class标签内部,不需要使用全名,只需要getUserByBirthday即可,但是在这个范围内,不能出现重名,如果在外部,那就需要全名了,cn.itcast.hibernate.domain.User.getUserByBirthday
在配置文件中
static List namedQuery(){
Session s=HibernateUtil.getSession();
Queryq=s.getNamedQuery("getUserByBirthday");
q.setDate("birthday",new Date());
return q.list();
}
(4).hibernate可以做到用Map代替Domain对象,存入到数据库,但是这就符合ORM定义了,同时也可以将数据库中的内容转换XML
17.多对多关联关系的查询
使用表之间的关联join,效率低
18.多对多关联关系的映射与原理分析
(1).多对多(teacher-student):在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;Hibernate会为我们创建中间关联表,转换成两个一对多.
ER图:teacher:id(PK);student:id(PK);teacher_student:teacher_id(PK,FK1),student_id(PK,FK2)
(2).
public class Teacher{
private int id;
private String name;
private Set students;
//省略get/set方法
}
public class Student{
private int id;
private String name;
private Set teachers;
//省略get/set方法
}
teacher的映射文件:
根据student_id去查询学生的相关信息
同理student的映射文件相似.
(3).测试类:
Set ts=newHashSet();
Teacher t1=new Teacher();
t1.setName("t1 name");
Teacher t2=new Teacher();
t2.setName("t2 name");
ts.add(t1);
ts.add(t2);
Set ss=newHashSet();
Student s1=new Student();
s1.setName("s1");
Student s2=new Student();
s2.setName("s2");
t1.setStudents(ss);//建立关联关系
t2.setStudents(ss);
ss.add(s1);
ss.add(s2);
s.save(t1);
s.save(t2);
s.save(s1);
s.save(s2);
在中间表中插入数据
19.多对一的懒加载分析
(1).查询员工的信息时,是否需要部门的信息,默认的情况下是懒加载的方式,怎样判断是否进行了懒加载,可以通过打印出的sql语句中的查询语句即可
(2).当IdCard中的id是主键也是外键,当id有值时,一定有一个person与之对应,所以可以使用懒加载,先生成一个代理对象,当需要 person的信息时,才去查询,反过来,因为person中的id只是个主键,知道person的id,IdCard中不一定有一个值与之对应,所以不 使用懒加载的方式,而是直接去查询数据库,这就是查询主表时不使用懒加载,查询从表时使用懒加载.
(3).但是多对一的部门和员工,直接就是用了代理,depart.getEmps()获取员工时,Hibernate中的集合把集合空对象和空集合是相同的概念.
20.多对一关联关系的检索与原理分析
(1).查询操作(department表的查询和以前一样,只是employee表不一样):
static Employee query(int empid){
Employee emp =(Employee)s.get(Employee.class,empid);
System.out.println("departname:"+emp.getDepart().getName());//得到department的名称.
return emp;
}
进行两次查询,首先根据id查询employee表,得到depart_id,在根据depart_id查询department表.
21.多对一关联关系的映射与原理分析
(1).多对一:映射文件:
ER图中定义Employee主键(PK):id和外键(FK):depart_id,Department的主键id;
(2).建立Department类
public class Department{
private int id;
private String name;
//省略get/set方法
}
建立Employee类
public class Employee{
private int id;
private String name;
private Department depart;
//省略get/set方法
}
(3).映射文件:
<hibernate-mappingpackage="cn.itcast.hibernate.domain">
不在使用标签property,是对象类型depart,使用标签
通过反射可以找到depart对应的映射文件,当depart_id与depart映射文件中的id相同时,就查找到了.也可以使用属性not-null="true",设置colum="depart_id"这列不为空
(4).column="depart_id"不设置可以,默认就是column="depart"
(5).staticDepartemnt add(){
//模板代码,省略
Department depart = new Department();
depart.setName("depart name");
Employee emp = new Employee();
emp.setDepart(depart);//直接赋值就可以了,只要在对象建立关系,数据库中的表就建立关系了.
emp.setName("emp name");
s.save(depart);
s.save(emp);
return depart;
}
(6).当s.save(depart);与s.save(emp)两条语句的顺序调换,会多出现一条更新语句,因为首先存储emp,当存储到 depart时,因为employee中定义了department,所以hibernate检测到employee中的depart发生改变了,就进行 了更新操作.此时是持久态
22. 分布式缓存的分析
大型网站有多个服务器,即就有多个cache,每个服务器对应一个cache,
23.关联关系的级联操作
(1).cascade和inverse:
Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也做类似的操作,常用的cascade:none,all,save- update,delete,lock,refresh,evict,replicate,persist,merge,delete- orphan(one-to-many),一般对many-to-one,many-to-many不设置级联,在one-to-one和one-to- many中设置级联
Inverse表“是否放弃维护关联关系”(在Java里两个对象产生关联时,对数据库表的影响),在one-to-many和many-to- many的集合定义中使用,inverse="true"表示该对象不维护关联关系;该属性的值一般在使用有序集合时设置成false(注意 hibernate的缺省值是false),one-to-many维护关联关系就是更新外键,many-to-many维护关联关系就是在中间表增减记 录
注:配置成one-to-one的对象不维护关联关系.
24.缓存的原理与模拟分析
(1).第一个人读的信息和后一个人读的信息可能相同,那第二个人读信息时能够加快速度了.
(2).第二人读取信息时,就不是到数据库中读取了,可以到缓存中读取数据.
(3).使用缓存cache存取数据.
25.继承_鉴别器与内连接器相结合
(1).子类的特有属性很多,就拿一张表进行对应,特有属性少的就和父类放在同一个表中,
(2).employee:id(PK),name,depart_id,type,skill;sales:employee_id(PK,FK),sales;
Skiller子类和Employee父类放在一起,Sale类自己对应一张表.
(3).映射文件中只需按照前两中方式进行改变.
26.继承_每个具体类映射一张独立表
(1).没有公共的属性,所有的属性都是自己特有的,在插入时候不需要涉及到多个表的关联了,效率高.如果employee不是抽象的,会有employee表
(2).employee:id(PK),name,depart_id;skiller:id(PK),name,skill,depart_id;sales:id(PK),name,sell,depart_id;
(3).映射文件:
(4).在查询的时候,多态查询时,还是要进行三种表的关联查询,但是插入只在一张表进行.
27.继承关系_每个类映射到一张表
(1).employee:id(PK),name,depart_id;sales:employee_id(PK,FK),sell;skiller:employee_id(PK,FK),skill;
(2).此时不需要鉴别器了,每个子类对应一张表
(3).映射文件:
<joined-subclassname="Skiller" table="skiller">
(4).插入子类时,相同的属性插入到employee表中,自己特有的属性插入到自己表中,如果插入一个技术员Skiller(name,skill)时skill插入skiller表中,name插入employee表中,这时就插入了两张表.
(5).当查询自己特有的属性时,会关联两张表,当查找相同的属性时,会关联三张表.所以查询时效率低.不要进行多态查询,最好查询具体的子类:
具体查询:Employee emp = (Employee)s.getId(Skiller.class,id);
多态查询:Employee emp = (Employee)s.getId(Skiller.class,id);
28.继承关系_整个继承树映射到一张表
(1).public classSkiller extends Employee{
private String skill;
//省略get/set方法
}
public class Sales extends Employee{
private int sell;
//省略get/set方法
}
(2).employee表中的字段:id(PK),name,depart_id,type(区分不同类型的员工,又称鉴别器),skill,sell
(3).这种方式当增加子类时,需要修改employee表结构.
(4).映射文件:
鉴别器,hibernate用来区分不同的子类.
(5).将一棵继承树映射到一张表中,所以在查询时,只对一张表进行操作,效率高,但是不灵活,当增加子类时,需要更改表结构,同时每个字段不能设置成非空约束.
29.实体对象的三种状态与saveOrUpdate方法
(1).Session的几个主要方法:
第一:save,persist保存数据,persist在事务外不会产生insert语句
第二:delete:删除对象
第三:update:更新对象,如果数据库中没有记录,会出现异常
第四:get:根据ID查询数据,会立刻访问数据库
第五:load:根据ID查询,(返回的是代理,不会立即访问数据库)
第六:saveOrUpdate,merge(根据ID和version的值来确定是save或update),调用merge你的对象还是托管的
第七:lock(把对象编程持久对象,但不会同步对象的状态)
(2).瞬时(transient):数据库中没有数据与之对应,超过作用域会被JVM垃圾回收器回收,一般是new出来且与session没有关联的对象
持久(persistent):数据库中有数据与之对应,当前与session有关联,并且相关联的session没有关闭,事务没有提交;持久对象状态发生改变,在事务提交时会影响到数据库(hibernate能检测到)
脱管(detached):数据库中有数据与之对应,但当前没有session与之关联;托管对象状态发生改变,hibernate不能检测到.
(3).当关闭session时,持久态就变成了脱管状态了,区分这三种状态的两个标准:是否与数据库中记录相对应,是否在session中.
(4).当在脱管的状态时,更新的时候需要执行update的更新语句,因为不在session中.
(5).对象new是瞬时的,get(),load(),find(),iterate()等是持久的,瞬时状态执行 save(),saveOrUpdate()时变成持久的,当持久状态执行delete()时变成瞬时的,当脱管状态执行 update(),saveOrUpdate(),lock()时变成持久状态,当持久状态执行evict(),close(),clear()时,持久 状态变成脱管状态.
(6).瞬时对象的id没有值,脱管对象的id是有值的.所以当没有值时执行save()方法,当有值时执行update()方法.
30.实体类或属性名与数据库关键字冲突问题
使用Oracle时,user是个关键字,可能出现问题,将表名添加反引号.
31.使用Hibernate完成CRUD实验的步骤说明
(1).实验步骤:
第一步:设计domain对象User
第二步:设计UserDao接口
第三步:加入hibernate.jar和其依赖的包
第四步:编写User.hbm.xml映射文件,可以基于hibernate/eg目录下的org/hibernate/auction/User.hbm.xml修改
第五步:编写hibernate.cfg.xml配置文件,可以基于hibernate/etc/hibernate.cfg.xml修改;必须提 供的几个参数:connection.driver_class、connection.url、connection.username、 connection.password、dialect、hbm2ddl.auto
第六步:编写HibernateUtils类,主要用来完成hibernate初始化和提供一个获得Session的方法
第七步:实现UserDao接口
32.事务的悲观锁和乐观锁
(1).悲观锁和乐观锁
悲观锁由数据库来实现;乐观锁hibernate用version和timestamp来实现,悲观锁就相当于写锁,当自己在操作时,别人不能进行任何操作,
(2).可能多个人来读取同一个数据源,可能后一个人修改后的结果覆盖前一个人修改的结果,存在并发问题
(3).悲观锁是不可取的,我们给每条记录添加一个版本号,当同时操作数据源时,判断版本号,如果版本号不符合,就不进行更新.假设刚开始版本号为 0,同时来两个人进行操作,判断版本号是否为0,如果为0,就进行操作,操作完后版本号加一,那第二个人就发现版本号不等于0,就不会进行操作了,也不会 覆盖前一个人进行的操作.
(4).在映射文件中:
<versionname="ver"/>该标签必须在id标签的下面,即是id的子标签.
(5).版本号的类型是整型的,也可以是日期型的
(6).
Session s1=HibernateUtil.getSession();
Transactiontx1=s1.beginTransaction();//第一个线程操作事务
User user1=(User)s1.get(User.class,id);
Session s2 =HibernateUtil.getSession();
Transactiontx2=s2.beginTransaction();//第二个线程操作事务
User user2=(User)s2.get(User.class,id);
user1.getName().setFirstName("firstName1");
user2.getName().setFirstName("firstName2");
tx2.commit();//线程二先提交,成功了
tx1.commit();//线程一提交不成功.因为版本号不一样.
33.事务与事务边界的相关知识
(1).一个SessionFactory对应一个数据库,由JDBC实现
(2).事务的控制应该在业务逻辑层实现.但是事务的对象是在DAO层,那么在业务逻辑层中调用事务的对象,就出现了耦合,所以要解决这个耦合,就需借助第三方架包了EJB,Spring
34.完善HibernateUtil类及hql查询入门
(1).HQL:面向对象的查询语言,与SQL不同,HQL中的对象名是区分大小写的(除了Java类和属性其他部分不区分大小写),HQL中查的 是对象而不是和表,并且支持多态;HQL主要通过Query来操作,Query的创建方式:Query q=session.createQuery(hql);
from Person
from User as userwhere user.name=:name//其中User是类不是表名,user是别名
form User as userwhere user.name=:name and user.birthday<:birthday
(2).Criteria:是一种比HQL更面向对象的查询方式;Criteria的创建方式:Criteria crit=session.createCriteria(DomainClass.class);
简单属性条件如:
criteria.add(Restrictions.eq(propertyName,value)),criteria.add(Restrictions.eqProperty(propertyName,otherPropertyName))
(3).public staticvoid add(Object entity){//能够保存所有对象
Session s=null;
Transactiontx=null;
try{
s=HibernateUtil.getSession();
tx.s.beginTransaction();
s.save(entity);
tx.commit();
}catch(HibernateExceptione){
if(tx!=null)
tx.rollback();//不仅要回滚,还有抛出异常
throw e;
}finally{
if(s!=null)
s.close();
}
}
同理更新,删除同时同样的道理
(4).执行HQL语句
Session s=null;
try{
s=HibernateUtil.getSession();
Stringhql="from User as user where user.name=?";
Queryquery=s.createQuery(hql);
query.setString(0,name);//替换占位符
Listlist=query.list();//JDBC中的executQuery()类似
for(Useruser:list{
System.out.println(user.getName());
}
//Object obj=query.uniqueResult();当确定返回值只有一个的时候,使用这种方法.当查询有多个结果时,会出现异常
}finally{
if(s!=null)
s.close();
}
支持多态,查询的话,子类对应数据库表也被查询,如果from Object的话,会把数据库中的表都查一遍,因为所有的类都是Object的子类.
35.一对多关联关系的映射与原理分析
(1).在Department的角度上是不是一对多了,在Department中定义:
privateSet emps;//一个department中有多个员工
(2).映射文件:
<classname="Department">
<idname="id">
<generatorclass="native"/>
<propertyname="name"/>
<setname="emps">用set属性进行映射
<keycoluem="depart_id"/>设置外键
<one-to-manyclass "Employee"/>
(3).System.out.println("empsize:"+depart.getEmps().size());
打印出department中所有的employee人数.
(4).首先添加employee,在添加到department,即告诉employee属于哪个department;多两条更新语句.
Setemps = new HashSet();
emps.add(emp1);
emps.add(emp2);
depart.setEmps(emps);
告诉department有哪些employee
emp1.setDepart(depart);
emp2.setDepart(depart);
(5):ER图:Deparment:id(PK);Employee:id(PK),depart_id(FK1);
36.一对多和多对多的懒加载分析
(1).对于one-to-one懒加载方式体现出的效率不是很明显,查询身份证号时,把person的信息也查询出来,没有查询太多的信息,对效率的影响不是很大
(2).对于one-to-many懒加载方式就体现的很明显的了,当我们查询部门的详细信息时,可能把该部门的所有员工都查询出来,因为一个部门可能有很多员工,所以这时效率就明显降低了.
(3).缺省的是懒加载,当depart.getEmps()时,才会查询员工的信息,因为java中的set集合没有懒加载的功能,当我们的代码 只是获取集合代理对象的引用,比没有调用该集合代理对象的方法,所以,hibernate在这里还用不着去查询数据库来填充集合代理,因此不会抛出"代理 未初始化"的异常,如果将代码改为depart.getEmps().size(),就可以看到异常了.
(4).对于many-to-many方式懒加载也很重要,因为涉及到三张表的查询.所以也需要懒加载的功能.
37.一对一的懒加载分析
(1).one-to-one在查询主对象的时候默认情况下不使用懒加载,使用一个关联查询.但是在查询从对象的时候使用了懒加载.
(2).constrain=true是建立外键约束
(3).lazy="proxy",使用懒加载,默认的值也是proxy,还有false,true的取值
(4).fetch="join",使用什么方式去抓取,默认值为select,join是一次查询(表的连接),select是两次查询.当lazy="proxy"时,fetch="join"是无效的,它们俩之间的设置是互斥的.
38.一对一外键关联关系的映射与原理分析
(1).一对一:基于外键的one-to-one,可以描述为多对一,加上unique="true"约束<many-to-onename="person" column="person_id" unique="true"not-null="true"/>区别于多对一.只需将外键设置为唯一.
(2).对于IdCard的映射文件,其的id不是外部生成的,而是自增长的.
<generatorclass="native"/>对于Person的映射文件:
39.一对一主键关联关系的检索
(1).查询主对象:
Personp=(Person)get(Person.class,id);
System.out.println(p.getIdCard().getId());
理论上是两次查询,但是实际只进行了一次查询,使用了表之间的关联join,效率上比两次查询高
查询从对象:
IdCardidCard=(IdCard)get(IdCard.class,id);
System.out.println(idCard.getPerson().getId());
理论上和实际上都进行了两次查询
40.一对一主键关联关系的映射与原理分析
(1).基于主键的one-to-one(person的映射文件)
<generatorclass="foregin"><paramname="property">idCard
(2).对象模型:
public class Person{
private int id;
private String name;
private IdCard idCard;
//省略get/set方法
}
public class IdCard{
private int id;
private String name;
private Person person;
//省略get/set方法
}
(3).Person的映射文件:
IdCard的映射文件:
主键是由外部得到,不是自己得到的
<paramname="property">personIdCard的id是由person得到的
添加约束,配置外键.
idcard中的主键是person中的外键
(4).测试代码:
IdCard idCard = new IdCard();
Person p=new Person();
p.setName("p1");
p.setIdCard(idCard);
idCard.setPerson(p);
s.save(p);
s.save(idCard);
IdCard中的id是由Person得到的.只有主对象(person)存在,从对象(idcard)存在.
(5).ER图:person:id(PK);IdCard:id(PK,FK)
41.组件关联关系的映射与原理分析
(1).组件映射(User-Name):关联的属性是个复杂类型的持久化类,但不是实体即:数据库中没有表与该属性对应,但该类的属性要之久保存的
当组件的属性不能和表中的字段简单对应的时候可以选择实现:
org.hibernate.usertype.UserType或
org.hibernate.usertype.CompositeUserType
(2).用户名name是个对象类型
public Class Name{
private String firstName;
private String lastName;
//get/set方法省略
}
想过使用一对一.一对多,多对一都可以,一个人只能有一个名字,一个人可以有多个名字.这是数据库中肯定有两张表:User和Name,但是现在 Name的内容很小,不想设计成一个实体,不想在数据库中对应一张表,因为它太小了,此时就是用组件相关联,将用户user和名字name设计到同一张表 中
六、 JDBC
1.DAO设计思想与搭建骨架
(1).建立Domain包,在包中建立一个实体对象(bean).
public class User{
private int id;
private String name;
private Date birthday;//java.util.Date
private float money;
//生成对应的get/set方法,省略
}
(2).定义Domain接口:
public interface UserDao{
public void addUser(User user);
public User getUser(int userid);
public void update(User user);
public void delete(User user);
public User findUser(StringloginName,String password);
}
这个接口是给service层使用的.
(3).实现UserDao接口
public class UserDaoImpl implements UserDao{
public void addUser(User user){};
public User getUser(int userid){};
public void update(User user){};
public void delete(User user){};
public User findUser(StringloginName,String password){};
}
(4).在UserDaoImpl中抛出的异常进行包装,定义一个异常类
(5).工厂模式:UserDao userDao = DaoFactory.getInstance().getUserDao();
2.Java的动态代理及使用该技术完善连接代理
(1).前面说到的静态代理模式,有点麻烦,因为需要实现接口Connection的所有方法.
(2).public classMyConnectionHandler implements InvocationHandler{
private Connection realConnection;
MyConnectionHandler(){
}
Connectionbind(Connection realConn){//通过此方法将连接传进来
ConnectionwarpedConnection = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),newClass[] {Connection.class},this);//动态的编写一个类,这个类实现Connection接口,最终会把Connection的所有方 法都交给InvocationHandler处理器处理,在内存中直接产生一个字节码.
returnwarpedConnection;
}
public Objectinvoke(Object proxy,Method method,Object[] args){
if("close".equals(method.getName())){//是close方法
this.dataSource.connectonsPool.addList(this.warpedConnection);
}
returnmethod.invoke(this.realConnection,args);
}
}
这就是动态代理模式,不管是动态的,还是静态的,最终到底都是关心操作Connection的方法.
3.JdbcTemplate类中的其他各个查询方法
(1).Spring的JdbcTemplate
第一:查询带有参数,和行映射方法:
public ObjectqueryForObject(String sql,Object[]args,RowMapper rowMapper),使用自定义的UserRowMapper完成映射
一个RowMapper的常用实现BeanPropertyRowMapper,该实现可将结果集转换成一个Java Bean(字段名与Java Bean属性名不符合规范,可用别名处理)返回一条记录.
第二:public List query(String sql,Object[]args,RowMapperrowMapper)返回多条记录
第三:public int queryForInt(String sql)(如:selectcount(*) from user),其他结果比如String可用queryForObject方法向下转型
public MapqueryForMap(String sql,Object[]args)返回不是对象类型的Map(key:字段名或别名,value:列值);当查询的结果不是一个对象时,就是用一个 Map进行存放结果.查询共多少条记录,最大值,最小值等信息时,当返回的是String类型时,就是用queryForObject(String sql);只是要对返回类型进行转换.
第四:public List queryForList(String sql,Object[]args)返回多个Map
4.JDBC的理论概述
(1).JDBC(Java数据库连接)由一些借口和类构成的api,j2se的一部分,由java.sql,javax.sql包组成
(2).应用程序、JDBC API、数据库驱动及数据库之间的关系:
应用程序-->JDBC-->MySql Driver,Oracle Driver,DB2Driver--->MySql,ORacle,DB2
5.jdbc中数据类型与日期问题
(1).rs.getInt("id"),getString("name"),rs.getDate("birthday"),rs.getFloat("money")),不同的类型的获取数据.
(2).java.sql.Date是继承java.util.Date,java.util.Date是日期和时间的,而java.sql.Date只有日期,而没有时间.
(3).不能将java.util.Date赋给java.sql.Date,所 以:newjava.sql.Date(birthday.getTime()));这样就可以将java.util.Date转换成 java.sql.Date,java.sql.Date直接赋给java.util.Date可以的.
(6).st.executeUpdate(sql),带参数的方法是Statement的,不带参数的方法是PreperedStatement的
6.JTA分布事务的简要介绍
(1).跨多个数据源的事务,使用JTA容器实现事务,分成两个阶段提交
javax.transaction.UserTransactiontx=(UserTransaction)ctx.lookup("jndiName");
tx.begin();//connection1,connection2(可能来自不同的数据库)
tx.commit()//tx.rollback();
(2).tomcat不支持这种容器.weblogic可以支持.
(3).第一阶段:向所有的数据库提交事务的请求,当有事务回滚的请求,所有的数据库都回滚,第二阶段:当没有回滚请求,就进行提交事务.
(4).分布式事务处理.
7.Statement的sql注入问题
(1).SQL注入:PreparedStatement和Statement:
在sql中包含特殊字符或SQL的关键字(如:'or 1 or')时,Statement将出现不可预料的结果(出现异常或查询的结果不正确),可用PreparedStatement来解决
PreperedStatement(从Statement扩展而来)相对Statement的优点:
第一:没有SQL注入的问题
第二:Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出
第三:数据库和驱动可以对PreperedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)
PreparedStatement是Statement的子接口.
(2).
PreparedStatementps=null;//预处理接口,需要进行预处理,所以在构造的时候就需要SQL语句了,ps=conn.prepareStatement(sql);而Statement是在查询的时候需要SQL语句.
Stringsql="select id,name from user where name=?";?问号是占位符
ps.setString(1,name);将传过来的name参数替换第一个占位符?,在此过程中,将name进行的处理,将特殊符号去除,当执行查询时,不需要SQL语句了,不然会报错,rs=ps.executeQuery();
(3).建立连接最消耗时间的,当程序执行多次时,PreperedStatement比Statement除去建立连接的时间,前者效率高.
8.编写一个基本的连接池来实现连接的重复使用
(1).连接池经常使用到插入和删除,所以使用LinkedList,
public class MyDataSource{
private LinkedListconnectionsPool = new LinkedList();
public MyDataSource(){
for(int i=0;i<10;i++){//开始时创建10个连接
this.connectionsPool.addLast(this.createConnection());
}
}
public Connection createConnection() {//创建连接
returnDriverManager.getConnection(url,user,password);
}
public Connection getConnection(){//获取连接
return this.connectionPool.removeFirst();
}
public void free(Connection conn){//释放连接
this.connectionsPool.addList(conn);
}
}
得到连接并不是重复的.想重复的拿取连接,创建的连接数n<用户取连接数m,即可.
(2).
private static int initCount=5;//定义初始化连接数
private static int maxCount=10;//最大连接数
private static int currentCount=0;//当前创建的连接数
(3).为了保证并发操作,需要在获取连接中同步:
synchronized(connectionsPool){
if(this.connctionPool.size()>0)//连接池中还有连接
return this.connectionsPool.removeFirst();
if(this.currentCount<maxCount)//当前连接数没有超过最大连接数,可以接着创建连接,
return this.createConnection();
throw new SQLException("已经没有连接了");//超过了最大连接数,抛出异常.
}
9.编写一个简单的JDBC的例子
(1).连接数据的步骤:
第一步:注册驱动(只做一次)
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
第二步:建立连接(Connection)
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3305/jdbc","root","");没有密码
第三步:创建执行SQL的语句(Statement)
Statementst=conn.createStatement();
第四步:执行语句
ResultSet rs =st.executeQuery("select * from user");
第六步:处理执行结果(ResultSet)
while(rs.next()){//遍历行
System.out.println(rs.getObject(1)+'\t'+rs.getObject(2));//第一列,第二列
}
第七步:释放资源
rs.close();//关闭资源和打开资源的顺序是相反的
st.close();
conn.close();
10.参数的元数据信息
(1).
Connection conn=JdbcUtils.getConnection();
PreparedStatementps=null;
ResultSet rs=null;
ps.conn.prepareStatement(sql);//sql中可能含有参数(占位符),Object[]params存储参数,可以动态的查看sql中含有哪些参数.
ParameterMetaDatapmd=ps.getParameterMetaData();
intcount=pmd.getParameterCount();//得到参数的个数
for(inti=1;i<count;i++){
System.out.println(pmd.getParameterClassName(i));//得到参数的类名
System.out.println(pmd.getParameterType(i));//得到参数的类型
ps.setObject(i,parames[i-1]);//遍历替换参数
}
String sql ="select * from user where name=? and birthday<? and money>?";
直接返回的类型都是String,VARCHAR
11.分析jdbc程序的编写步骤和原理
(1).连接是通过底层的TCP/IP协议进行的
(2).注册驱动:
方式一:Class.forName("com.mysql.jdbc.Driver");
推荐这种方式,不会对具体的驱动类产生依赖,类加载到内存中,会调用静态代码块:
static{
try{
DriverManager.registerDriver(new Driver());
}catch(SQLException e){
throws RuntimeException();
}
}
方式二:DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖,其内部定义了一个Vector列表,将多个驱动存放到Vector中
方式三:System.setProperty("jdbc.drivers","driver1:driver2");
虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用,可以注册多个驱动
(3).方式一接受的是一个字符串,方式二接受的是一个驱动类,所以具有依赖关系
(4).创建连接:
Stringurl="jdbc:mysql://localhost:3394/jdbc";
格式:jdbc:子协议:子名称//主机名:端口/数据库名
String user="root";
String password="";
Connectionconn=DriverManager.getConnection(url,user,password");
(5).释放资源:数据库建立连接的个数也是有限制的,当数据库创建了多个连接,数据库可能运行的很慢,可能导致数据库崩溃,占用系统资源.
12.分析在实际项目中该如何应用JDBC
(1).三层架构:
表示层:基于web的jsp、servlet、struts、webwork、spring web MVC等,基于客户端的swing,swt等
业务逻辑层:Pojo(service,manager),Domain,session EJB、spring
数据访问层:JDBC,IBatis,Hibernate,JDO,Entity Bean
层与层之间用接口隔离
13.规范和封装JDBC程序代码
(1).规范的代码:
Stringurl="jdbc:mysql://localhost:2332/jdbc";
String user="root";
String password="";
Statement st=null;
ResultSet rs=null;
Connecton conn=null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn=DriverManager.getConnection(url,user,password);
st = conn.createStatement();
rs=st.executeQuery("select * fromuser");
}finally{
try{
if(rs!=null)
rs.close();
}finally{if(st!=null)
try{
st.close();
}finally{
if(conn!=null)
conn.close();
}
}
}
}
(2).设计一个工具类:
public final class JdbcUtils{
private static Stringurl="jdbc:mysql://localhost:2332/jdbc";
private static String user="root";
private static String password="";
private JdbcUtils(){//不允许实例化
}
static{//驱动只注册一次
try{
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e){
throw new ExceptionInitializerError(e);
}
}
public static Connection getConnection(){//创建连接
returnDriverManager.getConnection(url,user,password);
}
public static void free(ResultSetrs,Statement st,Connection conn){//释放资源
try{
if(rs!=null)
rs.close();
}catch(SQLException e){
e.printStackTrace();
}finally{if(st!=null)
try{
st.close();
}catch(SQLException e){
e.printStackTrace();
}finally{
if(conn!=null)
conn.close();
}
}
}
}
}
14.将Dao中的修改方法提取到抽象父类中
(1).对于代码的重构,焦点就是将代码变化的部分和不变的部分分离开来.一个是sql语句不同,参数不同,提取一个超类,相同的部分,放到超类中,不同的部分由子类实现.
publci abstract class AbstractDao{
public void update(String sql,Object[]args){
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
conn=JdbcUtils.getConnection();
ps=conn.prepareStatement(sql);
for(int i=0;i<args.length;i++){//用args参数列表,更新数据
ps.setObject(i+1,args[i]);
}
}
//args就是参数列表,
}
public class UserDaoImpl extendsAbstractDao{
public void update(User user){
String sql="update user setname=?,birthday=?,money=?,where id=?";
Object[] args=new Object[]{user.getName(),user.getBirthday(),user.getMoney(),user.getId()};
super.update(sql,args);//调用父类的update方法.
}
}
15.可更新和对更新敏感的结果集
(1).
st=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,Result.CONUR_UPDATABLE);
在读取数据时,可以更改数据,可更新结果集.
(2)
while(rs.next()){
rs.getObject("name");
rs.getObject("money");
String name= rs.getString("name");
if("lisi".equals(name)){
rs.updateFloat("money",200f);
rs.updateRow();//更新行
}
}
(3).这种方式不常用,在查询时,把数据更改了,给人一种不明确感.
在查询结果集时,更新数据时数据库能不能感知到数据更新了.数据库敏不敏感SENSITIVE
16.利用结果集元素数据将查询结果封装为map
(1).将查询结果放在map中
key:列的名称
value:列的值
(2).
Connection conn=JdbcUtils.getConnection();
ps=conn.prepareStatement(sql);
rs=ps.executeQuery();
ResultSetMetaData rsmd =rs.getMetaData();//获取结果集的元数据
int count= rsmd.getColumnCount();//结果有多少列
for(int i=1;i<=count;i++){//循环遍历列
System.out.println(rsmd.getColumnClassName(i));//每一列的类型名
System.out.println(rsmd.getColumnName(i));//每一列的名称
System.out.println(rsmd.getColumnLabel(i));//每一列的别名
}
这里就可以准确的得到每一列的类型,而不像前面的都是String类型.
String[]colName=new String[count];//存放每一列的名称
Map<String,Object> data=null;
while(rs.next()){//按行循环
data = new HashMap<String,Object>();
for(int i=0;i<colNames.length;i++){//按列循环
data.put(colNames[i],rs.getObject(colNames[i]));//根据类名得到列的值
}
}
灵活性非常高.能够按照各种方式查询
16.如何使用开源项目DBCP
(1).dbcpconfig.properties数据源的配置文件:
连接配置:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
初始化连接:
initialiSize=10
最大连接数量:
maxActive=50
最大空闲连接://不同的时间段创建的连接不同,可能出现空闲连接.
maxIdle=20
最小空闲连接:
minIdle=5
超过等待时间以毫秒为单位 6000毫秒/1000等于60秒
maxWait=60000
//当没有连接可取的时候,让当前线程等待一段时间,在去拿连接
JDBC驱动建立连接时附带的连接属性,属性的格式必须为这样:[属性名=property;],注意:"user" 与"password"两个属性会被明确地传递,因此这里不需要包含它们(url后面携带的值)
connectionProperties=userUnicode=true;characterEncoding=gbk
指定由连接池所创建的连接的自动提交状态
defaultAutoCommit=true
driver default指定由连接池所创建的连接的只读(read-only)状态,如果没有设置该值,则"setReadOnly"方法将不被调用,(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
driver default指定 由连接池所创建的连接事务级别(TransactionIsoation),可用值为下列之一(详情可见javadoc)NONE,READ,UNCOMMITTED,READ_COMMITTE
defaultTransactionIsolation=READ_UNCOMMITTED
(2).DBCP是apache的开源项目.实现了DataSource接口.Data Base Connection Pool,修改代码需要从新编译,打包,修改配置文件只需要重启即可.
(3).
Properties prop = new Properties();
InputStream is =JdbcUtils.class.getClassLoader().getResource.AsStream("dbcp.property");//读取property文件
prop.load(is);
private static DataSoruce myDataSource=null;
myDataSource=BasicDataSourceFactory.createDataSource(prop);
(4).DataSource用来取代DriverManager来获取Connection;通过DataSource获得Connection 速度快;通过DataSource获得Connection都是已经被包裹过的(不是驱动原来的连接),它的close方法已经被修改了;一般 DataSource内部会用一个连接池来缓存Connection,这样可以大幅度提高数据库的访问速度;连接池可以理解成一个能够存放 Connection的Collection;我们的程序只和DataSource打交道,不会直接访问连接池.
(5).使用dbcp需要的三个包:common-dbcp.jar,common-collections.jar,common-pool.jar
17.使用JDBCTemplate工具类简化对象查询
(1).Spring框架中提供了一个JdbcTemplate工具类,JdbcTemplate类对JDBC API进行了很好的封装,这个类就像我们自己对JDBC进行封装一样,只是代码更健壮和功能更强大而已,我们以后在实际项目中可以使用 JdbcTemplate类来完全替换直接使用JDBC API,这与直接使用JDBC API没有太大的性能区别,使用JdbcTemplate类需要额外从spring开发包中导入spring.jar和commons- logging.jar包
(2).JdbcTemplate的设计思想和前面的MyDaoTemplate类是相同的
static User findUser(String name){
JdbcTemplate jdbc = newJdbcTemplate(JdbcUtils.getDataSource());//参数是拿到一个数据源
String sql = "select id,name from userwhere name=?";
Object[]args=new Object[]{name};
jdbc.queryForObject(sql,args,newRowMapper(){//行映射器
public Object mapRow(ResultSet rs,introwNum){
User user = new User();
user.setId(rs.getInt("id"));
return user;
}
});
return null;
}
//这里可以不需要实现行映射器,可以用类代替:
new BeanPropertyRowMapper(User.class);即可
18.使用JdbcTemplate实现Dao和用工厂模式灵活切换实现
(1).
public class UserDaoSpringImpl implementsUserDao{
private SimpleJdbcTemplatesimpleJdbcTemplate
= new SimpleJdbcTemplate(JdbcUtils.getDataSource());
//增加用户
public void addUser(User user){
String sql = "insert into user(name,money,birthday)values(:name,:money,:birthday);
KeyHolder keyHolder = newGeneratedKeyHolder();
SqlParameterSource param = new BeanPropertySqlParameterSource(user);
this.simpleJdbcTemplate.getNamedParameterJdbcOperations().update(sql,param,keyHoler);
user.setId(keyHolder.getKey().intValue());
}
}
//增加user的代码减少了太多了.
delete,update等方法都是大同小异
//删除用户
public void delete(User user){
String sql = "delete from user whereid=?";
this.simpleJdbcTemplate.update(sql,user.getId());
//使用了可变参数的特性,不需要复杂的Map存储参数
}
//查询用户
public User findUser(StringloginName,String password){
//如何简化返回查找集
String sql = "selectid,name,money,birthday from user where name=?";
returnthis.simpleJdbcTemplate.queryForObject(sql,ParameterizedBeanProertyRowMapper.newInstance(User.class),userId);
}
//更新用户
public void update(User user){//如何替换占位符?
String sql ="update user setname=?,birthday=?,money=? where id=?";
this.simpleJdbcTemplate.update(sql,user.getName(),user.getBirthday(),user.getMoney(),user.getId());
//这里也可以使用bean属性的参数源;
}
代码量大大减少了.
19.使用JDBC的批处理功能
(1).和数据库打交道的成本是很高的,当需要发送多条sql语句时,成本更高了,这时就需要使用批处理技术,将多条查询语句打成一个包.
(2).
for(int i=0;i<10000;i++){
ps.setString();
ps.setName();
ps.addBatch();//把一条更新语句增加到包中
}
int[] a = ps.executeBatch();//执行批处理,不是ps.executeUpdate();
(3).首先将语句打包时,并不是包越大越好,如果包过大的话,可能造成内存溢出,所以可能将一个打包在分成几个小包进行发送,不同的数据库,包的最适合大小是不同的.
(4).并不是所有的批处理都能提高性能,这和不同的数据库以及数据库驱动决定的.
(5).Hibernate就是用了批处理技术,但是它进行了一些优化技术.
20.使用JDBC调用的存储过程
(1).存储过程经常用在以前的两层结构中,现在的三层结构已经就用不到了
(2).CallableStatement(从PreparedStatement继承来的)
java代码:
CallableStatement cs=null;
String sql="{calladdUser(?,?,?,?)}";
cs=conn.prepareCall(sql);
//替换参数
cs.registerOutParameter(4,Types.INTEGER);
cs.setString(1,"ps name");
cs.setDate(2.new java.sql.Date(System.currentTimeMills()));
cs.setFloat(3,100f);
cs.executeUpdate();
int id = cs.getInt(4);
System.out.println(id);
存储过程:
create procedure 'jdbc'.'addUser' (in pnamevarchar(45),in birthday date,in money float,out pid int)
//in:输入参数,out:输出参数
begin
insert intouser(name,birthday,money)values(pname,birthday,money);
select last_insert_id() into pid;
//last_insert_id()是一个函数,最后一次插入的id号
end $$
21.使用SimplejdbcTemplate和泛型技术简化代码
(1).
public class SimpleJdbcTemplateTest{
static SimpleJdbcTemplate simple = newSimpleJdbcTemplate(JdbcUtils.getDataSource());
static T find(String nameClass clazz){
String sql = "selectid,name,money,birthday from user where name=? and money=?";
User user =
simple.queryForObject(sql,ParameterizedBeanPropertyRowMapper.newInstance(User.class),name,100f);
}//使用了可变参数功能,没有使用了参数数组,参数Map;使用泛型,将查询的类型也当做是参数传递过来.
}
(2).它的内部也是包装了NamedParameterJdbcOperations类,当将对象可变参数变成数组后,剩下的工作都交给NamedParameterJdbcOperations类做.
simple.getNamedParameterJdbcOperations()获取NamedParameterJdbcOperations对象
simple.getJdbcOperations()获取JdbcTemplate对象
22.使用策略模式对模板方法设计模式进行改进
(1).对于不同的查询语句,返回的结果集可能不同,只要一个name,但是把所有的信息都查询出来了,这就要求不同的映射结果.
public StringfindUserName(int id){
Stringsql="select name from user where id=?";
Object[]args=newObject[]{id};
}
protected ObjectrowMapper(ResultSet rs){//从新覆盖rowMapper方法
returnrs.getString("name");
}
这种方式可能导致有多少条不同的查询语句,就需要覆盖多少次rowMapper方法.
(2).java中是不允许传递方法的,但是可以传递一个类,接口
根据不同的sql中的内容,查询的列不同,如:
select name fromuser:可以得到name一列
select id,namefrom user:可以得到id,name这两列
selectid,name,money from user:可以得到id,name,money这三列.
public classMyDaoTemplate{
public Objectfind(String sql,Object[]args,RowMapper rowMapper){
obj =rowMapper.mapRow(rs);//映射的过程由一个接口去做
}
}
public interfaceRowMapper{//定义一个行映射器接口
public ObjectmapRow(ResultSet rs);
}
public classUserDaoImpl2{
MyDaoTemplate template= new MyDaoTemplate();
public UserfindUser(String loginName,String password){
Stringsql="select id,name,money,birthday from user where name=?";
Object[] args =new Object[]{loginName};
Object user =this.template.find(sql,args,new UserRowMapper());
retrun (User)user;
}
}
classUserRowMapper implements RowMapper{
public ObjectmapRow(ResultSet rs){//行映射器
User user = newUser();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setMoney(rs.getFloat("money"));
return user;
}
}
//当需要不同的查询结果集,只需实现RowMapper接口就行了(可以使用匿名内部方式实现)
(3).这是一种策略模式,根据不同的功能,调用不同的方法(策略),实现类组合的方式(在UserDaoImpl2类中定义一个MyDaoTemplate类)实现的,模板模式是根据继承的方式实现的.
23.使用模板方法设计模式处理DAO中的查询方法
publc abstractclass AbstractDao{
public Object find(String sql,Object[]args){//相同的部分在父类中实现
Connectionconn=null;
PreparedStatementps=null;
ResultSet rs=null;
conn=JdbcUtils.getConnection();
ps=conn.prepareStatement(sql);
for(inti=0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
rs=ps.executQuery();
Object obj-null;
while(rs.next()){
obj=rowMapper(rs);
}
return obj;
}
abstract protectedObject rowMapper(ResultSet rs);//父类中不知道具体的查询结果集.放到子类实现该方法.
}
public classUserDaoImpl extends AbstractDao{
public UserfindUser(String loginName,String password){//不变的部分放到子类实现.
Stringsql="select id,name,money,birthday from user where name=?";
Object[] args =new Object[]{loginName};
Object user = super.find(sql,args);
return (User)user;
}
@Override
protected ObjectrowMapper(ResultSet rs){//在子类中知道查询结果有几列
User user=newUser();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setMoney(rs.getFloat("money"));
user.setBirthday(rs.getDate("birthday"));
return user;
}
}
假设现在有一个账户AccountDao
public classAccountDaoImpl extends AbstractDao{
public UserfindAccount(int id){//不变的部分放到子类实现.
Stringsql="select id,name,money from account where id=?";
Object[] args =new Object[]{id};
Object user =super.find(sql,args);
return(Account)account;
}
@Override
protected ObjectrowMapper(ResultSet rs){//在子类中知道查询结果有几列
Accountaccount=new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
public classAccount{
private int id;
private Stringname;
private floatmoney;
//get/set方法省略
}
模板模式,相同的步骤放到父类中,不同的步骤放到子类中设计,service方法,doGet(),doPost()方法,首先调用service方法.service会根据method参数的值来调用doGet(),doPost()方法.
24.使用支持命名参数的JdbcTemplate
(1).Spring的NamedParameterJdbcTemplate
第一:NamedParameterJdbcTemplate内部包含了一个JdbcTemplate,所以JdbcTemplate能做的事情 NamedParameterJdbcTemplate都能干,NamedParameterJdbcTemplate相对于JdbcTemplate主 要增加了参数可以命名的功能
第二:public Object queryForObject(String sql,MapparamMap,RowMapper rowMapper)
第三:public Object queryForObject(Stringsql,SqlParameterSoruce paramSource,RowMapper rowMapper)
SqlParameterSource的两个主要实现MapSqlParameterSource和BeanPropertySqlParameterSource
第四:public int update(String sql,SqlParameterSourceparamSource,KeyHolder generatedKeyHolder)保存数据获得主键
(2).在传递参数时,需要将参数Object[]args与?占位符的位置对应好,如果对应错了,就会出现问题,这时,我们就可以给占位符起个别名
staticNamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate();
Stringsql="select id from user where name=:n and money>:m andid<:id";
Map params=newHashMap();//使用Map存放参数,而不是数组了
params.put("n",user.getName());
params.put("m",user.getMoney());
params.put("id",user.getId());
/Object[]args=new Object[]{user.getName(),user.getMoney(),user.getId()};/
Object u =named.queryForObject(sql,params,new BeanPropertyRowMapper(),User.class));
//注意sql的书写,将占位符?替换了,注意替换的规则.NamedParameterJdbcTemplate只干了一件事,就是将占位符?替换成变量名,将参数命名话后,之后的操作都会交给JdbcTemplate处理.
(3).为什么要使用命名参数:
SqlParameterSourceps = new BeanPropertySqlParameterSource(user);//关于user的bean参数源
Stringsql="select id from user where name=:name and money>:money andid<:id";
Object u =named.queryForObject(sql,ps,new BeanPropertyRowMapper(),User.class));
这时参数就存放在user参数源中,参数名必须和user的属性名一样,将参数封装成一个类(参数源),符合面向对象设计思想
(4).保存数据,拿到记录的主键.当主键是符合类型(就是多列组成),也可能是String类型的.
static voidaddUser(User user){
String sql ="insert into user(name,birthday,money) value(:name,:birthday,:money);
SqlParameterSourceps = new BeanPropertySqlParameterSource(user);//关于user的bean参数源
KeyHolderkeyHolder = new GeneratedKeyHolder();
named.update(sql,ps,keyHolder);
//插入的记录的主键放到keyHoler中
int id =keyHolder.getKey().inValue();
user.setId(id);
Map map =keyHolder.getKeys();//主键由多列组成的时候
}//重点
25.事务的保存点处理
(1).当事务进行回滚时,不是全部进行回滚,有时只想回滚一部分的操作,
(2).Savepoint sp=null;
sp=conn.setSavepoint();//设置保存点
if(conn!=null&&sp!=null){
conn.rollback(sp);//将保存点当做参数,只回滚到保存点
}
26.事务的概念与JDBC事务处理
(1).事务的特性:(ACID)
原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分
一致性(consistency):在事务处理执行前后,数据库是一致的(数据库数据完整性约束)
隔离性(isolcation):一个事务处理对另一个事务处理的影响持久性(durability):事务处理的效果能够被永久保存下来
(2).connection.setAutoCommit(false)//打开事务
connection.commit();//提交事务
connection.rollback();//回滚事务
(3).查看数据库表的引擎是否支持事务的操作
27.事务的隔离级别
(1).当两个事务同时去操作同一个数据源,这就是隔离性
(2).设置隔离级别:connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
隔离级别:
读未提交:可能出现脏读,不可重复度,幻度
读已提交:不可能脏读,可能出现不可重复读,幻读
可重复读:不可能脏读,不可重复读,可能出现幻读
可串行化:不可能脏读,不可重复读,幻读
级别逐渐提高.当级别越高的时候,需要的资源越多,对并发的操作有影响.
脏读:别人的数据没有提交,我就读到了.
不可重复读:第一次读和第二次读的结果不同
幻读:当我们正在查询id>10的记录,这时有另外一个事务增加一条正好id>10的记录.
(3).隔离级别的缺省值和数据库相关.不同数据库拥有的隔离级别的个数也是不同的.
28.数据库的元数据信息
(1).可以得到数据库的相关信息,是否支持事务,数据库的名称,版本号,隔离级别等信息.
Connectionconn=JdbcUtils.getConnection();
DatabaseMetaDatadbmd =conn.getMetaData()//返回数据的元信息
dbmd.getDatabaseProductName();//得到数据库的名称
(2).hibernate支持各种数据库,所以它肯定需要知道所有数据库的相关信息.
29.通过代理模式来保持用户关闭连接的习惯
(1).用户可能不使用JdbcUtils.free()方法释放连接,而是按照conn.close()方法释放连接,这时我们创建的连接池就没有用了,连接数也就减少了,所以我们希望用户始终使用我们自己编写的方法进行释放连接
(2).通过close()方法,还是能够将连接方法连接池中,所以我们要拦截close()方法,组合优先继承
(3).public classMyConnection implements Connection{
private ConnectionrealConnection;//使用组合方式
privateMyDataSource dataSource;
MyConnection(ConnectionrealConnection,MyDataSource dataSource){
this.realConnection=connection;
this.dataSource=dataSource;
}
//实现Connection的所有方法
public voidclose(){//这里就可以实现Connection的close()方法了
this.dataSource.connectionPool.addLast(this);//把自己重新放到池中.
}
(4).此时代码中的Connection处都使用MyConnection,这就是面向接口编程的好处.同时类MyConnection的访问权限是包访问权限,不准用户访问的,但是允许在dataSource中访问.
(5).DataSource类和MyConnection类之间相互调用.
(6).MyConnection是个代理,是Connection的代理模式,实现Connection的close()方法.这就是静态代理设计模式,在MyConnection类中定义一个Connection,这是组合方式,也可以使用集成方式实现代理.
30.完成数据库的CRUD操作
(1).书写SQL语句时应该注意的问题:select * from user,就是不应该写星号,最好书写列名,得到数据,可以根据列的索引号,也可以根据列名,建议使用根据列名取数据.
31.用jdbc访问大段文本数据
(1).数据库中的varchar最大是255个字节,所以就需要使用大文本类型TEXT.只有纯文本格式才能放进去.
(2).
Stringsql="insert into clob_test(big_text) value(?)";
ps=conn.prepareState(sql);
File file=newFile("src/cn/itcast/jdbc/JdbcUtils.java");
Reader reader =new BufferedReader(new FileReader(file));//可能含有IO的异常
ps.setAsciiStream(1,reader,(int)file.length());//需要一个Reader,字符流的长度Length,这个方法只能用于文本只含有Ascii码的
inti=ps.executeUpdate(sql);
reader.close();
rs=st.executeQuery("selectbig_text from clob_test");//读取文本类型数据
while(rs.net()){
Clob clob =rs.getClob(1);
Reader reader =clob.getCharacterStream();
File file=newFile("JdbUtils_bak.java");
Writer writer=newBufferedWriter(new FileWriter(file));
char[]buff=newchar[1024];
for(inti=0;(i=reader.read(buff))>0;){
writer.write(buff,0,i);
}
}
writer.close();
reader.close();
七、 iBaits
优点:
ibatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写,给程序的维护带来了很大便利。
ibatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象,大大简化了Java数据库编程的重复工作。
简单易于学习,易于使用,非常实用。
因为Ibatis需要程序员自己去编写sql语句,程序员可以结合数据库自身的特点灵活控制sql语句,因此能够实现比hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。
阿里巴巴、慧点科技等多家知名软件公司都使用Ibatis。
缺点:
1.CRUD的操作只能带一个参数
2.和Hibernate相比,需要编写Sql语句,但是Hibernate不需要编写Sql语句