Hibernate

 

基本配置和操作

1)配置hibernate-cfg.xml

<hibernate-configuration>

    <session-factory>

    <!-- hibernate的方言,用来确定连接的数据库 -->

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

    <!-- 数据库的连接类 -->

       <property name="hibernate.connection.driver_class">

com.mysql.jdbc.Driver

</property>

    <!-- 数据库的连接字符串和用户名密码 -->

       <property name="hibernate.connection.url">

jdbc:mysql://localhost:3306/itat_hibernate

</property>

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

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

123456

</property>

    <!-- 在使用hibernate时会显示相应的SQL -->

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

    <!-- 会自动完成类到数据表的转换 -->

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

    <!-- 加入实体类的映射文件 -->

       <mapping resource="org/zttc/itat/model/User.hbm.xml"/>

       <mapping resource="org/zttc/itat/model/Book.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

2)创建实体映射文件:

(略)

3)代码编写:创建SessionFatory

SessionFactory是线程安全的,所以SessionFatory要基于单利模式来创建。

    @Test

    public void test01() {

       Configuration cfg = new Configuration().configure();

       //cfg.buildSessionFactory();

//hibernate3中都是使用该种方法创建,但是在4中被禁用了,使用以下方法

       ServiceRegistry serviceRegistry =

new ServiceRegistryBuilder()

                         .applySettings(cfg.getProperties()).buildServiceRegistry();

       SessionFactory factory =

                       cfg.buildSessionFactory(serviceRegistry);

       Session session = null;

       try {

           session = factory.openSession();

           //开启事务

           session.beginTransaction();

           User u = new User();

           u.setNickname("张三");

           session.save(u);

           //提交事务

           session.getTransaction().commit();

       } catch (HibernateException e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           if(session!=null) session.close();

       }

    }

【总结】:

临时状态 à 持久状态:做save操作;

离线状态 à 临时状态:做delete操作;

有时候不知道对象是离线还是临时状态,这时候可以调用sessionsaveandupdate()方法

Hibernate的状态

l  Transient:是指临时状态,即new出一个对象,但是数据库中还没有这个对象;

l  Persistentnew一个对象并save后就是Persistent状态(持久化状态);

l  Detached:当session关闭并且对象保存在了数据库中,这时候就是离线状态。

关于Persistent

    @Test

    public void testTransient() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           u.setBorn(sdf.parse("1976-2-3"));

           u.setUsername("zxl");

           u.setNickname("赵晓六");

           u.setPassword("123");

//以上u就是Transient(瞬时状态),表示未被session管理且数据库中没有

//save之后,被session所管理,且数据库中已经存在,此时就是Persistent状态

session.save(u); //u变成持久化状态,被sessio管理,正常保存

u.setNickname("赵晓其");//更新缓存内容,提交时候更新数据库

//如果没有提交那么上面的(从save开始)就是持久化状态,session中有完整的对象的//信息处于持久化状态的对象设置了新数据,提交的时候就会做更新操作

           session.getTransaction().commit();//导致更新操作执行

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

继续解释:

    @Test

    public void testPersistent02() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           u.setBorn(sdf.parse("1976-2-3"));

           u.setUsername("zxq");

           u.setNickname("赵晓八");

           u.setPassword("123");

//save后缓存(session)保存这个u对象信息

           session.save(u);

           u.setPassword("222");//不会导致缓存中内容变化

           //缓存内容没有变化,所以不会执行(这部分有些不明白??)

           session.save(u);

           u.setNickname("赵晓吧");

           //下面语句不执行(持久化状态的update不执行,提交时候才会执行update

           session.update(u);

           u.setBorn(sdf.parse("1988-12-22"));

           //没有意义

           session.update(u);

            //执行update

           session.getTransaction().commit();

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

load出来的对象也是持久化对象:

    //此时uPersistent

    User u = (User)session.load(User.class, 10);

    @Test

    public void testDetach05() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           //u.setId(110);

           u.setNickname("abc");

           //如果u是离线状态就执行update操作,如果是瞬时状态就执行Save操作

           //但是注意:该方法并不常用

           session.saveOrUpdate(u);

           session.getTransaction().commit();

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

懒加载

load()get()

当使用load加载一个对象的时候,如果取出的对象没有使用,那么就不会发sql——这既是Hibernate的“延迟加载(懒加载)”。且,该对象其实是一个代理对象,该对象中只有一个id的值,若打印该id也不会发sql(不用从数据库中取)。

    @Test

    public void testLazy02() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

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

           //此时一条sql都没有发,这就是hibernate的延迟加载

/**延迟加载指的就是,当完成load操作之后,并不会马山发出sql语句,只有在使用到该对象时才会发出sql,当完成load之后,u其实是一个代理对象,这个代理对象中仅仅只有一个id的值*/

           //此时不会发sql

           System.out.println(u.getId());

           //nickname没有值,必须去数据库中取。所以会发出sql

           System.out.println(u.getNickname());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

【例】:

    @Test

    public void testLazy04() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           //get是只要一执行就会发出sqlget没有延迟加载

           User u = (User)session.get(User.class, 101);

//get的时候发现数据库中并没有该数据,所以unull,打印u.getId(),会抛出空指针异常,如果是使用load的话,会打印101(代理对象存储了id的值)

           System.out.println(u.getId());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

【例】:

    @Test

    public void testLazy05() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           User u = (User)session.load(User.class, 101);

           //由于id已经存在,所以不会抛出异常

           System.out.println(u.getId());

           //此时会去数据库中取数据,发现没有这个对象,但是u并不是空,所以会抛出ObjectNotFoundException

           System.out.println(u.getNickname());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

