hibernate 框架

1   ORM映射

ObjectRe alational Mapping对象关系映射,即把对象映射到关系型数据库中。Hibernate与ORM关系:Hibernate是Orm的具体实现。

2   Hibernate环境搭建

2.1  建立数据库和表

2.2  引入相关jar包(可以用myeclipse用户库管理)

hibernate3.jar(核心包);

required目录中所有jar(6个);

jpa目录(1个)简历实体类;

相关数据库驱动包。

2.3  写主配置文件(见Hibernate主配置

2.4  写实体类Src/hibernate.cfg.xml和映射文件

3   HibernateApi

|--Configuration      管理配置文件类: 加载主配置文件、创建session工厂

config.configure();    默认加载src/hibernate.cfg.xml

                        如果加载其他包下主配置文件,

                             config.configure(“cn/itcast/hibernate.cfg.xml”);

config.buildSessionFactory();        创建session工厂

config.addClass(Dept.class);         测试时候使用,相当于加载类映射文件

                                      会到当前类所在包下找Dept.hbm.xml

                                      使用方便,但不利于维护!

 

|--SessionFactory      session的工厂,用于创建session对象

                      (代表整个hibernate.cfg.xml)

                       一个应用主需要一个SessionFactory即可(单例)

Session创建2种方式:

sf.openSession();           创建Session对象            【测试】

sf.getCurrentSession();      获取当前session或者创建session  【项目】

                             以线程方式常见session

sf.close()    ;             关闭sessionFactory;  (应用程序结束时候调用!)      

 

|-- Session          session表示会话: 与数据库连接的会话信息!

                     主要维护了一个与数据库连接的对象(Connection)

                     Session封装:

                             比Connection具有更强大的功能;

                             开发时候,可以用session对象方法直接操作数据库!

                             Hibernate提供的session,操作的是对象!

    

更新:

session.save(object);        保存一个对象

session.update(dept);        更新一个对象(对象必须设置主键值)

session.saveOrUpdate(dept);  

                             主键自增:

                                 1. 没有设置主键,则保存

                                 2. 有设置主键,  则更新

                                 注意:

                                      如果设置主键不存在,hibernate则报错!

                             主键非自增:

                                 只要设置的主键不存在,就保存!

                                 否则,就更新!

session.delete(dept);        删除一个对象

 

查询:

主键查询、

session.get(Dept.class,10000000);        

及时加载数据,只要执行get,就向数据发送查询sql,如果查询的主键不存在,返回null;

session.load(Dept.class,10000000);

当用到数据的时候,才向数据发送查询sql;  如果查询的主键不存在,只要使用就报错!

HQL查询

 * Hql :hibernate query language   Hibernate提供的面向对象的查询语言!

 * 面试题: hql 与  sql 区别:

 *   sql : 

 *       结构化的查询语言,查询的是表、表中的字段、不区分大小写;

 *  hql :

 *  面向对象的查询语言,查询的是对象、对象属性!  区分大小写!

 

案例:

设计接口,用hibernate实现CRUD操作!

 

 

|--Query                 接口

     q.list();            查询全部

q.uniqueResult();     查询唯一结果

q.executeUpdate();    执行更新

4   Hibernate实现CRUD操作

4.1   

Hibernate工具类获取session对象

private static SessionFactory sf;

    

     //创建SessionFactory对象

     static {

         sf = new Configuration()         // 创建对象

              .configure()                // 加载主配置文件

              .addClass(Dept.class)       // 加载映射文件(测试时候用)

              .buildSessionFactory();     // 最后,创建session工厂

     }

    

     //返回session对象

     public static Session getSession() {

         return sf.openSession();

     }

实现crud操作

 

/**

 * hibernate作为dao操作的实现

 *

 * @author AdminTH

 *

 */

publicclass DeptDao implements IDeptDao {

 

    @Override

    publicvoid delete(int id) {

 

        Session session = null;

 

        try {

            // 1. 创建session

            session = HibernateUtils.getSession();

            // 2. 开启事务

            Transaction tx = session.beginTransaction();

            // 3. 执行操作

            Object dept = session.get(Dept.class, id);

            if (dept != null) {

                session.delete(dept);

            }

            // 4. 提交事务

            tx.commit();

        } catch (Exception e) {

            thrownew RuntimeException(e);

        } finally {

            // 关闭

            session.close();

        }

 

    }

 

    @Override

    public Dept findById(int id) {

        Session session = null;

 

        try {

            session = HibernateUtils.getSession();

            Transaction tx = session.beginTransaction();

            // 3. 执行操作

            return (Dept) session.get(Dept.class, id);

        } catch (Exception e) {

            thrownew RuntimeException(e);

        } finally {

            // 关闭

            session.close();

        }

    }

 

    @Override

    public List<Dept> getAll() {

        Session session = null;

 

        try {

            // 1. 创建session

            session = HibernateUtils.getSession();

            // 2. 开启事务

            Transaction tx = session.beginTransaction();

            // 3. 执行操作

            return session.createQuery("from Dept").list();

        } catch (Exception e) {

            thrownew RuntimeException(e);

        } finally {

            // 关闭

            session.close();

        }

    }

 

    @Override

    public List<Dept> getAll(int index, int count) {

        Session session = null;

 

        try {

            // 1. 创建session

            session = HibernateUtils.getSession();

            // 2. 开启事务

            Transaction tx = session.beginTransaction();

 

            Query q = session.createQuery("from Dept");

            // 设置起始行

            q.setFirstResult(index);// select * from limit 0,3

            q.setMaxResults(count);

 

            return q.list();

        } catch (Exception e) {

            thrownew RuntimeException(e);

        } finally {

            // 关闭

            session.close();

        }

    }

 

    @Override

    publicvoid save(Dept dept) {

        Session session = null;

 

        try {

            session = HibernateUtils.getSession();

            Transaction tx = session.beginTransaction();

            session.save(dept);

            // 4. 提交事务

            tx.commit();

        } catch (Exception e) {

            thrownew RuntimeException(e);

        } finally {

            // 关闭

            session.close();

        }

    }

 

    @Override

    publicvoid update(Dept dept) {

        Session session = null;

 

        try {

            // 1. 创建session

            session = HibernateUtils.getSession();

            // 2. 开启事务

            Transaction tx = session.beginTransaction();

            // 3. 执行操作

            session.update(dept);

            // 4. 提交事务

            tx.commit();

        } catch (Exception e) {

            thrownew RuntimeException(e);

        } finally {

            // 关闭

            session.close();

        }

    }

 

}

 

5   Hibernate执行流程

    1. 创建配置管理器configuration对象执行configure方法加载主配置文件(也加载了映射文件),不带参数的构造参数默认加载src目录下的hibernate.cfg.xml

    2. 创建sessionFactory对象

    3. 创建或者获取session操作数据库的对象

    4. 开启事务

    5. 操作数据库

      1. 查询操作:get()/load()/list()/uniqueResult()方法

      2. 更新操作:save()/update()/()saveOrUpdate()/delete()

        更新操作必须提交事务。

        Hibernate主配置

即src/hibernate.cfg.xml;主要包含:连接数据库的配置信息(连接字符串,数据库方言等);其他配置(显示sql,自动建表等);加载实体映射文件:*.hbm.xml;

参考hibernate常用配置:

hibernate-distribution-3.6.0.Final\project\etc\hibernate.properties

6.1  主配置文件示例:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

    <!-- 1. 连接数据库配置 -->

    <property name="hibernate.connection.url">jdbc:mysql:///emp_sys</property>

    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

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

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

    <!-- 数据库方言hibernate会根据方言配置,生成相应符合数据库语法的SQL!) -->

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

   

    <!-- 2. 常用的配置 -->

    <!-- 显示hibernate运行时期,生成的sql语句 -->

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

    <!-- 格式化显示sql -->

    <property name="hibernate.format_sql">false</property>

    <!-- 自动建表 -->

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

   

    <!-- 3. 加载映射文件  【项目工程中,加载映射必须通过mapping节点配置;】-->

    <mapping resource="cn/itcast/a_hello/Dept.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

