Hibernate

Hibernate的作用:

1、         Hibernate解决ORM(对象关系映射)的问题,大大减少了持久层的代码量

2、         hql方言,解决了可移植性问题

3、         效率问题,频繁的连接和关闭,自动的封装JDBC,自动使用连接池。Session

4、         具有缓存功能,节省查询时间

5、         通过设定自动给数据加锁,事务的隔离级别

Hibernate工作在持久层。

类似Hibernate的框架 apache的OJB,sun公司的JDO,Oracle的Toplink, apache 和 Google的Ibatis。

POJO:领域模型也叫实体类。包括表单bean和结果bean(不继承任何类)

<struts1.0的Form 不属于POJO,因为继承了ActionForm类>

1、为什么要用Hibernate?

Java中的对象,数据库中的关系。Hibernate主要是实现对象模型和关系模型直接的转换。轻量级的封装。面向对象的思想。

因为Hibernate有映射文件,所以才能对数据save,query等操作。

映射文件:***.hbm.xml文件,也叫源文件,把对象模型的继承、关联映射成数据库中的主外键关系。

Hibernate自动的保持数据库在的记录和对象属性的值同步(保证一致)。

Hibernate的session是持久化管理器,工作在持久层。

 

数据库中的表的主键生成策略:

Native:Hibernate根据不同的数据库生成不同的主键,自动增长

Uuid:UUID.randomUUID().toString(),32位字符串

Asigned:用户自己生成的

Foreign:外键维护叫做参照完整性,维护关系。来自于对象里关联属性的值

GUID:increment,线程不安全,建议不要使用

Identity:使用数据库自己的自动增长策略,oracle数据库不支持

 

对象与对象的关系:关联(has a )和继承(is a )

属性映射:主键一般不从对象中拿,要么从数据库中生成,要么hibernate給。外键从对象的关联属性上拿。(关联属性:属性类型为自定义类型一定是关联属性,如果属性为集合类型,该属性有可能是关联属性)

 

Hibernate3.2 的环境搭建:(测试环境—> 真实环境)

测试环境:(不占内存的,导入的包不在项目里面,只是做关联)

1、         创建java Project

2、         创建公共的jar包, windowsàjavaàUser Libraryà包名

3、         在创建的包下导入jar包,(主jar包和lib下的所有jar包+mysql的jar包)

4、         导入项目库,右键—>propertiesàjava buildPathà选择自己创建的存放jar包的包名

5、         创建source folder (src文件夹)

6、         把ext文件夹下的hibernate.cfg.xml.,log4j.properties文件放在src下

7、         在hibernate.cfg.xml文件中配置持久化数据库信息,配置方言(不同的数据库有不同的方言)<property>

8、         导入映射文件<mapping resource=”com/hibernate/User.htm.xml”/>:将User对象和数据库中的表进行ORM(对象关系映射)

 

真实环境:将jar包直接导入WEB-INF/lib下,配置信息与虚拟环境相同

 

Hibernate小案例

1、         创建实体类:POJO(表单bean、结果bean)User

2、         创建和实体类对应的元文件  User.hbm.xml(可能多个实体类对应一个元文件)

<hibernate-mapping>

      <class name=”类的全路径名 cn.bean.User”>//根据类名创建表 user

      <id name=”id”>//id映射表示该属性为数据库中的表的主键

<generator class=”uuid”>//generator 表示主键的生成策略。设置主键为uuid,Hibernate自动生成

</id>

<property  name=”name”/>//普通属性用property映射,在表中的字段也叫name

      </class>

</hibernate-mapping>

数据库中主键映射:

如果在对象中为String id,generator一般为uuid,32位码,Hibernate自动生成

如果在对象中为int id,generator一般为native,数据库中自动生成,自增。

 

Hibernate会自动根据表单bean创建和表单bean类名相同的表。

会根据表单bean中的属性自动创建表的字段,数据类型会自动解析。Int->int,Stringàvarchar,util Date à datetime 类型相对应。长度取默认值。

 

DDL:数据库定义语言、DCL数据库控制语言、DML:数据库操纵语言

DDL(data definition language):
DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表时使用

DML(data manipulation language):
SELECT、UPDATE、INSERT、DELETE,这4条命令是用来对数据库里的数据进行操作的语言
DCL(Data Control Language):
是数据库控制功能。是用来设置或更改数据库用户或角色权限的语句,包括(grant,deny,revoke等)语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行DCL

 

Hbm2ddl工具类:

Configuration cfg=new Configuration().configure();//将src下的hibernate.hbm.xml文件读到cfg中

Configuration cfg=new Configuration();//将src下的hibernate.properties文件读到cfg中

SchemaExport  export=new SchemaExport(cfg);

export.create(true,true);//按照User.hbm.xml文件中配置在数据库中生成对应的表

 

Hiberante的配置信息:

<hibernate-configuration>

<session-factory> 

<!—配置数据库的连接信息-- >

<property name="hibernate.connection.url">jdbc:mysql://localhost/hibernate_fist</property>

<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

<property name="hibernate.connection.username">root</property>

<property name="hibernate.connection.password">mysql</property>

<!—配置mysql数据的方言 -- >

<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

<!—配置显示sql语句,建表,查询之类的,为了方便跟踪sql执行 -- >

<property name="hibernate.show_sql">true</property>

<!—配置表的自动生成策略,如果表不存在就创建,存在就不再创建-- >

<property name="hibernate.hbm2ddl.auto">update</property>

<!—配置映射文件信息,用于数据库中的表的生成-- >

<mapping resource="com/hibernate/User.hbm.xml"/>

</session-factory>

</hibernate-configuration>

 

JDK动态代理:对目标类实现同一个接口的代理(通过反射实现)

Cglib代理:代理类是目标类的子类               

 

Hibernate使用的是连接池,dbcp连接池(内建连接池)

autocommit(),自动提交事务默认为false,需要手动提交事务

 

测试类:把对象变成表中的记录

1、         Configuration cfg=new Configuration().configure();//读取配置文件,xml

2、         SessionFactory factory=cfg.buildSessionFactory();//创建工厂,session工厂是重量级的,线程安全,非常耗时,原则上只创建一次。最好是单例+同步。

SessionFactory的作用:生成Session对象,二级缓存,并且可以配置缓存策略

3、         Session session=null;

session=factory.openSession();//通过工厂拿到Session对象,session是持久化管理器,session是线程不安全的。可以拿到多个session。

4、         session.beginTransaction();//开启事务(从数据库的连接池中拿到连接),session开启的事务是session级别的事务,本地事务,局部事务。(通过SessionFactory创建的事务是全局事务,可以被多个SessionFactory对象使用)

什么是事务?事务是对数据库的操作(一致性,原子性,持久性,隔离性)

5、         创建User对象,给User对象赋值

当配置中的<id><generator class =”uuid”/></id>时,自己设定的主键是没有用的。赋值后的user对象是瞬时态

6、         session.save(user);//session会为user分配主标识,uuid,j将user对象纳入session的缓存,临时集合insertron中。 保存数据

7、         session.getTransaction().commit();//session 拿到事务后提交事务。在这里生成sql语句,将user对象中的数据存入数据库中对应的字段里。(操纵数据库

8、         session.getTransaction().rollback();// 发生异常就catch并且回滚

                                                             

Hibernate的优点:

代码量少,提高生产力

更加面向对象,对象化

可移植性好(改方言,改配置就可以啦)

支持透明持久化:轻量级,没有侵入性

 

事务:

事务分为总事务和子事务,子事务预提交,总事务发现其中一个子事务出错就回滚。

公共请求事务对象代理:不是真正的操作只是代理对象。-- > 中间件(代理完成事务的提交)

事务的边界:session.beginTransaction();

JDNI:java的名称目录接口,对连接池进行管理。

Hibernate可以处理多种事务(本地事务,跨资源事务,局部事务),提供统一的事务边界,(事务边界由session获取)

SessionFactory可以生成全局事务,供多个session使用,并且具有二级缓存功能。

 

Configuration:封装和管理hibernate的主配置文件。

1、         封装和管理 :数据库的底层信息<property>

2、         封装和管理:映射文件元信息(元数据)<mapping-resources>

获取方式:

1、         new Configuration();//拿到hibernate.properties文件

2、         new Configuration().configure();//拿到hibernate.hbm.xml的配置信息

 

SessionFactory:(session工厂也叫会话工厂)

SessionFactory通过Configuration创建,创建特点:cfg.buildSessionFactory();

SessionFactory功能:

1、         取得持久化对象session(factory.openSession())

2、         生成全局性事务(Tansaction) 事物共享控制安全性

3、         缓存(缓存元数据和不常修改的数据) 缓存后,一般不释放。

缓存定义:第一次读取后,第二次访问时已经存入内存,直接读取效率会高。

缓存目的:提高对数据库的访问效率

SessionFactory性能:

1、         SessionFactory属于重量级,耗时,一般只在初始化的时候创建一次PlugIn

2、         原则上一个Hibernate可以管理多个数据库,每个数据库都配置同一个SessionFactory(但是这样系统效率低,一般一个sessionFactory只管理一个数据库)

3、         sessionFactory是线程安全的(支持多线程并发访问操作)

 

Session:持久化管理器

作用:完成对数据库的操作 ORM(对象关系映射)

Session的功能:

1、         Session一级缓存,具有缓存功能(缓存后才能对对象进行持久化操作)

2、         保证所管理的对象中的属性和数据库中的记录同步(自动update)

3、         Session可以取得事务(对session中缓存的数据完成实际的存取)

4、         Session作为HQL查询à生成的Query对象,执行hql语句(session.createQuery())

Query query = session.createQuery(“from User”); 其中User是实体类。该语句将把数据库中t_user表中的全部查询出来放到query对象中。

5、         Session可以从连接池中拿到连接(关闭session,连接释放)

6、        Session的主要作用是对数据库进行操作

Session的性能:

1、         线程不安全(操作完数据库就关闭session)

        

Lifecycle,Validatable:可以对User请求做监听,不使用的原因是因为需要实现接口,太过于重量级了。

UserType(转换器),Interceptor(拦截器):都属于轻量级接口,对对象自动拦截,很少用,不需要实现Hibernate的任何接口和类

 

实际测试环境的搭建:

测试类和被测试类在同一个包下 src

测试类的路径和被测试类相同

 

使用Junit测试原则:

1、         测试类继承TestCase(Junit包下的一个类)

2、         测试类的类名 suffix=*Test(eg:UserTest)

3、         测试类的方法名:test开头,后面是正常的方法名(eg:testLogin())

4、         测试类的方法不能有参数和任何返回值

 

调试时在debug—> Junit

Log4j.propeties:是为了测试的时候打印日志信息。

 

持久化对象的生命周期:

对象的三种状态:瞬时、持久、离线

瞬时态(transient):new User(),刚new 出类的对象处于瞬时态,没有纳入session的管理,数据库中没有对象的记录,事务提交后这种对象无法提交到数据库(和数据库无关联)

持久态(presist):session. save(user),这时候user对象处于持久态,session管理,具有主标识,事务对持久态的对象做操作(和数据库同步)

离线态(Detached):session关闭后对象状态从持久态变为离线态。不纳入session管理中,对象中的数据在数据库中有对应的记录,对象发生变化,数据库中的值不会发生变化。(和数据库不同步)

 

Junit测试和Debug调试

测试类的代码和hibernate的代码在同一个包下

 

Session缓存的特点:

Session session=factory.openSession();//通过session工厂拿到session对象

Variable à 查看actionQueue: session队列中有三个临时集合(数组)

1、insertions:elementData session.save();存放要插入的对象

2、updates :elementData session.update();存放要修改的对象

3、deletions:elementData,存放要删除的对象

Session事务一提交,先到insertion数组中查看是否有对象,如果有对象,就发送有关插入数据的sql语句:insert  into()

再从deleteions数组中查看是否有对象,如果有对象就发送关于删除的sql语句:delete()

再从updates数组中查看是否有对象,如果有对象就发送关于更新的sql语句:update()

这三个集合都是临时集合,发送完数据就会清空。

 

PersistanceContext:持久化容器

存放真正缓存中的数据,实体化类对象一定要和数据库中记录的状态一致。

数据结构为entityEntries 是一个Map

里面有个Map集合,进入 entries 里面有个table ,真正纳入session的中的数据存在table里面。

 

局部事务:

1、         session.openTransaction();开启事务,事务一提交,就去找session的actionQueue中的数组,发现哪个数组中有对象就执行其对应的语句,

2、         User user=new User();创建user对象,user.setName(“zhangsan”);,这时候的user对象没有id,(如果主键的生成策略是uuid,手动设定的id是无效的)这时候还的user对象处于瞬时状态,没有纳入session管理

3、         session.save(user);执行这条语句,一般不会发sql语句,只是将user对象纳入session管理,语句执行完后:session中的临时数组:insertions数组中会有数据。User会分为两份存储:

一份是instance :实例

一份是 state:状态,session按照state状态发送数据(执行insert语句)

Insert之后,如果发现实例和状态的数据不一样,会马上执行update语句

4、         session.save(user)后,查看persistanceContext à entityEntries à map à table(table是HashMap的元素) value有值就继续进去, 查看以下几个状态值:

existsInDatabase的状态为false,表示数据库中没有数据

这时候id已经被赋值(uuid)

loaderSate:存储状态,数据都存在这里了(这里的数据必须和数据库中的数据同步)这就是session缓存的数据。

目前的user对象处于持久态

处于持久态对象的特点:一定有ID(不是所有持久态的数据会存放在临时数组中,但是持久态的数据一定会在map集合中)

5、         user.setName(“tom”);纳入session管理之后,变为持久态之后,修改持久态对象的值,

6、         session.commit();修改后的数据提交,

actionQueue下的insertions数组中:  instance实例中的值为:tom;

state状态的值为:zhangsan

persisstanceContext底下的map:数值还是zhangsan

会按照insertions数组的state(状态)的值发送insert into (zhagnsan)到数据库中