开发中的懒加载的问题

【情景演示】:

Dao中的load方法:

public User load(int id) {

    Session session = null;

    User u = null;

    try {

       session = HibernateUtil.openSession();

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

    } catch (Exception e) {

       e.printStackTrace();

    } finally {

       HibernateUtil.close(session);

    }

    return u; //这里返回的仅是一个代理对象,且此时session已经关闭

}

控制器中调用这个方法用来在页面显示:

@Test

public void testLazyQuestion() {

    UserDao ud = new UserDao();

    /*由于使用了loadload是有延迟加载的,返回的时候的u是一个代理对象,仅仅只有一个id

     * 但是在返回的时候Session已经被关闭了,此时当需要使用u的其他属性时就需要去数据库中

     * 但是Session关闭了,所以就抛出org.hibernate.LazyInitializationException no session

     */

    User u = ud.load(2);

    System.out.println(u.getUsername());

}

解决方法:

方法1:使用get方法;

方法2:在过滤器中获取session放到context中,然后在过滤器中关闭。Spring中使用openSessionInView

ID生成策略

<hibernate-mapping package="org.zttc.itat.model">

    <class name="Book" table="t_book">

        <id name="id">

            // assigned:表示要手动指定主键

            // uuid:表示自动生成一个uuid字符串,所以主键必须是String

            // native:也是自动生成,生成的是123等有序列,所以检索会快些

           // native的缺点是每次插入一条数据库都会查询一下数据库,开发中查询

           // 操作会比较多,所以开发中会使用native

            <generator class="uuid"/>

        </id>

        <property name="name"/>

        <property name="price"/>

        <property name="bookPage" column="book_page" type="int"/>

    </class>

</hibernate-mapping>

实体表映射-使用xml

单张表映射

【提示】:

l  实体字段名,比如bookName,映射到数据库最好映射成book_name

<property name="bookPage" column="book_page" type="int"/>

l  在实际开发中,一般不会使用Hibernate来生成表,一般的开发中,都是首先用PowerDesigner设计好数据库,然后生成SQL语句,然后用这个SQL语句来生成表。

这样的好处是,在开发数据库的时候可以为数据库创建索引和视图。

l  对效率要求不高的项目适合使用Hibernate

l  对效率比较高的项目,如果使用Hibernate的话,若想要效率也高,则可以这么做:增删改使用Hibernate,而查询使用原生态的SQL

l  Hibernate语句时,比如清楚一句Hibernate代码执行了几条SQL语句,否则性能可能大降!

关系映射

多对一(学生和班级)

l  关系一般是由“多”的一方来维护;

<!-- inverse=true表示不在自己这一端维护关系 -->

<set name="stus" lazy="extra" inverse="true">

    <key column="cid"/>

    <one-to-many class="Student"/>

</set>

l  添加的时候,一般都是先添加“一”的一方;

l  懒加载会多发一条SQL语句(抓取策略可以只发一条SQL语句……?)

l  关联(cascade:不要在“多”的一方使用关联。因为,比如,删除多的一方的一条记录,同时会删除“一”的一方对应的记录,但是如果“一”的一方的该条记录又关联了其他记录的话,就不能删除了。使用cascade的情况一般是:在“一”的一方删除时使用,特殊情况才会在add上做级联。

一对多(留言和评论)

        <property name="title"/>

        <property name="content"/>

<!-- 使用了lazy=extra之后会稍微智能一些,会根据去的值的不同来判断是调用count和获取投影 ,比如取出总条数一般情况下会取出所有数据的list然后计算listsize,但是用了extra后就会直接count(*)-->

        <set name="comments" lazy="extra">

        <!-- key用来指定在对方的外键的名称 -->

            <key column="mid"/>

        <!-- class用来设置列表中的对象类型 -->

            <one-to-many class="Comment"/>

        </set>

    </class>

双向关联(班级和学生)

(略)

实体表映射-使用注解

l  一个Hibernate项目是使用注解还是Annotation呢?

——小项目使用注解,大项目(一两百万行代码的)使用xml。因为大项目的类比较多,看xml文件更方便。大项目不用外键的方式????

多对一(学生和教室)

@Entity

@Table(name="t_classroom")

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

// mappedBy表示关系由对方的classroom属性维护,如果没有mappedBy的话,会生

// 成中间表

    @OneToMany(mappedBy="classroom")

    // 使用extrasql语句会较智能,比如计算条数会自动使用count(*)

    @LazyCollection(LazyCollectionOption.EXTRA)

    public Set<Student> getStus() {

       return stus;

    }

    public void setStus(Set<Student> stus) {

       this.stus = stus;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

}

@Entity

@Table(name="t_student")

public class Student {

    private int id;

    private String name;

    private String no;

    private Classroom classroom;

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    // fetch的默认值是EAGER,表示不使用懒加载,一条语句就能把相关的数据查询

    // 出来;如果值设置为LAZY的话,就会使用懒加载,会多发一条sql

    @ManyToOne(fetch=FetchType.LAZY)

    // 在自己这边生成的外键名是cid,哪一方维护关系就在哪一方加JoinColumn(就是

// 外键的意思)一般都由“多”的一方维护关系,即在自己这方配置对方的外键。

    @JoinColumn(name="cid") 

    public Classroom getClassroom() {

       return classroom;

    }

}

一对多(学生和教室)

(主要内容就是使用Extra的问题,查询属于教室的学生的数目)。

一对一(人和身份证号)

多对多

以上都不需要中间表,而这个“多对多”关系需要中间表。

【提示】:

多对多关系开发中一般不用,而是使用两个一对多关系来代替。

@Entity

@Table(name="t_admin")

public class Admin {

    private int id;

    private String name;

    private Set<Role> roles;

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    // 设置由对方维护关系

    @ManyToMany(mappedBy="admins")

    public Set<Role> getRoles() {

       return roles;

    }

}

@Entity

@Table(name="t_role")

public class Role {

    private int id;

    private String name;

    private Set<Admin> admins;

   

    public Role() {

       admins = new HashSet<Admin>();

    }

    public void add(Admin admin) {

       admins.add(admin);

    }

    @ManyToMany(mappedBy="admins")

    public Set<Role> getRoles() {

       return roles;

    }

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    // joinColumns:用来设置自己在中间表的外键名;inverseJoinColumns用来设置

    // 对方在中间表的外键名

    @ManyToMany

    @JoinTable(name="t_role_admin",

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

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

    public Set<Admin> getAdmins() {

       return admins;

    }

    public void setAdmins(Set<Admin> admins) {

       this.admins = admins;

    }

}

专业-教室 -学生

基于Annotation

专业-教室:1对多

教室-学生:1对多

@Entity

@Table(name="t_stu")

//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

    public void setVersion(int version) {

       this.version = version;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    //LAZY就是XML中的select,EAGER就表示XML中的join

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="cid")

    public Classroom getClassroom() {

       return classroom;

    }

}

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    //由多的一方维护关系

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.SUBSELECT)

    public Set<Student> getStus() {

       return stus;

    }  

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