6.2  为什么要配置数据库方言?

告诉Hibernate使用的是哪种数据库。(hibernate会根据方言配置,生成相应符合数据库语法的SQL! )

6.3  自动建表:

#hibernate.hbm2ddl.autocreate-drop  每次在创建SessionFactory的时候建表,

                             在执行sf.close(); 方法的时候,就删除表!

#hibernate.hbm2ddl.autocreate    每次运行程序都会新创建表,如果表存在先删除再创建!

                                  有时Hibernate可能有bug,表删掉不能自动建表!!!

#hibernate.hbm2ddl.autoupdate     创建表,如果表已经存在,则不创建!

#hibernate.hbm2ddl.autovalidate   验证映射文件与数据库是否一致,不一致就报错!  (生产环境用!)

      1. 直接通过配置建表

        <propertyname="hibernate.hbm2ddl.auto">create</property>

      2. 代码创建

// 代码的方式创建表

    @Test

    publicvoid testApp() throws Exception {

        // 创建配置管理器对象

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

        // 创建工具类对象

        SchemaExport export = new SchemaExport(cfg);

        // 建表

        /*

         * @param script print the DDL to the console    是否在控制台打印DDL语句(数据创建语言)

         * @param export export the script to the database 是否执行SQL脚本语句

         */

        export.create(true, true);

    }

7   Hibernate实体映射配置

作用: 把对象通过这个映射文件(xml), 映射到数据库中;映射文件描述了对象与表的对应关系;映射文件要能完全描述一张表。Hibernate也就是通过这个配置文件实现了ORM(对象关系模型)。

7.1  基本配置介绍

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!-- package (可选) 指定当前映射文件中,出现的所有对象,所属的包。

如果不写,下面的class name属性的值要写类全名。

 -->

<hibernate-mapping package="cn.itcast.c_crud">

    <!-- class  表示要映射一个javabean对象到数据库表!

            name  表示要映射的对象!值为实体类名。

            table (可选) 对象,对应的表。如果没有写,对象名与表名应保持一致

               子结点主要由2部分组成:

            1. 主键配置        id节点】

            2. 普通字段配置    property节点】

     -->

    <class name="Dept" table="t_dept">

        <!-- id 表示主键映射

               generator  指定主键生成策略!即主键怎样生成(手动指定、自增长...

               (api :   章节 5.1.2.2.1. Various additional generators)

               increment  自增长(不适合多线程项目使用! )

               identity   自增长, mysql中自增长的方式!

               sequence   序列的方式自增长;Oracle数据库中自增长的方式;DB2数据库

               native  自增长会根据底层数据库的能力选择 identitysequence 等中的一个。

                        如果是oracle数据库,使用 sequence 序列的方式自增长

                        如果是mysql数据库,使用identity作为自增长方式;

              

               assigned  手动指定主键的值!  【自己要控制主键不重复!】

               uuid       自动生成的uuid的值作为主键

               foreign    (主键的生成策略,外键的策略!把外键作为主键!)  一对一

         -->

        <id name="id" column="id">

            <generator class="uuid"></generator>

        </id>

       

        <!-- name 必填,表示的是Dept对象中的属性

            column 属性对应的字段名称,如果不写默认与属性名同名

            type  指定字段的类型

                 # hibernate类型首字母小写

                    string

                 # java类型      要指定为类的全名

                    java.lang.String

                 (可选) 如果没有指定,匹配bean的属性的类型!

            length  指定字符的长度  (字符类型有效)

            unique="true"  给某个字段添加唯一约束!

         -->

        <property name="detpName" column="detp_name" type="java.lang.String" length="20"/>  

        <property name="remark"></property>

        <property name="desc" column="`desc`"></property>

    </class>

</hibernate-mapping>

另一种配置方式是:将column属性分为property的子节点来写,这样更加清晰。

<id name="infoId" type="java.lang.String">

            <column name="info_id" length="32"/>

            <generator class="uuid.hex" />

        </id>

        <property name="type" type="java.lang.String">

            <column name="type" length="10" />

        </property>

7.2  联合主键映射配置

数据库设计中,当表中找不到合适的列作为主键时(因为主键列,必须非空且唯一)有时就会用到联合主键多列,联合在一起,作为一个主键),当然一般会采用id列作为主键,主键的维护交给数据库服务端!

注:一个表只能有一个主键。但一个表可以没有主键!

例:假设用数据库存储用户名、地址、年龄等信息!用户名、地址,作为一个联合主键!

联合主键需要定义联合主键实体类对象,当一个实体涉及到联合主键一般写的步骤为:联合主键映射,步骤:1. 联合主键实体类对象;2. 实体类对象;3.写映射文件

联合主键实体类

/**

 * 1. 联合主键实体类对象

 * 注意:必须实现可序列接口!  hibernate主键查询的时候,要求主键要实现可序列接口!

*/

publicclass CompositeKeys implements Serializable{

    private String userName;

    private String address;

}

实体类

publicclass User {

    // 联合主键对象

    private CompositeKeys keys;

    privateintage;

    }

实体类映射文件

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.d_composite">

    <class name="User" table="t_user">

       

        <!-- 联合主键映射 -->

        <composite-id name="keys" class="CompositeKeys">

            <key-property name="userName"></key-property>

            <key-property name="address"></key-property>

        </composite-id>

       

        <property name="age"></property>

    </class>

</hibernate-mapping>

 

 

7.3  集合映射配置

集合映射,映射的数据都只有一个、两个字段!如果要映射到另外一张表中有多个字段:此时,映射的集合元素,要封装为对象!即:多对一、一对多映射  (关联对象的映射!)

例:假设一个用户对应多个地址,需要建立两个表。用户表:id,name.地址表:user_id,name.

// 用户信息

publicclass User {

    privateintid;

    private String name;

    // 存储多个地址   (必须用接口, 因为hibernate会根据接口生成代理对象【jdk代理】)

    //set集合保存(常用)

private Set<String> addressSet = new HashSet<String>(); // 无序

   

    // list集合保存

    private List<String> addressList = new ArrayList<String>();

   

    // map 集合

    private Map<String,String> addressMap = new HashMap<String,String>();

}

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

 

<hibernate-mapping package="cn.itcast.a_collection">

    <class name="User" table="t_users">

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name" type="string" length="20"></property>

       

        <!-- 用户对应的多个地址 -->

        <!--

            set 集合映射

                name 映射的集合属性

                table  集合数据要映射到哪一张表

                key   集合表(t_adddressSet), 中的外键字段   【引入t_users(id)

                element  指定集合中的数据,映射到集合表的哪个字段上 【必须指定类型!】

         -->

         <set name="addressSet" table="t_adddressSet">

            <key column="user_id"></key>

            <element column="address" type="string"></element>

         </set>

         

         <!--

            list集合映射

                list-index   对应的数据库中排序字段,保证list集合的数据在db中有序

          -->

          <list name="addressList" table="t_addressList">

              <key column="user_id"></key>

              <list-index column="index_"></list-index>

              <element column="address" type="string"></element>

          </list>

         

          <!--

            map集合映射

                key 外键字段

                map-key  指定map集合的key映射到集合表的哪个字段上

                element  指定map集合的value 映射到集合表的哪个字段

          -->

          <map name="addressMap" table="t_adddressmap">

              <key column="user_id"></key>

              <map-key column="addressKey" type="string"></map-key>

              <element column="addressValue" type="string"></element>

          </map>

    </class>

