这个属性的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
4、 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数据.
5、 Hibernate
1. Criteria查询方式
(1).Criteria查询方式(条件查询):
Criteriac=s.createCriteria(User.class);
c.add(Restrictions.eq("name",name));//添加查询条件,User中的name属性的值是否等于"name"
List
list=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)
List
emps = 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;多两条更新语句.
Set
emps = 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设计到同一张表 中
6、 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 LinkedList
connectionsPool = 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();
7、 iBaits
优势:
ibatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写,给程序的维护带来了很大便利。
ibatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象,大大简化了Java数据库编程的重复工做。
简单易于学习,易于使用,很是实用。
由于Ibatis须要程序员本身去编写sql语句,程序员能够结合数据库自身的特色灵活控制sql语句,所以可以实现比hibernate等全自动orm框架更高的查询效率,可以完成复杂查询。
阿里巴巴、慧点科技等多家知名软件公司都使用Ibatis。
缺点:
1.CRUD的操做只能带一个参数
2.和Hibernate相比,须要编写Sql语句,可是Hibernate不须要编写Sql语句