admin 发表于 2015-6-2 12:46

五、       Hibernate
1.Criteria查询方式
(1).Criteria查询方式(条件查询):
Criteriac=s.createCriteria(User.class);
c.add(Restrictions.eq("name",name));//添加查询条件,User中的name属性的值是否等于"name"
List<User>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元素加入子元素:<cache usage="read-write"/>
其中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"/>hibernate缺省的监听器,自己定义的监听器会覆盖缺省的,所以在这里还要把缺省的监听器注册一下.
</event>
当保存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">
<class name="User">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="birthday"/>
</class>
</hibernate-mapping>
在配置文件中hibernate.cfg.xml:
<hibernate-configuration>
<session-factory>
<propertyname="connection.url">jdbc:mysql://localhost:3306/jdbc</property>
<propertyname="connection.username">root</property>
<propertyname="connection.password"></property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<propertyname="hbm2ddl.auto"></property>
<mappingresource="org/hibernate/test/legacy/User.hbm.xml"/>
<class-cacheclass="org.hibernate.test.legacy.Simple" region="Simple"usage="read-write"/>
</session-factory>
</hibernate-configuration>
方言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内容解释:
第一:数据类型:<property name="name"type="java.lang.String"/> 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<Employee>emps = new ArrayList<Employee>();
映射文件中:
<listname="emps">
</list>配置和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:article,Object:user;

11.Iterate查询与N+1次查询
(1).假设已经加入到了10个用户
static void iterator(){
Session s=HibernateUtils.getSession();
Query q=s.createQuery("fromUser");
Iterator<User> 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<IdCard> 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</property>//可以打印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">
<!]>
</query>
这个定义可以放到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会为我们创建中间关联表,转换成两个一对多.
<set name="teacher"table="teacher_student">
<key column="teacher_id"/>
<many-to-many class="Student" column="student_id"/>
</set>
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<Student> students;
//省略get/set方法
}
public class Student{
private int id;
private String name;
private Set<Teacher> teachers;
//省略get/set方法
}
teacher的映射文件:
<class name="Teacher">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<set name="students" table="teacher_student">
<key cloumn="teacher_id"/>
<many-to-many class="Student"column="student_id">根据student_id去查询学生的相关信息
</set>
</class>
同理student的映射文件相似.

(3).测试类:
Set<Teacher> ts=newHashSet<Teacher>();
Teacher t1=new Teacher();
t1.setName("t1 name");
Teacher t2=new Teacher();
t2.setName("t2 name");
ts.add(t1);
ts.add(t2);
Set<Student> ss=newHashSet<Student>();
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).多对一:映射文件:<many-to-one name="depart"column="depart_id"/>
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">
<class name="Emplyee">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<many-to-one name="depart"colum="depart_id"/>
</class>
</hibernate-mapping>
不在使用标签property,是对象类型depart,使用标签<many-to-one>
通过反射可以找到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).映射文件:
<union-subclass name="Skiller"table="skiller">
<property name="skill"/>
</union-subclass>
<union-subclass name="Sales"table="sales">
<property name="sell"/>
</union-subclass>

(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">
<key column="employee_id"/>
<property name="skill"/>
</joined-subclass>
<joined-subclass name="Sales"table="sales">
<key column="employee_id"/>
<property name="sell"/>
</joined-subclass>

(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).映射文件:
<class name="Employee"discriminator-value="0">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="type"type="int"/>鉴别器,hibernate用来区分不同的子类.
<subclass name="Skiller"discriminator-value="1">
<property name="skill"/>
</subclass>
<subclass name="Sales"discriminator-value="2">
<property name="sell"/>
</subclass>
</class>

(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<Employee> emps;//一个department中有多个员工

(2).映射文件:
<classname="Department">
<idname="id">
<generatorclass="native"/>
</id>
<propertyname="name"/>
<setname="emps">用set属性进行映射
<keycoluem="depart_id"/>设置外键
<one-to-manyclass "Employee"/>
</set>
</class>

(3).System.out.println("empsize:"+depart.getEmps().size());
打印出department中所有的employee人数.

(4).首先添加employee,在添加到department,即告诉employee属于哪个department;多两条更新语句.
Set<Employee>emps = new HashSet<Employee>();
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"约束<one-to-onename="idCard" property-ref="person"/><many-to-onename="person" column="person_id" unique="true"not-null="true"/>区别于多对一.只需将外键设置为唯一.

(2).对于IdCard的映射文件,其的id不是外部生成的,而是自增长的.
<generatorclass="native"/>对于Person的映射文件:<one-to-one name="idCard"property-ref="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的映射文件)
<id name="id">
<generatorclass="foregin"><paramname="property">idCard</param></generator>
</id>
<one-to-one name="idCard"constrained="true"/>

(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的映射文件:
<class name="Person">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<one-to-one name="idCard"/>
</class>
IdCard的映射文件:
<class name="IdCard">
<id name="id">
<generator class="foregin">主键是由外部得到,不是自己得到的
<paramname="property">person</param>IdCard的id是由person得到的
</generator>
</id>
<property name="name"/>
<one-to-one name="person" constrained="true"/>添加约束,配置外键.
</class>
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):关联的属性是个复杂类型的持久化类,但不是实体即:数据库中没有表与该属性对应,但该类的属性要之久保存的
<component name="name"class="com.test.hibernate.domain.Name">
<property name="initial"/>
<property name="first"/>
<property name="last"/>
</component>
当组件的属性不能和表中的字段简单对应的时候可以选择实现:
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设计到同一张表中

admin 发表于 2015-6-2 12:46

六、       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<Connection>connectionsPool = new LinkedList<Connection>();
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);//遍历替换参数
}
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);
}
}
//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;//存放每一列的名称
Map<String,Object> data=null;
while(rs.next()){//按行循环
data = new HashMap<String,Object>();
for(int i=0;i<colNames.length;i++){//按列循环
data.put(colNames,rs.getObject(colNames));//根据类名得到列的值
}
}
灵活性非常高.能够按照各种方式查询

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> T find(String nameClass<T> 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);
}
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;
for(inti=0;(i=reader.read(buff))>0;){
writer.write(buff,0,i);
}
}
writer.close();
reader.close();

admin 发表于 2015-6-2 12:46

七、       iBaits
优点:
1. ibatis把sql语句从Java源程序中独立出来,放在单独的XML文件中编写,给程序的维护带来了很大便利。
2. ibatis封装了底层JDBC API的调用细节,并能自动将结果集转换成Java Bean对象,大大简化了Java数据库编程的重复工作。
3. 简单易于学习,易于使用,非常实用。
4. 因为Ibatis需要程序员自己去编写sql语句,程序员可以结合数据库自身的特点灵活控制sql语句,因此能够实现比hibernate等全自动orm框架更高的查询效率,能够完成复杂查询。
5. 阿里巴巴、慧点科技等多家知名软件公司都使用Ibatis。

缺点:
1.CRUD的操作只能带一个参数
2.和Hibernate相比,需要编写Sql语句,但是Hibernate不需要编写Sql语句
页: 1 [2]
查看完整版本: JavaWeb方面的所有知识听课记录