</hibernate-mapping>

publicclass App {

 

    privatestatic SessionFactory sf;

    static {

        sf = new Configuration()//

                .configure()//

                .addClass(User.class)//

                .buildSessionFactory();

    }

   

    @Test

    publicvoid testSave() throws Exception {

        // 模拟数据

        User user = new User();

        user.setName("Jack");

        user.getAddressSet().add("广州");

        user.getAddressSet().add("深圳");

        user.getAddressList().add("zz");

        user.getAddressList().add("aa");

        user.getAddressMap().put("gzth", "广州市天河区");

        user.getAddressMap().put("szlh", "深圳市罗湖区");

       

        Session session = sf.openSession();

        session.beginTransaction();

        // 保存

        session.save(user);

       

        session.getTransaction().commit();

        session.close();

    }

   

    @Test

    publicvoid testGet() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 主键查询

        User user = (User) session.get(User.class, 3);

       

        // 测试set

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

        System.out.println(user.getAddressSet());    // 在用到set集合数据的时候,才向数据库发送sql语句!load方法。

       

        // 测试list数据

        System.out.println(user.getAddressList());

       

        // 测试map数据

        System.out.println(user.getAddressMap());

        session.getTransaction().commit();

        session.close();

    }

}

7.4  一对一映射

例如,用户信息与身份证信息的关系是一对一。

映射文件书写方式有两种:基于外键的形式;基于主键的形式

用户信息实体

publicclass User {

privateintid;

private String name;

private String address;

/*

 * 用户对应的身份证, 一对一关系, 无外键

 */

private IdCard idCard;

}

身份证信息实体(基于外键)

publicclass IdCard {

private String cardNo;

private String place;

/*

 * 一对一关系,有外键

 * (映射: user对象,要映射生成外键字段)

 */

private User user;

}

身份证信息实体(基于主键)

publicclass IdCard {

 

private int user_id;  // 主键字段, 外键字

private String cardNo;

private String place;

/*

 * 一对一关系,有外键

 * (映射: user对象,要映射生成主键外键约束)

 */

private User user;

}

用户映射

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.b_one2one_2">

<class name="User" table="t_user">

     <id name="id">

         <generator class="native"></generator>

     </id>

     <property name="name" length="20"></property>

     <property name="address" length="20"></property>

     <!-- 一对一,无外键 -->

     <one-to-one name="idCard" class="IdCard"></one-to-one>

</class>

</hibernate-mapping>

身份证映射(基于外键)

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!-- 一对一映射,基于外键的映射实现: -->

<hibernate-mapping package="cn.itcast.b_one2one_1">

<class name="IdCard" table="t_IdCard">

     <id name="cardNo">

         <!—assigned 手动指定主键 -->

<generator class="assigned"></generator>

     </id>

     <property name="place" type="string" length="20"></property>

     <!-- 一对一,有外键方(相当于员工与部门的关系中,员工的一方配置)

             unique="true" 给当前字段(外键),添加唯一约

      -->

     <many-to-one name="user" class="User" column="user_id" unique="true"></many-to-one>

</class>

</hibernate-mapping>

身份证映射(基于主键)

<class name="IdCard" table="t_IdCard">

     <id name="user_id">

         <!-- 主键生成: 外键的策略! -->

         <!--

             param  指定参

                 name="property"  固定写法,指定引用的对象属性是哪

                 <param name="property">user</param>  

                     user指应用的对象是  <one-to-one name="user"> 保持一

          -->

         <generator class="foreign">

             <param name="property">user</param>

         </generator>

     </id>

     <property name="cardNo" type="string" length="40"></property>

     <property name="place" type="string" length="40"></property>

     <!-- 基于主键的一对

          constrained="true"  指定在主键上,添加外键约束

      -->

      <one-to-one name="user"

class="User" constrained="true"></one-to-one>

    </class>

测试

@Test

publicvoid test_1() throws Exception {

     Session session = sf.openSession();

     session.beginTransaction();

    

     IdCard idCard = new IdCard();

     idCard.setCardNo("3412XX");

    

     User user = new User();

     user.setName("Jet");

     user.setAddress("广州天河");

    

     //

     idCard.setUser(user);

    

     // -----

     session.save(user);

     session.save(idCard);

    

     session.getTransaction().commit();

     session.close(); 

}

 

7.5  组件映射与继承映射

类与类之间的关系:组合关系:一个对象中包含另外一个对象,这两个类可以看成组合关系;继承关系:子类继承父类!

7.5.1  组件映射

一个类中包含了另一个类我们称之为组合关系组件映射: 组件类与被包含的组件类共同映射到一张表中,需要使用组件映射。

例如:汽车与车轮

汽车实体

publicclass Wheel {

privatedoublesize;

privateintcount;

}

车轮实体

publicclass Car {

privateintid;

private String name;

privatedoubleprice;

// 汽车的,轮

private Wheel wheel;

}

映射文件

<class name="Car" table="t_car">

     <id name="id">

         <generator class="native"></generator>

     </id>

     <property name="name" length="50"></property>

     <property name="price" type="double"></property>

    

     <!-- 组件映射,将车轮(被包含的组件类)的实体映射写在这里 -->

      <component name="wheel" class="Wheel">

         <property name="size" type="double"></property>

         <property name="count" type="int"></property>

      </component>

    </class>

7.5.2  继承映射

类与类之间也有继承关系,例如动物—猫—狗。写映射文件有四种方式(包含简单继承映射),但在数据库中有三中存储方式。

简单继承映射(映射文件写法同普通的映射文件)

动物类

publicclass Animal {

    privateintid;

    private String name;

}

publicclass Dog extends Animal {

    // 玩耍

    private String play;

}

publicclass Cat extends Animal{

    // 抓老鼠

    private String catching;

}

简单映射文件,以猫为例(与一个普通类的映射文件没有区别)

<!-- 继承映射,简单继承映射

          每一个子类,对应一个映射文件,父类、子类字段写到一起!

     -->

    <class name="Cat" table="t_cat">   

        <!-- 父类 -->

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name"></property>

       

        <!-- 子类 -->

        <property name="catching"></property>

    </class>

这种写法与普通映射文件写法没有区别,只不过一些字段是从父类继承过来的。

这种映射配置的写法特点:每一个子类用一个映射文件写;

每一个子类对应一个映射文件,对应一张表!

缺点:重复配置较多!(父类字段的配置)

继承映射其他方式

数据中存储继承映射有三种方式,但无论哪种存储方式,映射文件都只有一个。本例图解。

方式一:父类与子类都在一张表中。

本例的一张表映射写法:

<!-- 复杂继承映射

        1. 整个继承结构一张表

    -->

    <class name="Animal" table="t_animal">

        <!-- 父类 -->

        <id name="id">

            <generator class="native"></generator>

        </id>

        <!-- 指定鉴别器字段: 区分是哪一个字段信息-->

        <discriminator column="type_" type="string"></discriminator>

        <property name="name"></property>

       

        <!-- 子类:

            每一个子类用一个subclass节点,最终把所有的子类字段都存储到一张表!

            discriminator-value

               指定鉴别器字段,即上面type_字段的值!

               如果不指定,默认为当前子类全名!

         -->

        <subclass name="Cat" discriminator-value="cat_b">

            <property name="catching"></property>

        </subclass>

       

        <!-- 子类: -->

        <subclass name="Dog" discriminator-value="dog_a">

            <property name="play"></property>

        </subclass>

    </class>