insert语句执行完后,发现instance  和state 中的值不一样时,就会再发一条update语句,保证一致。发完sql语句后,临时集合中的数据会全部清空。

7、         session.commit()状态:persistanceContext à entityEntries à map àtableà[0]àvalues

existsInDatabase的状态为true,表示数据库中已经有了相应的记录。这时候map中的name会改为tom(因为update了)

8、         session.close(),关闭session,把session中存放的实体对象全部清除,这时候user对象处于离线状态,user对象还有主键,而且这个主键和数据库中的主键一样。离线态的对象在数据库中有对应的记录

瞬时态:没有纳入session的管理,数据库中没有对应的记录

离线态:没有纳入session的管理,数据库中 有对应的记录。(只要主标识和数据库中的主键相同,主标识的class为asigned,就为离线态(new出来的对象也可以是离线态)),离线态的对象不能直接save()进入持久态。

9、        将离线态变为持久态:update(),saveOrUpdate(),lock(),重新开启session,拿到事务,session.update(user); <如果new 出来的对象,自己设定的id和数据库中主键相同,id标识为assigned,那么就只能用update方法,让该对象变为持久态>

10、      对象从离线态变为持久态,临时集合(insertions/updates/deletions)中 都 没有数据。persistanceContext--àMap集合有数据,但是map中只有一个id记录,数据库中也会有相应的数据。

11、      session.commit();就算没有修改user对象的属性值,hiberante也会发送update语句。Update之后,user也被纳入session管理

12、      session.commit()后,再进入persistanceContext à entityEntries à map à table à[0]àvalues

loadedSate会有相应的user实体对象中的属性的记录了

瞬时态—》持久态:插入(save)

离线态—》持久态:更新( update )

 

加载:(查询)将数据库中的记录加载到对象中

1、         拿到session(通过静态方法 自定义的getSession())

2、         User user=(User)Session.get(User.class,”这里写数据库中的主键值”);//把关系模型转换为对象模型。通过User类中找到对应的表。(这里是因为session通过sessionFactory创建,sessionFactory通过Configuration创建,Configuration可以读取整个hibernate.hbm.xml文件中的配置信息,所以session可以找到对应的user.hbm.xml文件)找到User.hbm.xml文件。Session通过hbm.xml元数据找到对应的表。通过主键找到表中对应的那条记录,自动生成对象,并且自动将数据库中记录的字段设定到user对象中对应的属性中。

这里发送了一条sql语句:select * from user where id=”上面的id参数值”;

这就叫做加载(load())。这时候的user对象处于持久态。(和数据库同步)

3、         user对象已经进入持久态,纳入session管理。这时候缓存域(三个集合 insertions/deletions/updates)中是没有数据的,persistanceContextà底下的map 的 value中会有数据。

4、         user.setName(“lisi”);//加载后,修改user对象中值。这时候session缓存的临时集合中没有数据,persistanceContext中的map中的数据也没有变化,user对象中的数据被修改了。 Session发现user对象中的值和map中的值不一样时,会自动发update语句,保证数据同步

5、        session.commit();//修改后提交数据。Update之后,数据会变为和对象中的值一样。Session缓存中的数据也会变成和user对象中的值一样。

持久态对象特点:一提交就发update.确定保证对象和数据库中数据同步。

处于持久态的对象在数据库中不一定有对应的记录。

session.commit()后:数据库中记录一定要和persistanaceContextàlocalState中的数据一致

使用get()方法加载数据的缺点:

1、如果从数据库中找到了指定ID对应的记录?

如果不需要使用User对象,会浪费内存

2、没有从数据库中找到ID对应的记录?

Hiberante就不会创建对应的User对象,会返回null。

再调用该对象的方法时,会出现NullPointerException

 

根据以上问题,修改方案:使用Load();

User user=(User)Session.load(User.class,”这里写数据库中的主键值”);//从这里引入懒加载。执行load方法后,不会生成sql语句,意味着还没有访问数据库。但是会生成Cglib 代理的User对象,这个User对象是目标User对象的一个子类对象。所以这里的User类不能定义为final类型。

Cglib代理的原理:创建目标类的子类对象。

这时候代理User对象的target 值为null;

user.getName();//调用代理对象的getName()方法,如果不为null,代理对象会调用父类的getName()方法。然后à发送sql语句à创建User对象。

 

本周作业:

理解hiberante:juint,debug,生命周期,缓存,

理解物料管理项目

 

Get方法查询数据:session.get(User.class,”对应的数据库中的表的ID”);

1、通过类找表

2、通过主键找记录

3、记录映射

不管数据存不存在都会发送select语句,如果找到了就会生成user对象,将对应的记录映射到对象的属性中,如果没有对应记录就返回null。(不支持懒加载)

使用get查询,会马上发select语句,select * from user where id=””;

如果后面不再修改User对象的值,就不会再发update语句了

如果修改user对象的值就会发送update语句。

这里主要比较user对象里面的值和loadedState里面的值,如果相同就不会发送update语句,如果不同就会发送update语句。

 

懒加载:(最大的问题就是会产生懒加载异常)

使用时再访问数据库,不使用数据的时候就不会访问数据库

懒加载有类上的懒加载,集合上的懒加载,单端上的懒加载

懒加载不支持多态查询

悲观锁不支持懒加载

 

Load方法查询数据:session.load(User.class,”对应的数据库中的表的ID”);

不会马上就发select语句,使用的时候才会发送select语句,如果不使用就不发送

1、         创建目标对象的cglib代理对象(目标对象的子类对象)

目标类User必须要有明确的无参构造方法,(为目标类创建代理对象,创建子类对象要先调用父类的无参构造方法),目标类不能是final类型

2、         对应的cglib代理对象下,有个target标识,标明目标对象是否生成,没有生成目标对象的时候 target=null;

3、         执行代理对象的user.getName()时,;先看目标对象产生了没有(判断target的值),如果产生了就执行目标对象的getName()方法,如果没有就从数据库中查出主键所标识的记录,根据记录生成目标User对象,再调用该User对象的getName()方法。(生成目标对象后,target=user(目标对象的地址))

Load()方法在数据库中找不到主键所标识的记录时,会产生异常 :ObjectNotFoundException,而不是NullPointerException。这个异常在load()的时候产生这个异常,但是使用目标对象的时候就会抛出这个异常。

Get()方法在数据库中找不到主键所标识的记录时,会返回null,再在使用目标对象时,会抛出NullPointerException.

 

Get和load:

共同点:通过ID(主键)去数据库中找相同的记录,生成对应的User 对象

不同点:

1、         Get:不支持懒加载,马上发送select语句

Load:支持懒加载,使用的时候才发送select语句

2、         如果使用get,发现主键在数据库中找不到对应的记录,不会生成user对象,也不会抛出异常,会返回null.(使用对象是抛出NullPointerException.)

如果使用load,发现主键在数据库中找不到对应的记录时, 使用时会抛出ObjectNotFoundException。

一般使用load,很少使用get.

 

