Hibernate初体验

1 什么是Hibernate
Hibernate是ORM框架,能够建立面向对象的模型和关系数据库数据模型之间的映射。使Java开发人员只需要操纵Java对象,而无需编写SQL语句,就可以将Java对象持久化到数据库中。ORM是Object-Relation Mapping的简称,对象-关系映射,指的是负责Java对象的持久化,封装数据访问细节。Hibernate位于分层架构的持久层,封装数据访问的细节。持久层的解决方案有JDBC,iBatis,Hibernate,Spring jdbcTemplate,EJB的CMP等等。

优势:
1)开发简洁。如果使用jdbc那么数据插入的话您要构造PreparedStatement或者构造SQL,查询的话您要处理ResultSet,这些代码很啰嗦,而使用Hibernate的话代码更加清晰简洁,Hibernate会自动解析配置文件生成相应的SQL语句。

2)跨数据库。对于一个特定的项目,对数据库移植性通常要求不高,一般选定一个数据库后不会轻易更改。但是对于一个通用的中间件产品来讲通常要求能够支持多种数据库,例如您要开发一个工作流产品,那么您面对的客户用哪种数据库产品都有可能,这就要求您开发的工作流引擎支持多种数据库,具备跨数据库特性。

劣势:
1)复杂的查询。对于复杂的查询通常要编写针对性的SQL,已达到最优化的效能,这时如果依靠ORM工具自动生成的SQL就不太现实了。

2 Hibernate之helloworld
下面以学生,班级,课程,这几个实体对象及对象之间的关系来简单的体验Hibernate。
helloworld程序介绍简单的课程的增、删、改、查。学生和班级的关系是N:1,即一个学生只能属于一个班级,一个班级可以有多个学生。学生和课程的关系是M:N,即一个学生可以学习多门课程,一个可能也可以有多个学生来学。

数据库脚本如下:
create table clazz(id int not null AUTO_INCREMENT,name varchar(20) not null,primary key(id))
create table student(id int not null AUTO_INCREMENT,name varchar(20) not null,clazzid int not null,primary key(id));
create table course(id int not null AUTO_INCREMENT,name varchar(20) not null ,primary key(id));
create table student_course(studentid int not null,courseid int not null,primary key(studentid,courseid));


先来最简单的单表操作吧。

Hibernate开发依赖的最小包列表:

antlr.jar cglib-full.jar asm.jar asm-attrs.jar commons-collections.jar commons-logging.jar ehcache.jar hibernate3.jar jta.jar dom4j.jar log4j.jar




主配置文件:
hibernate.cfg.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//HIbernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/demo</property>
        <property name="connection.username">root</property>
        <property name="connection.password">frank1234</property>
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="show_sql">true</property>
        <mapping resource="course.hbm.xml"/>
    </session-factory>
</hibernate-configuration>



Course对象:

public class Course implements Serializable {
    public Course(){}
    private int id;
    private String name;
   
    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 String toString(){
        return "id="+id+",name="+name;
    }
}


course配置文件:
course.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//HIbernate/HibernateMapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="org.frank1234.hibernate.helloworld.Course" table="course">
        <id name="id" column="id">
            <generator class="identity"/>
        </id>
        <property name="name" />
    </class>
</hibernate-mapping>

这个有4个要说明的地方:
1)需要将这个配置文件,添加到hibernate.cfg.xml中,否则Hibernate不认Course对象。
抛出异常: org.hibernate.MappingException: Unknown entity: org.frank1234.hibernate.helloworld.Course
2)<generator class="identity"/>表示使用数据库的自增字段。
3)由于Course对象和数据库course表的字段名称和类型一样,所以可以直接写成<property name="name" />
4)可以给hibernate-mapping添加属性package,这样后面的class属性就可以省去包路径了。
获取Session公共类:
HibernateUtil对象:
public class HiberanteUtil {
    public static SessionFactory sessionFactory = null;
    static{
        try{
            sessionFactory = new Configuration().configure().buildSessionFactory();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static final ThreadLocal session = new ThreadLocal();
    public static Session currentSession() throws HibernateException{
        Session s = (Session) session.get();
        if(s == null){
            s = sessionFactory.openSession();
            session.set(s);
        }
        return s;
    }
    public static void closeSession() throws HibernateException{
        Session s = (Session)session.get();
        if(s != null)
            s.close();
        session.set(null);
    }
}


1)其中SessionFactory是重量级的,创建和销毁都会消耗较大,他要读取配置文件信息,加载到缓存中,一般一个数据库一个,启动的时候加载一次就可以了。
2)Session对象是非线程安全的,是轻量级的,所以一个线程要创建一个Session对象,主要的增删改查接口都在Session对象中。