这种写法不符合数据库三大范式

 

方式二:父类一张表,每个子类一张表。

本例的三张表写法

<!-- 复杂继承映射

        2. 每个类一张表,2个子类,三张表!

      -->

    <class name="Animal" table="t_animal">

        <!-- 父类 -->

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name"></property>

       

        <!--

            猫:子类

                每个子类对应一张表

            joined-subclass  用这个映射标签,就表示每个对象都对应表

                key :  t_cat表的外键

                property  对象属性

         -->

         <joined-subclass name="Cat" table="t_cat">

            <key column="animal_id"></key>

            <property name="catching"></property>

         </joined-subclass>

           

           

         <joined-subclass name="Dog" table="t_dog">

            <key column="animal_id"></key>

            <property name="play"></property>

         </joined-subclass>

 

    </class>

这种写法符合数据库三大范式,且更加面向对象。但是, 表的结构过于复杂, 不方便查找数据,也影响查找效率!

 

方式三:父类没有表,每个子类有一张表。

本例两张表的写法

<!-- 复杂继承映射

        3. 每个子类对应一张表、父类不对应表、所有子类写到一个映射文件

        abstract="true"  表示当前映射的对象不对应表,没有默认表!

     -->

    <class name="Animal" abstract="true">

        <!-- 父类 -->

        <id name="id">

              <!—- 此时主键字段不能设置为自增长 -->

            <generator class="assigned"></generator>

        </id>

        <property name="name"></property>

       

        <!-- 子类, -->

        <union-subclass name="Cat" table="t_cat">

            <property name="catching"></property>

        </union-subclass>

       

        <!-- -->

        <union-subclass name="Dog" table="t_dog">

            <property name="play"></property>

        </union-subclass>

    </class>

注意:主键字段不能设置为自增长,否则会报异常:

MappingException: Cannot use identitycolumn key generation with <union-subclass> mapping

7.6  多对一、一对多映射

例:员工跟部门的关系。员工对部门是多对一,部门对员工一对多。

 

部门实体类,省略get和set方法。

publicclass Dept {

    privateintid;

    private String name;

    // 多个员工  (一对多)

    private Set<Employee> employees = new HashSet<Employee>();

}  

Dept.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.b_one2many">

    <class name="Dept" table="t_dept">

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name" type="string" length="20"></property>

       

        <!--

            一对多配置

                name  映射的集合属性

                table  集合属性对应的表(集合表)

                key   集合表的外键字段

                      (集合表的哪个字段,引用了当前表的主键, 这样就关联上了!即:  t_employee.dept_id=t_dept.id )

         -->

         

         <!--

            inverse 维护关联关系的属性

                                默认值为false表示不控制反转,即当前方有控制权,控制表的关联关系(即可以维护关系)。

                   true        表示控制反转,放弃当前表的维护,解除关系。

          -->

         <set name="employees" table="t_employee" inverse="true">

              <!—- 该表中被别的表作为外键引用的字段。-->

            <key column="dept_id"></key>

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

         </set>

    </class>

</hibernate-mapping>

员工实体类,省略get和set方法

publicclass Employee {

    privateintempId;

    private String empName;

    private Date birth;

    // 员工对应的部门 (多对一)

    private Dept dept // 注意:这里一定不能实例化!

    }

Employee.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.b_one2many">

    <class name="Employee" table="t_employee">

        <id name="empId">

            <generator class="native"></generator>

        </id>

        <property name="empName" type="string" length="20"></property>

        <property name="birth" type="date"></property>

       

        <!--

            多对一映射配置

                name  映射的对象属性

                column 属性对应的外键字段

                       (指定了当前表t_employee,的外键字段)

                class  属性的类型

         -->       

         <many-to-one name="dept" column="dept_id" class="Dept"></many-to-one>

    </class>

</hibernate-mapping>

注:两个映射文件中的配置都有列:dept_id。是为了告诉Hibernate两种表是如何关联起来的。(也就是告诉Hibernate两张表是通过dept.dept_id = emp.dept_id关联的)

7.6.1  两张表如何维护关系效率最高

publicclass App_1_save {

    privatestatic SessionFactory sf;

    static {

        sf = new Configuration()//

                .configure()//

                .addClass(Dept.class)//

                .addClass(Employee.class)//

                .buildSessionFactory();

    }

    // 1. 一的一方维护关系

    @Test

    publicvoid testSave() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 保存数据,通过部门方维护关系!

        Dept dept = new Dept();

        dept.setName("开发部");

       

        Employee emp1 = new Employee();

        emp1.setEmpName("老张");

        emp1.setBirth(new Date());

       

        Employee emp2 = new Employee();

        emp2.setEmpName("老李");

        emp2.setBirth(new Date());

       

        // 处理关系,----> 1. 部门方

        dept.getEmployees().add(emp1);

        dept.getEmployees().add(emp2);

       

        //-------------------------

        session.save(emp1);   // 在通过一的一方维护关系,先保存哪个对象没有关系;因为都是通过update语句维护的两张表之间的关系(事务并没有提交,由Hibernate对插入数据进行了处理,所以当先插入员工时,即使没有对应的部门也能执行代码,因为并没有先将部门数据插入到数据库的表中。)

        session.save(emp2);

        session.save(dept);

       

        session.getTransaction().commit();

        session.close();

    }

   

    // 2. 【推荐】通过多的一方维护关系

    // 建议: 在一对多与多对一的关系的维护上,要通过多的一方维护关系!【在保存的时候,先保存一的一方,可以减少生成的update语句!】

    @Test

    publicvoid testSave2() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 保存数据,通过部门方维护关系!

        Dept dept = new Dept();

        dept.setName("开发部");

       

        Employee emp1 = new Employee();

        emp1.setEmpName("老张");

        emp1.setBirth(new Date());

       

        Employee emp2 = new Employee();

        emp2.setEmpName("老李");

        emp2.setBirth(new Date());

       

        // 处理关系,----> 2. 员工方维护

        emp1.setDept(dept);

        emp2.setDept(dept);

       

        // 保存

        session.save(dept);  // 通过多的一方维护关系,先保存哪个对运行时有影响的!  当先保存一的方法时候,可以减少生成的update语句,效率提升!

        session.save(emp1);

        session.save(emp2);

       

       

        session.getTransaction().commit();

        session.close();

    }

 

    // 3. 双向维护  【有可能生成更多的update语句,效率最低  sql

    @Test

    publicvoid bak() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 保存数据,通过部门方维护关系!

        Dept dept = new Dept();

        dept.setName("开发部");

       

        Employee emp1 = new Employee();

        emp1.setEmpName("老张");

        emp1.setBirth(new Date());

       

        Employee emp2 = new Employee();

        emp2.setEmpName("老李");

        emp2.setBirth(new Date());

       

        // 通过部门维护一次

        dept.getEmployees().add(emp1);

        dept.getEmployees().add(emp2);

       

        // 再通过员工方维护一次

        emp1.setDept(dept);

        emp2.setDept(dept);

       

        //----------------

        session.save(dept);

        session.save(emp1);

        session.save(emp2);

       

        session.getTransaction().commit();

        session.close();

    }

}