@Entity

@Table(name="t_special")

public class Special {

    private int id;

    private String name;

    private String type;

    private Set<Classroom> clas;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    @OneToMany(mappedBy="special")

    @LazyCollection(LazyCollectionOption.EXTRA)

    public Set<Classroom> getClas() {

       return clas;

    }

}

基于XML

略,重点提示:

(1)       设置<set>的时候,使用inverse,表示不在自己这边维护关系,如下配置classroom

<many-to-one name="special" column="spe_id" fetch="join"/>

<set name="stus" inverse="true" lazy="extra" fetch="subselect">

    <key column="cid"/>

    <one-to-many class="Student"/>

</set>

基于HQL的查询(重要)

/*基于?的条件的查询,特别注意:jdbc设置参数的最小下标是1hibernate0*/

List<Student> stus = session

.createQuery("from Student where name like ?")

                           .setParameter(0, "%%")

                                   .list();

/*还可以基于别名进行查询,使用:xxx来说明别名的名称,基于列表查询一定要用别名*/

List<Student> stus = session

.createQuery("from Student where name like :name and sex=:sex")

                  .setParameter("name", "%%")

                       .setParameter("sex", "")

                            .list();

常用HQL的查询(重要)

/*使用uniqueResult可以返回唯一的一个值注意返回值类型(uniqueResult的返回类型是Object*/

Long stus = (Long)session

