Hibernate注解方式处理映射关系

来源:互联网 发布:高清网络播放器 编辑:程序博客网 时间:2024/05/21 17:06

在hibernate中,通常配置对象关系映射关系有两种,一种是基于xml的方式,另一种是基于annotation的注解方式,熟话说,萝卜青菜,可有所爱,每个人都有自己喜欢的配置方式,我在试了这两种方式以后,发现使用annotation的方式可以更简介,所以这里就简单记录下通过annotation来配置各种映射关系,在hibernate4以后已经将annotation的jar包集成进来了,如果使用hibernate3的版本就需要引入annotation的jar包。

一、单对象操作

复制代码
@Entity  --->  如果我们当前这个bean要设置成实体对象,就需要加上Entity这个注解@Table(name="t_user")  ---->  设置数据库的表名public class User{    private int id;    private String username;    private String password;    private Date born;    private Date registerDate;    @Column(name="register_date")  --->  Column中的name属性对应了数据库的该字段名字,里面还有其他属性,例如length,nullable等等    public Date getRegisterDate()    {        return registerDate;    }    public void setRegisterDate(Date registerDate)    {        this.registerDate = registerDate;    }    @Id  --->  定义为数据库的主键ID  (建议不要在属性上引入注解,因为属性是private的,如果引入注解会破坏其封装特性,所以建议在getter方法上加入注解)    @GeneratedValue  ---->  ID的生成策略为自动生成      public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }   ............}
复制代码

最后只需要在hibernate.cfg.xml文件里面将该实体类加进去即可:

<!-- 基于annotation的配置 -->        <mapping class="com.xiaoluo.bean.User"/><!-- 基于hbm.xml配置文件 -->        <mapping resource="com/xiaoluo/bean/User.hbm.xml"/>

这样我们就可以写测试类来进行我们的CRUD操作了。

二、一对多的映射(one-to-many)

这里我们定义了两个实体类,一个是ClassRoom,一个是Student,这两者是一对多的关联关系。

ClassRoom类:

复制代码
@Entity@Table(name="t_classroom")public class ClassRoom{    private int id;    private String className;    private Set<Student> students;        public ClassRoom()    {        students = new HashSet<Student>();    }        public void addStudent(Student student)    {        students.add(student);    }    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getClassName()    {        return className;    }    public void setClassName(String className)    {        this.className = className;    }    @OneToMany(mappedBy="room")  --->  OneToMany指定了一对多的关系,mappedBy="room"指定了由多的那一方来维护关联关系,mappedBy指的是多的一方对1的这一方的依赖的属性,(注意:如果没有指定由谁来维护关联关系,则系统会给我们创建一张中间表)    @LazyCollection(LazyCollectionOption.EXTRA)  --->  LazyCollection属性设置成EXTRA指定了当如果查询数据的个数时候,只会发出一条 count(*)的语句,提高性能    public Set<Student> getStudents()    {        return students;    }    public void setStudents(Set<Student> students)    {        this.students = students;    }    }
复制代码

Student类:

复制代码
@Entity@Table(name="t_student")public class Student{    private int id;    private String name;    private int age;    private ClassRoom room;        @ManyToOne(fetch=FetchType.LAZY)  ---> ManyToOne指定了多对一的关系,fetch=FetchType.LAZY属性表示在多的那一方通过延迟加载的方式加载对象(默认不是延迟加载)    @JoinColumn(name="rid")  --->  通过 JoinColumn 的name属性指定了外键的名称 rid (注意:如果我们不通过JoinColum来指定外键的名称,系统会给我们声明一个名称)    public ClassRoom getRoom()    {        return room;    }    public void setRoom(ClassRoom room)    {        this.room = room;    }    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    public int getAge()    {        return age;    }    public void setAge(int age)    {        this.age = age;    }    }
复制代码

三、一对一映射(One-to-One)

一对一关系这里定义了一个Person对象以及一个IDCard对象

Person类:

复制代码
@Entity@Table(name="t_person")public class Person{    private int id;    private String name;    private IDCard card;        @OneToOne(mappedBy="person")  --->  指定了OneToOne的关联关系,mappedBy同样指定由对方来进行维护关联关系    public IDCard getCard()    {        return card;    }    public void setCard(IDCard card)    {        this.card = card;    }    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    }
复制代码

IDCard类:

复制代码
@Entity@Table(name="t_id_card")public class IDCard{    private int id;    private String no;    private Person person;        @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getNo()    {        return no;    }    public void setNo(String no)    {        this.no = no;    }    @OneToOne  --->  OnetoOne指定了一对一的关联关系,一对一中随便指定一方来维护映射关系,这里选择IDCard来进行维护    @JoinColumn(name="pid")  --->  指定外键的名字 pid    public Person getPerson()    {        return person;    }    public void setPerson(Person person)    {        this.person = person;    }}
复制代码

注意:在判断到底是谁维护关联关系时,可以通过查看外键,哪个实体类定义了外键,哪个类就负责维护关联关系

四、Many-to-Many映射(多对多映射关系)

多对多这里通常有两种处理方式,一种是通过建立一张中间表,然后由任一一个多的一方来维护关联关系,另一种就是将多对多拆分成两个一对多的关联关系

1.通过中间表由任一一个多的一方来维护关联关系

Teacher类:

复制代码
@Entity@Table(name="t_teacher")public class Teacher{    private int id;    private String name;    private Set<Course> courses;        public Teacher()    {        courses = new HashSet<Course>();    }    public void addCourse(Course course)    {        courses.add(course);    }        @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    @ManyToMany(mappedBy="teachers")  --->  表示由Course那一方来进行维护    public Set<Course> getCourses()    {        return courses;    }    public void setCourses(Set<Course> courses)    {        this.courses = courses;    }    }
复制代码

Course类:

复制代码
@Entity@Table(name="t_course")public class Course{    private int id;    private String name;    private Set<Teacher> teachers;        public Course()    {        teachers = new HashSet<Teacher>();    }    public void addTeacher(Teacher teacher)    {        teachers.add(teacher);    }    @ManyToMany   ---> ManyToMany指定多对多的关联关系    @JoinTable(name="t_teacher_course", joinColumns={ @JoinColumn(name="cid")},     inverseJoinColumns={ @JoinColumn(name = "tid") })  --->  因为多对多之间会通过一张中间表来维护两表直接的关系,所以通过 JoinTable 这个注解来声明,name就是指定了中间表的名字,JoinColumns是一个 @JoinColumn类型的数组,表示的是我这方在对方中的外键名称,我方是Course,所以在对方外键的名称就是 ridinverseJoinColumns也是一个 @JoinColumn类型的数组,表示的是对方在我这放中的外键名称,对方是Teacher,所以在我方外键的名称就是 tid    public Set<Teacher> getTeachers()    {        return teachers;    }    public void setTeachers(Set<Teacher> teachers)    {        this.teachers = teachers;    }    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }}
复制代码

2.将Many-to-Many拆分成两个One-to-Many的映射(Admin、Role、AdminRole)

Admin类:

复制代码
@Entity@Table(name="t_admin")public class Admin{    private int id;    private String name;    private Set<AdminRole> ars;    public Admin()    {        ars = new HashSet<AdminRole>();    }    public void add(AdminRole ar)    {        ars.add(ar);    }    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    @OneToMany(mappedBy="admin")  --->  OneToMany关联到了AdminRole这个类,由AdminRole这个类来维护多对一的关系,mappedBy="admin"    @LazyCollection(LazyCollectionOption.EXTRA)      public Set<AdminRole> getArs()    {        return ars;    }    public void setArs(Set<AdminRole> ars)    {        this.ars = ars;    }}
复制代码

Role类:

复制代码
@Entity@Table(name="t_role")public class Role{    private int id;    private String name;    private Set<AdminRole> ars;    public Role()    {        ars = new HashSet<AdminRole>();    }    public void add(AdminRole ar)    {        ars.add(ar);    }    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    @OneToMany(mappedBy="role")  --->  OneToMany指定了由AdminRole这个类来维护多对一的关联关系,mappedBy="role"    @LazyCollection(LazyCollectionOption.EXTRA)    public Set<AdminRole> getArs()    {        return ars;    }    public void setArs(Set<AdminRole> ars)    {        this.ars = ars;    }}
复制代码

AdminRole类:

复制代码
@Entity@Table(name="t_admin_role")public class AdminRole{    private int id;    private String name;    private Admin admin;    private Role role;    @Id    @GeneratedValue    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    @ManyToOne  --->  ManyToOne关联到Admin    @JoinColumn(name="aid")      public Admin getAdmin()    {        return admin;    }    public void setAdmin(Admin admin)    {        this.admin = admin;    }    @ManyToOne  --->      @JoinColumn(name="rid")    public Role getRole()    {        return role;    }    public void setRole(Role role)    {        this.role = role;    }}
复制代码

小技巧:通过hibernate来进行插入操作的时候,不管是一对多、一对一还是多对多,都只需要记住一点,在哪个实体类声明了外键,就由哪个类来维护关系,在保存数据时,总是先保存的是没有维护关联关系的那一方的数据,后保存维护了关联关系的那一方的数据,如:

复制代码
       Person p = new Person();            p.setName("xiaoluo");            session.save(p);                        IDCard card = new IDCard();            card.setNo("1111111111");            card.setPerson(p);            session.save(card);

复制代码


Hibernate的Session机制

 hibernate的缓存机制对应于两种缓存:session中的一级缓存,sessionFactoury中的二级缓存。

  缓存中的对象存在内存,如果数据量大大得时候,可以存在硬盘。

1.session缓存实现原理

 实现原理:Session缓存是由它的实现类SessionImpl中定义的一些集合属性构成的,原理是保证有一个引用在关联着某个持久化对象,保持它的生命周期不会结束。这样说来,

当我们User user=new User(),然后对user数值值赋值,使它对应数据库一条记录,但是由于其没有与session相关联,即session中没有引用关联这个对象,所以它是托管状态的。

2,使用flush清理缓存

   1)使用flush()可以清理掉缓存。但是一般不显示调用,显示调用会浪费系统资源(与数据库交互),一般由hibernate自己维护,尽量延迟到最后调用它。

   2)session.save(user);

        session.save(student)

        session.save(employee)

       System.out.println("=======");

        transaction.commit();

一般情况下事务提交的时候,hibernate才会调用flush()方法清理掉缓存。控制台显示sql语句:

 Hibernate: insert into user(name, dept_id, id) values(?, ?, ?)

 =======  

 Hibernate: insert into student(name, dept_id, skill, id) values(?, ?, ?, ?)
  Hibernate: insert into employee(name, dept_id, sell, id) values(?, ?, ?, ?)

原因解释:

先打印=======,说明在提交的时候,hibernate才清理缓存,将缓存中的数据与数据库中同步。但是为什么对user的插入出现在======之前呢?

这是因为user的主键是自增长的,user必须保存之后才能拿到主键。

 

3,缓存的作用

   1)更新对象时,减少与数据库打交道的次数

从上面的例子可以知道,缓存的存在,可以使对象的改变存在缓存中,当使用flush清理缓存时,可以对对象的所有改变进行批量操作(与数据库打一次交道),减少与数据库打交道的次数。

  2)查询对象时,减少与数据库打交道的次数

Customer c1 = (Customer)session.get(Customer.class, 1L);  

Customer c2 = (Customer)session.get(Customer.class, 1L);

hibernate比较聪明,由于缓存中有此对象了,所以第二次get()时不会从数据库中查询,而是直接使用缓存中的对象。(注意缓存要定时清理,与数据库同步)

 

Session清理缓存的时间点
Session通过setFlushMode()方法来设置清理缓存的时间点。FlushMode类定义了三种不同的清理模式:FlushMode.AUTO、FlushMode.COMMIT、FlushMode.NEVER
无论设置为哪种FulshMode,当session.flush()时,都会清理缓存。
若设置为FulshMode.NEVER,则即便transaction.commit()也不清理缓存,只能通过session.flush()来清理。
若设置为FulshMode.AUTO,则session.find(),session.iterate()(),transaction.commit(),session.flush()都将清理缓存。
所以,优先考虑使用FlusthMode.AUTO,这也是默认值。这也就是说大部分时候,无须手动执行session.flush()。

Hibernate实体关联

实体关联:

一般有两种方式:
第一种是annotation方式(注解方式):在hibernate配置文件中加入  
<mapping class="com.neusoft.hibernate.Student"/>
1定义嵌入类private Grade grade;//grade为枚举类型
//@Enumerated(EnumType.STRING)
@Enumerated(EnumType.ORDINAL)
2定义暂态属性
@Transient
public String getAddress() {
return address;
}
3定义序列
@SequenceGenerator( name="tea", sequenceName="teacher_seq")//GeneratedValue中调用
例如在对应id属性上加上@GeneratedValue(strategy= GenerationType.SEQUENCE, generator="tea")
4联合主键@Id(TeacherPk必需可序列化)
private TeacherPk pk
@EmbeddedId
public TeacherPk getPk() {
return pk;
}
public void setPk(TeacherPk pk) {
this.pk = pk;
}