7.6.2  对获取数据进行测试

/**

 * 关联关系的维护:

 * 1. 保存数据

 *  2. 通过一方,获取另外一方

*/

publicclass App_2_get {

    privatestatic SessionFactory sf;

    static {

        sf = new Configuration()//

                .configure()//

                .addClass(Dept.class)//

                .addClass(Employee.class)//

                .buildSessionFactory();

    }

   

    // 2. 通过一方,获取另外一方

    @Test

    publicvoid get() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 部门方,

        Dept dept = (Dept) session.get(Dept.class, 1);

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

        System.out.println(dept.getEmployees());  // 懒加载,使用时才向数据库发送sql

       

        // 员工,

//      Employee emp = (Employee) session.get(Employee.class, 1);

//      System.out.println(emp.getEmpName());

//      System.out.println(emp.getDept().getName());  // 懒加载

       

        session.getTransaction().commit();

        session.close();

    }

   

    //3. 解除关系

    @Test

    publicvoid releaseRelation() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

        // 主键查询

        Dept dept = (Dept) session.get(Dept.class, 1);

        dept.getEmployees().clear();// update t_employee set dept_id=null where dept_id=1

       

        session.getTransaction().commit();

        session.close();

    }

   

    //4. 删除数据,对关联关系的影响,会先解除关联在进行删除

    @Test

    publicvoid deleteData() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 先查询

        Dept dept = (Dept) session.get(Dept.class, 2);

        session.delete(dept);

       

        session.getTransaction().commit();

        session.close();

    }

}

7.6.3  inverse属性对关联关系的维护

// inverse 属性,在维护关联关系的时候起作用, 表示控制权是否转移!

// 注意: 这个只在一的一方有效!(因为例如只在部门一方有效,即可以配置是否放弃控制权。多的一方,即员工方的表的字段是自己的(dept_id),当然应该拥有完全的控制权,所以不用配置。)

// inverse 表示控制反转

// ---> false  不控制反转,  当前方不进行控制权转移,有控制权

//----> true   控制反转,   当前方控制反转,当前方没有控制权

publicclass App_3_inverse {

    privatestatic SessionFactory sf;

    static {

        sf = new Configuration()//

                .configure()//

                .addClass(Dept.class)//

                .addClass(Employee.class)//

                .buildSessionFactory();

    }

    /**

     * 1. 保存数据

     * inverse = false (默认值)有控制权,可以保存, 及处理关联关系!

      *  inverse = true  控制反转,当前方没有控制权,也可以保存,但不能处理关联关系!即员工表中dept_id=null

     */

    @Test

    publicvoid testSave2() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 保存数据,通过部门方维护关系!

        Dept dept = new Dept();

        dept.setName("开发部");

       

        Employee emp1 = new Employee();

        emp1.setEmpName("老张");

        emp1.setBirth(new Date());

       

        Employee emp2 = new Employee();

        emp2.setEmpName("老李");

        emp2.setBirth(new Date());

       

        dept.getEmployees().add(emp1);

        dept.getEmployees().add(emp2);

       

        // 保存

        session.save(dept);  // 通过多的一方维护关系,先保存哪个对运行时有影响的!  当先保存一的方法时候,可以减少生成的update语句,效率提升!

        session.save(emp1);

        session.save(emp2);

       

        session.getTransaction().commit();

        session.close();

    }

    /**

     *  2. 获取数据

     *  是否设置inverse属性,对获取数据,无影响!(前提员工表dept_id != null

     */

    @Test

    publicvoid get() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 部门方,

        Dept dept = (Dept) session.get(Dept.class, 3);

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

        System.out.println(dept.getEmployees());  // 懒加载

       

        // 员工,

//      Employee emp = (Employee) session.get(Employee.class, 1);

//      System.out.println(emp.getEmpName());

//      System.out.println(emp.getDept().getName());  // 懒加载

       

        session.getTransaction().commit();

        session.close();

    }

    /**

     * 3. 解除关系

     * inverse=false, 表示有控制权, 可以通过一的一方解除关系:清空所有对当前部门的引用!

     * inverse=true,  表示没有控制权, 不能解除关系(但不会报错!)

     * @throws Exception

     */

    @Test

    publicvoid releaseRelation() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

        // 主键查询

        Dept dept = (Dept) session.get(Dept.class, 4);

        dept.getEmployees().clear();  // inverse=true, 不执行任何操作

       

        session.getTransaction().commit();

        session.close();

    }

    /**

     * 4. 删除数据,对关联关系的影响

     * inverse=false , 有控制权, 先清空对当前主键(dept_id)的所有引用, 再删除自身!

     * inverse=true,   没有控制权,  如果要删除的记录主键(dept_id)没有被引用,可以删除; 

     *  如果有被外键引用,因为没有控制权不能维护对方,所以会直接生成一条delete 语句, 违反外键约束,报错!

     * @throws Exception

     */

    @Test

    publicvoid deleteData() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 先查询

        Dept dept = (Dept) session.get(Dept.class, 3);

        session.delete(dept);

       

        session.getTransaction().commit();

        session.close();

    }

}

7.6.4  lazy属性详解

lazy,即hibernate中懒加载!懒加载就是在用到数据的时候,才向数据库发送查询的sql语句!(所谓的懒就是我不得不做的时候才去做)

get/load主键查询的区别:get 及时加载,只要执行get方法,就向数据库发送查询的sql,如果查询的主键不存在,返回null;load 懒加载,当用到数据的时候才向数据库发送sql查询!如果查询的主键不存在,使用会报错!

设置位置:可以在类、字段、一对多、多对一上设置lazy属性,开启/关闭懒加载功能。类级别,默认开启懒加载!字段级别,只对长文本类型有效(longtext),即我们不用这个长文本字段时可以先不查,等用到了在查,可以提高系统的执行效率;集合,默认懒加载;多对一,默认懒加载。

集合懒加载 :Lazy  的属性有三个值。true  默认,表示默认支持懒加载;false  关闭懒加载;extra也是开启懒加载(但会更懒),在真正用到数据的时候才查询;当只是使用集合的size()/isEmpty()方法的时候,只是发送统计sql,而不是查询数据的sql,只有真正用到集合数据的时候才会发送查询sql。

懒加载会出现的异常信息:(当session关闭后,使用懒加载数据,就会有懒加载异常!)org.hibernate.LazyInitializationException:

failedto lazily initialize a collection of role: cn.itcast.c_lazy.Dept.employees, nosession or session was closed

异常的解决方式(并非真正解决)

//懒加载, 异常

    @Test

    publicvoid lazy_exeception() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

        // 查询,

        Dept dept = (Dept) session.get(Dept.class, 5);

       

        // 方式1  先用一下懒加载数据

        //System.out.println(dept.getEmployees());  // 已经查询!

        // 方式2强迫代理对象初始化

        Hibernate.initialize(dept.getEmployees());//相当于先用一下

        // 方式3关闭懒加载,  即设置lazy=false

        // 总结,  session关闭前,先使用懒加载数据;  然后session关闭,再获取的数据就不是懒加载的数据了!

        // 方式4连接查询   open  session  in view  模式

       

        session.getTransaction().commit();

        session.close();

       

        // Session关闭后,不能使用懒加载数据!

        System.out.println(dept.getEmployees());// 执行这里,发送SQL,到数据库服务器端!

    }

7.6.5  cascade级联操作

