笔记来源于马士兵老师的hibernate课程

1、类到表

    @table指定表名

    @column指定字段名

    @Transient指定不需要映射成表的字段

    @Temporal指定时间格式有DATE,TIME,DATESTAMP

 

2、id生成策略

一个主键

    @generatedValue四种类型AUTO,IDENTITY,SCQUENCE,TABLE

联合主键,两个以上

    将联合主键属性单独成类,实现serializable,重写equals和hashcode方法

    @EmbeddedID()在pojo类型属性上(有三种方法,先掌握这一种)

 

3、核心开发接口

AnnotationConfiguration(Configure)

SessionFactory(openSession getCurrentsession)

Session(save,delete,update,get,load,clear,flush,gettansaction(commit))

 

4、关系映射(对象间的关系,不是数据表的关系)

一对一(单向、外键),

  Annotation: 加一个参考的对象属性,在被约束表字段的get方法上加@0ne2one @JoinColumn

xml中有特殊<many to one unique="true">

 

一对一(双向、外键)

  Annotation: 互相加参考的对象属性,在被约束表字段的get方法上加@0ne2one(其中一方加mappedBy) @JoinColumn(一方设定)

特别说明: 一对一单向外键关联与一对一双向外键关联在数据库的表的格式是一样的,区别在于

java程序中. 双向外键关联可通过Hibernate在两个类间互相调用彼此,而单向外键关联只能单方向调用.

 

组件映射(一张表是另外一张表的一部分)

Annotation: @ Embeddable(可嵌入的,,附表类名前) @Embbeded(嵌入的,主表的附表类属性get方法上)

 

多对一单向(多方加单方的外键)

@ManyToOne(多方的单方类属性get)@JoinColumn(name="")指定加入字段名

 

一对多单向(单方加多方类型属性的集合)

@One2Many

@JoinColumn(name="groupid")//指定User表中生成与Group对应的字段名 注意此处与多对一配置方式不同(user表中可以不要事先定义grouId)

Hibernate默认将OneToMany理解为ManyToMany的特殊形式,如果不指定生成的外键列@JoinColumn(name="groupId"),则会默认生成多对多的关系,产生一张中间表。

 

一对多(多对一)双向关联

 配置规则:一般以多的一端为主,先配置多的一端

      在多的一端User端配置group

      @ManyToOne

      @JoinColumn(name="groupid")

      在一的一端Group端配置时,在users只需要加个mappedBy="groupid"

      @OneToMany(mappedBy="group")

以上的关系在数据库中都不会新增加关系表

 

下面多对多的关系就会在数据库中增加关系表

多对多单向关联

 private Set<Student> students = new HashSet<Student>();

       @ManyToMany //多对多关联 Teacher是主的一方 Student是附属的一方

       @JoinTable(name="t_s", //指定中间表表名

              joinColumns={@JoinColumn(name="teacherid")},//本类主键在中间表生成的

              对应字段名

              inverseJoinColumns={@JoinColumn(name="studentid")}//对方类主键在中间表

              生成的对应字段名

       )

       public Set<Student> getStudents(){……}

 

多对多双向关联

    Annotation:

    在Teacher这一端的students上配置

    @ManyToMany

       @JoinTable(name="t_s",

              joinColumns={@JoinColumn(name="teacher_id")},

              inverseJoinColumns={@JoinColumn(name="student_id")}

              )

    在Student一端的teachers只需要配置

    @ManyToMany(mappedBy="students")

    注意:mappedBy 与 @JoinTable等一类的配置要分开,不然表字段可能乱

 

总结1:单向配一方,双向配两方(mappedby);一配属性,多配集合;属性无表(一对多需要特殊指定),集合有表;

总结2:在设计数据库中中的外键参考关系是在关系映射中实现的。单向关系时,设置级联关系需要设到,制定关系的那个类中;双向关系需要在制定表属性的那个类中(没有mappedBy的那个类),如果将cascade设到mappedBy那个类中一对一双向则会出现没有id的错误,多对多会出现中间表没值的问题。

 

5、关系中的crud

cascade设置级联关系(主管cud三个操作),CascadeType有五个取值

    CascadeType取值

    ALL    Cascade all operations所有情况

    MERGE  Cascade merge operation合并(merge=save+update)

    PERSIST  Cascade persist operation存储 persist()

    REFRESH  Cascade refresh operation刷新

    REMOVE   Cascade remove operation删除

 