测试类:
CourseMain :
public class CourseMain {
    public static void main(String[] args) throws Exception{
        save();
//        delete();
//        update();
//        list();
    }
    private static void save(){
        Course course = new Course();
        course.setName("语文");
        Session session = HiberanteUtil.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            tx.begin();
            session.save(course);
            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
           HiberanteUtil.closeSession();
        }
    }
    private static void delete(){
        Course course = new Course();
        course.setId(3);
        Session session = HiberanteUtil.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            tx.begin();
            session.delete(course);
            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            HiberanteUtil.closeSession();
        }
    }
    private static void update(){
        Course course = new Course();
        course.setId(5);
        course.setName("英语");
        Session session = HiberanteUtil.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            tx.begin();
            session.update(course);
            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            HiberanteUtil.closeSession();
        }
    }
    private static void list(){
        Session session = HiberanteUtil.currentSession();
        try{
//            List list = session.createQuery("from Course").list();
            //            for(int i =0;i<list.size();i++){
//                System.out.println(list.get(i));
//            }
//            Course course = (Course)session.load(Course.class, 5);
//            System.out.println(course);
//            Course course = (Course) session.get(Course.class,5);
//            System.out.println(course);
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            HiberanteUtil.closeSession();
        }
    }
}


值得注意的有2个:
1)最后一定要记得关闭Session。
2)查询获取的几种方式。

3 1:N关联
笔者觉得这个必须得理解好,理解好了它M:N就很容易理解了。
班级和学生是1:N关系。
Clazz对象:
public class Clazz implements Serializable {

    private int id;
    private String name;
    private Set<Student> students = new HashSet<Student>();
    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 Set<Student> getStudents() {
        return students;
    }

    public void setStudents(Set<Student> students) {
        this.students = students;
    }

    public String toString(){
        return "id="+id+",name="+name;
    }
}

clazz中有private Set<Student> students = new HashSet<Student>();属性。

Student对象:
public class Student implements Serializable {
    public Student(){}
    private int id;
    private String name;
    private Clazz clazz;
    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 Clazz getClazz() {
        return clazz;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    public String toString(){
        return "id="+id+",name="+name;
    }
}

Student对象有   private Clazz clazz;属性,这是一个双向的关联关系。

但是数据库中只在Student表中保存了到Clazz表的clazzid字段,这点同对象模型不大一样,对象模型可以是多对多,一对多,甚至可以是双向的多对多,但是在数据库中只有单向的多对一。

clazz配置文件:
clazz.hbm.xml:
<hibernate-mapping>
    <class name="org.frank1234.hibernate.helloworld.onetomany.Clazz" table="clazz">
        <id name="id" >
            <generator class="identity"/>
        </id>
        <property name="name"/>
        <set name="students" table="student" >
            <key column="clazzid"/>
            <one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
        </set>
    </class>
</hibernate-mapping>


由于Clazz对象有一个到Student对象的Set属性。是1:N关系,
所以clazz.hbm.xml配置文件要配置如下属性:
<set name="students" table="student" >
            <key column="clazzid"/>
            <one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
        </set>

Student配置文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//HIbernate/HibernateMapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="org.frank1234.hibernate.helloworld.onetomany.Student" table="student">
        <id name="id" column="id">
            <generator class="identity"/>
        </id>
        <property name="name" />
        <many-to-one name="clazz"  class="org.frank1234.hibernate.helloworld.onetomany.Clazz" column="clazzid" />
    </class>
</hibernate-mapping>


因为Student对象有一个到Clazz对象的属性,所以其中要添加如下属性:
<many-to-one name="clazz"  class="org.frank1234.hibernate.helloworld.onetomany.Clazz" column="clazzid" />

保存的单元测试类:
public class OnetoManyMain {
    public static void main(String[] args) throws Exception{
        save();
    }
    private static void save() throws Exception{
        Student student1 = new Student();
        student1.setName("小红");
        Student student2 = new Student();
        student2.setName("小兰");

        Clazz clazz = new Clazz();
        clazz.setName("小班");
//既然设置的是双向关联,就同时更新对象的状态,解除关系的时候也一样,同时解除
        student1.setClazz(clazz);
        student2.setClazz(clazz);

        clazz.getStudents().add(student1);
        clazz.getStudents().add(student2);


        Session session = HiberanteUtil.currentSession();
        Transaction tx = session.beginTransaction();
        try{
            session.save(clazz);
            session.save(student1);
            session.save(student2);
            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            HiberanteUtil.closeSession();
        }
    }
}
输出为:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: update student set clazzid=? where id=?
Hibernate: update student set clazzid=? where id=?


几点说明:
1)由上面的输出可见,执行了insert 还执行了update多执行了一次,这样对性能必然有较大影响,在clazz.hbm.xml中设置上 inverse="true"
<set name="students" table="student" inverse="true" >
    <key column="clazzid"/>
    <one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>