cascade  表示级联

         save-update    级联保存或更新【保存的时候,会保存当前对象,以及其关联的对象】

         delete     表示级联删除;先删除对当前主键的所有引用,再删除自身

         none      不级联(默认)

         save-update,delete或all     级联保存更新、删除

例:部门与员工的例子中,如果想保存部门的时候,级联保存部门下的员工,需要在部门的映射文件的set标签中配置cascade=“save-update”

7.7  多对多映射

例如:项目与开发人员是多对多的关系。

7.7.1  实体设计代码:

项目实体

publicclass Project {

    privateintpid;

    private String pname;

    // 项目对应的多个开发人员  【一对多】

    private Set<Developer> developers = new HashSet<Developer>();

开发人员实体

publicclass Developer {

    privateintid;

    private String name;

    // 开发人员,参与的多个项目

    private Set<Project> projects = new HashSet<Project>();

7.7.2  实体映射配置:

Project.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.d_many2many">

    <class name="Project" table="t_project">

        <id name="pid">

            <generator class="native"></generator>

        </id>

        <property name="pname" type="string" length="20"></property>

        <!--

            多对多映射

                项目,对应的多个开发人员

         -->

         <set name="developers" table="t_relation">

            <key column="pid"></key>

            <many-to-many class="Developer" column="tid"></many-to-many>

         </set>

    </class>

</hibernate-mapping>

Developer.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="cn.itcast.d_many2many">

    <class name="Developer" table="t_developer">

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name" type="string" length="20"></property>

        <!--

            多对多映射

                name  要映射的集合属性

                table 集合属性,对应的中间表

                key   指定中间表的外键字段(自动引用当前表的主键t_developer(id)   )

                many-to-many

                    class  集合元素的类型

                    column  外键字段,对应的中间表字段

                   

                (t_developer.id=t_relation.tid,  t_relation.pid=t_project.pid)

         -->

         <set name="projects" table="t_relation">

            <key column="tid"></key>

            <many-to-many class="Project" column="pid"></many-to-many>

         </set>

    </class>

</hibernate-mapping>

 

7.7.3  Inverse关联关系的维护

是否设置inverse,对维护关联关系的影响:

1) 保存数据时有影响。

   Inverse=false, 有控制权,才可以往中间表中插入数据

   Inverse=true,  不会往中间表插入数据!

2) 获取数据没有影响。

注意:a. 多对多映射配置,也可以只配置一方(一方配置,三张表的关系已经体现):这样,就只能通过配置的一方维护对方;

       b. 如果多对多双向配置,2个方向都可以维护另外一方;

       c. (了解)双方维护时候,对应的中间表可以不是同一个;

          单独的一方,维护一个独立的中间表!

3) 解除关系有影响。

   Inverse=false,  有控制权,解除关系就是指删除中间表数据!

   Inverse=true, 没有控制权, 不能解除关系(不会报错!), 什么操作都不执行

4) 删除数据有影响。

 inverse=false, 有控制权, 会先删除中间表数据,再删除自身!

inverse=true,  没有控制权,如果主键被中间表引用,删除报错!  否则,可以删除!

 

总结:Inverse属性对关联关系的影响:在中间表中就是对外键约束的维护,多对多中就是对中间表的维护。获取数据,根本就不会影响外键和中间的数据,所以不会受影响。保存,删除和解除关系时会外键字段和中间的数据进行置空或删除,故会影响关联关系的维护。

8   Hibernate中对象的三种状态

8.1  临时状态

      1. 一般是直接new出来的对象

      2. 不处于Session的缓存中;

      3. 在数据库中没有对应的记录。

        持久化状态

      4. 在session管理范围内;

      5. 数据库中有对应的记录;

      6. 处于持久化状态的对象

当对象属性修改的时候,提交事务时,会把修改的结果同步到数据库!只要调用session的save/get/load/update/saveOrUpdate/list()等方法的时候,对象就会变为持久化状态;

8.3  游离状态

      1. 一般指session关闭后由持久化状态转换来的状态 ,因此在数据库中可能还有对应的记录(前提是记录没有被其它程序删除)

      2. 不处于session管理中

注:只要对象的(对应数据库中主键的)属性在数据库中存在,就可以说对象在数据库中有对应的记录!

状态转化图:

演示代码

publicclass App_1_status {

    privatestatic SessionFactory sf;

    static {

        sf = new Configuration()//

                .configure()//

                .addClass(Dept.class)//

                .addClass(Employee.class)//

                .buildSessionFactory();

    }

   

    // 1.  对象3种状态,之间的转换

    @Test

    publicvoid test_1() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

       

        // 对象

        Dept dept = new Dept();     //---> 【临时状态】

        dept.setName("HR");

       

        // 保存

        session.save(dept);         //---> 【持久化状态】   insert into t_dept (name) values (?)

   

        // 现在,dept处理session的管理!

        dept.setName("HR");                  //update t_dept set name=? where id=?

       

        session.getTransaction().commit();

        session.close();

       

        // 对象

        //System.out.println(dept.getName());   // //---> 【游离状态】

    }

   

    // 持久化状态

    // 只要调用sessionsave/get/load/update/saveOrUpdate/list()   等方法的时候,对象就会变为持久化状态状态

    @Test

    publicvoid test_2() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

        // 状态?

        Dept dept = (Dept) session.get(Dept.class, 1);

        dept.setName("Developer");

       

        session.getTransaction().commit();

        session.close();

    }

    // 思考: 对象状态,哪些状态的对象,在数据库中有对象的记录?持久化/游离

    // 游离状态

    @Test

    publicvoid test_3() throws Exception {

        Session session = sf.openSession();

        session.beginTransaction();

   

        // 删除数据两种方式:

        // 方式1先获取,再删除

//      Dept dept = (Dept) session.get(Dept.class, 6);

//      if (dept != null) {

//          session.delete(dept);// 对象在数据库中要有记录

//      }

        //方式2. 手动模拟一个游离状态对象

        Dept dept = new Dept();

        dept.setId(6);

        session.delete(dept);

       

        session.getTransaction().commit();

        session.close();  // 1. session关闭后对象处于游离状态

    }

}

 

9   Hibernate一级缓存和二级缓存

9.1  一级缓存

Hibernate的一级缓存是基于session对象的缓存,用于减少对数据库的访问次数,从而提升程序执行效率!(先从缓存中找,缓存中有数据直接获取,没有数据在向数据库发送sql获取数据。缓存实际上是map<主键, 数据>的形式存储数据)所以一级缓存只是在创建sessionsession关闭之间有效

当调用sessionget/load/save/update/saveOrUpdate/list/iteraor等方法操作的对象会自动放入一级缓存!如果用户想操作一级缓存的内容,只能通过hibernate提供的相应方法操作!

session.flush();        让一级缓存的数据与数据库中的数据同步,

相当于执行了一次事务的commit方法

session.clear();        清空一级缓存中所有的数据

session.evict(object);   清空一级缓存中指定的对象数据

 

总结:可以在短时间内减少对数据库访问次数;

一级缓存只在session范围有效:缓存时间短、作用范围小

9.1.1  示例代码解析

Session session = HibernateSessionFactory.getSession();

    @Test

    publicvoid testSave(){

        session.beginTransaction();

       

        Dept dept = new Dept();

        dept.setDeptName("开发一部");

        dept.setDeptAddress("广州");

       

        dept = (Dept) session.get(Dept.class, 15);

        dept.setDeptName("a");

        dept.setDeptName("b");

/*

        * 执行flush方法会将session中的数据与数据库同步

         * 即如果session中的数据与数据库中一样,此方法执行跟没执行效果一样

         * 但若session中的数据与数据库中的不一样,会生成update语句更新数据库

         */

        session.flush();

       //也会将session中的数据跟数据库同步      session.getTransaction().commit();

        HibernateSessionFactory.closeSession();

    }