fetch也设置级联关系(主管r:load和get),fetchType有两个取值LAZY和EAGER

读单方的时候默认不会读出多方即fetch默认为lazy,如果需要读出多方的引用可以设置为eagerly

 

CRUD对关系映射总共种类太多,只需根据正常思维进行即可,不行就做个实验。

save的时候有个需要注意的情况:

铁律:双向关系在程序中要设定双向关联(即使两边都设了cascade=all,也要在多的对象里手动设置‘一’的字段,否则hibernate仅仅帮我们创建,不帮我们设置值)

 

delete的时候避免全部被删除的方法

三种方法可避免全部删除的情况:

1.  去掉@ManyToOne(cascade={CascadeType.All})设置;要注意fetch的设置,如果在

group中fetch设置为eager,则会报错(原因不详)

2.  直接写Hql语句执行删除;

3.  将user对象的group属性设为null,相当于打断User与Group间的关联,代码如下

              session.beginTransaction();

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

              user.setGroup(null);

              session.delete(user);

  session.getTransaction().commit();

删除一的那一方时,需要先循环制空集合中的元素的属性值(一般不这么做)

 

6、集合映射(不太重要)

将关系映射中单方保存多方的集合set换成list(@order可以排序)和map(@mapkey指定表的键名)

 

7、继承映射(不太重要,知道数据库表的设计形式,实现会查即可)

1 三种方式

a)一张总表SINGLE_TABLE---需要一个区分器,用于区分具体子类

b)    每个类分别一张表TABLE_PER_CLASS 

c)    每个子类一张表jOINED 

第一种方式所有的子类属性都存在一张表中,第二种每个子类单独一张表,主类在数据库中没有对应表,因此主类在实现时id必须要用table保证不重复;第三种,主类保存共同属性,子类保存独有属性,因为数据库中会生成主类的表,因此主类的id的生成,可以采用默认。

 

8.树状结构的设计(至关重要--它能把前面的知识综合起来)

设计流程

第一步、设计数据库表模型

第二步、设计面向对象的模型

private int id;

private String name;

private Org parent;//org 为本本身的类型

private Set<Org> children = new Set<Org>();

第三步、从对象映射到表(属性逐一映射)

hibernate-hibernate笔记总结-LMLPHP

怎么由parent和children映射到p_id?

分析对象间的关系

hibernate-hibernate笔记总结-LMLPHP

上图可知,当前的这个对象是parent的孩子,并且存在多对一的关系;当前这个对象还是children的父亲,存在一对多的关系。因此:

parent @manytoone(joincolumn("P_id"))

children @onetomany(mappedBy("parent"))

第四步、考虑CURD

read:读取一个集合需要用递归

hibernate-hibernate笔记总结-LMLPHP

onetomany的fetch设为EAGER会自动读取全部的孩子,但是打印需要用递归

(这边有个奇怪的现象level++,++level,level+1输入的值各不相同);如果fetch的值设为LAZY的话则每访问一个对象发一条sql语句。

 

9、学生,课程,分数

 

10、性能优化(主要为了面试)

例一、注意session.clear()的运用,尤其在不断分页循环的时候

a)    在一个大集合中进行遍历,遍历msg,取出其中的含有敏感字样的对象

b)    另外一种形式的内存泄露  (  //面试题:Java有内存泄漏吗?语法级别没有 但是可由java引起,例如:连接池不关闭,或io读取后不关闭)

 

例二、1+N问题:想取某一个对象的属性,这个对象和别的对象有关联,顺带发出多条sql语句,比如一对多或者多对一问题。

解决办法

a)    @ManyToOne(fetch=FetchType.LAZY)---fetch设为LAZY

       //fetch=FetchType.LAZY 解决N+1问题说明如下:

       //当多对一(@ManyToOne)已经设定属性" fetch=FetchType.LAZY "时

       //只有当需要时(如:t.getCategory().getName()时)才会去获取关联表中数据可以解决N+1问题

b)    @BatchSize---在被关联对象设置batchsize(单次取出的记录数)

       //@BatchSize 解决N+1问题说明如下:

       //在与查询表(此例中为Topic类)关联的表类(此例中为Category类)头处加@BatchSize(size=5)

       //表示每次可查出5条记录从而减少了select语句的个数