@Entity//只能用在类上,用于定义一个Hibernate管理的bean
@Table(name="表名")//对应数据库表的名字,默认和类名相同的表
public class Student {


private int id;
private String name;
private double score;
private Date birthday;

private Set<Course> courses=new HashSet<Course>();
@ManyToMany
//定义联合表的名字和相应列
@JoinTable(name="s_c", joinColumns={@JoinColumn(name="s_id")},  
inverseJoinColumns={@JoinColumn(name="c_id")})
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
@Id//定义标示符
@GeneratedValue//用默认方式
//属性有1strategy:设定生成策略(有四种:table,sequence,identity,auto),默认为auto 属性2 generator

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
//@Column(name="列名")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//定义日期类型默认为TIMESTAMP另外可选值Date和Time
//@Temporal(TemporalType.TIME)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}


public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}


}
第二种通过xml方式:在hibernate配置文件中加入 

<mapping resource="com/neusoft/entity/Address.hbm.xml"/>

注意级联(<many-to-one cascade="true">)

  一对多(多对一):
  
   a)多对一单向关联
       annotation:在多方的实体类中添加@ManyToOne
        如果需要修改关联字段的名字,添加@JoinColumn(name="Class_ID")
       xml:
         在多方xml文件中添加
          <hibernate-mapping package="com.neusoft.entity">
 <class name="Person">
<id name="id">
<generator class="native"></generator>
 <!--自动生成id -->
</id>
<property name="name"></property>
<property name="age"></property>
<many-to-one name="address" class="Address"></many-to-one>
<!--many-to-one 和propertity层次相同-->
 </class>
</hibernate-mapping>
   b)一对多单向关联(会生成中间表)
        annotation:在一方中实体类中加入 private Set<Student> stus=new HashSet<Student>();
   在一方的实体类中添加@OneToMany           
        XML:
          在一方添加
             <hibernate-mapping package="com.neusoft.hibernate">
 <class name="Class" >
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<set name="stus">
<key column="c_id"></key>
<one-to-many class="Student"/>
</set>
<!-- set和propertity层次相同-->
 </class>
 
</hibernate-mapping>
   c)一对多(多对一)双向关联
    
        annotation:在多方添加@ManyToOne,
   在一方添加@OneToMany(mappedBy="c")由另一个many的c(c为many中一个属性)维护关系
        xml:在一方添加: 
                <set name="stus">
               <key column="c_id"></key>
                 <one-to-many class="Student"/>
                </set>
            在多方添加:
               <many-to-one name="c" class="Class" column="c_id"></many-to-one>


  d)多对多: 
   1单向关联
      annotation:在某一方添加@ManyToMany
      如果需要修改中间表的表名、列名
      @JoinTable(name="s_c", joinColumns={@JoinColumn(name="s_id")},  
inverseJoinColumns={@JoinColumn(name="c_id")})
      xml:
      <set name="courses" table="stu_course">
      <key  column="stu_id"></key>
     <many-to-many class="Course" column="CID"></many-to-many>
    </set>
     
   2双向关联:
      annotation:双方均添加@ManyToMany,在其中一方上添加 (mappedBy="courses") 
由另一个many的courses(courses为many中一个属性)维护关系      
      xml: inverse="true"关系由另一方维护默认为false关系由双方维护
        <set name="courses"  inverse="true" >
           <key  column="stu_id"></key>
           <many-to-many class="Course"></many-to-many>
       </set>
    
<set name="stus" table="stu_course" inverse="false">
<key column="CID"></key>
<many-to-many class="Student" column="stu_id"></many-to-many>
</set>


有关联关系的CRUD的级联:
添加:cascade={CascadeType.ALL}
查询:fetch
   一对多默认值是LAZY(懒执行,如果用不到数据不执行查询)
   多对一默认值是EAGER(渴望默认查询所有)
   千万不要双方均设EAGER


修改:
删除:双方均做了级联,级联删除,数据有可能都删掉
      解决方法:
        1)取消关联关系:将对应属性  setXxx(null);
        2)HQL:
          Query q=session.createQuery("delete from Student s where id=211");
q.executeUpdate();


关于Java的垃圾回收


垃圾收集算法的核心思想

  Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。

  垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。

  2.触发主GC(Garbage Collector)的条件

  JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对系统影响很明显。总的来说,有两个条件会触发主GC:

  ①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。

  ②Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。

  由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。

  3.减少GC开销的措施

  根据上述GC的机制,程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。具体措施包括以下几个方面:

  (1)不要显式调用System.gc()

  此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。

  (2)尽量减少临时对象的使用

  临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

  (3)对象不用时最好显式置为Null

  一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

  (4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章JAVA中String与StringBuffer)

  由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

  (5)能用基本类型如Int,Long,就不用Integer,Long对象

  基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

  (6)尽量少用静态对象变量

  静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

  (7)分散对象创建或删除的时间

  集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。


0 0