9.1.2  不同的session对象是否共享一级缓存的数据

每一个session对象都有自己的一个独立的缓存区,他们之间互不影响,所以不共享数据。

9.1.3  session的Query接口的2个查询方法list()/iterator()区别

list和itrator两个方法都会将数据放入一级缓存,但list方法不会从缓存中获取数据,而是直接发送sql从数据库中获取数据。执行itrator方法的时候发送的sql只是获取全部的id,然后会逐个根据id从缓存中获取数据,缓存中若没有数据,才会根据id从数据库中获取。

//3. 区别

        /**

         * 1. iterator会有n+1问题

         * 2. 缓存:

         *      list,  会放入缓存,但不会从缓存中取

         *      iterator, 会放入返回,也会从缓存中取!

         */

        @Test

        publicvoid test_2_3_list_iterator() throws Exception {

            Session session = sf.openSession();

            session.beginTransaction();

           

            Query q = session.createQuery("from Dept");

           

            /*  两次执行list

            List<Dept> list = q.list();             // 会放入缓存。

           

            q = session.createQuery("from Dept");

            list = q.list();

            */                      // 从缓存中取?

            /*

             * 两次执行iterator

             

            Iterator<Dept> it = q.iterate();        // 主键

            while (it.hasNext()) {

                System.out.println(it.next());      // 先从缓存找,每一找到再根据主键去数据库查询

            }

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

            it = q.iterate();                       // 主键

            while (it.hasNext()) {

                System.out.println(it.next());      // 从缓存找,找打后就返回,不查找数据库

            }

            /*

             * list,再iteraor,证明list方法查到的数据会放入sesison缓存!

             */

            q.list();                       // 有放入缓存

           

            Iterator<Dept> it = q.iterate();       

            while (it.hasNext()) {

                System.out.println(it.next());      // 没有查找数据库

            }

            session.getTransaction().commit();

            session.close();

        }

 

 

9.2  二级缓存

一级缓存作用范围小,时间段。Hibernate支持二级缓存,但二级缓存默认是关闭的。二级缓存是基于应用程序或者说基于SessionFactory的缓存!二级缓存的内容可以给不同的session访问!

获取数据的过程其实是先去一级缓存,再去二级缓存(前提是开启),最后才去数据库中找。所以只会把一些经常用来查询,却不怎么修改的数据放入二级缓存。并且一级缓存的内容修改时,数据会与二级缓存同步(前提指定该数据可以放入二级缓存)。

9.2.1  具体配置参考hibernate.properties

## disable the second-level cache                                                

 

#hibernate.cache.use_second_level_cachefalse                     【是否开启二级缓存】

 

## enable the query cache

 

#hibernate.cache.use_query_cache true                                    【是否开启查询缓存】

 

## choose a cache implementation                                               【选择二级缓存的实现框架】

 

#hibernate.cache.provider_classorg.hibernate.cache.EhCacheProvider

#hibernate.cache.provider_classorg.hibernate.cache.EmptyCacheProvider

【默认支持的二级缓存的实现】

hibernate.cache.provider_classorg.hibernate.cache.HashtableCacheProvider  

#hibernate.cache.provider_classorg.hibernate.cache.TreeCacheProvider

#hibernate.cache.provider_classorg.hibernate.cache.OSCacheProvider

#hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider

9.2.2  二级缓存使用步骤:

1.开启Hibernate二级缓存;2.指定使用哪种二级缓存实现框架;3,指定哪些类可以加入二级缓存。

配置代码:

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

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

     <class-cache usage="read-only" class="cn.itcast.b_second_cache.Dept"/>

测试代码:

Session session1 = HibernateSessionFactory.getSessionFactory().openSession();

        session1.beginTransaction();

        // 1. session1 get

        Dept dept = (Dept) session1.get(Dept.class, 15);     // 发送sql,查询数据库中的原始数据是:开发部

       

        // 2. sessoin1. update,修改数据库中的数据

        session1.createQuery("update Dept set deptName='new_dept' where id=15").executeUpdate();

       

        session1.getTransaction().commit();    //发送sql,修改数据

        // 3. 输出一级缓存取出数据

        dept = (Dept) session1.get(Dept.class, 15); //一级缓存中获取的数据

        System.out.println(dept.getDeptName());     // 开发部 , 一级缓存数据没有改, 说明修改数据没有通知一级缓存

       

        session1.close();

        /******************Session2*******************/

        Session session2 = HibernateSessionFactory.getSessionFactory().openSession();

        session2.beginTransaction();

        // 4. session2, get

        dept = (Dept) session2.get(Dept.class, 15);

        System.out.println(dept.getDeptName());      // new_dept  说明,修改数据通知二级缓存了!

       

        session2.getTransaction().commit();

        session2.close();

控制台打印结果

若将session1的修改操作注释后:

试验显示,因为session1对数据进行了修改后,session1再次获取数据没有发送sql证明是从一级缓存中获取,但是数据仍为修改之前的数据:开发部。这证明修改数据没有通知一级缓存。此时session2同样获取数据,却发送了sql这足以证明session2不是从二级缓存中获取,这证明修改数据通知了二级缓存

若session1不进行修改操作,则session1第二次获取数据是从一级缓存中获取,session2是从二级缓存中获取。根据输出结果进一步证明了上述观点。

9.2.3  集合缓存

经过以上配置就可以从缓存中获取部门,不能通过部门获取员工数据(例:dept.getEmployees()),需要以下配置:

<class-cache usage="read-write" class="cn.itcast.b_second_cache.Employee"/>

<collection-cache usage="read-only"

collection="cn.itcast.b_second_cache.Dept.employees"/>

9.2.4  Query接口使用缓存

query.list()是不使用一级缓存的,同样也不使用二级缓存,若想使用二级缓存,需要以下配置。

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

并且在代码中使用query.list()方法时需要使用setCacheable()方法。例:

Query q = session1.createQuery("fromDept").setCacheable(true);

q = session2.createQuery("fromDept").setCacheable(true);

这个方法表名查询数据放入缓存或者从缓存中取数据。这样session2就是从二级缓存中获取的数据。

9.2.5  Hibernate的缓存策略:

 

只读缓存策略:放入二级缓存的数据时只读的

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

读取策略,可以读取/修改二级缓存数据

<class-cache usage="read-write"/>

非严格的读写策略, 不会对数据锁定, 效率高于读写策略;可能会有无效数据的存在

<class-cache usage="nonstrict-read-write"/>

基于事务的缓存策略(一般,二级缓存框架,目前不支持)

<class-cache usage="transactional"/>

 

10  Hibernate提供的查询

10.1 主键查询

// get: 及时加载

Employee emp = (Employee) session.get(Employee.class, 1);

// load: 懒加载

Employee emp = (Employee) session.load(Employee.class, 1);

10.2 对象导航查询

//对象导航查询

     Employee emp = (Employee)session.get(Employee.class, 1);

     System.out.println(emp.getEmpName());

     System.out.println(emp.getDept().getName());  // 懒加载。

员工是多方