c)    join fetch(左连接列出目标表中所有的元祖,右连接列出参考表中的所有元祖,这边采用左连接)

       //join fetch 解决N+1问题说明如下:

       //修改hql语句为--"  from Topic t left join fetch t.category c  "

d)    QBC

       //QBC(Query By Criteria) 解决N+1问题说明如下:

       //使用QBC的 createCriteria(*.class)执行查询也可避免N+1问题

cd实际上为同一种

 

例三、list和iterator的区别

a)    list取所有

b)    iterate先取 ID,等用到的时候再根据ID来取对象

c)    session中list第二次发出,仍会到数据库査询

d)    iterate 第二次,到数据库中找到所有id,然后首先找session 级缓存取其它字段,没有再发sql语句

用法:第一次取得时候用list一次取出,以后要用用iterator直接上缓存中找

 

例四、一级缓存和二级缓存

a)    什么是缓存

b)    什么是一级缓存,session级别的缓存

c)    I什么是二级缓存,SessionFactory级别的缓存,可以跨越session存在--总的缓存

         i.   经常被访间

        ii.   改动不大不会经常改动

      iii.   数重有限

d)    打开二级缓存

         i.   hibernate.cfg.xml 设定:

<property

name= "cache.use_second_level_cache">true</property>

<property

name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

        ii.   @Cache注解(由hibernate扩展提供--注明那个类要使用二级缓存)

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)

 

注:使用EhCache二级缓存需要导入ehcache-1.2.3.jar及commons-logging-1.0.4.jar包

e)    load默认使用二级缓存,iterate默认使用二级缓存

f)    list默认往二级缓存加数据,但是查询的时候不使用

g)    如果要query用二级缓存,需打开查询缓存

<property name="cache.use_query_cache">true</property>

调用Query的setCachable (true)方法指明使用二级缓存

例如:session.createQuery("from Category").setCacheable(true).list();

h)    缓存算法:(纯为了面试)

         i.   LRU  LFU  FIFO

1.      Least Recently Used –最近很少被使用

2.      Least Frequently Used (命中率高低)

3.      First In First Out 按顺序替换

        ii.   memoryStoreEvictionPolicy = "LRU" (ehcache.xml中配置)

 

例五、事务并发处理

a)事务的概念:事务是访问并且可能更新的程序单元

b)特征:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。这四个特性简称为 ACID 特性。

c)事务并发可能出现的问题

    第一类丢失更新(lose update)-回滚覆盖了别人的更新

    脏读(dirty read)-读到别的更新没有保存的事务

    不可重复读(non-repeatble read)-一样的sql语句也读不到一样的数据

    第二类丢失更新(second lose update problem)-保存覆盖了别人的更新

    幻读(phantom read)-一样的sql语句读不到一样的值(对方在插入)

d)数据库自身的隔离机制(五种)    

hibernate-hibernate笔记总结-LMLPHP

e)设定hibernate的事务隔离级别(使用hibernate.connection.isolation配置 取值1、2、4、8),

hibernate.connection.isolation = 2(如果不设 默认依赖数据库本身的级别)

一般事务隔离级别都设置为2,这种隔离机制不允许脏读,但是不可重复读和幻读可以发生

可以用两种方式进行控制(悲观锁和乐观锁)

    i)悲观锁(依赖于数据库的锁)

                                         使用另一种load方法--load(xx.class , i , LockMode.Upgrade)

        a)  LockMode.None无锁的机制,Transaction结束时,切换到此模式

        b)  LockMode.read在査询的时候hibernate会自动获取锁

        c)  LockMode.write insert  update hibernate 会自动获取锁

        d)  以上3种锁的模式,是hibernate内部使用的(不需要设)

        e)  LockMode.UPGRADE_NOWAIT是 ORACLE 支持的锁的方式

        f)产生的sql语句是select for update

    

    ii)乐观锁(在数据库的表中加入version字段)  

        实体类中增加version属性(数据库也会对应生成该字段,初始值为0),并在其get方法前加

                    @Version注解,则在操作过程中没更新一次该行数据则version值加1,即可在事务提交前判断该数据是否被其他事务修改过.如果判断事务已被修改则会报错自己可行异常处理。

10-04 16:20