.createQuery("select count(*)

 from Student where name like :name and sex=:sex")

                         .setParameter("name", "%%")

                            .setParameter("sex", "")

                                .uniqueResult();

/*基于投影的查询,通过在列表中存储一个对象的数组,注意返回值类型

基于投影的查询还可以基于DTO——DTO使用来传输数据*/

List<Object[]> stus = session

.createQuery("select stu.sex,

count(*) from Student stu group by stu.sex").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]);

}

【我的总结】:

group by的作用,select后的显示、计算是按照group by后进行的(group by后的记录相当于一张张字表,对这些字表进行select操作)。

/*如果对象中相应的导航对象,可以直接导航完成查询*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.classroom.name=? and stu.name like ?")

                           .setParameter(0, "计算机教育班")

.setParameter(1, "%%")

.list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

/*可以使用in来设置基于列表的查询,此处的查询需要使用别名进行查询。

特别注意:使用in的查询必须在其他的查询之后*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.name like ? and stu.classroom.id in (:clas)")

                  .setParameter(0, "%%")

.setParameterList("clas", new Integer[]{1,2})

                            .list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

【提示】:

基于列表查询要用别名:name

/*可以通过is null来查询为空的对象,sql一样不能使用=来查询null的对象*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.classroom is null")

                            .setFirstResult(0)

.setMaxResults(15)

                                          .list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

/*使用对象的导航(内部使用Cross JOIN<笛卡尔积>)可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOINJoin = Inner Join)来完成连接*/

List<Student> stus = ession

.createQuery("select stu from Student stu

left join stu.classroom cla where cla.id=2")

                             .setFirstResult(0)

.setMaxResults(15).list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

HQL的连接查询(重要)

连接(Join)有三种:

(1)      left join

以下sqlt1left)中不符合条件(t1.id=t2.cid)的也打印出来,无值的字段为null

           select * from t_classroom t1

left join t_stu t2 on(t1.id=t2.cid)

(2)      right join:同理(1

(3)      inner join(即join):两边都有的才显示。

左、右连接常用语统计数据。

// 以下sql语句显示不正常,本意是想显示每个班级的学生人数,但是没有学生的班级也会显示人数:

select t1.name,count(*) from t_classroom t1

join t_stu t2

on(t1.id=t2.cid) group by t1.id

// 改成以下sql

select t1.name,count(t2.cid) from t_classroom t1

left join t_stu t2

on(t1.id=t2.cid) group by t1.id

l  查询每个班级的男生和女生的人数:

select t1.name,t2.sex,count(t2.cid)

from t_classroom t1

left join t_stu t2

on(t1.id=t2.cid)

group by t1.id,t2.sex

【提示】:

Hibernate的导航连接内部使用CROSS JOIN(全连接),效率很低!

l  查询2班的所有学生:

/*使用对象的导航可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOIN来完成连接*/

List<Student> stus = session

                  .createQuery("select stu from Student stu

left join stu.classroom cla where cla.id=2").setFirstResult(0)

.setMaxResults(15).list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

l  查询每个班的人数

/*使用对象的导航可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOIN来完成连接

思考:如果把classroom放前面呢?如果使用count(*)会如何?*/

List<Object[]> stus = session

                  .createQuery("select

cla.name,count(stu.classroom.id)

from Student stu

right join stu.classroom cla

group by cla.id").list();

for(Object[] stu:stus) {

System.out.println(stu[0]+","+stu[1]);

}

l  查询学生的信息、包括所在班级(班级名)、专业名

SQL_1

List<Ojbet[]> stus =

session.createQuery(“select stu.id,stu.name,stu.sex,cla.name,spe.name

from Student stu

left join stu.classroom cla

left join cla.special spe”).list();

for( Object[] obj:stus ){

    ……

}

之前查询的字段,然后获取信息的时候,都是用Object[]接收返回值然后通过Object的索引获取索引值的但是实际开发中如果这样做就很不方便(如SQL_1,因为一一的取出索引值。这时候就可以使用DTO了,专门用来传输数据,没有任何存储意义。

>>>使用DTO解决以上问题:

新建一个DTO对象,要从数据库查询的哪些字段名就添加哪些属性名:

public class StudentDto {

    private int sid;

    private String sname;

    private String sex;

    private String cname;

    private String spename;

    get()…;

    set()…;

}

HQL查询:

/*直接可以使用new XXObject完成查询,注意,一定要加上Object的完整包名这里使用的new XX,必须在对象中加入相应的构造函数*/

List<StudentDto> stus = session

                  .createQuery("select

new org.zttc.itat.model.StudentDto(

stu.id as sid,stu.name as sname,stu.sex as

sex,cla.name as cname,spe.name as spename)

                    from Student stu

left join stu.classroom cla

left join cla.special spe").list();

for(StudentDto stu:stus) {        

System.out.println(stu.getSid()

+","+stu.getSname()+","+stu.getSex()+","

+stu.getCname()+","+stu.getSpename());

}

【提示】:

Criteria基本上在开发中不用!(我们项目组竟然用了……)

l  统计每个专业的学生的人数:

List<Object[]> stus = session.createQuery("

select spe.name,count(stu.classroom.special.id)

from Student stu

right join

stu.classroom.special spe

group by spe");

l  统计人数大于150的专业:

/*having是为group来设置条件的*/

List<Object[]> stus = session.createQuery("

select spe.name,

(count(stu.classroom.special.id))

from Student stu

right join

stu.classroom.special spe

group by spe

having count(stu.classroom.special.id)>150").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]);

}

l  统计每个专业的男女生人数:

/*having是为group来设置条件的*/

List<Object[]> stus = session.createQuery("

select stu.sex,spe.name

(count(stu.classroom.special.id))

from Student stu

right join

stu.classroom.special spe

group by spe,stu.sex").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]+","+obj[2]);

}

抓取策略(XML-对多的一方抓取)

基本文件

使用映射文件的情况下:

/*

(1)默认情况会发出3SQL语句,一条取student,一条取Classroom,一条取Special

(3)通过设置XML中的<many-to-one name="classroom" column="cid" fetch="join"/>可以完成对抓取的设置

(4)如果使用Annotation的话,Hibernate会自动使用Join查询,只发出一条SQL

使用Annotation默认就是基于join抓取的,所以只会发出一条sql*/

session = HibernateUtil.openSession();

Student stu = (Student)session.load(Student.class, 1);  System.out.println(stu.getName()+","+stu.getClassroom().getName()+","+stu.getClassroom().getSpecial().getName());

如果不想发出3SQL,可以在映射文件中这么配置,使用fetch=”join”(默认值是select

<class name="Student" table="t_stu">

    <!-- <cache usage="read-only"/> -->

    <id name="id">

        <generator class="native"/>

    </id>

     <version name="version"/>

    <property name="name"/>

    <property name="sex"/>

    <many-to-one name="classroom" column="cid" fetch="join"/>

</class>

专业是通过classroom来获取的,所以,同时要在classroom中配置fetch=”join”

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【警告】:

以上使用fetch=”join”会有这样的问题,就是,即使我query中不查询班级和专业,发出的SQL也会去查专业和学生!也就是说,使用了fetch=”join”后,延迟加载就不生效了!

/*使用fetch=join虽然可以将关联对象抓取,但是如果不使用关联对象也会一并查询出来这样会占用相应的内存*/

session = HibernateUtil.openSession();

Student stu = (Student)session.load(Student.class, 1);

//使用fetch=”join”,延迟加载就失效

System.out.println(stu.getName());

【对于Annotation的问题】:

因为在使用Annotation的情况下,默认就是基于join的抓取策略,所以,比如,我们只查学生的话,同时会把班级、专业信息都查出来,即把关联的信息都查出来。

实例以及解决方案

【基于“多”的一方进行抓取:实例及解决方案】

session = HibernateUtil.openSession();

/**

 * XML中配置了fetch=join仅仅只是对load的对象有用,对HQL中查询的对象无用,

 * 所以此时会发出查询班级的SQL,解决的这个SQL的问题有两种方案,

 * 1)设置对象的抓取的batch-size

 * 2)在HQL中使用fecth来指定抓取

 * 特别注意,如果使用了join fetch就无法使用count(*)

 */

List<Student> stus = session.

createQuery("select stu from Student stu).list();

for(Student stu:stus) {

    System.out.println(stu.getName()+","+stu.getClassroom());

}

XML配置:

// batch-size:值默认为1,这里设置成20,即一次抓取20个班;

// 缺点是占用内存较大,且session关闭后数据就丢失(??),这里可以看出

// Hibernate的一个缺点就是很容易影响性能

<class name="Classroom" table="t_classroom" batch-size="20 ">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

为了解决batch-size的问题,我们可以在xml中不使用batch-size,在HQL使用fetchy来指定抓取

// 没有fetch进行查询会发出多条sql,有了fetch后只发一条

List<Student> stus = session.

createQuery("select stu from

Student stu join fetch stu.classroom").list();

for(Student stu:stus) {

    System.out.println(stu.getName()+","+stu.getClassroom());

}

【提示】:

因为HQL使用了join fetch,就无法使用count(*)了,如果还想使用count(*),可以将HQL格式化,将fetch替换成空。

【小结】:

抓取策略有两种:

(1)       batch-size:在被抓取方的xml中配置,可以一次抓取大量的数据;

(2)       HQL中使用fetch

抓取策略(XML-对一的一方抓取)

针对“一”的抓取一般不会使用双向关联,只会做“单向关联”。

【实例1

l  取出班级和班级对应的学生

session = HibernateUtil.openSession();

Classroom cla = (Classroom)session.load(Classroom.class, 1);

/*此时会在发出一条SQLclass对象*/

System.out.println(cla.getName());

/*取学生姓名的时候会再发一条SQL取学生对象,可以在Classroom的配置文件中对学生属性添加fetch=”join”,就会用一条sql完成相关查询了*/

for(Student stu:cla.getStus()) {

    System.out.println(stu.getName());

}

classroomxml配置文件中加入fetch=join

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="join">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【实例2

l  取出一组班级

classroomxml配置还是使用实例1中的配置内容。)

List<Classroom> clas = session.createQuery("from Classroom").list();

for(Classroom cla:clas) {

    System.out.println(cla.getName());

/*对于通过HQL取班级列表且获取相应学生列表时,fecth=join就无效了,这里,每次查询一个班级就会发出一个SQL语句,这样肯定有问题,解决方法:

1)第一种方案可以设置setbatch-size来完成批量的抓取

2)可以设置fetch=subselect,使用subselect会完成根据查询出来的班级进行一次对学生对象的子查询(推荐)*/

    for(Student stu:cla.getStus()) {

       System.out.print(stu.getName());

    }

}

l  方法(1)的xml配置:

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="join" batch-size="400">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【说明】:

这里设置batch-size=400后,虽然发出的SQL大大减少,但是,即使我们实际使用10条,Hibernate也会查询出400条,这样很耗内存!(实际怎么使用还是要根据项目实际情况来定)。

l  方法(2)的xml配置:

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect" >

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【说明】:

这中方法只发出两条sql:查班级和查学生(subselect)。

另:这里好像<many-to-one>fetch只有两个选项:joinselect,而<set>fetch多一个subselect

抓取策略(注解-对多的一方抓取)

Student实体配置:

@Entity

@Table(name="t_stu")

//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

    public void setVersion(int version) {

       this.version = version;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

   

    //LAZY就是XML中的select,EAGER就表示XML中的join

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="cid")

    public Classroom getClassroom() {

       return classroom;

    }

}

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.SUBSELECT)

    public Set<Student> getStus() {

       return stus;

    }

    //使用注解默认都是join查询的,所以这里的配置默认是:

    //fetch=FetchType.EAGER所以这里默认会将专业信息也查询出来

    //所以这里要配置成LAZY

    //但是,如果配置成LAZY后,如果去班级再取专业,就会多发一条SQL查询专业

    //这个的解决方法有有两种:

//1)在HQL中使用fetch join cla.special

//2

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

方法(1)的HQL

/* 基于Annotation由于默认的many-to-one的抓取策略是EAGER的,所以当抓取classroom时会自动发出多条SQL去查询相应的Special;

此时:

1)可以通过join fecth继续完成对关联的抓取(这样SQL很容易变得很长,尤其是大项目中)

2)直接将关联对象的fecth设置为LAZY(就是说不希望查出关联对象),但是(所以)使用LAZY所带来的问题是在查询关联对象时需要发出相应的SQL,很多时候也会影响效率(要查出关联对象的话还是要发出SQL的)

(效率是第一重要的!)*/

List<Student> stus = session.createQuery("select stu from " +

       "Student stu join fetch stu.classroom cla join fetch cla.special").list();

for(Student stu:stus) {

    System.out.println(stu.getName()+","+stu.getClassroom());

}

抓取策略(注解-对一的一方抓取)

【实例1

session = HibernateUtil.openSession();

Classroom cla = (Classroom)session.load(Classroom.class, 1);

System.out.println(cla.getName());

/*此时会在发出两条SQL(取班级、取学生)取学生对象*/

for(Student stu:cla.getStus()) {

    System.out.println(stu.getName());

}

只发出一条SQL的方法,配置classroom实体:

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.JOIN) //方法1:使用join

@Fetch(FetchMode.SUBSELECT) //方法2:使用subselect(同时会查专业,另外,即使只查一个班级也会发出两条sql语句进行查班级和查学生)

    public Set<Student> getStus() {

       return stus;

    }

    @ManyToOne(fetch=FetchType.LAZY) //使用lazy不关联不查询

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

缓存(基于XML)(问题较多)

(一级缓存)N+1问题

实例1:基本查询:

/*此时会发出一条sql取出所有的学生信息*/

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

实例2:使用Iterator展示N+1问题:

/*如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql。在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息这就是典型的N+1问题。

存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来,而使用iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取*/

session = HibernateUtil.openSession();

Iterator<Student> stus = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).iterate();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

实例3:用Iterator从一级缓存(session)中取值

/*此时会发出一条sql取出所有的学生信息*/

//发出查classsql

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*使用iterate仅仅只会取Studentid,此时Student的数据已经在缓存中,所以不会在出现N+1*/

//发出查idsql

stus = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).iterate();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

实例4:实例3的变形(注意和实例3对比)

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*会发出SQL取完整的学生对象,占用内存相对较多*/

ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

【小结】:

(1)       多次在session中查询使用Iterato(此情况很少!);

(2)       一级缓存释义;

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*id=1Student对象已经在session的缓存(一级缓存)中,此时就不会发sql去取Student*/

Student stu = (Student)session.load(Student.class, 1);

System.out.println(stu.getName()+",---");

(3)       一级缓存中session关闭,一级缓存就关闭。

二级缓存

【提示】:

(1)       二级缓存是sessionFactory级别的缓存;

(2)       二级缓存是优化比较好、使用比较多的缓存。

实例1:演示一级缓存

@SuppressWarnings("unchecked")

public class TestCache {

    @Test

    public void test01() {

       Session session = null;

       try {

           /*此时会发出一条sql取出所有的学生信息*/

           session = HibernateUtil.openSession();

           List<Student> ls = session.createQuery("from Student")

                  .setFirstResult(0).setMaxResults(50).list();

           Iterator<Student> stus = ls.iterator();

           for(;stus.hasNext();) {

              Student stu = stus.next();

              System.out.println(stu.getName());

           }

/*id=1Student对象已经在session的缓存(一级缓存)中,此时就不会发sql去取Student*/

           Student stu = (Student)session.load(Student.class, 1);

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

       try {

           session = HibernateUtil.openSession();

/*上一个Session已经关闭,此时又得重新取Student,这里会再发一条sql*/

           Student stu = (Student)session.load(Student.class, 1);

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

实例2:使用二级缓存

使用二级缓存步骤:

步骤1要在配置文件(Hibernate.cfg.xml)中配置,打开二级缓存。

<!-- 设置二级缓存为true -->

<property name="hibernate.cache.use_second_level_cache">

true

</property>

步骤2加入二级缓存包(常用的是ehache-corteHibernate-ehcache,在Hibernateoptional目录下);

步骤3要在配置二级缓存提供的类:

<!-- 设置二级缓存所提供的类 -->

<property name="hibernate.cache.provider_class">

net.sf.ehcache.hibernate.EhCacheProvider

</property>

步骤4Hibernate4.0中还要配置工厂(目的是为了提高效率):

<!-- hibernate4.0之后需要设置facotory_class -->

<property name="hibernate.cache.region.factory_class">

org.hibernate.cache.ehcache.EhCacheRegionFactory

</property>

步骤5设置ehcache.xml文件,在该文件中配置二级缓存的参数:

Hibernateproject目录中拷贝ehcache.xml配置文件到src目录下

<ehcache>

<diskStore path="java.io.tmpdir"/>

<!—默认缓存 ->

    <defaultCache

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        />

   <!-- 每一个独立的cache可以单独为不同的对象进行设置

没有配置的就使用默认的->

    <cache name="org.zttc.itat.model.Student"

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="300"

        timeToLiveSeconds="600"

        overflowToDisk="true"

        />

    <cache name="sampleCache2"

        maxElementsInMemory="1000"

        eternal="true"

        timeToIdleSeconds="0"

        timeToLiveSeconds="0"

        overflowToDisk="false"

        /> -->

</ehcache>

步骤6Hibernate.cfg.xml中配置ehcache.xml配置文件的路径

<!-- 说明ehcache的配置文件路径 -->

<propertyname="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>

步骤7开启二级缓存

<hibernate-mapping package="org.zttc.itat.model">

    <class name="Student" table="t_stu">

        //这个使用了锁的机制。也可以是read-write,但是效率更低了。

<cache usage="read-only"/>

        <id name="id">

            <generator class="native"/>

        </id>

         <version name="version"/>

        <property name="name"/>

        <property name="sex"/>

        <many-to-one name="classroom" column="cid" fetch="join"/>

    </class>

</hibernate-mapping>

二级缓存使用实例:

/*此时会发出一条sql取出所有的学生信息*/

           session = HibernateUtil.openSession();

           List<Student> ls = session.createQuery("from Student")

                  .setFirstResult(0).setMaxResults(50).list();

           Iterator<Student> stus = ls.iterator();

           for(;stus.hasNext();) {

              Student stu = stus.next();

              System.out.println(stu.getName());

           }

/*id=1Student对象已经在session的缓存(一级缓存)中,此时就不会发sql去取Student*/

           Student stu = (Student)session.load(Student.class, 1);

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

       try {

//Student配置了二级缓存了,所以还能从缓存中取出数据

           session = HibernateUtil.openSession();

/*上一个Session已经关闭,此时又得重新取Student*/

           Student stu = (Student)session.load(Student.class, 1);

//因为二级缓存设置了read-only的话,若改变值stu.setName(“”)会报错

           System.out.println(stu.getName()+",---");

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

l  二级缓存缓存的是对象

二级缓存是把所有的对象都缓存到内存中,是基于对象的缓存。

【实例】:

try {

/*此时会发出一条sql取出所有的学生信息*/

session = HibernateUtil.openSession();

List<Object[]> ls = session

.createQuery("select stu.id,stu.name from Student stu")

           .setFirstResult(0).setMaxResults(50).list();

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

try {

    session = HibernateUtil.openSession();

    session.beginTransaction();

/*以上代码仅仅取了idname,而二级缓存是缓存对象的,所以上一段代码不会

将对象加入二级缓存此时就是发出相应的sql*/

Student stu = (Student)session.load(Student.class, 1);

//会报错,因为二级缓存设置为read-only

//stu.setName("abc");

System.out.println(stu.getName()+",---");

session.getTransaction().commit();

}

l  二级缓存和Iterator配合使用

Iterator的最主要作用就是在此了!

Session session = null;

try {

/*此时会发出一条sql取出所有的学生信息*/

    session = HibernateUtil.openSession();

    List<Object[]> ls = session

.createQuery("select stu from Student stu")

                     .setFirstResult(0).setMaxResults(50).list();

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

try {

    session = HibernateUtil.openSession();

/*由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题而且内存占用也不多*/

    Iterator<Student> stus = session.createQuery("from Student")

           .setFirstResult(0).setMaxResults(50).iterate();

    for(;stus.hasNext();) {

       Student stu = stus.next();

       System.out.println(stu.getName());

    }

} catch (Exception e) {

}

l  如果使用list()如果想少发SQL就需要使用查询缓存:

session = HibernateUtil.openSession();

/*这里发出两条一模一样的sql,此时如果希望不发sql就需要使用查询缓存*/

List<Student> ls = session

.createQuery("select stu from Student stu")//HQL和上面的一样

                     .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

查询缓存是针对HQL语句的缓存,查询缓存仅仅只会缓存id而不会缓存对象。

使用查询缓存:

Hibernate.cfg.xml中配置查询缓存:

<!--设置相应的查询缓存 -->

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

l  代码中的用法

【实例1】:

session = HibernateUtil.openSession();

List<Student> ls = session.createQuery("from Student")

    .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级缓存

       .setFirstResult(0).setMaxResults(50).list();

Iterator<Student> stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

/*会发出SQL取完整的学生对象,占用内存相对较多*/

ls = session.createQuery("from Student")

       .setCacheable(true)

       .setFirstResult(0).setMaxResults(50).list();

stus = ls.iterator();

for(;stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

【实例2】:

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

    .setCacheable(true)

    // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

    .setParameter(0, "%%").setFirstResult(0).setMaxResults(50)

           .list();

    Iterator<Student> stus = ls.iterator();

    for (; stus.hasNext();) {

       Student stu = stus.next();

       System.out.println(stu.getName());

    }

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

session = null;

try {

/*如果两条sql不一样,就不会开启查询缓存,查询缓存缓存的是HQL语句只有两个HQL完全一致(且参数也要一致)才能使用查询缓存*/

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

    .setCacheable(true)

    // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

    .setParameter(0, "%%").setFirstResult(0).setMaxResults(50)

       .list();

Iterator<Student> stus = ls.iterator();

for (; stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

【实例3】:

/*查询缓存缓存的不是对象而是id,关闭二级缓存后很容易发出大量的sql*/

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

       .setCacheable(true)

       // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

       .setParameter(0, "%%").setFirstResult(0).setMaxResults(50)

           .list();

    Iterator<Student> stus = ls.iterator();

    for (; stus.hasNext();) {

       Student stu = stus.next();

       System.out.println(stu.getName());

    }

} catch (Exception e) {

    e.printStackTrace();

} finally {

    HibernateUtil.close(session);

}

session = null;

try {

/*查询缓存缓存的是id,此时由于在缓存中已经存在了这样的一组学生数据,但是仅仅只是缓存了id,所以此处会发出大量的sql语句根据id取对象,这也是发现N+1问题的第二个原因 所以如果使用查询缓存必须开启二级缓存*/

session = HibernateUtil.openSession();

List<Student> ls = session

       .createQuery("from Student where name like ?")

       .setCacheable(true)

       // 开启查询缓存,查询缓存也是SessionFactory级别的缓存

       .setParameter(0, "%%").setFirstResult(0).setMaxResults(50)

       .list();

Iterator<Student> stus = ls.iterator();

for (; stus.hasNext();) {

    Student stu = stus.next();

    System.out.println(stu.getName());

}

缓存(基于注解)

开启查询缓存

配置实体中这么配置,其他的查询语句跟在xml中一样:

@Entity

@Table(name="t_stu")

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

【小结】:

l  经常修改的对象不用二级缓存;

l  list还是Iterator?如果对象取出来就用Iterator没有的话;

l  查询缓存缓存的是id,但是只有在hql是一样的情况下才使用查询缓存,所以一般不建议使用查询缓存;

并发

【并发情景演示】:

test01()方法中将id1的学生取出来,并将名字修改为”Jack”,在test02()方法中,也将id1的学生取出来,并将名字改为”Tom”,并且在对方commit之前完成改名操作,这样的话,名字不会被改掉。

所以,一般情况下,并发会导致更新丢失

解决方法有两种:

(1)      悲观锁:

悲观锁Hibernate基于数据库的机制实现的,但是在Hibernate3Hibernate4它们的实现机制是不一样的——Hibernate3是基于同步的机制实现的(同步的大量的并发就容易导致效率的问题),所以在Hibernate4中就没有使用同步,如果两个同时修改同一个记录的话,就抛出异常。解释错误了,Hibernate3Hibernate4的机制是一样的,都是同步的。

(2)      乐观锁:

乐观锁是在数据库中增加一个version的字段来实现的,每次修改都会让这个字段的数字增加1,在读取的时候根据Version这个版本的值来读取,这样如果并发修改就会抛异常。

<class name="Student" table="t_stu">

    <cache usage="read-only"/>

    <id name="id">

        <generator class="native"/>

</id>

//数据库会增加一个version这个字段

    <version name="version"/>

    <property name="name"/>

    <property name="sex"/>

    <many-to-one name="classroom" column="cid" fetch="join"/>

</class>

Hibernate提高效率的一种方案(重点!)

(1)      在做关系时,尽可能使用单向关联,不要使用双向关联;

(2)      在大项目中(数据量超过百万条)使用Hibernate可以考虑以下几个原则(个人总结):

【原则1】:不要使用对象关联,尽可能使用冗余字段来替代外键(因为百万级别的数据使用跨表查速度会非常慢);

【基于冗余的关联-实例】:

l  上面的实例中,我们的学生表是和班级表关联的,但是,实际上,显示学生的信息时,一般都只是希望显示班级名称即可,并不需要更多的班级信息。

【设置冗余字段的方法:】

1) 给班级增加一个用当前时间毫秒级+随机数作为班级的主键;

2) 我们给Student实体增加班级编号(classBh班级名(className这两个特冗余字段;这样我们在显示班级名的时候就不需要关联班级表了;

3) 同样,学生还需要专业信息,我们就再在Student实体中增加专业编号(speBh)和专业名称(speName),同时,班级实体中也增加专业的编号和专业的名称;

4) 使用冗余的缺点:比如,我们修改了班级名称的时候,学生表里的班级名称也要跟着做修改(但是性能基本没有影响)

Q:但是,如果有一个对象同时需要学生对象、班级对象、专业对象,那又怎么办呢?

——答案是使用DTO(数据传输对象)。

public class StudentDto {

  private Student stu;

  private Classroom cla;

  private Special spe;

}

【原则2】:不使用HQL,而全部使用SQL;如果需要缓存,就使用自己的缓存,而不适用Hibernate的缓存(Hibernate的缓存在SessionSessionFatory会有耗内存)  

(3)      

使用Hibernate SQL查询

【实例1】:

public void test01() {

    Session session = null;

    try {

       session = HibernateUtil.openSession();

       List<Student> stus = session

.createSQLQuery("select * from t_stu where name like ?")

           .addEntity(Student.class)

           .setParameter(0, "%%")

           .setFirstResult(0).setMaxResults(10)

           .list();

       for(Student stu:stus) {

           System.out.println(stu.getName());

       }

    } catch (Exception e) {

       e.printStackTrace();

    } finally {

       HibernateUtil.close(session);

    }

}

【实例2】:将查询结果映射到DTO

session = HibernateUtil.openSession();

//注意SQL语句上加了{},返回值用Object来接收,实例3中使用DTO接收

List<Object[]> stus = session

.createSQLQuery("select {stu.*},{cla.*},{spe.*} from

t_stu stu left join t_classroom cla

on(stu.cid=cla.id)

left join t_special spe on(spe.id=cla.spe_id)

where stu.name like ?")

.addEntity("stu",Student.class)

.addEntity("cla",Classroom.class)

.addEntity("spe",Special.class)

.setParameter(0, "%%")

.setFirstResult(0).setMaxResults(10)

.list();

Student stu = null;

Classroom cla = null;

Special spe = null;

List<StuDto> list = new ArrayList<StuDto>();

for(Object[] obj:stus) {

    stu = (Student)obj[0];

    cla = (Classroom)obj[1];

    spe = (Special)obj[2];

    list.add(new StuDto(stu, cla, spe));

    //这里会将学生姓名打印3遍,所以sql语句中加了花括号,用来自动将结果映射

//到前缀对应的对象中

    System.out.println(stu.name+cla.name+spe.name);

}

【实例2】:使用DTO接收返回值

l  StudentDto实体:

public class StudentDto {

    private int sid;

    private String sname;

    private String sex;

    private String cname;

    private String spename;

    public StudentDto(int sid, String sname, String sex, String cname,

           String spename) {

       super();

       this.sid = sid;

       this.sname = sname;

       this.sex = sex;

       this.cname = cname;

       this.spename = spename;

    }

   

    public StudentDto() {

    }

}

l  查询

session = HibernateUtil.openSession();

//这里的sql为了和DTO对应,

//可以在selectnew com.zmj…StudentDto(stu.id as sid,……)

List<StudentDto> stus = session.createSQLQuery("select

stu.id as sid,

stu.name as sname,

stu.sex as sex,

cla.name as cname,

spe.name as spename

from t_stu stu

left join t_classroom cla

on(stu.cid=cla.id) "

left join t_special spe

on(spe.id=cla.spe_id)

where stu.name like ?")

//这里第一感觉可能会想到使用setEntitysetEntity的参数必须是和数据库

//对应的,但是DTO明显不需要和数据库对应,所以这里使用以下这个方法:

    .setResultTransformer(Transformers.aliasToBean(StudentDto.class))

    .setParameter(0, "%%")

    .setFirstResult(0).setMaxResults(10)

    .list();

for(StudentDto sd:stus) {

    System.out.println(sd);

}

【提示】:

这样使用Hibernate的方法(查询使用sql,增、删、改使用Hibernate),效率会大大提高!

 

你可能感兴趣的:(Hibernate)