10.3 Hql查询(重要)

       //1. 使用hql: 查询的是对象、属性

       // 注意:需要试着auto-import="true" ,表示自动导入包!如果为false,再使用hql的时候,需要写上类的全名

       Query q = session.createQuery("from cn.itcast.a_query.Employee e");

      

      

       //2. 查询全部列

       //q = session.createQuery("select * from Employee e"); // 不能用 * 符号

       q = session.createQuery("select e from Employee e");

      

       //3. 查询指定列,把每一行记录返回一个Object[], 再把数组对象添加到list集合

       // 查询一列,返回List<字段对应的Java类型>

       // 查询多列,返回List<Object[]>

       q = session.createQuery("select e.empId,empName from Employee e");

      

       //查询指定的列,封装为对象。此时需要bean中有对应的构造函数。

       q = session.createQuery("select new Employee(e.empId,empName) from Employee e");

      

       /*

        * 4. 条件查询【一个条件、多个条件(in or and)、模糊、between and

        */

      

       //4.1 简单条件

       q = session.createQuery("from Employee e where e.empName='老张'");

       // 设置条件参数,方式1占位符

       q = session.createQuery("from Employee e where e.empName=? or birth=?");

       q.setParameter(0, "老张");      // 参数从0开始设置

       q.setParameter(1, new Date());

      

       // 设置条件参数,方式2命名参数查询 :name则是参数名定义为name

       q = session.createQuery("from Employee e where e.empName = :name or birth = :birthday");

       q.setString("name", "老张");

       q.setDate("birthday", new Date());

      

       // in  查询

       q = session.createQuery("from Employee e where e.empId in(1,2,3)");

      

       // like 模糊查询

       String name = "";

       q = session.createQuery("from Employee e where e.empName like ?");

       q.setParameter(0, "%"+name+"%");

      

       // between and

       q = session.createQuery("from Employee e where empId between 1 and 2");

      

       // 测试题:查询部门id1的记录

       q = session.createQuery("from Employee e where e.dept.id=?");//要找到具体的条件

       q.setParameter(0, 1);

      

        //5. 聚合函数统计、分组、筛选

       q = session.createQuery("select count(*) from Employee e ");

       Long obj = (Long) q.uniqueResult();

      

       // 需求:统计每个部门的人数

        // HQL 分组,方式1

       q = session.createQuery("select e.dept.id,count(*) from Employee e group by e.dept.id");

      

       // HQL分组,方式2hql不用后面的id也可以

       //q = session.createQuery("select e.dept,count(*) from Employee e group by e.dept");

      

       // 分组筛选,显示部门人数 > 2 的部门

       q = session.createQuery("select e.dept,count(*) from Employee e group by e.dept having count(*)>2");

       // 内连接

       //Query q = session.createQuery("from Employee e inner join e.dept");

       Query q = session.createQuery("from Dept d inner join d.employees");

       // 查询返回数据

       List<Object[]> list = q.list();

       for (int i=0; i<list.size(); i++) {

           Object[] obj = list.get(i);

           // 获取第一个元素

           Dept dept = (Dept) obj[0];

           // 获取第二个元素

           Employee e = (Employee) obj[1];

          

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

           System.out.println(e.getEmpName());

          

       }

/*

        * 需求1: 查询显示部门信息,以及部门下的员工

        *    左外连接

        */

       //Query q = session.createQuery("from Dept d left join d.employees");

      

       // 改为右外连接,

       Query q = session.createQuery("from Employee e right join e.dept");

 

    Dept dept = (Dept) session.get(Dept.class, 1);  // 没有查询关联的数据

//     System.out.println(dept.getName());

//     System.out.println(dept.getEmployees());//-->懒加载  select

      

       // 迫切左外连接,fetch关键字

       //Query q = session.createQuery("from Dept d left join fetch d.employees");

      

       // 内连接,迫切内连接, 自动封装数据右表数据填充到左表中,将员工信息封装到部门的set中,可以像懒加载一样使用。确是即时加载】

       Query q = session.createQuery("from Dept d inner join fetch d.employees");

      

       List<Dept> list = q.list();                      // 关联的所有数据都有查询出来

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

       for (Dept d : list){

           System.out.println(d);

           System.out.println(d.getEmployees());         // 及时加载

       }

 

10.4 Criteria面向对象的查询(不会数据库的可以使用,缺乏灵活性)

// 获取QBC查询接口

        Criteria criteria = session.createCriteria(Employee.class);

        // 设置条件

        //criteria.add(Restrictions.idEq(1));// 根据id查询

        criteria.add(Restrictions.eq("empId", 1));

        criteria.add(Restrictions.eq("empName", "老张"));

       

        // 查询

        List<Employee> list = criteria.list();

        System.out.println(list);

 

10.5 Sql查询(执行原生态sql)

        // hibernate也支持对原生态的sql语句的查询,返回的Object[]

        SQLQuery q = session.createSQLQuery("SELECT * FROM t_dept d");

        List<Object[]> list = q.list();

       

        // 也可以封装为对象,但要有映射

        SQLQuery q = session.createSQLQuery("SELECT * FROM t_dept d");

        q.addEntity(Dept.class); // 查询的结果封装为指定的对象类型

       

        List<Dept> list = q.list();

       

        System.out.println(list);

11  Hibernate连接池

Hibernate提供c3p0连接池提供支持(本身的连接池值维护一个连接),具体配置可以查看hibernate.properties  。

hibernate.connection.pool_size 1                     默认支持的连接池

 

###########################

### C3P0 Connection Pool###                            c3p0连接池支持

###########################

 

#hibernate.c3p0.max_size 2                               最大连接数

#hibernate.c3p0.min_size 2                                最小连接数

#hibernate.c3p0.timeout 5000                          超时时间

#hibernate.c3p0.max_statements 100           一次最多执行的sql语句个数

#hibernate.c3p0.idle_test_period 3000          空闲测试时间,单位:秒(因为数据库的连接当一定时间(大概八小时)不使用时,会自动断开连接。)

#hibernate.c3p0.acquire_increment 2             连接不够用时候一次增加的连接数

#hibernate.c3p0.validate false

        

Hibernatec3p0连接池支持驱动类(相当于CombopooledDataSource)

#hibernate.connection.provider_classorg.hibernate.connection.C3P0ConnectionProvider

主配置文件中的代码

<!-- 3. 连接池配置 -->

    <!-- a. 驱动类 -->

    <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>

    <!-- b.连接池参数配置 -->

    <property name="hibernate.c3p0.min_size">2</property>

    <property name="hibernate.c3p0.max_size">4</property>

    <property name="hibernate.c3p0.max_statements" >100</property>

    <property name="hibernate.c3p0.acquire_increment">2</property>

    <property name="hibernate.c3p0.timeout">5000</property>

    <property name="hibernate.c3p0.idle_test_period">3000</property>

12  Hibernate两种创建session的方式

方式一:openSession

      

// 每次创建一个新的session, 必须手动关闭

       Session session1 = sf.openSession();

       Session session2 = sf.openSession();

       System.out.println(session1 == session2); // false

       session1.close();

       session2.close();

 

方式二:getCurrentSession创建或者获取session

        * 注意:

        *  1, 需要配置session创建方式

<property name="hibernate.current_session_context_class">thread</property>

        *  2. 这种方式创建session, 可以不用关闭,线程结束自动关闭!

       // 首先,从当前线程上获取session,如果没有获取到就执行openSession,创建新的session。然后,把创建的session,绑定到当前线程上;

       // 当再获取session的时候,就从当前线程取出已绑定的session,就不创建新的session 。

      

Session session3 = sf.getCurrentSession();

       Session session4 = sf.getCurrentSession();

       System.out.println(session3 == session4);   // true

       session3.close();

       //session4.close();

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值