zoukankan      html  css  js  c++  java
  • Hibernate基础知识

    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文件.

  • 相关阅读:
    010 排序: 冒泡 选择
    洛谷 P1540 机器翻译
    洛谷 P1011 车站
    周期串
    2019.03.29 大数据图解
    2019.03.29 算法解读
    2019.03.28 博客反省
    2019.03.27 常用的模块
    2019.03.25 git
    2019.03.25 Ajax三级联动
  • 原文地址:https://www.cnblogs.com/DFX339/p/8535155.html
Copyright © 2011-2022 走看看