变为了如下输出,没有了update:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)

inverse="true"的含义是Clazz端的关联只是Student端关联的镜像,当Hibernate检测到持久化对象Clazz和Student对象均发生变化时,仅按照Student对象的变化来更新数据库。

2)级联保存和更新。
注释掉测试类的如下部分。

session.save(clazz);
//            session.save(student1);
//            session.save(student2);

如果配置文件不变,则输出:
Hibernate: insert into clazz (name) values (?)

如果将clazz.hbm.xml设置为:
<set name="students" table="student" inverse="true" cascade="save-update" >
    <key column="clazzid"/>
    <one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>

那么输出为:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)

表示保存Clazz对象的同时也保存它关联的Student对象。

注释掉测试类的如下部分:
//            session.save(clazz);
            session.save(student1);
            session.save(student2);

如果不修改配置文件会抛出如下异常:
Hibernate: insert into student (name, clazzid) values (?, ?)
2015-01-03 17:19:30,200: Column 'clazzid' cannot be null
因为没有保存clazz,而student表的字段clazzid不允许为空。

如果将student.hbm.xml也加上cascade="save-update"
<many-to-one name="clazz"  class="org.frank1234.hibernate.helloworld.onetomany.Clazz" column="clazzid" cascade="save-update"/>

就可以正确输出:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)
哪个配置文件设置了cascade就可以对保存哪个对象的时候检查它的关联对象。

3)级联删除。
如果将clazz.hbm.xml配置上级联删除。
<set name="students" table="student" inverse="true" cascade="save-update, delete" >
    <key column="clazzid"/>
    <one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>

测试代码:
Clazz clazz16 = (Clazz)session.load(Clazz.class,16);
session.delete(clazz16);
输出:
Hibernate: select clazz0_.id as id2_0_, clazz0_.name as name2_0_ from clazz clazz0_ where clazz0_.id=?
Hibernate: select students0_.clazzid as clazzid1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.clazzid as clazzid1_0_ from student students0_ where students0_.clazzid=?
Hibernate: delete from student where id=?
Hibernate: delete from student where id=?
Hibernate: delete from clazz where id=?

Hibernate生成的查询SQL还真够奇葩的。

但是如果你仅仅创建了一个Clazz对象,则级联删除是不生效的。
例如
Clazz clazz17 = new Clazz();
clazz17.setId(17);
session.delete(clazz17);

输出:
Hibernate: delete from clazz where id=?

Student表并不做删除。


这些规则咋看起来挺复杂的,其实理解了以后也不难。
4 M:N关联
M:N 同1:N类似,这里就不贴代码了。
关键配置文件如下:
<set name="courses"
     table="student_course"
        >
    <key column="studentid"/>
    <many-to-many
            column="courseid"
            class="org.frank1234.hibernate.helloworld.Course"/>
</set>

猜你喜欢

转载自frank1234.iteye.com/blog/2172205