删除:(删除的内容:数据库中的数据

先查 à 后删

User user=(User)session.load(User.class,”ID”);//对象处于持久态

session.delete(user);//在session的缓存persistanceContext中的map集合中有该对象,existInDatabase=true,loadedState有该对象;

临时集合中的deletions中有该对象,

User代理对象中也有数据。target=user;

 

session.getTransaction().commit();//这里发送delete语句(进入瞬时态)

临时集合deletions中数据被清空

Session里面的persistanceContextàmap中没有数据

数据库中的数据也没有

User对象依旧存在,target=user.

删除了User对象对应的数据库中的表的记录,删除后对象进入瞬时态。(不纳入session管理,数据库中的记录也没有了)

(课堂作业:完成hiberante的简单的增删改查)

 

查看表中的所有记录:(Query

Query query=session.createQuery(“from User”);//hql语句,from后面跟的是类名。通过session创建Query对象,实参为hql语句。从User类所映射的user表中拿到每一条记录,封装到Query对象中。

Query对象的作用是:执行hql语句。

List list=query.list();//Query中的list方法,把Query封装的每条记录放到List集合中。

 

Hibernate的基本映射

如果主键为String类型,一般配置为uuid<generator class=”uuid”/>

关联映射:

<hiberante-mapping  package=”com.hibernate”>//多个实体类在同一个包下,这里配置了包名,使用时直接写具体类名就可以了

<class name=”User” table=”t_user”>//根据类名为User的实体类生成表,table:指定数据库中的表名为t_user

<id name=”id” column=”user_id” length=”33”>//定义主键名在数据库的表中的字段名为 user_id,长度位33,(最好不要修改主键字段长度,使用默认长度255

<generator class=”uuid”/>//标识主键生成策略为uuid

</id>

 

/* Name:标识在User类的属性

Unique:标识该字段在数据库的表中必须唯一(不能重复)

Not-null:标识该字段在数据库的表中不能为null

为空验证:user.setName(“”);这里不会产生异常,空串不为空。

如果直接不给name赋值,String类型默认值为null,就会产生NulPointerException

Length:指定该字段的最大长度

Column:指定该属性映射在数据库中的表中的字段名为 t_name

*/

<property name=”name” unique=”true” not-null=”true” length=”232” column=”t_name”>

 

</class>

<hibernate-mapping>

 

主键生成策略:(作用/目的:一旦实体类纳入session的管理,session根据实体类的主键生成策略生成主标识,这个主标识就是数据库中的记录的主键

主标识:

业务主键和业务标识:(有意义的唯一标识)--学号

逻辑主键和逻辑标识:(没有意义的唯一标识),由数据库或者是Hibernate生成uuid

 

根据实体类的ID类型确定主键生成策略:

如果是字符串,hibernate生成 。uuid

如果是int/long,由数据库生成。native

用<id>标签来标识主键,后面必须要跟<generator class=” 主键生成策略

”>

 

1、由数据库生成:

native:根据不同的数据库自动使用不同的主键生成策略。(不是真正的生成策略,只是选择对应的生成策略)

identity:(mysql,sqlserver)

sequence:(DB2,Oracle)

2、由程序生成:

increment(递增):多台应用服务器,可能会主键相冲,很少使用

uuid.hex:Hibernate采用这种方式生成主键,多态服务器同时生成主键,不会重复;

1、         IP地址

2、         JVM的启动时间(1/4秒)

3、         系统时间

4、         随机数(和指令计数器有关)

以上四种数据进行随机组合,最后生成32 uuid 码

3、用户自己维护主键:

assigned:用户自己输入

4、外部引用

foreign:依赖于另一个实体类的主键。

 

主键生成策略为uuid: (主属性类型为String类型)

13、      session.openTransaction();开启事务,事务一提交,就去找session的actionQueue中的数组,发现哪个数组中有对象就执行其对应的语句,

14、      User user=new User();创建user对象,user.setName(“zhangsan”);,这时候的user对象没有id,(主键的生成策略是uuid,手动设定的id是无效的)这时候还的user对象处于瞬时状态,没有纳入session管理

15、      session.save(user);执行这条语句,一般不会发sql语句,只是将user对象纳入session管理,语句执行完后:session中的临时数组:insertions数组中会有数据。User会分为两份存储:

一份是instance :实例

一份是 state:状态,session按照state状态发送数据(执行insert语句)

Insert之后,如果发现实例和状态的数据不一样,会马上执行update语句

16、      session.save(user)后,查看persistanceContext à entityEntries à map à table(table是HashMap的元素) value有值就继续进去, 查看以下几个状态值:

existsInDatabase的状态为false,表示数据库中没有数据

这时候id已经被赋值(uuid)

loadedSate:存储状态,数据都存在这里了(这里的数据必须和数据库中的数据同步)这就是session缓存的数据。

目前的user对象处于持久态

处于持久态对象的特点:一定有ID(不是所有持久态的数据会存放在临时数组中,但是持久态的数据一定会在map集合中)

17、      user.setName(“tom”);纳入session管理之后,变为持久态之后,修改持久态对象的值,

18、      session.commit();修改后的数据提交,

actionQueue下的insertions数组中:  instance实例中的值为:tom;

state状态的值为:zhangsan

persisstanceContext底下的map:数值还是zhangsan

会按照insertions数组的state(状态)的值发送insert into (zhagnsan)到数据库中

insert语句执行完后,发现instance  和state 中的值不一样时,就会再发一条update语句,保证一致。发完sql语句后,临时集合中的数据会全部清空。

session.commit()状态:persistanceContext à entityEntries à map àtableà[0]àvalues

existsInDatabase的状态为true,表示数据库中已经有了相应的记录。这时候map中的name会改为tom(因为update了)

19、      session.close(),关闭session,把session中存放的实体对象全部清除,这时候user对象处于离线状态,user对象还有主键,而且这个主键和数据库中的主键一样。离线态的对象在数据库中有对应的记录

瞬时态:没有纳入session的管理,数据库中没有对应的记录

离线态:没有纳入session的管理,数据库中 有对应的记录。(只要主标识和数据库中的主键相同,主标识的class为asigned,就为离线态(new出来的对象也可以是离线态)),离线态的对象不能直接save()进入持久态。

 

主键生成策略为native: (主属性类型为 int类型)

先看使用的数据库,然后根据数据库选择主键生成方式,如果是mysql数据库:identityà auto_increment

数据库自动生成主键:首先是主属性为int类型,大部分都是从1开始(基本上没有从0开始的数据)。

session.save(user);//就发送sql语句了,insert语句中不会插入ID,ID由数据库自动生成,人为设定的sql语句无效。发送sql语句:insert into(),目的是生成主键

一旦发送sql语句了,数据库中一定会有对应的记录。

主键生成策略为native,save后的特点:(进入持久态)

1、         session的临时集合中没有数据,(临时集合是为了发sql语句而存在的)

2、         session的缓存persistanceContext下的map下的existInDatabase=true;表示数据库中有数据,但是是脏数据。(查不到)

脏数据:未经证明和检测的数据

事务提交特点:先插入数据,再对数据进行验证,如果正确就提交到数据库中,如果有误就回滚。没有提交的数据叫做脏数据,mysql事务的传播特性是可提交读,看到的数据是提交后的数据,看不到脏数据,所以提交前在数据库的表中查不到数据。

session.getTransaction().commit();//commit()后才会检查数据的正确性。(对数据库中的脏数据进行确认,如果正确就将脏数据变为可用数据《在数据库中可以查到》,如果不正确就将脏数据回滚。)

主键生成策略为assigned(用户提供)

主键标识 为String类型

1、         不设置对象的ID值,直接save(),会从user对象中共取ID,发现没有会抛出异常

2、         2.1、设置ID值

2.2、   session.save(user);从瞬时态进入持久态:不发送sql语句,user对象纳入session缓存,存放在session的临时集合中的insertions中,session的缓存persistanceContext下的map的loadedState下也会有数据。数据库中没有数据,existInDatabase=false;

2.3、   session.getTransaction().commit();临时集合中的数据清空,数据库中有数据。existInDatabase=true;

 

通过配置让数据库自动生成表:

<property  name=” hiberante.hbm2ddl.auto”>update</property>

 

 

作业:分析三种主键生成策略在session缓存中的异同:

主键生成策略为native和uuid,assigned的异同:

native根据数据库不同而自动选择对应数据库的主键生成策略。比如:mysql数据库à主键生成策略为:identity.主属性类型为int类型。(用户手动设定的ID无效)

uuid将IP地址,JVM的启动时间,系统时间以及和指令相关的随机数相结合随机生成32位uuid码。主属性类型为String类型。(用户手动设定的ID无效)

assigned用户在创建对象的时候,手动设定主键(ID的值)。主属性类型为String类型,如果不设定ID 就会抛出异常。

session.getTransaction();//开启事务

 

User user=new User();//创建User对象

user.setID(“123”);

user.setName(“zhangSan”);

这时候三种主键生成方式(native,uuid,assigned)都相同: user对象处于瞬时态,没有纳入session管理(临时集合和session缓存map中都没有user对象的相关数据)

 

session.save(user);

相同点:将user对象纳入session管理,进入持久态。

native:(发送insert 语句)。临时集合中没有数据,session缓存的map下的table中有数据,loadedState中有user对象值,existInDatabase=true,user对象依旧存在,user对象的ID值变为数据库中自增的ID值。

uuid:临时集合insertions中的instance和state中都有数据,ID为hibernate自动生成的32位码,session缓存的map下的table中有数据,loadedState中有user对象值,existInDatabase=false,user对象依旧存在(ID为32位码,其余属性值不变)

assigned;临时集合insertions中的instance和state中都有数据,ID为用户手动设定的值,session缓存的map下的table中有数据,loadedState中有user对象值,existInDatabase=false,user对象依旧存在(值和设定的一样)。

 

user.setName(“Tom”);//修改进入持久态后的数据

相同点:user对象中的name属性值变为Tom,session 缓存的map下的table中的值不变。

native:临时集合中依旧没有数据。

uuid: 临时集合insertions中的instance 指定的name属性改变为 Tom, state指定的name属性依旧为zhangSan,

assigned: 临时集合insertions中的instance 指定的name属性改变为 Tom, state指定的name属性依旧为zhangSan。

 

session.getTransaction().commit();//提交事务

相同点:session缓存的map下的table中有数据,loadedState中user对象的name属性值为Tom,existInDatabase=true,

native:(发送update语句), user对象依然存在。

uuid: (发送两条sql语句,先insert,后update)临时集合insertions的数据清空, user对象依然存在。

assigned: (发送两条sql语句,先insert,后update)临时集合insertions的数据清空, user对象依然存在。

 

session.close();//关闭事务

相同点:session缓存map下的table中的数据被清空, user对象依然存在。user对象处于瞬时态。

native:临时集合中一直没有数据,

uuid:临时集合中数据被清空,

assigned:临时集合中数据被清空

 

 

Many-to-one

关联属性:多对一:(Many-to-one

关联属性的特点:

如果属性的类型时自定义类型,那么这个属性就是关联属性

如果属性的类型时集合或者数组,那么这个属性有可能是关联属性

对象模型通过关联属性建立关联关系

关系模型的关联关系通过外键建立关系

谁往外键里存放数据,谁就在维护关系。

 

核心技术:配置*.hbm.xml

 

关联属性的映射:(外键)

<many-to-one name=”group”  column=”groupid” cascade=””/>

column=”groupid”:这个值的设定参照group属性对应的Group对象的id属性。

这是怎么知道引用的表是group表的主键?

因为User实体类中,有Group类型的group属性。根据关联属性找到引用的外键表。

 

存储数据:(主键生成策略都为native)

Group group=new Group();//先创建Group对象,

group.setName(“zte”);// 给该对象的属性赋值,(id自动分配)

session.save(group);//发送insert into 语句,取得id

 

将Group对象的地址赋值给User对象的group属性中。

User user=new User();//再创建User对象,

user.setName(“zs”);// 给该对象的属性赋值,(id自动分配)

user.setGroup(group);//通过user拿到group属性所指的Group对象,取出该Group对象的id值,将取到的id设定到groupid字段中。(groupid是 hbm.xml文件中 many-to-one 标签中的name指定的字段名)

session.save(user);//数据库发送sql语句,insert into ,生成id。将user对象属性的属性值设定到数据库中的user表中的对应的记录的各个字段中。

 

cascade(级联属性): all,update-save,delete,none

 

加载:(将关系模型转换为对象模型)

User user=(User)session.load(User.class,1);

将从数据库中查出来的记录,将字段依次赋值给User对象的对应属性。

Many-to-one:通过外键(groupid)找到对应的 Group对象对应的group表中的记录,根据记录再创建Group对象赋值,将Group对象设定到User对象的group属性中。(通过外键找主键)

 

user.getName();//zs,这叫类上的懒加载,只发送一条sql语句,只查询user表

user.getGroup().getName();//这就叫单端上的懒加载,用到两张表的时候查询两张表

单端上的懒加载在外键设定的标签上,<set><many-to-one>

外键可以为空,如果不为空,一定是所关联表的有效值(一般为关联表的主键)

TransientObjectException:持久态对象引用了瞬时态对象

如果不给Group对象的id属性设值,那么默认值为0 ,

不save Group对象。Group对象为瞬时态。id依旧为默认值0

创建User对象,给user对象的各个属性赋值。

session.save(user);//发送sql语句,insert into

将Group对象的id属性值(0)设置到User对象中的group属性所指Group对象的id ,对应数据库中的字段为groupid上。

Mysql数据库的主键不能为0 ,所以这就违反了参照完整性。

session.getTransaction().commit();

提交,就检查数据,发现错误就回滚。(commit以前的数据都是脏数据

这里的user对象处于持久态,user对象使用了瞬时态数据 group对象,瞬时态的对象的id是无效值。所以会出现TransientObjectExecption.

主要原因:持久态对象引用了瞬时态对象,就会出现TransientObjectException

结论:持久态对象不能引用瞬时态对象。

解决办法:

  1. 1.   创建group对象的时候就session.save(group);

2、添加级联属性。

<many-to-one name=”group” column=”groupid” cascade=”all”>//持久态对象引用瞬时态对象,加了cascade时,会自动先把瞬时态对象save进入持久态。在执行session.save(user)这条语句时,会先执行 insert into group(),再执行insert into user();

 

cascade:(级联属性)

cascade的取值:all、none、save-update,delete

cascade的作用:存储,而不是为加载服务的。

级联:两个对象中的操作联动性,对一个对象操作后,对其指定的级联对象也需要执行相同的操作。(对象的连锁操作)

all:所有情况下执行级联操作

none:任何情况下都不执行级联操作

delete:在删除的时候执行级联操作(容易出错)

save-update:在保存和更新的时候执行级联操作

 

Hql语句对所有的数据库都一样:

Query query=session.createQuery(“from User user where user.group.name=’zte’ ”);//从数据库中找到User类对应的t_user表,从user表中找到user对象的group属性所指定的Group对象对应的t_group表,找出t_group表中的name=‘zte’的那条记录。

相当于sql语句;

select * from t_user ,t_group

where t_user.groupid=t_group.id

and  t_group.name=’zte’;

 

One-to-one:(一对一)

一对一单向关联:

对象模型:

Person: (类)

int id;

String name;

IdCard idCard;//关联属性

 

IdCard:(类)

int id;

String cardNo;

 

关系模型:

Person表和idCard表的主键相同,一 一对应

(两个表的主键相同并不意味着两个表有关系)

 

三大问题:(在*.hbm.xml文件中体现)

1、如何建立关系

2、如何存储关系

3、如何加载关系

 

建立关系:(在数据库中建表时)

t_person:(表)

具有关联属性的那个对象生成对应的表时,需要设定主键生成策略为 foreign:t_person表中这样设定,使得对象一 一对应。

<id name=”id”>

<generator class=”foreign”>

<param name=”property”>idCard</param>//引用的是idCard属性的所指的IdCard对象的主标识 (id属性值)

</generator>

 </id>

一旦纳入session的管理,person对象的id需要和IdCard的id相同。(通过session保证一对一关系:执行save语句的时候将IdCard对象的id赋值给person对象的id)

必须加约束: <one-to-one name=”idCard” constrained=”true”/> 创建t_person表的时候不会创建idCard字段,这里表示引用的外键是当前t_person表的主键,外键参照idCard属性对应的IdCard对象所映射的t_idCard表。

1、        建立关系:表示当前person类所映射的表的主键就是外键,

2、        存储关系:外键参照idCard属性对应的类所映射的表生成

3、        加载关系:one-to –one ,通过主找主,为当前映射属性idCard赋值

如果不添加 constrained=true,在创建表的时候会缺少 foreign key(id) references t_idCard (id)语句,就不会有外键。这样t_person表和t_idCard表就没有任何关系了。

 

t_idCard:(表)

和普通表一样,没有外键,主键为id.<id name=id>

<generator class=”native” /></id>

 

存储关系:(在生成对象时建立存储关系)

1、         先创建IdCard对象,

IdCard idCard=new IdCard();

idCard.setCardNo(“123”);

session.save(idCard);//发送sql语句,生成id主标识,纳入session管理

2、         创建Person对象

Person person =new Person();

person.setName(“tom”);

person.setIdCard(idCard);

session.save(person);//这里不发送sql语句,(因为不需要数据库为之生成id)直接从Person对象的idCard属性所指的IdCard对象中拿到主标识(IdCard对象中的id属性值)设为Person对象的主标识(id属性)。从这里建立了对象和对象之间的一对一关系。临时集合中有数据(insertions),数据库中没有数据。

commit()就把Person的id属性映射到t_person的id字段上。

主标识:为对象中的id

主键:为数据库表中的id

 

3、        t_person表在数据库中存储Person对象的idCard(关联)属性的时候:把关联属性的值存在数据库的表的外键上。 往对应的外键设值,通过映射发现,存储idCard属性的时候,外键就是主键 id。往主键id上设定值,t_person表的主键id值来自于idCard属性所指的IdCard对象映射的t_idCard表的主键(id).

 

One-to-one:主键关联,自动级联。cascade=”all”

假若在session.save(person);语句执行之前,不执行session.save(idCard)语句,

IdCard对象还没有纳入session管理,也没有为 IdCard对象分配主标识,IdCard对象处于瞬时态。

1、         执行session.save(person)时,发现IdCard没有主标识,Person对象无法拿到主标识。

2、         自动先发送IdCard的sql语句,insert into t_idCard (),先为IdCard对象分配主标识(IdCard对象纳入session管理,进入持久态,IdCard在数据库中有数据)

3、         将IdCard的主标识赋给Person对象的主标识,Person对象纳入session管理,进入持久态。Person在数据库中没有数据,因为person还没有发送sql语句,所以Person对象在临时集合insertions中有数据。

 

加载关系: one-to-one(主找主)

Session.load(Person.class,1);

通过t_person表的主键找到所关联的t_idCard表的主键对应的记录,创建IdCard对象,并将该IdCard对象赋值给Person对象的idCard属性。其余属性直接赋值。

 

Session不允许同一个类的不同对象使用相同的ID

如果两个不同的对象引用同一个对象的id,会抛出异常。

例如:

IdCard idCard=new IdCard();//创建Idcard对象

idCard.setCardNo(“123”);

session.save(idCard);//发送sql语句,生成id主标识,纳入session管理

 

Person person =new Person();//创建第一个Person对象

person.setName(“tom”);

person.setIdCard(idCard);

      session.save(person);//不会发送sql语句,将IdCard对象的主标识id给该Person对象

 

Person person1 =new Person();//创建第二个Person对象

person.setName(“lisa”);

person.setIdCard(idCard);//引用同一个IdCard对象

      session.save(person1);// //不会发送sql语句,将IdCard对象的主标识id给该Person对象,所以在这里会抛出异常,因为one-to-one不同对象不能引用同一个对象的id.

 

一对一主键双向关联: 两张表中的数据可以相互查询

IdCard.hbm.xml中:主键生成策略为native

这里的person在实体类中为Person类型。

<one-to-one name=”person”>:单独使用不会在创建表的时候不会指定外键,也不能往外键中设值,只会加载关系,给Person属性赋值。

person 不是关联属性,

 

Person.hbm.xml中:主键生成策略为foreign

这里的idCard在实体类中为IdCard类型。

<one-to-one name=” idCard” constrained=”true”>:指定外键就是主键,外键的生成参照idCard属性对应的IdCard类映射的idCard表。还可以加载。

idCard为关联属性,

 

建立关系:两张表的关系依靠Person类中的idCard属性建立关系。

维护关系:存储Person的idCard属性时,外键就是t_person的主键,维护关系。

加载关系:

session.load(IdCard.class,1);

IdCard类中的person属性通过one-to-one(主找主),到t_person表中找到对应的记录,创建Person对象给IdCard类的person 属性赋值。

 

不是所有的类中的自定义类型都是关联属性,当前IdCard类中的person属性就不是关联属性,

 

 

一对一唯一外键单向关联:(外键 unique=”true”

让外键唯一,两条记录便是一 一对应。

Person的配置:使用 many-to-one.配置

<generator class=”native”/>

<many-to-one name=”idcard” unique=”true”>//在映射关联属性创建关系的时候会在表中创建字段idCard,关系通过many-to-one :创建外键,外键参照idCard属性的类所映射的t_idcard表。 设定unique=”true”,使得外键唯一。

<many-to-one name=”idcard”>:一定要先save所引用的对象,它不会自动级联,在数据库的表中会生成idcard字段。

<one-to-one name=”idcard”>指定主键就是外键,可以自动级联,在数据库的表中不会生成idcard字段。

 

建立关系:

<one-to-one name=”idcard”>相当于:<many-to-one unique=”true” name=”idcard” cascade=”all”> 由many-to-one建立关系,指定idcard为外键。

 

加载关系:

Many-to-one:找到idCard在t_idcard表中对应的记录,生成IdCard对象,设定到Person对象中的idcard属性上。

 

存储关系:

Person存储关系:存储Person的idcard属性值,往数据库的idcard字段设值是存储,因为idcard字段是外键。(如果往不同的对象的idcard属性中存同一个的idcard对象,由于设定了unique=”true”,就会抛出异常,外键重复。)

在这时候 持久态对象引用瞬时态对象,由于瞬时态对象的默认id值为0,不影响数据库的存储,在save()的时候不会产生异常,commit()的时候数据库就会检测0为非法数据就会抛出异常。

 

一对一唯一外键的双向关联:

Person表的映射不变:

int id;

IdCard idcard;//关联属性,建立关系,维护关系,加载关系(外键

 

<id name=”id”><generator class=”native”/><id>

<many-to-one name=”idcard” unique=”true”>

 

 

IdCard表的映射:

int id;

String cardNo;

Person person;//不建立关系,不维护关系,只加载关系(非外键

 

<id name=”id”><generator class=”native”/><id>

<property name=”cardNo”/>

<one-to-one name=”person” property-ref=”idcard”/ >

property-ref=”idcard”:将位置指针挪到t_person表中的idcard属性上。

property-ref表示引用person属性对应的Person对象所映射的t_person表中的idcard字段。

 

加载关系:

给IdCard的person属性设值,one-to-one

主找主找不到对应的数据,property-ref=”idcard”,位置指针挪到idcard字段上,找到idcard字段对应的那条记录,生成Person对象,将该person对象赋值到person属性中,

 

MyEclipse快捷键:

Ctrl+shift+T:查找类

Ctrl+F:查找类中的具体方法

 

 

one-to-many

one-to-many 单向关联:

一对多:由一的一方来建立关系,存储关系,使用关系(加载)。

外键一定在多的一方。

一怎么管理多:建立集合,管理多方(Set集合)

为什么不是List集合和map集合,因为Hibernate为Set集合重写了懒加载。

一个班级多个学生

Classes 类:(一方)外键关系创建方、维护方、使用方

int id;

String name;

Set students;

 

Student类:(多方)外键关系存放方

int id;

String name;

 

Set 集合可以存放任意类型的数据。(关联属性)

 

建立关系:

t_classes表在映射关联属性的时候,会创建外键,并且把外键设定在t_student表中,所以t_student表中有classesid字段。但是这个外键的关系由Classes维护。

 

Student.hbm.xml

<id name=”id”><generator class=”native”/></id>

<property name=”name”/>

 

Classes.hbm.xml

<id name=”id”><generator class=”native”/></id>

<property name=”name”/>

<set name=”students”>

       <key column=”classesid”/>

       <one-to-many class=”cn.hiberante.bean.Student”/>

</set>

1、         用set标签映射关联属性,当前Classes对象中的students属性

2、         创建Classes对象的时候在Set集合中存放 Student类型的数据

3、         创建外键字段 key column=”classesid”,加在<one-to-many>指定的class àStudent类所映射的t_student表中

4、         增加的字段classesid为外键,通过 one-t-many:外键与当前表建立关联,外键参照t_classes表

 

通过上面的映射:

t_student表中有三个字段, id,name,classesid;

t_classses表中有两个字段:id,name

 

 

维护关系:(关系存储,往外键里面设值) native

1、创建多个学生对象(两个或者两个以上)

Student stu=new Student();

stu.setName(“tom”);

session.save(stu);//id 生成,name为tom,外键classesid为空,进入持久态,纳入session管理(外键可以为空) insert into t_student(name) values(?);

 

Student stu1=new Student();

stu.setName(“Jam”);

session.save(stu1);//id 生成,name为Jam,外键classesid为空,进入持久态,纳入session管理(外键可以为空)insert into t_student(name) values(?);

 

 

2、创建Set集合,将Student对象添加到集合中

Set students =new HashSet();

students.add(stu);

student.add(stu1);

3、创建Classes对象

Classes classes=new Classes();

classes.setName(“java1”);

classes.setStudents(students);//将创建的Set集合students设到Set集合类型属性students

session.save(classes);//发送sql语句,id自动生成,把Classes的name属性存在t_classes表中的name字段上。insert into t_classes(name) values(?);

关联属性的存储:(外键设值)

1、         从students这个Set集合中拿到第一个元素stu,取得stu对象

2、         拿到stu对象主标识,(因为stu已经save过了,所以能取到该对象的主标识)

3、        通过stu对象的主标识找到数据库中t_student表中对应的记录

4、         存储的时候拿到Classes对象的主标识id,(因为classes对象已经save)

5、        将Classes对象的主标识(id值)设定到stu对象对应的t_student表中的对应记录的classesid字段上。

第二个元素stu1和第一个一样;

 

session.beaginTransaction().commit();//提交后会发送update语句,有多少个学生对象会发送多少条update语句,update t_student set classesid=?where id=?;

 

加载关系:(将数据库中查出来的记录给对象赋值)

session.load(Classes.class,1);

classes.getName();//直接取出记录中的name字段的值设置到name属性中

classes.getStudents();

one-to-many,主找外:(一个对应多个)

1、         通过主键到对应的t_student表中找到外键,找到对应的第一条记录

2、         创建Student对象,

3、         创建Set集合(有记录时才会创建)

4、         将Student对象add到Set集合中

5、         然后继续通过主找外,依次找到所有的Student对象都分别add到Set集合中

6、         最后把Set集合设置到Classes对象的students属性上。(students属性为Set类型)

One-to-many单向映射的缺点:

1、         如果t_student表中的classesid字段设置为非空,就无法保存数据

2、         因为不在Student这一端维护关系,所有Student不知道是哪个班的

3、         需要发出多余的update语句(数据库负载大)

 

one-to-many 双向关联:

 

建立关系:

Student

int id;

String name;

Classes classes;

 

Student.hbm.xml文件:

<id class=”id”><generator class=”native”/></id>

<property name=”name”/>

<many-to-one name=”classes” column=”classesid”>

让外键重名,保证关系维护的一致性。

 

Classes 类:

 int id;

String name;

Set students;

 

Classes.hbm.xml文件:

<id class=”id”><generator class=”native”/></id>

<property name=”name”/>

<set name=”students” inverse=”true” cascade=”all”>

<key column=”classesid”/>

<one-to-many class=”cn.hiberante.bean.Student”/>

</set>

 

Key指定的字段名必须和<many-to-one name=”classes”  column=”classesid”>指定的一样

外键的关系由一端维护,两头使用关系。

双向关联时,由多的一方存储关系,维护关系。

<set name=”students”  inverse=”true” cascade=”all”>

<key column=”classesid”/>

<one-to-many class=”cn.hiberante.bean.Student”/>

</set>

让多的一方(Student)维护关系。添加inverse=”true”

 

维护关系:(关系存储,往外键里面设值)

1、创建多个学生对象(两个或者两个以上)

Student stu=new Student();

stu.setName(“tom”);

session.save(stu);//id 生成,name为tom,外键classesid为空,进入持久态,纳入session管理(外键可以为空)Student可以维护关系但是不维护关系。Classes不设值就为null. insert into t_student(name,classesid) values(?,?);

 

Student stu1=new Student();

stu.setName(“Jam”);

session.save(stu1);//id 生成,name为Jam,外键classesid为空,进入持久态,纳入session管理(外键可以为空)Student可以维护关系但是不维护关系。Classes不设值就为null. insert into t_student(name,classesid) values(?,?);

 

2、创建Set集合,将Student对象添加到集合中

Set students =new HashSet();

students.add(stu);

student.add(stu1);

3、创建Classes对象

Classes classes=new Classes();

classes.setName(“java1”);

classes.setStudents(students);//将创建的Set集合students设到Set集合类型属性students

session.save(classes);//发送sql语句,id自动生成,把Classes的name属性存在t_classes表中的name字段上。insert into t_classes(name) values(?);

这时候Classes想维护关系,但是看到inverse=”true”,就无法维护关系,Student可以维护关系但是不维护。创建的表 t_student 中的 classesid列为null.

Inverse的作用:(值为true/false

1、         指定谁反转另一方来维护关系

2、         主要使用在set集合标签上

3、         主要用于存储关系

 

正确流程:

先创建classes对象,只给name属性设值;(id,set集合都不设值)

再创建Student对象,每个值都设值(id,name,classes)

再创建Set集合将Student对象add到set集合中

session.save(classes);//insert into t_classses(name) values(?);

 

inverse=true:

设定由另一端往外键中设值。(对方来维护关系)

用在Set标签上

用在存储上(加载用不着inverse)

通常inverse后面会加 cascade 级联属性

cascade=all

session.save(classes)当Classes对象准备维护关系的时候,inverse属性为true,让Student属性维护关系.所以就自动发送session.save(stu);就创建Student对象的 id主标识,往外键设值(Student对象的classid属性)à拿到classes所指的Classes对象的主标识设到classesid上。

优点:没有发送多余的sql语句(update

 

Inverse和cascade

1、         都是进行存储的时候使用

2、         inverse à翻转,指定由对方来存储关系

3、         cascade在存储数据以前,如果需要存储另一种数据,就会自动的发送save语句,先存储另一种数据再save当前对象。

4、         inverse:是关联关系的控制方向,用在set标签上

5、         cascade操作上的连锁反应,用在 one-to-one,many-to-one,one-to-many标签上

 

 

周末任务:

1、         整理出Hibernate案例,把笔记分析都添加到对应的案例上

2、         Many-to-one *

3、         One-to-one *

4、         One-to-many *

5、         Many-to-many

6、         单继承映射关系

7、         看Oracle视频,学会Oracle数据库的基本操作 建表以及增删改查

8、         复习一下struts1.0,尽量完善自己的struts框架

9、         找到Hibernate源码,看看源码,增强自己对映射关系的理解

 

Many-to-many:(多对多)

多对多---单向关联:

示例:一个人可以有多个角色,一个角色可以由多个人担当

从关系模型中体现多对多关系:创建中间表

中间表的特点:

至少有两个外键,外键分别参照两张表的主键

这两个外键共同生成主键à联合主键

两个外键的值都由一个对象的关联属性设定,关联属性为Set集合类型。

 

建立关系:

Role:(类)

int id;

String  name;

Role.hbm.xml

<id name=”id”>

<generator class=”native”/></id>

<property name=”name”>

 

User (类)

int id;

String  name;

Set roles

 

Roles属性映射指定:

1、         Set里面放什么类型的数据

2、         创建第三张表 t_user_role

3、         往第三张表中添加两个字段(两个外键分别参照两张表)

 

User.hbm.xml

<id name=”id”>

<generator class=”native”/></id>

<property name=”name”>

<set name=”roles” table=”t_user_role”>

<key column=”userid”/>

<many-to-many class=”cn.hibernate.Role” column=”roleid”>

</set>

1、         指定roles的Set集合中存放cn.hibernate.Role类型的数据

2、         Many-to-many, 需要创建中间表,映射时roles属性时,创建t_user_role表

3、        <key column=”userid”/>指定外键字段userid,把这个字段添加到t_user_role表中

4、         userid这个外键字段 参照当前对象所对应的表t_user表

5、         在many-to-many 后面的column=”roleid” 指定外键字段 roleid, 把这个字段添加到t_user_role表中

6、         roleid这个外键字段参照class指定的Role所映射的t_role表

7、         userid和roleid为 t_user_role表的联合主键,分别参照t_user和t_role表

 

存储关系:

先创建Role对象:

Role role1=new Role();

role1.setName(“zs”);

session.save(role1);//insert   into t_tole(name) values (?);

Role role2=new Role();

role2.setName(“ls”);

session.save(role2); //insert   into t_tole(name) values (?);

 

再创建User对象

User  user=new User();

user.setName(“wife”);

 

创建Set集合,将Role对象添加到set集合中

Set set=new HashSet();

set.add(role1);

set.add(role2);

 

user.setRoles(set);//把存储Role对象的Set集合设定到roles属性中

session.save(user);//发送三条sql语句,在t_user_role表中创建两条记录

//insert   into t_user (name) values (?);

//insert   into t_user_role(userid,roleid) values (?,?);

//insert   into t_user_role(userid,roleid) values (?,?);

 

在当前对象Set集合中有几个元素,就会在t_user_role表中创建几条记录

 

id自动生成,name字段直接映射

存储User对象的关联属性roles:

从roles的Set集合中拿出第一个元素role1,拿到role1对象的主标识,

将role1对象的(主标识)id,设到t_user_role的roleid字段上

拿到当前User对象的id, 设到t_user_role的userid字段上

 

从roles的Set集合中拿出第二个元素role2,拿到role2对象的主标识,

将role2对象的id主标识,设到t_user_role的roleid字段上

拿到当前User对象的id, 设到t_user_role的userid字段上

 

加载关系:

关联属性的加载,给关联属性roles赋值:

1、         many-to-many:分成一对多(one-to-many),和多对一(many-to-one);

2、         one-to-many(主找外),通过t_user表中 id 找找t_user_role表中对应的userid

3、         通过userid自动找到 roleid字段

4、         many-to-one(外找主),通过roleid字段找到t_tole表中的对应的id指定的行生成记录

5、         根据记录生成Role对象,设到Set集合中

6、         继续重复2-5(直到找完t_user表中指定id对应的t_user_role表中的所有记录)

7、         将赋完值后的Set集合设定到roles属性中

 

 

多对多---双向关联:

 

建立关系:

Role:(类)

int id;

String  name;

Set users

Role.hbm.xml

<id name=”id”>

<generator class=”native”/></id>

<property name=”name”>

<set name=”users” table=”t_user_role”>

<key column=”roleid”/>

<many-to-many class=”cn.hibernate.User” column=”userid”>

</set>

 

User (类)

int id;

String  name;

Set roles

 

Roles属性映射指定:

4、         Set里面放什么类型的数据

5、         创建第三张表 t_user_role

6、         往第三张表中添加两个字段(两个外键分别参照两张表)

 

User.hbm.xml

<id name=”id”>

<generator class=”native”/></id>

<property name=”name”>

<set name=”roles” table=”t_user_role”>

<key column=”userid”/>

<many-to-many class=”cn.hibernate.Role” column=”roleid”>

</set>

 

<set name=”” table=”t_user_role”>//这里table指定的表必须一样

两张表都可以维护关系,

User维护关系,set集合中存放Role类型的数据,

key column=”userid”,参照 t_user表

<many-to-many class=”cn.hibernate.Role” column=”roleid”>

roleid参照 class指定的类Role对应的t_role表

 

Role维护关系,set集合中存放User类型的数据,

key column=”roleid”,参照 t_role表

<many-to-many class=”cn.hibernate.User” column=”userid”>

user参照 class指定的类User对应的t_user表

 

创建关系可以双方创建,

维护关系只能一方维护

使用关系可以双方使用

 

存储关系:

存储的时候,只能由一方维护,只让一方给关联属性设值,设值的一方就是在维护关系。

 

加载关系:

和单向关联类似

关联属性的加载,给关联属性roles赋值:

1、         many-to-many:分成一对多(one-to-many),和多对一(many-to-one);

2、 one-to-many(主找外),通过t_user表中 id 找找t_user_role表中对应的userid

3、         通过userid自动找到 roleid字段

4、         many-to-one(外找主),通过roleid字段找到t_tole表中的对应的id指定的行生成记录

5、         根据记录生成Role对象,设到Set集合中

6、         继续重复2-5(直到找完t_user表中指定id对应的t_user_role表中的所有记录)

7、         将赋值后的Set集合设定到roles属性中

 

 

单表继承映射

建立关系:

一个父类,两个子类

一个hbm.xml文件

Hibernate默认 lazy=”true”

Extends.hbm.xml文件的配置:

<!— Animal为父类,具有id,name,sex三个基本属性-->

<class name=”Animal” table=”t_animal” lazy=”true”>

<id name=”id”><generator class=”native”/></id>

 

<!--注意元素的顺序:discriminator要在property的上面-->

<!—discriminator :为鉴别器,指定type为鉴别器字段(not-null=true)。string是Hibernate定义的String类型。在t_animal中增加type字段。类型为string,在数据库中会转换为varchar类型。自动把 discriminator-value的值自动设定到该字段上。

-->

<discriminator column=”type”  type=”string”/>

<property name=”name”/>

<property name=”sex”/>

 

<!—Pig继承了Animal,为子类,Pig子类有新增的weight属性

discriminator-value:指定鉴别值,设到鉴别器字段中

-->

<subclass name=”Pig” discriminator-value=”P”>

<property name=”weight”/>

</subclass>

 

<!—Bird继承了Animal,为子类,Bird子类有新增的height属性

discriminator-value:指定鉴别值,设到鉴别器字段中

-->

<subclass name=”Bird” discriminator-value=”B”>

<property name=”height”/>

</subclass>

</class>

 

Discriminator(鉴别器)的作用:加载时,自动识别记录生成对应的对象类型。

 

存储关系:(识别子类类型,自动往鉴别器字段加鉴别值)

Pig pig=new Pig();

pig.setName(“xiaohua”);

pig.setSex(true);

pig.setWeight(100);

session.save(pig);//id自动生成

1、通过Pig这个类,找到映射文件中的子类subclass name=”Pig”

2、通过subclass子类找到父类class=”Animal”,

3、根据class=”Animal”找到对应的表 t_animal,

4、往Pig对象设定到t_animal表中id指定的记录上的的各个字段。没有height属性的就设为null,

5、type字段的设值:看到是subclass,就往type鉴别器字段自动设 discriminator-value指定的值。(这个值Hibernate根据配置信息自动设定)

 

 

加载关系:

 

加载子类对象:

session.load(Pig.class,1);// Sql:select * form  t_animal where id=1 and type=’P’.

通过*.hbm.xml文件中找,通过Pig找到t_animal表

到表中找到id=1对应的记录

在查找的时候会自动添加查找条件 type=’P’.

生成的记录对应的对象为Pig类型

 

加载父类对象:

Animal animal =(Animal)session.get(Animal.class,1);

if(animal instanceof Pig) ==true

到t_animal表中查出id=1的记录,拿到所有字段,

发现type=”P”,知道应该创建Pig类型的对象。

 

load方法查询不支持多态查询,get方法查询支持多态查询。

Load支持懒加载,不支持多态查询:因为load拿到的是cglib代理对象,代理类不是真正的实例 无法用instenceof判断类的归属

Query支持多态查询,

session.createQuery(form Animal);

拿出所有的记录并且生成对应子类对象。(指定类型)

 

多态查询:单表查出数据,Hibernate可以自动鉴别类的数据类型(指定生成对象类型)。

单表继承唯一缺点是:多了冗余字段。

如果关闭懒加载,load就支持多态查询。(直接发送sql语句)lazy=false,默认情况下lazy为true

 

多表继承映射:

继承关系:每一个具体的类映射成一张表

Animal为父类,Pig为子类。

建立关系:(建立三张表,t_animal,父类对应的表中有三个字段,t_pig,t_bird子类对应的表中只有两个字段,主类的主键也为外键,参照父类的主键。对应子类和父类主键保持一致)

映射文件:

<class name=”Animal” table=”t-animal”>

<id name=”id”>

<generator class=”native”/>

</id>

<property name=”name”/>

<property name=”sex”/>

 

<joined-subclass name=”Pig” table=”t_pig”>

<!—pid是当前表t_pig的主键,也是外键参照父类的t_animal表。-- >

<key column=”pid”/>

<property name=”weight”/>

</joined-subclass>

</class>

 

存储关系:

Pig pig=new Pig();

pig.setName(“hua”);

pig.setSex(“male”);

pig.setWeight(100);

session.save(pig);

1、通过子类Pig找到父类Animal,再找到Animal对应的表t_animal

2、先往父类的表中存数据,t_animal(id,name,sex)

3、再往子类的表中存数据,将t_animal的id设到t_pig表中的pid字段。t_pig(weight,pid)

<从t_animal中拿到主键作为当前t_pig表的主键(因为pid即使主键也是外键)>

 

加载关系:

Pig pig=(Pig)session.load(Pig.class,1);

1、先从t_pig中找对应记录,拿到该记录的主键(先找子表),找到weight字段,设到weight属性中。

2、再通过外找主,找到t_animal中对应的记录,将查到的记录设到pig对象中。Sex,name.

 

优点:结构清晰,一个类一张表

缺点:查找效率低,(需要多个表关联查询)

 

继承映射:每个子类一张表,父类没有对应的表

缺点:主键生成策略不能为native,只能为assigned

Animal为父类,Pig为子类。

建立关系:

<!-- abstract=”true”:表示父类不生成表,映射的属性字段被子类继承,让子类应用-- >

<class name=”Animal” abstract=”true”>

<id name=”id”>

<!—这里只能为assigned-- >

<generator class=”assigned”/>

</id>

<property name=”name”/>

<property name=”sex”/>

 

<union-subclass name=”Pig” table=”t_pig”>

<property name=”weight”/>

</union-subclass>

</class>

只有t_pig表,子类的映射的表不仅有自己的属性对应的字段,还会有父类的属性对应的字段。(weight+name,sex)

 

存储关系:

Pig pig=new Pig();

pig.setId(2);

pig.setName(“hua”);

pig.setSex(“male”);

pig.setWeight(100);

session.save(pig);

/*这里不会发送sql语句,因为主键生成策略为assigned,发sql语句是为了拿到主键。这里的主键已经自己手动指定,所有不需要发送sql语句。

Pig对象已经纳入session管理,id为2.。*/

如果再创建一个Bird,

Bird bird=new Bird();

bird.setId(2);

session.save(bird);//这里就会抛出异常,因为id已经在session中存在。

同一个类的不同对象,一旦纳入session管理,id必须不同。

同一个类的不同子类对象,id也必须不同。否则会出异常。

 

 

关于主键生成策略:

父类的属性会被子类继承,映射在子类的字段中,

父类的主键生成策略会被子类继承。

如果为native或者是uuid,有数据库或者hibernate自动生成,只生成一次。

session.save()的时候,会为父类对象生成主标识

同一个id会被父类的所有子类都继承作为子类的主标识id,就会造成多个子类对象引用相同的主标识,就会抛出异常。同一个类的不同子类对象,id必须不同。

 

单表继承:

优点:效率高

缺点是数据冗余

 

一个类映射一张表:

优点:结构清晰,一个类一张表

缺点:查找效率低,(需要多个表关联查询)

 

一个子类映射一张表:

优点:结构清晰,效率高

缺点:主键不能自动生成,必须制定为assigned(不符合hibernate

建议使用第一种,单表继承.

 

物料项目:

持久层:继承映射和关联映射同时使用

用户需求: 主键生成策略为assigned

鉴别器字段 discriminator column=”category” type=”string”

 

存储关系:(继承关系的表中的数据的存储)

通过子类对象找到子类所在的映射文件,拿到鉴别器值 discriminator-value=pig”

在映射文件中,通过子类找到父类,通过父类找到对应的表 t_data_dict

往父类的表(t_data_dict)中设值。

 

加载关系:(继承关系的表中的数据的加载)

session.createQuery(from ItemCategory);

相当于sql语句:select *from t_data_dict where category=itemCategory;

因为查询的是子类对象,多态查询,会自动加上鉴别器的值category=itemCategory。

将查询出来的记录封装到Query对象中,再通过.list()方法,将Query对象中的记录存到list集合中。

 

 

关联映射:(关于关联属性的映射- Item对象)

Item:

Private ItemCategory category;

Category为关联属性,通过配置hbm.xml文件

<many-to-one name=”category”>

表明category为外键,参照的是category属性所属的类ItemCategory所映射的表,但是由于ItemCategory为子类并且在单表继承映射中,所有参照的是ItemCategory的父类所映射的表t_data_dict。

 

存储关系:(关联映射--存Item对象)

在Action中:

1、         从表单中拿到表单中的参数(普通属性以及关联属性的值)

2、         创建Item对象,将普通属性设到Item对象中(Item处于瞬时态)

3、         创建ItemCategory对象,ItemCategory category=new ItemCategory();(从数据库中取出来的数据是离线态,在数据库中有对应的记录)

4、         category.setId (categoryId);// categoryId为从表单中取出来的数据

5、         item.setItemCategory(category);//给关联属性赋值

6、         创建ItemUnit对象,ItemUnit unit=new ItemUnit();//离线态

7、         unit.setId(unitId);//unitId为从表单中取出来的数据

8、         item.setUnitItem(unit);// //给关联属性设置

 

持久态对象可以引入离线态对象,不会有异常,因为离线态对象在数据库中有对应的id.

 

加载(关联映射—给关联属性赋值):

session.load(Item.class,itemNo);

1、         找到hbm文件

2、         找到对应的表 t_items

3、         根据itemNo找到对应的记录

4、         将对应的记录设到Item对象中

5、         给关联属性ItemCategory赋值,many-to-one,外找主,找到t_data_dict中对应的记录

6、         根据记录生成对象,将对象赋值给Item对象中的category属性

 

懒加载(Lazy)

懒加载概念:只有真正使用该对象时,才会创建对象;对于Hibernate而言,真正使用对象的时候才会发送sql语句查询数据库。

懒加载--lazy主要用于加载数据。

lazy使用的标签:

<class>:

<peroperty>:

<set><list>:

<one-to-one><one-to-many>:

懒加载的实现原理:产生cglib代理

1、         生成目标类的子类(cglib代理类)

2、         一旦需要使用对象的方法时,判断target是否等于null(target为代理对象中的一个状态标识,默认为null,对象创建后就会等于对象的类型 target=Group)

3、         如果target不等于null就直接使用

4、         如果target等于null 就发sql语句查询数据库根据记录生成对象再使用。

懒加载的分类:

1、类上的懒加载<class>::true/false,默认为true,开启懒加载

2、集合上的懒加载<set>:: true/false/extra 默认为true

3、单端关联懒加载<one-to-one><one-to-many>::

:false/proxy/noproxy,默认为proxy

 

类上的懒加载:(默认开启lazy=”true”

<class name=”Group” table=”t_group” lazy=”true”>

Group gourp=session.load(Group.class,1);//执行这条语句时不会马上发送sql语句

创建的Group对象是cglib代理对象,target=null;

group.getId();//取得id的时候也不会发送sql语句,因为上面查询条件中已经给出id的值。group依旧是cglib代理对象,target=null

group.getName();//这时候group还是cglib代理对象。然后发送sql语句。

cglib代理对象是目标对象的子类,会继承目标对象的方法,会先判断目标对象是否存在(target==null)。如果存在就会调用目标对象的方法;如果不存在就会发送sql语句,从数据库中查询记录,根据记录创建目标对象,执行目标对象的getName()方法。target=Group。

再执行一次 group.getName();这里还是代理对象,目标对象是由cglib代理对象调用。会判断target==null,发现不等于null,就直接调用目标对象的getName()方法。

 

加载可以不需要事务,事务只是用在存储。

Hibernate支持三种事务:局部事务,全局事务,代理事务

 

懒加载异常:

懒加载开启,创建的是代理对象,先不使用代理对象(就不会发送sql语句创建目标对象),执行commit()方法后,执行session.close()方法把session关闭。然后再使用代理对象,需要发送sql语句创建目标对象时,发现连接已经关闭,就会抛出异常LazyInitializationException:懒加载初始化异常。

Session是线程不安全的,所以用完了要马上关闭。

 

解决方案:

1、         关闭懒加载:但是效率会大大降低

2、         使用ThreadLocal

 

懒加载的实际应用:物料项目

1、load()方法在lazy=true的情况下查询出来的对象都是代理对象,在持久层是不会使用对象的

2、将代理对象下传到业务层(struts的Action类中)

3、在Action类中通过request.setAttribute(“item”,item);传到jsp页面

4、在jsp页面通过EL表达式输出。通过item属性名拿到对应的属性值 item代理对象。

5、发现要使用的时候就会发送sql语句创建目标对象,但是session已经关闭所以会抛出异常。

 

正确处理方法:

在持久层不关闭session,使用Filter来管理session的创建和关闭

Public class HibernateFileter implements Filter{}

ThreadLocal hibernateHolder=new ThreadLocal();

1、加载类创建工厂类型的静态变量(全局)

2、Tomcat一启动就创建过滤器对象,一旦创建过滤器对象就会执行该对象的init()方法

3、init()方法中,读取Hibernate.hbm.xml文件,创建SessionFactory对象。

4、把工厂对象赋值给工厂类型的静态全局变量

5、在HibernateFilter类中,创建getSession静态方法,在此方法中得到session

public static Session getSession(){

Session session=(Session)hibernateHolder.get();//取得当前线程绑定的session

if(session==null){

session=factory.openSession();

hibernateHolder.set(session);//把session和当前线程绑定

}

return session.

}

 

6、持久层通过HibernateFilte类中的的静态方法 getSession()拿到session。

7、在实现Filter接口的类中,doFilter方法中:chain.doFilter()执行后对结果进行拦截,关闭session。先判断当前线程是否已经绑定session,

Session session=(Session)hibernateHolder.get();//取得当前线程绑定的session

如果绑定了,取出的session不为null,就执行判断session.isOpen(),如果为true就继续执行session.close()方法关闭session,然后再将session从map集合中移除(解除当前线程与session的绑定关系)

try {

chain.doFilter(servletRequest, servletResponse);

} finally {

Session session = (Session)hibernateHolder.get();//从Map中取出session

if (session != null) {

if (session.isOpen()) {

session.close();//关闭session

}

hibernateHolder.remove();//移除session

}

}

 

ThreadLocal类的分析:

原理:在ThreadLocal类中有一个线程安全的Map,用于存储每一个线程的变量的副本。

public class ThreadLocal{

private Map values = Collections.synchronizedMap(new HashMap());;//线程安全

}

ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>

Thread thraed=Thread.currentThread();//得到当前线程对象

ThreadLocal:是线程做并发用的技术,主要方法get(),set(T),initialValue(),remove()

 

1set(T):--设置到当前线程的局部变量

将当前线程和给session做绑定,线程作为key,T(session)作为value存入一个加锁的线程安全的map集合中。values.put(thread,session);

public void set(Object newValue) {

values.put(Thread.currentThread(), newValue);

}

2T get():返回当前线程的局部变量

从线程安全的map中集合中取出线程对应的session,如果有就直接用,如果没有就创建session,再绑定到当前线程上  values.put(thread,session);

public Object get() {

Thread curThread = Thread.currentThread();

Object  obj=values.get(currentThread);//先从map集合中取

if (obj == null && !values.containsKey(curThread)){//发现map集合中没有当前线程作为key对应的value,就创建再添加到map

 obj = initialValue();

values.put(curThread, obj);

}

return obj;

}

 

3T initialValue():---返回当前线程的局部变量的初始值

protected Object initialValue(){

 return null;

}

4remove():--移除当前线程的局部变量

map.remove(key);//根据map集合的移除元素的原则,根据key移除,

public void remove() {

values.remove(Thread.currentThread());

}

 

为什么一定要用线程安全的map?

多个线程并发的拿到session并发的访问数据库会造成数据混乱。线程安全的map,使得一次只能有一个线程访问数据库。并行变串行

资源共享:

对于多线程资源共享的问题,同步机制采用了以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而ThreadLocal为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

Hibernate和struts的集成,使用拦截器(Filter)继承。

 

OpenSessionInview模式:一直开启session,直到数据输出完再关闭。

 

集合上的懒加载:(extra,true,false)

在<set>标签上使用,使用set标签的主要有<one-to-many><many-to-many>

<class>标签上的懒加载,不影响<set>上的懒加载。

普通属性的加载是使用类上的懒加载

关联属性是使用集合上的懒加载

1、集合上的懒加载开启lazy=true,类上的懒加载是开启的 lazy=true

session.load(Classes.class,1);//生成的是代理对象,target=null,不会发送sql语句

classes.getName();//只发一条sql语句,访问t_classes,只给普通属性设值

Set students=classes.getStudents();//不会发送sql语句访问t_student表,说明当前students是PersistantSet(类似cglib)代理对象,不是真正存放Student对象的Set集合。

Iterator it=students.iterator();

While(it.hasNext()){

Student stu=it.next();

stu.getName();//真正使用到Set集合中的元素时才会发送sql语句访问t_student

}

 

2、集合上的懒加载关闭lazy=false,类上的懒加载是开启的 lazy=true

加载Set集合类的students属性和加载普通属性一样。

session.load(Classes.class,1);//生成的是代理对象,target=null,不会发送sql语句

classes.getName();//发送两条sql语句,访问t_classes,t_student表(后面都不发送sql语句了)

 

3、集合上的懒加载关闭lazy=false,类上的懒加载关闭lazy=false

session.load(Classes.class,1);//生成的是目标对象,会发送两条sql语句,分别访问t_classes,t_student表

 

4、集合上的懒加载开启lazy=true,类上的懒加载关闭lazy=false

session.load(Classes.class,1);//生成的是目标对象,会发送一条sql语句,访问t_classes表

Set students=classes.getStudents();//不会发送sql语句访问t_student表,说明当前students是PersistantSet(类似cglib)代理对象,不是真正存放Student对象的Set集合。

Iterator it=students.iterator();

While(it.hasNext()){

Student stu=it.next();

stu.getName();//真正使用到Set集合中的元素时才会发送sql语句访问t_student

}

 

5、集合上的懒加载lazy=extra,类上的懒加载关闭lazy=true

session.load(Classes.class,1);//生成的是代理对象,不会发送sql语句,

classes.getName();//会发一条sql语句,访问t_classes表

Set students=classes.getStudents();//不会发送sql语句访问t_student表,说明当前students是PersistantSet(类似cglib)代理对象,不是真正存放Student对象的Set集合。

students.size();//这时候会使用函数从数据库中查询数据,select count(*)form t_student where classesid=1;,如果集合上的lazy为true,就会先发送select *from student where classesid=1语句,查询数据库中的所有记录,再计算出记录的条数。

Iterator it=students.iterator();

While(it.hasNext()){

Student stu=it.next();

stu.getName();//真正使用到Set集合中的元素时才会发送sql语句访问t_student

}

 

lazy=true和lazy=extra的功能差不多,但是extra更加智能,它会根据用户的的条件发送比较智能的sql语句。

 

单端上的懒加载:(proxy,false,no-proxy

般用在<many-to-one><one-to-one>标签上面

默认为lazy=”proxy”,开启状态

<many-to-one name=”group”  lazy=”proxy”>

主要是在加载group(关联)属性的时候有用,

1、单端上的懒加载开启lazy=proxy,类上的懒加载是开启的 lazy=true

session.load(User.class,1);//生成的是代理对象,target=null,不会发送sql语句

user.getName();//发送sql语句,访问t_user表

Group group=user.getGroup();//拿到的而是Group的代理对象

Group.getName();发送sql语句访问t_group.

 

2、单端上的懒加载关闭lazy=false,类上的懒加载关闭 lazy=false

session.load(User.class,1);//发送两条sql语句,访问t_user和t_group

user.getName();

Group group=user.getGroup();

Group.getName();

 

3、单端上的懒加载开启lazy=proxy,类上的懒加载关闭lazy=false

session.load(User.class,1);//发送一条sql语句,只访问t_user

user.getName();

Group group=user.getGroup();//不会发送sql语句,group为代理对象

Group.getName();//发送sql语句访问t_group

 

4、单端上的懒加载开启lazy=no-proxy,类上的懒加载关闭lazy=false

session.load(User.class,1);//发送一条sql语句,只访问t_user

user.getName();

Group group=user.getGroup();//不会发送sql语句,group为代理对象

Group.getName();//发送sql语句访问t_group

 

Lazy的取值为proxy和no-proxy功能和性能上都没有很大的区别:

proxy:做懒加载是使用cglib代理,no-proxy使用的是增强的字节码工具,在字节码生成的时候增加了懒加载策略。

 

今天的任务:

将老师讲的案例自己写一遍(懒加载) *

晚上投简历 *

做>=1道算法题 *

查看hibernate源码

 

Session_Flush

Session_flush的作用:

1、         清理缓存(清理session临时集合中的数据)

2、         生成sql语句(发送sql)

3、         将session的persistanceContext中的map->existsInDatabase的标记置为true。

Flush:用在存储上。

 

主键生成策略为uuid(session.flush()的特点)

session.save(user)前—>> 对象处于瞬时态

执行save(user)方法后,一般不会发送sql语句,因为主键是由hibernate自动生成。

session.save(user)后-->> 对象处于持久态。existsInDatabase=false

session.flush();//主要做了以下三件事:

1、         清理缓存(清理session临时集合中的数据)

2、         生成sql语句(发送sql)

3、         将session的persistanceContext中的map->existsInDatabase的标记置为true

数据库中会有相应记录,但是会看不到。(因为是 脏数据:没有提交的数据)

session.commit();

1、         执行commit()方法的时候会执行flush()方法,所以可以不显示的写出flush()方法。

2、        就算是显示写了flush()方法,commit()的时候还是会再执行一次flush()

3、        执行flush()方法的步骤:

3.1、   先判断临时集合中有没有数据

3.2、   如果没有的话就直接commit().

3.3、   如果临时集合中有数据的话就发送sql语句再清空临时集合中的数据,并且将existsInDatabase标记置为true,然后再执行commit()

 

主键生成策略为native(session.flush()的特点)

session.save(user)前—>> 对象处于瞬时态

执行save(user)方法后,一般会发送sql语句,因为主键是由数据库自动生成。

session.save(user)后-->> 对象处于持久态。existsInDatabase=true

session.flush();//这时候flush()什么都不做,因为临时集合中没有数据,sql语句已经发送,existsInDatabase=true

session.commit();//会再次执行flush()方法,依旧什么都不做。commit()方法对提交到数据库的数据做验证,成功就添加到数据库,不成功就回滚。

 

主键生成策略为uuid(session.evict()的特点)

session.save(user)前—>> 对象处于瞬时态

执行save(user)方法后,一 般不会发送sql语句,因为主键是由hibernate自动生成。

session.save(user)后-->> 对象处于持久态临时集合中有数据existsInDatabase=false

session.evict(user);//将user对象从session-- >> persistanceContext下的map中逐出。临时集合中依旧有数据.

session.commit();//抛出异常,(persistanceContext下的map)EntityEntries属性中的table中的数据不存在,找不到数据,无法提交数据。(缓存使用不当)

产生异常的原因:

主键生成策略为uuid

session.save(user);//在临时集合中有数据,session的map中有数据,existsInDatabase=true,loadedState中存放了user对象

session.evict(user);// 把user从map中逐出(session缓存),为了管理缓存

然后 临时集合中有数据,但是map中没有数据

 

执行commit()方法前会先调用session.flush(),做以下三步操作:

  1. 从临时集合中拿到数据,清空临时数据
  2. 发送sql语句
  3. 3.   将map下的existsInDatabase的标记置为true,但是user的map已经被清除,找不到map也找不到map里面的existsInDatabase标记,就会抛出异常。

 

解决办法:(调用session.evict(user)以前先显示调用session.flush())

session.save(user);

显示的写flush方法

session.flush();//根据临时集合中的数据发送sql语句,然后清空临时集合,到session缓存中将existsInDatabase标记置为true

session.evict(user);//将user对象从session缓存的map集合中移除

最后session.commit();//这时候依旧会执行session.flush()方法,但是不会抛出异常,因为flush()方法,是先查看临时集合中有没有数据,发现临时集合是空的没有数据就不会发送sql语句,也不用再次清空临时集合,也不需要将existsInDatabase标记置为true;直接执行commit()方法。

 

主键生成策略为native(session.evict()方法的特点)

session.save(user)前—>> 对象处于瞬时态。

执行save(user)方法后,一般会发送sql语句,因为主键是由数据库自动生成。

session.save(user)后-->> 对象处于持久态。临时集合中没有数据。existsInDatabase=true

session.evict(user);//自动把数据从session缓存的map中逐出

session.commit();//执行flush()方法,但是由于主键生成策略为native,临时集合中已经没有数据了,就不要用发送sql语句也不用将existsInDatabase标记置为true了。直接执行commit()方法。 commit()方法对提交到数据库的数据做验证,成功就添加到数据库,不成功就回滚。这里不会抛出异常,因为这里的flush()方法什么都不用做。

 

主键生成策略为assigned(session.evict()的特点)

session.save(user)前—>> 对象处于瞬时态。

执行save(user)方法后,一般不会发送sql语句,因为主键是由用户手动生成。

session.save(user)后-->> 对象处于持久态。existsInDatabase=false,临时集合中有数据。

session.evict(user);//自动把数据从缓存中逐出

session.commit();//不管assinged知道的主标识类型是int还是String,执行session.evict(user)后执行session.commit()一定会抛出异常

解决办法,在调用sessionn.evict(user)方法前显示的调用session.flush()

 

Sql语句在数据库中的执行顺序:

发送sql语句的顺序:

创建两个对象 user,user1;

session.save(user);

user.setName(“ls”);

session.save(user1);

session.commit();

先发送insert into  user () 存储的对象是user

再发送 insert into user() 存储的对象是user1

再发送 update user set name=ls 更新的对象是 user

 

可以使用flush()方法调整语句的发送顺序

创建两个对象 user,user1;

session.save(user);

user.setName(“ls”);

session.flush();

session.save(user1);

session.commit();

先发送insert into  user ()存储的对象是 user

再发送 update user set name=ls user(因为flush()方法)

再发送 insert into user()存储的对象是 user1

 

悲观锁和乐观锁:

1、         为什么要加锁?

在数据库中不加锁会丢失更新

原因:用户同时访问数据库,查看的数据相同,一方对数据做了修改但是另一方不知道,所以就在原来的数据上修改。就数据不一样。

 

不能并发的访问数据库中的数据,不然数据库会崩溃的哦

保证数据库的安全,并行变串行

 

悲观锁:(悲观锁不支持懒加载)

测试示例:

两个方法串行执行:(load1(),load2())money=1000

1、load1();---- session.load(Test.class,1);

2、Debugà 查完数据后修改数据库中的值 money-200但是 不执行commit()方法

3、执行另一个查询方法load2()-----ssession.load(Test.class,1);

4、修改数据 money-200并且一次执行完.

5、再接着执行第一个方法,commit()。更新后数据库中的数据就是错误的。最后结果为money=800。更新丢失

 

session.load(Test.class,1,LockMode.UPGRADE);//悲观锁,使用数据库自身的锁

LockMode.UPGRADE//利用数据库的for  update 子句加锁—---记录锁(会出现幻读)

锁的分类:记录锁,表锁,库锁

记录锁:只给当前查询出来的记录加锁,其他记录依旧没有变化

 

加悲观锁后的示例:(悲观锁中懒加载自动失效,load方法执行后就会发送sql语句)

两个方法串行执行:(load1(),load2())money=1000

1、load1();---- session.load(Test.class,1,LockMode.UPGRADE);//马上发sql语句查询

2、使用debug调试à 查看记录money=1000,然后修改数据库中该记录的的money字段, ,money=money-200但是 不执行commit()方法

3、执行另一个查询方法load2()-----ssession.load(Test.class,1,LockMode.UPGRADE); //发送sql语句查询的时候发现查不出来数据。id=1的记录已经被上了锁。

4、修改数据 money值,money=money-200,执行commit()方法,会停留在记录外面,查询不出来记录,因为前面已经上了锁,要等前面那个锁释放才可以访问那条记录。

5、再接着执行第一个方法,commit()。更新后数据库中的数据money=800;

6、这时候load2()方法执行,查询出来的数据money=800,之后才能对记录做修改。

悲观锁中的记录锁的特点:避免了不可重复读,但是存在幻读。(效率低)

 

乐观锁:(乐观锁支持懒加载)

表中必须添加一个版本控制字段version,version默认值为0,用户读到的记录是一样的 version和money。version在User类中定义为 private int version;

version在映射文件中使用<version>标签配置,version是版本控制字段,由Hibernate自动管理。

<version name=”version”/>:标识对乐观锁的控制

Load1()方法(debug调试,执行commit()方法以前的代码)

User user=(User)session.load(User.class,1);//不会发送sql语句,生成代理对象

int money= user.getMoney();//使用到对象方法时发送sql语句select  * from user where id=1 and version=0),

user.setMoney(money-200);

 user.setVersion(1); //查询出数据后,修改表中记录的值money = money-200;version=1。

session.update(user);//发送update语句,update user set money=money-200 and version=1 where id=1 and version=0;

暂不执行session.commit()这条语句。

 

Load2()方法(一次性执行完)

User user1=(User)session.load(User.class,1);//不会发送sql语句,生成代理对象

int money= user.getMoney();//使用到对象方法时发送sql语句select  * from user where id=1 and version=0),

user.setMoney(money-200);

 user.setVersion(1); //查询出数据后,修改表中记录的值money = money-200;version=1。

session.update(user);//发送update语句,update user set money=money-200 and version=1 where id=1 and version=0;

执行session.commit()方法。修改数据库中id=1对应的记录

 

再继续执行load1()方法中的session.commit()语句,发送sql语句update user set money=money-200 and version=1 where id=1 and version=0;

但是版本号version的值已经改成1,所以commit()的时候就会抛出异常。

 

 

事物的隔离级别:

1、         未提交读:没有提交就能在数据库中读到数据

2、         可提交读:提交后才能在数据库中读到数据,解决不可重复读的方法是加悲观锁。(mysql和oracle的默认处理级别)

3、         可重复读:加了悲观锁(记录锁)

4、         序列化读:不会出现幻读,加表锁(不会出现幻读,不会出现脏读,也不会出现不可重复读)

5、         脏读:读取未提交到数据库中数据。

6、         不可重复读:读取数据时,如果有人修改了数据就发现不是原来的数据,会出错(加悲观锁解决问题)

7、         幻读:每次读取数据的时候读取到的记录数一直在变化(解决方法应该加表锁)

安全性强的使用使用悲观锁

并发性强的时候使用乐观锁

 

缓存

缓存的分类:

一级缓存:session级别,生存周期和session一样

session.save(obj);将数据纳入session管理。load()get(),iterator()方法都使用一级缓存。如果iterator()方法使用不当就会造成n+1问题。

Query 的list()方法不使用一级缓存,但是往一级缓存中放数据。(可能有n+1问题)

 

二级缓存:sessionFactory级别,工厂不会轻易销毁,一般只创建一次。放到二级缓存中的数据特点:查询次数频繁,但是修改次数少

 

二级缓存:一级缓存可以控制对二级缓存的使用get(),save(),load() 既要往一级缓存中存数据,也要往二级缓存中存数据。

查询缓存:为了提高Query list的查找效率

 

一级缓存和二级缓存主要是缓存实体对象,不会缓存属性

查询缓存只缓存实体属性

 

一级缓存:(load,get,list,iterator都支持一级缓存)

直接查询load()或者get()

Session级别的缓存,一般不需要配置也不需要专门的管理,直接使用就可以了。

Student stu=(Student)session.load(Student.class,1);

stu.getName();//先从一级缓存session中查看有没有id=1的Student对象,有的话就直接使用,没有的话就发sql语句访问数据库根据记录生成对象再使用。<load支持一级缓存>

stu.getSex();//再次使用Student对象的时候,先从一级缓存session中查有没有该对象,发现有就直接使用,不再创建了。

Student stu1=(Student)session.get (Student.class,1);//这里也不会发送sql语句,因为一级缓存中有id=1的Student对象,所以直接使用。<get()也支持一级缓存>

 

先save后查询get()或者load(),查到的是save()过的数据

主键生成策略为:native

Student stu=new Student();

stu.setName(“ls”);

stu.setSex(“male”);

Serializable id=session.save(stu);//save()方法的返回值是Integer类型的数据。得到的id是对象的主标识。这里save()后Student对象纳入session管理,进入缓存。

Serializable:序列化接口,String ,Integer都支持序列化协议。

Student stu1=(Student)session.get(Student.class,1);//这里不会发送sql语句,因为先从缓存中查找数据,发现有id=1的Student对象,就直接使用。

缺点: 可能会读到脏数据。

 

Query的iterate()方法

Student stu1=(Student)session.createQuery(“from Student s where s.id=1”).iterate().//执行到iterator()方法才会发送sql语句

1、        通过id先从数据库中查对象的id(select id from t_stu where id=1)

2、        根据id到session中查对象

3、        如果没有就再发sql语句访问数据库 (select* from t_stu where id=1)

Student stu2=(Student)session.createQuery(“from Student s where s.id=1”).iterate().//再执行一次,还是会发查询id的sql语句(select id from t_stu where id=1)

然后发现session中有对象,就不发sql语句(select* from t_stu where id=1)了,直接从session中拿到对象使用。

 

N+1问题:(iterator()缓存使用不当)

session.createQuery(“from Student ).iterate();

1、        先发一条sql语句取出所有的id(select id from t_stu

2、        查询session中有没有id对应的对象

3、        根据id依次发送sql语句访问数据库(select * from t_stu),数据库中有多少条记录就会发送多少条sql语句,如果数据库中的记录特别的多,这里就会出现数据库访问拥塞,效率下降,造成n+1问题。

session.createQuery(“from Student ).iterate();//再查一次,这里会先发sql语句查id,然后发现以及缓存session中有id对应的对象,就直接使用。不会再发送sql语句访问数据库了。

 

list()方法:只会往一级缓存中放数据,不会从一级缓存中取数据

List stus=session.createQuery(“from Student”).list();//直接发sql语句访问数据库,不会先查id,一次性直接把数据库中的所有记录放到一级缓存session

Stus= session.createQuery(“from Student”).list();//list()只放不拿,还是会再次发送sql语句访问数据库,查出所有记录放到session中。

 

N=1问题的解决方法:

第一次使用list(),第二次使用iterator()方法

1、        list()先把从数据库中查询出所有数据放到一级缓存session

2、         再使用iterator()方法使用一级缓存session中的数据

3、        iterator()会先发送sql语句查所有的id,然后去一级缓存session中查找id分别对应的对象

4、         发现对象已经存在一级缓存session中就直接使用。不会再发送多条sql语句访问数据库了。这样就只发送了两条sql语句,解决了N+1问题。

 

一级缓存的管理:(evict(),clear(),close()

使用flush()清除session中的缓存

session.flush();//清除临时集合中的数据

session.clear();//清除persistanceContext下的map中的数据

一定要先flush(),然后再clear(),不然可能会产生异常

session.evict(user);//清除一条

session.clear();//清除多条数据

clear()和evict()的区别:clear()方法清除多条,evict()只能清除一条。

 

一级缓存只缓存对象,不缓存对象的属性。

如果sql语句查询的结果是记录(一条或者多条),就会根据记录生成对象(一个或多个)放到一级缓存session中。

如果sql语句查询的结果是记录的字段,就不会把字段映射成属性放入一级缓存session中了。

 

一级缓存里面的数据不能共享:session关闭后所有数据都销毁。

Hibernate的缺点:Hibernate不会保证批量数据的同步,只能保证单条数据的同步。

 

二级缓存:(缓存的内容是对象,load,get,save都支持一级缓存)

二级缓存只有一个,生成周期很长,和SessionFactory的生命周期一样。

缓存策略的作用是: 提供对缓存的方式

Hibernate一般使用Hcache这种缓存策略。

 

 

二级缓存的配置:

1、准备缓存策略的配置文件,在配置文件中写缓存策略

在ehcache.xml中配置缓存策略:(配置在src下)

默认的缓存策略:

<defaultCache

maxElementsInMemeory=”10000”//缓存的最大空间

eternal=”false”//设置没有任何对象常驻二级缓存中

timeToldleSeconds=”120”//如果120秒不使用就移除缓存

timeToLiveSeconds=”120”//最多能在二级缓存中呆120秒

overflowToDisk=”true”//如果缓冲存的数据超过10000,就将数据存到硬盘中

/>

 

配置指定的缓存策略:

<cache name=”cache1”

maxElementsInMemeory=”10000”//缓存的最大空间

eternal=”true”//设置所有对象都常驻二级缓存中

timeToldleSeconds=” 0”//不管多长时间没有使用都不会移除缓存

timeToLiveSeconds=” 0” //不管使用多长时间都不会移除缓存

overflowToDisk=”false”//溢出的时候不支持将数据存到硬盘上

/>

 

二级缓存通过缓存策略设定缓存中能存放多少个对象,存放多久,有多长的生存时间。

 

2、在Hibernate.cfg.xml文件中配置哪个类的对象来管理二级缓存

<!-- 指定缓存产品提供类-->

<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider</property>

org.hibernate.cache. EhCacheProvider:创建sessionFactory时,读取Hibernate.cfg.xml文件,创建EhCacheProvider对象。通过插件执行对象的相应的方法--init(),读取ehcache.xml配置文件。通过读取ehcache.xml配置文件对缓存进行管理。

3、配置哪些对象使用二级缓存

<class-cache class=”cn.Student” usage=”read-only”/>

4、指定使用二级缓存使用对象的使用方式

也可以在实体类的hbm.xml文件中配置 <cache usage=”read-only”/>

缓存策略的集中方式:read-only(常用)read-write , nonstrict-read-write

5、        开启二级缓存:

<!-- 开启二级缓存 -->

<property name="hibernate.cache.use_second_level_cache">true</property>

 

二级缓存的使用:(只存放配置了的对象,get,load,save

load()方法的二级缓存解析:

Student stu=(Student)session.load(Student.class,1);

Stu.getName();//发送sql语句从数据库中拿到id=1的记录生成对象,存入二级缓存也会存入一级缓存中。

Session.close();//关闭session,一级缓存中的数据被清空

Student stu=(Student)session.load(Student.class,1);

Stu.getName();再次访问时,先从一级缓存session中查看有没有对象,没有再从二级缓存中查,发现有就直接使用。

 

get()方法的二级缓存解析:

Student stu=(Student)session.get(Student.class,1); //发送sql语句从数据库中拿到id=1的记录生成对象,存入二级缓存也会存入一级缓存中

stu.getName();

session.close();//关闭session,一级缓存中的数据被清空

Student stu=(Student)session.get(Student.class,1); 再次访问时,先从一级缓存session中查看有没有对象,没有再从二级缓存中查,发现有就直接使用。

stu.getName();

结论:在二级缓存开启的状态下,load()和get()不仅使用一级缓存,还会使用二级缓存。

 

二级缓存的管理:(没有临时集合,基本上都是map

Student stu=(Student)session.get(Student.class,1); //发送sql语句从数据库中拿到id=1的记录生成对象,存入二级缓存也会存入一级缓存中

stu.getName()

session.close();//关闭session

factory =HibernateUtils.getSessionFactory();//拿到sessionFactory对象

factory.evict(Student.class);//清除二级缓存中的所有Student对象

factory.evict(Student.class,1);//只把二级缓存中id=1的Student对象

Student stu=(Student)session.get(Student.class,1); //再次发送sql语句从数据库中拿到id=1的记录生成对象,存入二级缓存也会存入一级缓存中(因为一级缓存和二级缓存中的数据都被清空了)

SessionFactory的作用:

1、         创建Session对象

2、         管理二级缓存

 

一级缓存和二级缓存的交互:

一级缓存可以控制如何使用二级缓存

1、设定二级缓存只能读不能写

session.setCacheMode(CacheMode.GET);//设定仅从二级缓存中读数据,而不向二级缓存中写数据

Student stu=(Student)session.get(Student.class,1); //发送sql语句从数据库中拿到id=1的记录生成对象,只将数据存入一级缓存中。

stu.getName()

session.close();//关闭一级缓存,这时候一级缓存和二级缓存中都没有数据,下一次使用对象时,会发送sql语句。

2、设定对二级缓存不做任何约束

Student stu=(Student)session.get(Student.class,1); //发送sql语句从数据库中拿到id=1的记录生成对象,将数据存入一级缓存和二级缓存中。

stu.getName()

session.close();//关闭一级缓存,这时候一级缓存没有数据,二级缓存中有数据

3、设定二级缓存只能写不能读

session.setCacheMode(CacheMode.PUT); //设定只往二级缓存中写数据,但是不能读取二级缓存中的数据。

Student stu=(Student)session.get(Student.class,1); //虽然二级缓存中有数据,但是因为二级缓存中的数据不能读取,所以要发送sql语句从数据库中拿到id=1的记录生成对象,将数据存入一级缓存和二级缓存中。

Stu.getName();

Session.close();/关闭session,一级缓存中的数据被清空。二级缓存中的数据还在。

4、设定对二级缓存不做任何约束

Student stu=(Student)session.get(Student.class,1); //不会发送sql语句,因为二级缓存中有数据,可以读取。

Stu.getName();

Session.close();/关闭session,一级缓存始终都没有数据,二级缓存中的数据依旧在。

 

查询缓存:(只能缓存属性,不能缓存对象,只有list可以使用查询缓存)

list通过查询缓存使用二级缓存

查询缓存的配置:(不需要配置缓存策略)

1、         在Hibernate.cfg.xml中配置开启查询缓存:(二级和查询缓存都开启 true)

2、         在使用查询缓存前要启动查询缓存

query.setCacheable(true);

 

1、        开启查询缓存(true),关闭二级缓存(false) 使用list()方法

Query query=session.createQuery(“select s.name form Student s);//查出所有的name属性对应的字段

query.setCacheable(true);//启动查询缓存

List name=query.list();//发送sql语句,将查到的name属性放到查询缓存中

 

Query query=session.createQuery(“select s.name form Student s);//查出所有的name属性对应的字段

query.setCacheable(true);//启动查询缓存,查询缓存配置后也是默认开启的

List name=query.list();//不会再发送sql语句,因为list()方法使用了查询缓存,查询缓存中已经有数据

结论:查询缓存,值缓存属性,不缓存对象;只有list()方法能使用查询缓存

 

2、        查询缓存不受一级缓存的影响

 

3、        开启查询缓存(true),关闭二级缓存(false) 使用iterate()方法

Query query=session.createQuery(“select s.name form Student s);//查出所有的name属性对应的字段

query.setCacheable(true);//启动查询缓存

List name=query.iterate();//发送sql语句,因为是iterate()方法,不能使用查询缓存,所以无法将查到的name属性放到查询缓存中

 

Query query=session.createQuery(“select s.name form Student s);//查出所有的name属性对应的字段

query.setCacheable(true);//启动查询缓存

List name=query.iterator();//还是再次发送sql语句,因为iterator()方法不能使用查询缓存,查询缓存中没有数据

结论:iterator()不使用查询缓存。

4、        关闭查询缓存(false),关闭二级缓存(false) 使用list()方法

Query query=session.createQuery(“select s form Student s);//查出所有的Student对象

查询两次,查询完后关闭session,第二次查询依旧会发送sql语句

 

5、        开启查询缓存(true),关闭二级缓存(false) 使用list()方法

Query query=session.createQuery(“select s form Student s);//查出所有的Student对象

query.setCacheable(true);//启动查询缓存

List stus=query.list();//查出的所有对象都存入一级缓存中,查出所有对象的id放入查询缓存中。

Session.close();//关闭session,一级缓存中的数据被清空

 

Query query=session.createQuery(“select s form Student s);//查出所有的Student对象

query.setCacheable(true);//启动查询缓存

List stus=query.list();//先到查询缓存中查看存放的所有对象的id,再到二级缓存中找id分别对应的对象,但是二级缓存已经关闭,所有查询缓存会发送sql语句:slelect * from t_stu where id= id;有多个id就会发送多少条语句。这样就会造成数据库访问拥塞,N+1问题。

 

6开启查询缓存(true),开启二级缓存(true) 使用list()方法

就不会出现N+1问题,因为开启二级缓存,二级缓存中就会有数据,直接使用就可以了,不用再次发sql语句访问数据库拿到记录生成对象。

 

N+1问题:

iterator()是对一级缓存使用不当,造成N+1问题

list()是对二级缓存的使用不当,造成N+1问题。

 

结论:Query的list方法不能使用一级缓存,可以使用二级缓存,但是要开启查询缓存才有用。

Iterate()也会使用二级缓存。

 

 

注意:

list()方法:直接取出所有对象

iterate()方法:先取出所有的记录的id,再根据id到一级缓存中查看有没有对应的对象,没有的话就发送sql语句访问数据库拿到记录生成对象。查询出了对少个id就发送多少条sql语句。

 

 

今天的任务:

将缓存示例写一遍(尤其是二级缓存和查询缓存)

写>=1道算法题

看python文档

复习一遍Hibernate

Lunix的指令

数据结构,算法

 

Hibernate查询语句:hql

hibernate的查询语言种类:

1、         标准化对象查询(Criteria Query:完全面向对象但是不够成熟,不支持投影也不支持统计函数。

2、         原生的sql查询语句(Native SQL Queries) :直接使用标准SQL语言和特定的数据库相关联的sql进行查询。<和数据库耦合性太强>

3、        Hibernate查询语言(Hibernate Query Language HQL

使用sql的语法,以面向对象的思想完成对数据库的查询,独立于任何数据库

以类和属性来代替表和数据列

支持多态,支持各种各样关联

减少了SQL的冗余, hql:不区分大小写

 

Hql支持的数据库操作:

连接(joins),笛卡尔积(cartesian products),投影(projection),聚合(max,avg),排序(ordering),子查询(subquering),sql函数,分页

 

HQL导航查询示例:

Query query=session.createQuery(“from User user where user.group.name=’zte’ ”);

//查找User类对象的属性group,group属性所属的类型Group对象的name

相当于sql语句:

select  * from t_user ,t_group where t_group.name=’zte’ and t_group.id =t_user.groupid;

 

randomDate(“2017-1-1”,”2017-2-9”);//随机生成一个“2017-1-1”,”2017-2-9”之间的日期

 

两个或者两个以上的普通属性查询

使用Object[]数组接收查询出来的属性

List students=session.createQuery(“select id ,name from Student”).list();

Iterator it=students.iterator();

While(it.hasNext()){

      Object[] obj=(Object) it.next();

      System.out.println(obj[0]+”---”+obj[1]);

}

先创建一个Object类型的数组对象Object[]

Object[]数组的长度和查询的属性的个数相同,一 一对应,obj[0]存放从数据库中取出来的id值,obj[1]存放从数据库中取出的name值。

Object[]数组元素的类型和Student实体类的属性类型一 一对应。

List集合students中存放的元素是Object[]类型的数据,查询出来多少条记录就有多少个元素,有多少个Object[]数组。

 

使用Object[]数组接收查询出来的属性(添加别名,更加清晰)

List students=session.createQuery(“select s.id ,s.name from Student (as) s”).list(); //as可以省略也可以显示的写出来

Iterator it=students.iterator();

While(it.hasNext()){

      Object[] obj=(Object) it.next();

      System.out.println(obj[0]+”---”+obj[1]);

}

 

把从数据库查到的记录生成对象,再查出id和name属性(不建议使用)

List students=session.createQuery(“select new Student( id ,name) from Student”).list();//自动调用Student类的有参的构造方法,创建Student对象

Iterator it=students.iterator();

While(it.hasNext()){

      Student stu=(Student) it.next();

      System.out.println(stu.getId()+”---”+stu.getName());

}

 

实体对象的查询:(Select 查找对象 不能使用*)error: unexpected token *

List students=session.createQuery(“from Student”).list();//正确

List students1=session.createQuery(“from Student s”).list();//正确

List students2=session.createQuery(“select * from Student”).list();//这是错误的

List students3=session.createQuery(“select s from Student  as s”).list();//这才是正确的,as可以省略

Iterator it=students.iterator();

While(it.hasNext()){

      Student stu=(Student) it.next();

      System.out.println(stu.getId()+”---”+stu.getName());

}

 

条件查询:

1、写定查询条件

List students=session.createQuery(“select s.id,s.name from Student s where s.name like ‘%1%’ ”).list();//查询出所有名字包含1的学生id和name

2、使用填充符,动态查询条件

List students=session.createQuery(“select s from Student s where s.name like ”).setParameter(0,”%1%”).list();

3、使用参数,把条件设到参数中

List students=session.createQuery(“select s from Student s where s.name like :myname ”).setParameter(“myname”,”%1%”).list();

 

4、使用多个参数,把多个条件分别设到参数中

List students=session.createQuery(“select s from Student s where s.name like :myname and s.id= : myid ”).setParameter(“myname”,”%1%”). setParameter(“myid”,1).list();

参数顺序不一样没有关系。只和参数名有关。

 

5、使用一个参数,把多个条件设到一个参数中(setParameterList

List students=session.createQuery(“select s from Student s where s.id in (:myids )”).setParameterList(“myids”,new Object[]{1,2,3}).list();

使用参数数组对象,查询出id 为1 ,2,或者3的学生对象

 

6、日期类型数据作为条件的查询(使用数据库中的date_format()函数)

List students=session.createQuery(“select s from Student s where date_format(s.createTime,’%Y-%m’)=?”).setParameter (0,”2017-2”).list();

先把字符串转换为日期类型

再把日期和数据库中日期做比较

 

6、日期类型数据作为条件的查询(使用自定义的日期转换函数 sdf.parse())

SimpleDateFormat sdf=new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

List students=session.createQuery(“select s from Student s where sdf.parse (s.createTime,’%Y-%m’)=?”).setParameter (0,”2017-2-12 11:22:11”).list();

先把字符串转换为日期类型

再把日期和数据库中日期做比较

 

如果非要使用select *语句 就要使用原生的sql语句

List students=session.createSQLQuery(“select * from Student).list();

 

分页查询:

List students=session.createQuery(“select s from Student s )

.setFirstResult(5)//查询从第五条记录开始的数据 ((pageNo-1)*pageSize)

.setMaxResult(2)//每页最多显示两条(pageSize)

.list();

 

对象导航查询:

List students=session.createQuery(“select s.name  from Student s where s.classes.name like ‘%1%’ “).list();

 

连接查询:

内连接

List students=session.createQuery(“select s.name ,c.name  from Student s join s.classes c”).list();//通过学生类的关联属性把学生类和班级类做内连接。(找出既有学生又有班级的记录)

外连接:

左连接:

List students=session.createQuery(“select s.name ,c.name  from Classes c left join c.student s”).list();拿到有学生的班以及没有学生的班级(班级全部显示,学生只显示有班级的)

右连接:

List students=session.createQuery(“select s.name ,c.name  from Classes c right join c.students s”).list();拿到有学生的班以及没有班级的学生(学生全部显示,班级只显示有学生的)

 

分组查询:

List students=session.createQuery(“select s.name ,count(*)  from Classes c left join c.Student s group by s.name order by s.name”).list();

 

查询过滤器:

使用场合:要求数据录入人员只能看到自己录入的数据时 ---使用查询过滤器

使用条件:

在Student.hbm.xml映射文件中定义过滤器参数:

<filter-def name=”filtertest”>//查询过滤器的名字

<filter-param name=”myid” type=”integer”/>//参数名 参数类型为integer

</filter-def>

 

使用步骤:(先在Student.hbm.xml文件中)

<filter name=”filtertest”  condition=”id &lt; :myid”/>//condition配置条件,id< myid(参数)

在查询方法中:session.enableFilter(“filtertest”) .setParameter(“myid”,10)//启用查询过滤器,并且设定参数。 表示查询id<10的数据记录。

 

外置命名查询:

在Student.hbm.xml映射文件中采用<query>标签来定义hql:

<query name=”searchStudents”>

<![CDATA[SELECT s FROM Student s where s.id<?]]>

</query>

List students=session.getNameQuery(“searchStudents”).setParameter(0,5).list();//安全性比较差,显示写出sql语句在配置文件中,容易被黑。

 

DML风格:(批量更新,批量操作)和缓存不同步

session.createQuery(“update Student s set s.name=? where s.id<?”)

.setParameter(0,”小仙女”)//更新name都为小仙女

.setParameter(1,5)//id<5的记录都被更新

.executeUpdate();

Hibernate的缺点:不支持批量更新,不是说不能批量更新,而是批量更新的操作不能保证缓存中的数据和数据库中的数据同步。

 

Struts1Hibernate的集成:

1、         创建web项目

2、         导入两个框架的jar包以及jstl的jar包—--WEB-INF/lib

3、         在src下存放国际化资源文件

4、         在web.xml中配置struts1.0  <ActionServlet>,配置编码过滤器

5、         配置struts-config.xml(通常用DispatchAction),放在WEB-INF下

6、         在src下配置hibernate.cfg.xml,log4j.properties

7、         在web.xml文件中配置HibernateFilter,一启动就创建SessionFactory

8、         也可以在struts-config.xml文件中使用插件配置Hibernate的启动,但是建议在Filter中配置,因为filter还要处理openSessionInview问题。

9、         模型层:包括实体类和类对应的*hbm.xml文件.

 

 posted on 2018-03-09 17:43  阿叮339  阅读(543)  评论(0编辑  收藏  举报