Hibernate - 传智汤阳光

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37546891/article/details/81067479

>> Hibernate 基础

应用程序架构:B/S、C/S;

应用程序分层一般分三层;(目的是解耦

  • View(表示层):没有业务逻辑,Action与JSP等都是显示层的技术,结合使用,配合现实界面,单独任何一个都不能完成工作;
    • Action / Servlet / XXX:提供页面显示的数据,
    • JSP:页面显示的模板,
  • Service(业务层)
  • Dao(数据访问层)

MVC三层架构:与上面分层不一样,两者是站在不同的角度来看待程序的分层结构,没有可比性;上面表示程序在结构上分成三层:上中下,是从代码的整体层次上划分的,MVC表示从View表示层看一个全局,相当于从表示层按“前中后”往后看;

数据库:Mysql、SQLserver、Oracle、Db2 ……
Dao通过JDBC访问数据库,访问不同的数据库java代码一样,但是SQL语句不一样;SQL语句在具体的每一个数据库都不太一样,同样的操作具体的语句会有一点区别,那是因为不同的数据库都遵循了SQL的标准,但还有一些自己的扩展,比如 函数 不一样;

使用框架优势:不使用框架时,对象存到数据库需要做转变:从对象到表记录(转换成insert语句);从数据库查询对象需要做转变:从表记录到对象;不使用数据库支持不同的数据库麻烦,来回数据转换也麻烦,so出现了框架;框架完成了对象与表记录之间的转换,换句话说,框架向我们屏蔽了数据库,我们不用再关心对象与表记录之间的怎么转换了;框架操作数据库也需要使用JDBC,但是框架对JDBC进行了包装,框架支持的数据库我们都可以使用;
这里写图片描述


>> 框架学习内容

这里写图片描述

(1)怎么样命令框架做事情 - API

  • Configuration
  • SessionFactory
  • Session
  • Transaction
  • Query:session.createQuery查询 - 使用hql语句进行查询;【推荐使用HQL,更强大】
  • Criteria:session.createCriteria 查询 - 使用面向对象的构建查询条件的方法;【已过时】

(2)跟数据库进行交互时用到的配置

  • 主配置文件hibernate.cfg.xml
    • 数据库信息(方言、URL、驱动、用户名、密码)
    • 导入映射配置文件
    • 其他配置(是否显示SQL语句、是否自动建表)
  • 映射配置文件Xxx.hbm.xml;【类 – 表、属性 – 列】
    • 映射基础
    • 普通属性
    • 主键
    • 集合属性
    • 关联关系:一对多/多对一、多对多、一对一
    • 继承结构

(3)其他特性:

  • 数据库连接池:提高效率;
  • 懒加载:提高效率;
  • 二级缓存;

>> Hibernate 所需jar包

  • hibernate核心jar包;
  • required文件夹下所有jar包;
  • hibernate-jpa-2.1-api-1.0.0.Final.jar
  • Mysql的JDBC驱动包:mysql-connector-java-5.1.5-bin.jar

>> Hibernate 配置文件与映射文件

  • hibernate.cfg.xml - 主配置文件
  • hibernate.hbm.xml - 映射配置文件

(1)配置文件一般放在classpath(src)文件下;

  • hibernate.cfg.xml模板参考..\project\etc本地文件夹下有,可以直接拷贝到项目中修改使用;
  • 配置文件所有可配置的项的模板参考..\project\etc本地文件夹下的hibernate.properties文件;

(2)映射文件名称通常与对应的实体名称一样,并与实体放在同一个包中;此文件里面说明的就是对象(实体)与表,对象的属性与表中的列之间的对应关系,一个对象对应一个映射文件,对应一个表;

与表相对应的类最基本要求:【前三个必须满足,后两个自愿】

  • 1、提供一个无参构造器;因为session的get方法User user = session.get(User.class, 1);可以获取一个对象,但是没有new,而对象创建了,说明肯定做了new操作,new操作是hibernate里面做的;hibernate通过反射new的对象,反射需要用到无参的构造函数;
  • 2、提供一个标识属性(对象标识OID):与主键对应的属性;
  • 3、有set/get方法
  • 4、使用非final类;
  • 5、重写equals()和hashCode()方法;

>> hibernate.cfg.xml

<!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 name="foo">
        <!-- 基本配置:配置数据库信息 (可配置项设置参考 ..\project\etc 文件) -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> //前缀可有可不有,MySQL5Dialect
        <property name="connection.url">jdbc:mysql:///test?serverTimeZone=UTC</property> //test是数据库的名称 
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</property>

        <!-- 其他配置 -->
        <!-- 在控制台显示生成的SQL语句,并格式化,默认false;使用不同的数据库的SQL语句会有不同 -->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>

        <!-- 自动生成表结构 -->
        <!-- 
            create:先删除,再创建;(一般测试时用)
            update:如果表不存在就创建,不一样就更新,一样就什么都不做;(一般开发时使用)
                   一般在增加信息时有效,更改时一般无效;比如增加/删除非空约束、修改length值,就会无效(不更新表);
            create-drop:初始化时创建表,SessionFactory显示调用执行close()时删除表;(一般测试时用)
            validate:验证表结构与hbm中是否一致,如果不一致,就抛异常;(一般生产环境下用)
         -->
        <property name="hbm2ddl.auto">update</property>

        <!--设置默认的事务隔离级别:只在同时执行多个事务时才有效,同一个事务里面没关系;
            隔离级别             对应的整数表示
            READ UNCOMMITED    1
            READ COMMITED      2
            REPEATABLE READ    4
            SERIALIZEABLE      8
         -->
        <property name="connection.isolation">2</property>

        <!-- C3P0连接池设定-->
        <!-- 使用c3p0连接池  配置连接池提供的供应商-->
        <property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>                                                                                                                                 
        <!--在连接池中可用的数据库连接的最少数目 -->
        <property name="c3p0.min_size">5</property>
        <!--在连接池中所有数据库连接的最大数目  -->
        <property name="c3p0.max_size">20</property>
        <!--设定数据库连接的过期时间,以秒为单位,
        如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
        <property name="c3p0.timeout">120</property>
         <!--每3000秒检查所有连接池中的空闲连接 以秒为单位-->
        <property name="c3p0.idle_test_period">3000</property>

        <!-- 使用二级缓存,默认是未打开的; -->
        <!-- 指定要使用的缓存的提供商,这也就打开了二级缓存 
        <property name="cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
        -->
        <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
        <!-- 开启使用查询缓存 -->
        <property name="cache.use_query_cache">true</property>
        <!-- 指定要使用二级缓存的实体类,不指定类的话相当于打开但没有使用二级缓存,没效果 -->
        <class-cache usage="read-write" class="cn.itcast.l_second_cache.Employee"/>
        <class-cache usage="read-write" class="cn.itcast.l_second_cache.Department"/>
        <collection-cache usage="read-write" collection="cn.itcast.l_second_cache.Department.employees"/>

        <!-- 导入映射文件:Xxx.hbm.xml 映射文件的全限定名 -->
        <!-- 配置此属性之后,读取到主配置文件就能找到所有的映射文件 -->
        <mapping resource="cn/itcast/a_helloworld/User.hbm.xml" /> //映射文件在classpath下的路径
        <mapping file="映射文件路径">  //不能与resource同时使用
    </session-factory>
</hibernate-configuration>

>> xxx.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping  package="cn.itcast.a_helloworld">
    <class name="User" table="t_user">
        <!-- 主键 -->
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>

        <!-- 普通类型:int、string -->
        <property name="name" type="string" column="name" length="20"/>
        <property name="age" type="int" column="age_"/>

        <!-- 日期类型:date;type="date/timestamp"-->
        <property name="birthday" type="date" column="birthday_"/>  //type="date"要写上

        <!-- 大文本类型:text;指定使用此类型时,最好再指定length,以确定生成的SQL类型是能够存放指定数量
             的字符的;因为text有多种类型; -->
        <property name="desc" type="text" length="5000" column="`desc`" ></property>

        <!-- 二进制类型:binary;  头像,最好指定长度 -->
        <property name="photo" type="binary" length="102400"></property>

        <!-- 集合类型:Set、List、Map、数组、bag-->
        <!-- set - name属性:类的集合属性名;
             set - table属性:集合表的名称;
             set - order-by属性:写的是order by 子句,是SQL语句,是操作的集合表;这是在查询数据时指定orderby子句;
             key子元素 - column属性:表中集合外键的列名;
             element子元素 - type属性:存放集合元素的列的信息;
             sort属性:"unsorted|natural|comparatorClass";默认为:unsorted;set和map有这个属性,list没有;
                       sort是在内存中进行排序的,效率低,一般不使用,除非在数据库中排序实现不了的情况下才使用内存排序;
        -->
        <set name="addressSet" table="user_addressSet" order-by="address ASC"> //DESC降序
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </set>

        <!-- List集合 
             list-index:用于存放索引的列;
        -->
        <list name="addressList" table="user_addressList">
            <key column="userId"></key>
            <list-index column="idx"></list-index>
            <element type="string" column="address"></element>
        </list>

        <!-- Array数组;  与List的映射基本一致,只是标签名不一样 -->
        <array name="addressArray" table="user_addressArray">
            <key column="userId"></key>
            <list-index column="idx"></list-index>
            <element type="string" column="address"></element>
        </array>

        <!-- Map集合 -->
        <map name="addressMap" table="user_addressMap">
            <key column="userId"></key>
            <map-key type="string" column="key_"></map-key>
            <element type="string" column="address"></element>
        </map>

        <!-- Bag集合:无序,可重复;与Set集合的映射基本一致 -->
        <bag name="addressBag" table="user_addressBag">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </bag>
    </class>    
</hibernate-mapping>

(1)<hibernate-mapping>

  • package属性:实体类所在的包名的全限定名,若省略的话,下面的class标签的name需要写类的全限定名;

(2)<class>:表示对实体类的描述;

  • name:哪个类; package属性定义的包下的 对应的实体类的名;
  • table:哪个表; 该实体类 对应的数据库中的表名;如果不写,默认的表名就是类的简单名称;

(3)<id>:表示对主键的描述;

  • generator :指定主键的自动增长(生成)策略;class=identify/sequence/hilo/**native**/assigned/uuid 【最常使用native】
    • identity:使用(支持identity的)数据库的自动增长策略,不是所有数据库都支持,比如oracle就不支持,Oracle支持序列;
    • sequence:在 DB2,PostgreSQL,Oracle,SAP DB,McKoi等支持序列的数据库中使用序列(sequence);在使用Oracle数据库时可以使用这一个;
    • Hilo: 使用高低位算法生成主键值;只需要一张额外表,所有的数据都支持
      这里写图片描述
    • native:【推荐使用!!】根据底层数据库的能力选择 identity、sequence 或者 hilo中的一个; 【主键是数字(推荐使用包装类型)类型】
    • increment:【不使用!!】 由Hibernate维护的自动增长,不是数据库维护的;先查询当前最大的id值,再加1使用;不推荐使用,因为在多线程下会问题;
    • assigned:手工指定主键值,多和UUID结合使用 UUID.randomUUID().toString()
    • uuid: 【推荐记住!!】由Hibernate自动生成UUID并指定为主键值; 【主键是String类型】

(4)<property>:表示对一般属性的描述; 普通的属性(数据库中的基本类型,如字符串、日期、数字等);

  • name:实体类中的属性名,必须要有;
  • type:实体类的属性的类型;如果不写,Hibernate会自动检测;一般不写,但检测有歧义或可能会失败的时候需要写;
    日期、大文本类型一般需要写明;
    可以写Java中类的全名:java.lang.String ; 或是写hibernate类型:string 【一般是小写关键字】
  • column:对应表中的列名,如果没有,默认为属性名;【当列名与关键字冲突时,可以通过column属性指定一个其他的列名,或是使用反引号包围起来】
  • length:长度,默认255;不是所有的类型都有长度属性:日期、int不需要长度属性;
  • not-null:非空约束,默认false;

(5)<set>、<List>、<map>、<array>、<bag>:表示对集合属性的描述;
这里写图片描述


>> Session会话

  • 增删改查都是通过一个核心的类Session实现的,Session对象里面有封装好的增删改查方法,可以直接拿来使用;
  • Session通过sessionFactory创建,so要获取Session,需要创建sessionFactorysessionFactory根据读取到的配置信息创建一个有指定配置的对象;sessionFactory是根据配置生成的; 配置文件hibernate.cfg.xml里面引用了映射文件Xxx.hbm.xml,所以读取到配置文件就能找到所有的映射文件;有一个类Configure来专门维护配置文件的信息;
  • sessionFactory只有一个,所以设置成static,只加载一次,所以在static代码块里面创建;
  • 正规情况下要创建一个工具类,在工具类里面创建sessionFactory,然后对外提供get方法来获取全局唯一的sessionFactory
  • sessionFactory是线程安全的,可以在多线程的情况下来使用它;

>> sessionFactory工具类

public class HibernateUtils {
    private static SessionFactory sessionFactory; //定义静态变量,全局只需要有一个就可以了
    static {                                      //设置成静态的,只加载一次
        Configuration cfg = new Configuration();
        cfg.configure();                          //读取默认的配置文件(hibernate.cfg.xml)
        //cfg.configure("hibernate.cfg.xml");     //读取指定位置的配置文件(src下)
        sessionFactory = cfg.buildSessionFactory();

        // 通过编程的方式增加映射文件的两种方法:addResource、addClass
        // cfg.addResource("cn/itcast/a_helloworld/User.hbm.xml");
        // 增加一个映射文件,等价于主配置文件中的<mapping resource="cn/itcast/a_helloworld/User.hbm.xml" />
        // cfg.addClass(User.class); 
        // 去User类所在的包中查找名称为User,后缀为.hbm.xml的文件,等价于addResource;   
    }

//  static { //使用方法链的方式初始化sessionFactory
//      sessionFactory = new Configuration()//
//              .configure()//
//              .cfg.addClass(User.class);  //添加Hibernate实体类(加载对应的映射文件);
//                                            调用这个方法就不用在主配置文件里面使用<mapping>标签了;
//              .buildSessionFactory();
//  }

    public static SessionFactory getSessionFactory() {
        return sessionFactory; //获取全局唯一的SessionFactory
    }
    public static Session openSession() {
        return sessionFactory.openSession(); //从全局唯一的SessionFactory中打开一个Session
    }
}

>> 增删改查方法+User类+测试类

调用session工具类的session对象,直接使用session对象的增删改查方法尽心封装,然后在其他地方直接调用封装的增删改查方法;

~ 实体类 - User、QueryResult

与主键对应的属性:如果是数字,建议使用包装类型;因为原始类型int,默认值是0;包装类型integer默认值是null;包装类型null意义更好,因为0是一个有效的数字,可以将主键设置成0;这就有一个问题:当我们希望将字段值等于默认值的时候,使用某一个值来代表它是没有数据的,这时使用null比较合适,null代表与数据库中是没有对应关系的;

public class User {        //User实体类
    private int id;    //与主键对应的属性:如果是数字,建议使用包装类型;
    private String name;
    private Integer age;
    private Date birthday; // 生日
    private String desc;   // 一大段说明
    private byte[] photo;  // 头像图片
    //set、get方法
    @Override
    public String toString() {
        return "[User: id=" + id + ", name=" + name + "]";
    }
}
public class QueryResult {  //QueryResult 实体类
    private int count;      // 总记录数
    private List list;      // 一页的数据
}

~ Dao方法类 - UserDao

在类名上右键 new Junit Test Case,创建测试类;

public class UserDao {
    //保存:传入要保存的对user象,  无返回值;
    public void save(User user) {
        Session session = HibernateUtils.openSession();  //使用session工具类打开一个新的Session
        try {
            Transaction tx = session.beginTransaction(); // 开始事务

            session.save(user);     //除了这句关键代码,其他都是模板代码

            tx.commit();                                 // 提交事务
        } catch (RuntimeException e) {
            session.getTransaction().rollback();         // 获取当前Session中关联的事务对象
            throw e;
        } finally {
            session.close();                             // 关闭Session,释放资源
        }
    }
//运行时异常不用捕获,因为运行时很严重,捕获之后也补救不了;若要catch之后不做补救措施就要抛出;
//catch运行时异常不是为了要异常,为的是在有异常的时候回滚操作,对异常的处理就是什么也不做,so一定要抛出异常; 

    //更新:传入要保存的对user象,   无返回值;
    public void update(User user) {
        Session session = HibernateUtils.openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            session.update(user);             //关键操作的代码
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
            throw e;
        } finally {
            session.close();
        }
    }

    //删除:传入要删除的记录的id,根据id获取对象,再删除对象,   无返回值;
    public void delete(int id) {
        ......
        try {
            tx = session.beginTransaction();
            Object user = session.get(User.class, id); // 要先根据id定位,获取到这个对象
            session.delete(user);                      // 再删除 id对应的实体对象
            tx.commit();
        }.......
    }

    //获取查询 - 根据id查询一个User数据: 传入要查询的记录的id;返回User对象;
    public User getById(int id) {
        ......
        try {
            tx = session.beginTransaction();
            User user = (User) session.get(User.class, id); 
            //get方法需要两个参数:指定要查询的记录的id所属的类;
            //不是表,而是对象,因为使用框架,将数据库屏蔽了,接触不到表;
            tx.commit();
            return user;      //返回User对象;
        } ......
    }

    //查询 - 所有: 返回结果集;
    public List<User> findAll() {
        ......
        try {
            tx = session.beginTransaction();
            // 方式一:使用HQL查询:添加查询条件直接在HQL语句上添加;WHERE、ORDER BY、COUNT ......
            List<User> list = session.createQuery("FROM User").list(); 

            // 方式二:使用Criteria查询:以面向对象的方式来构建查询;【已过时】
            //创建对象时只添加一个查询条件:要查询的对象Xxx.class;添加其他限制条件时使用对象的方法调用;
            Criteria criteria = session.createCriteria(User.class); 
            List<User> list = criteria.list();

            tx.commit();
            return list;
        } .......
    }

    /**  分页的查询数据列表
     * @param firstResult : 从结果列表中的哪个索引开始取数据
     * @param maxResults : 最多取多少条数据
     * @return  一页的数据列表 + 总记录数
     */
    public QueryResult findAll(int firstResult, int maxResults) {
        ......
        try {
            tx = session.beginTransaction();
            // 查询一页的数据列表
            // 方式一:
            Query query = session.createQuery("FROM User");
            query.setFirstResult(firstResult);
            query.setMaxResults(maxResults);
            List<User> list = query.list();
            // 方式二:方法链
            List<User> list = session.createQuery(//
                                "FROM User")//
                                .setFirstResult(firstResult)//
                                .setMaxResults(maxResults)//
                                .list();
            // 查询总记录数
            Long count = (Long) session.createQuery(//
                                        "SELECT COUNT(*) FROM User")//
                                        .uniqueResult();
            tx.commit();
            //  返回结果
            return new QueryResult(count.intValue(), list);
        }......
    }
}

~ Junit 测试类

public class UserDaoTest {
    private UserDao userDao = new UserDao();   //创建方法类的实例,直接调用类的增删改查方法;
    @Test     // 保存
    public void testSave_1() {
        User user = new User();
        user.setName("张三");
        userDao.save(user);     
    }
    @Test   //获取一条数据
    public void testGetById() {
        User user = userDao.getById(1);  
        System.out.println(user);
    }
    @Test   //更新记录
    public void testUpdate() {
        User user = userDao.getById(1);  //从数据库获取一条存在的数据
        user.setName("李四");            //修改记录
        userDao.update(user);            //更新
    }
    @Test    //删除
    public void testDelete() {
        userDao.delete(1);
    }
    //----------------------------------------------
    @Test    //保存多条记录
    public void testSave_25() {
        for (int i = 1; i <= 25; i++) {
            User user = new User();
            user.setName("test_" + i);
            userDao.save(user); // 保存
        }
    }
    @Test      //查询所有
    public void testFindAll() {
        List<User> list = userDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
    }
    @Test     //分页查询
    public void testFindAllIntInt() {
        // 查询
        QueryResult qr1 = userDao.findAll(0, 10);  // 第1页,每页10条
        QueryResult qr2 = userDao.findAll(10, 10); // 第2页,每页10条
        QueryResult qr3 = userDao.findAll(20, 10);    // 第3页,每页10条
        // 显示结果
        System.out.println("总记录数:" + qr.getCount());
        for (User user : (List<User>) qr.getList()) {
            System.out.println(user);
        }
    }
}

>> 执行测试报错

无法连接数据库Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
可能是导入的数据库驱动包版本有问题:使用5.x.x版本能运行,使用8.x.x版本报错;
这里写图片描述


>> API 简介

  • Configuration:配置

    • configure():读取默认配置文件hibernate.cfg.xml,(放在src下,按规定命名);
    • configure(String resource):读取指定配置文件;
    • addResource(String resource) :通过编程的方式导入一个指定位置的映射文件;
    • addClass(Class clazz):通过编程的方式导入与指定类同一个包中的以类名为前缀,后缀为.hbm.xml的映射文件;
    • buildSessionFactory():创建session工厂;
  • SessionFactory:Session工厂

    • openSession():打开session;
    • getCurrentSession()
    • close():关闭资源(一般不用写,程序关闭工厂资源自动关闭)
  • Session:很重要的一个对象,所有的持久化对象都是通过它来完成的;

    • 操作对象的方法:
      save(Object)
      update(Object)
      delete(Object)
    • 查询的方法:
      createQuery(HQLString):返回Query对象;
      createCriteria(Class):已过期;
    • 管理事务的方法:
      beginTransaction() :开启事务,返回 Transaction对象;
      getTransaction():获取当前Session中关联的事务对象,返回 Transaction对象;
      其他的方法(管理缓存)……
  • Transaction:事务

    • commit():提交事务;
    • rollback():回滚事务;
  • Query:使用HQL查询

    • list():查询一个结果集合;
    • uniqueResult():查询一个唯一的结果,如果没有结果,则返回null,如果结果有多个,就抛异常;

>> 生成表结构的两种方式

  • 1,在主配置文件中:<property name="hbm2ddl.auto">update</property>
  • 2,使用SchemaExport工具类:
    注意:只能建表,不能建库

生产环境下,一般有专门的技术人员做部署的工作,先把表建好,表里有一些基本的数据:超级管理员、权限的一些初始数据;
通常通过执行脚本文件插入这些初始数据;所有数据库都能执行脚本文件;Eg:Mysql > source c:\a.sql
脚本文件根据表结构生成;

public class CreateSchema {
    // 根据配置生成表结构
    @Test
    public void test() throws Exception {
        Configuration cfg = new Configuration().configure();
        SchemaExport schemaExport = new SchemaExport(cfg);
        // 第一个参数script的作用: print the DDL to the console
        // 第二个参数export的作用: export the script to the database
        schemaExport.create(true, true);
    }
}

>> 映射 - 集合属性 - 值类型的集合

值类型的集合:集合元素是普通类型;

  • Set:在写Hibernate的时候,默认标签无序,加上sort属性就有序了,但是是内存中排序;当希望按照数据库中存在的列进行排序的时候,使用order-by属性(效率高);
    • HashSet :无序,不重复;
    • TreeSet :有序,自动排序; 在内存里面进行排序的;
    • LinkedHashSet:有序,插入的顺序;
  • List
    • ArrayList:有序,可重复;
  • Map:键值对
    • HashMap: 无序,不重复(以key为准,value可重复)
    • TreeMap :有序
  • 数组:初始化之后不能再变化(不常用,使用不方便)
  • Bag:无序,可重复;使用List接口来引用它;(Hibernate的集合,java里面没有)

~ User.hbm.xml

这里写图片描述

<hibernate-mapping package="cn.itcast.e_hbm_collection">    
    <class name="User" table="user">
        <id name="id"> <generator class="native"></generator> </id>
        <property name="name"/>
        <set name="addressSet" table="user_addressSet" order-by="address ASC">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </set>
        <list name="addressList" table="user_addressList">
            <key column="userId"></key>
            <list-index column="idx"></list-index>
            <element type="string" column="address"></element>
        </list>
        <array name="addressArray" table="user_addressArray">
            <key column="userId"></key>
            <list-index column="idx"></list-index>
            <element type="string" column="address"></element>
        </array>
        <map name="addressMap" table="user_addressMap">
            <key column="userId"></key>
            <map-key type="string" column="key_"></map-key>
            <element type="string" column="address"></element>
        </map>
        <bag name="addressBag" table="user_addressBag">
            <key column="userId"></key>
            <element type="string" column="address"></element>
        </bag>      
    </class>
</hibernate-mapping>

~ 实体类 - User

public class User {
    private Integer id;
    private String name;
    //集合若不初始化,需要在new User之前创建 创建,然后通过set方法传入,初始化之后直接调用get方法即可;
    //数组不要初始化,因为不知道数组的长度,一旦初始化长度就固定不能修改了;
    private Set<String> addressSet = new HashSet<String>();                 // Set集合
    private List<String> addressList = new ArrayList<String>();             // List集合
    private String[] addressArray;                                          // 数组 
    private Map<String, String> addressMap = new HashMap<String, String>(); //Map集合
    private List<String> addressBag = new ArrayList<String>();              //Bag集合
    //bag引用的时候,没有对应的数据类型,所以通常使用List,因为List里面可以存放重复的元素
}

~ 测试类 - App

public class App {
    private static SessionFactory sessionFactory = new Configuration()//
            .configure()//
            .addClass(User.class)     // 添加Hibernate实体类(加载对应的映射文件)
            .buildSessionFactory();
    @Test
    public void testSave() throws Exception {
        Session session = sessionFactory.openSession();
        session.beginTransaction();    //开启事务
        // --------------------------------------------
        // User类中Set不初始化:private Set<String> addressSet;
        // Set<String> set = new TreeSet<String>(); //创建Set集合实例
        // set.add("2御富科贸园");                   //添加记录
        // set.add("1棠东东路");
        // User user = new User();                  //创建实体
        // user.setName("张天");
        // user.setAddressSet(set);                 //将集合赋值给类的集合属性

        // 构建对象
        User user = new User();
        user.setName("张天");
        // >> Set集合
        user.setAddressSet(new TreeSet<String>()); // 当设置了<set>标签中sort属性时,就要使用SortedSet类型
        user.getAddressSet().add("2御富科贸园");    // 实体类中集合属性初始化之后可以直接调用get方法使用
        user.getAddressSet().add("1棠东东路");
        // >> List集合
        user.getAddressList().add("御富科贸园");
        user.getAddressList().add("棠东东路");
        user.getAddressList().add("棠东东路");
        // >> 数组
        user.setAddressArray(new String[] { "御富科贸园", "棠东东路" }); // 数组在User类中没有初始化,所以这里需要new String[]
        // >> Map集合
        user.getAddressMap().put("公司", "御富科贸园");
        user.getAddressMap().put("家庭", "棠东东路");
        // >> Bag集合
        user.getAddressBag().add("御富科贸园");
        user.getAddressBag().add("棠东东路");
        user.getAddressBag().add("棠东东路");

        // 保存
        session.save(user);
        // --------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
    @Test
    public void testGet() throws Exception {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // --------------------------------------------
        // 获取数据
        User user = (User) session.get(User.class, 9);
        // 显示信息
        System.out.println(user.getName());
        System.out.println(user.getAddressSet());
        System.out.println(user.getAddressList());
        System.out.println(Arrays.toString(user.getAddressArray()));//数组不是对象,没有toString方法,直接输出的不是数组的内容
        System.out.println(user.getAddressMap());
        System.out.println(user.getAddressBag());
        // --------------------------------------------
        session.getTransaction().commit();
        session.close();
    }
}

~ 集合映射 注意事项

  • session.save(user);处打断点,调试运行;代码执行到断点处就停止执行;
  • 这里写图片描述:分别是进入方法内部、执行下一步、跳出方法体;
  • 查看变量的情况:
    这里写图片描述
  • F6,向下走一步,查看变量的变化情况:带颜色的是发生了变化;
    可以看出,集合的类型不再是初始化时指定的类型了,而是变成了PersistentXXX类型,
    这里写图片描述
  • 在断点行代码处右键Watch,出现表达式窗口:
    这里写图片描述
  • 在表达式处右键:Edit Watc hExpression,出现表达式编辑窗口:在这里可以看一个当前表达式的值;这里查看user类的Set集合类型是不是还是HashSet;
    这里写图片描述
  • 原本Set集合的类型是HashSet,现在Hibernate的一个Set实现:PersistentXXX;
    这里写图片描述
  • PersistentXXX是实现了Set接口的一个自己的实现类,这样做是为了更好的保存、监测这些对象的状态,当对象发生改变的时候,会把它重新同步到数据库当中,有一些管理的工作要做,so它自己有一个这样的集合;
  • 有了PersistentXXX,我们需要注意,在使用集合属性时,一定要使用接口,而不能声明为具体的实现类;因为经过Session操作后,集合就变成了Hibernate自己的集合实现类;

>> 映射 - 关联关系 - 实体类型的集合 - 一对多、多对一映射

实体类型的集合:集合元素是另一个实体;

维护关联关系:只要外键列改变,就是维护;

  • 对于一对多:就是设置(多方)外键列的值;
  • 对于多对多:就是插入或删除中间表中的记录;

这里写图片描述

~ Employee/Department.hbm.xml

<class name="Employee" table="employee">
        <id name="id"> <generator class="native"></generator> </id>
        <property name="name" type="string" column="name"/>
        <!-- department属性: 
             many-to-one:表达的是本类与Department实体的多对一关系; 
             name属性:本类中的属性名;
             class属性:关联的实体类型;
             column属性:数据库中外键列(引用关联对象的表的主键)的名称;
        -->
        <many-to-one name="department" class="Department" column="departmentId"></many-to-one>
    </class>
//-----------------------------------------------------------------------------------------------
<class name="Department" table="department">
        <id name="id"> <generator class="native"></generator>  </id>
        <property name="name"/>
        <!-- employees属性:
             Set集合,表达的是本类与Employee类的一对多关系; 
             one-to-many:表达的是本类与Employee实体的一对多关系; 
             one-to-many:class属性:关联的实体类型;
             key子元素:对方表中的外键列(多方的那个表)
             inverse属性:让没有外键的一方不维护,有外键的一方不能放弃维护!没有外键的一方有属性inverse;
                 默认为false,表示本方维护关联关系;
                 如果为true,表示本方不维护关联关系;
                 只是影响是否能设置外键列的值(设成有效值或是null值),对获取信息没有影响;    
             cascade属性:
                 默认为none,代表不级联;
                 级联是指操作主对象时,对关联的对象也做相同的操作;
                 可设为:delete, save-update, all, none ...
                 一般一对一、一对多时使用级联,  多对多、多对一不使用级联;
             lazy属性:属性级别的懒加载,默认为true;控制get方法什么时候获取集合的内容;
                 true:懒加载,在第一次使用时加载;
                 false:立即加载,在获取本对象时就加载这个集合的所有元素;
                 extra:增强的懒加载策略;
        -->
        <set name="employees" cascade="all" lazy="true" inverse="true" >
            <key column="departmentId"></key>  //column值是多方数据表中外键列的名称;
            <one-to-many class="Employee" />   
        </set>
    </class>

~ 实体类 - Employee/Department

public class Employee {
    private Integer id;
    private String name;
    private Department department;   // 关联的部门对象
    set/get/toString方法......
}
public class Department {
    private Integer id;
    private String name;
    private Set<Employee> employees = new HashSet<Employee>();  // 关联的很多员工
    set/get/toString方法......
}

~ 测试类 - App

public class App {
    private static SessionFactory sessionFactory = new Configuration()//
            .configure()//
            .addClass(Department.class) // 添加Hibernate实体类Department(加载对应的映射文件)
            .addClass(Employee.class)   // 添加Hibernate实体类Employee(加载对应的映射文件)
            .buildSessionFactory();

    @Test  // 保存,有关联关系 
    public void testSave() throws Exception {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        // --------------------------------------------
        // 新建对象
        Department department = new Department();
        department.setName("开发部");
        Employee employee1 = new Employee();
        employee1.setName("张三");
        Employee employee2 = new Employee();
        employee2.setName("李四");
        // 关联起来
        employee1.setDepartment(department);
        employee2.setDepartment(department);
        department.getEmployees().add(employee1);
        department.getEmployees().add(employee2);
        // 保存:被依赖(Department)的一般放在前面保存,需要依赖别人(Employee)的一般放在后面保存;
        session.save(department); // 保存部门
        session.save(employee1); 
        session.save(employee2);
        // --------------------------------------------
        session.getTransaction().commit();
        session.close();
    }

    @Test  // 获取,可以获取到关联的对方:获取一方,并显示另一方信息;
    public void testGet() throws Exception {
        // --------------------------------------------
        Department department = (Department) session.get(Department.class, 1);
        System.out.println(department);
        System.out.println(department.getEmployees());

        Employee employee = (Employee) session.get(Employee.class, 1);
        System.out.println(employee);
        System.out.println(employee.getDepartment());
        // --------------------------------------------
    }

    @Test  // 解除关联关系
    public void testRemoveRelation() throws Exception {
        // --------------------------------------------
        // 从员工方解除
        Employee employee = (Employee) session.get(Employee.class, 1);
        employee.setDepartment(null);   //将外键列的值设置成其他或null;

        // 从部门方解除(与inverse有关系,为false时可以解除)
        Department department = (Department) session.get(Department.class, 1);
        department.getEmployees().clear();   //清空实体集合属性值;
        // --------------------------------------------
    }

    @Test  // 删除对象,对关联对象的影响
    public void testDelete() throws Exception {
        // --------------------------------------------
        // 删除员工方(多方),对对方没有影响;
        Employee employee = (Employee) session.get(Employee.class,2);
        session.delete(employee);
        // 删除部门方(一方)
        // a, 如果没有关联的员工:能删除;
        // b, 如果有关联的员工且inverse=true,由于不能维护关联关系,所以会直接执行删除,就会有异常
        // c, 如果有关联的员工且inverse=false,由于可以维护关联关系,他就会先把关联的员工的外键列设为null值,再删除自己;
        Department department = (Department) session.get(Department.class, 4);
        session.delete(department);
        // --------------------------------------------
    }

    @Test
    public void testLazy() throws Exception {
        // --------------------------------------------
        Department department = (Department) session.get(Department.class, 1);
        Department department2 = (Department) session.get(Department.class, 1);//只获取一次

        System.out.println(department.getName());  //直接打印,因为已经获取到了
        System.out.println(department.getEmployees());//使用类中集合信息时才查询【属性级别的懒加载特性】
        // System.out.println(department.getEmployees().size());

        Hibernate.initialize(department.getEmployees()); // 立即加载指定的懒加载对象
        // --------------------------------------------
        session.getTransaction().commit();
        session.close();
        // 在使用懒加载特性时,要注意LazyInitializationException异常;
        // 避免懒加载异常:(1)延迟session关闭时间,即在关闭之前使用get;
                       (2)在属性级别的lazy属性处关闭懒加载(false),使所有方法中get时都立即加载左右数据;
                       (3)Hibernate.initialize(...); // 只在这个方法中关闭懒加载;
        System.out.println(department.getEmployees()); //这句话放在提交之后,关闭之前不会有异常;
    }
// 在什么时候会导致去加载集合中的所有元素?
// 1、查询集合中所有信息;
// 2、查询集合中元素的数量size;
// 3、查询集合是否为空isEmpty;
// 4、查询集合是否包含contains;
// 5、...跟集合元素有关的所有操作;
// 这么做有点浪费,除了查询所有元素时,其他情况例如查询集合size,设置只查询部分信息,使用增强的懒加载策略extra;
}

>> 映射 - 关联关系 - 实体类型的集合 - 多对多映射

这里写图片描述

~ Teacher/Student.hbm.xml

<class name="Teacher" table="teacher">
        <id name="id"><generator class="native"></generator></id>
        <property name="name" type="string" column="name"/>
        <!-- students属性:
            Set集合,表达的是本类Teacher与Student类的多对多关系;
         -->
        <set name="students" table="teacher_student" inverse="true">
            <key column="teacherId"></key>    //集合外键(引用当前表主键的那个外键)
            <many-to-many class="Student" column="studentId"></many-to-many>
        </set>
    </class>
//------------------------------------------------------------------------------
<class name="Student" table="student">
        <id name="id"><generator class="native"></generator></id>
        <property name="name"/>
        <!-- teachers属性: 
            Set集合,表达的是本类Student与Teacher类的多对多关系;
            set - table属性:中间表(集合表)名称;
            key子元素:集合外键(引用当前表主键的那个外键);
         -->
        <set name="teachers" table="teacher_student" inverse="false">
            <key column="studentId"></key>
            <many-to-many class="Teacher" column="teacherId"></many-to-many>
        </set>      
    </class>

~ 实体类 - Teacher/Student

public class Teacher {
    private Long id;
    private String name;
    private Set<Student> students = new HashSet<Student>(); // 关联的学生们
}
public class Student {
    private Long id;
    private String name;
    private Set<Teacher> teachers = new HashSet<Teacher>(); // 关联的老师们
}

~ 测试类 - App

public class App {
    private static SessionFactory sessionFactory = new Configuration().configure().......

    @Test  // 保存,有关联关系
    public void testSave() throws Exception {
        // --------------------------------------------
        // 新建对象
        Student student1 = new Student();  student1.setName("王同学");
        Student student2 = new Student();  student2.setName("李同学");
        Teacher teacher1 = new Teacher();  teacher1.setName("赵老师");
        Teacher teacher2 = new Teacher();  teacher2.setName("蔡老师");
        // 关联起来:其中一方在xml中放弃维护关联关系;或在代码中只写一边add;
        student1.getTeachers().add(teacher1);
        student1.getTeachers().add(teacher2);
        student2.getTeachers().add(teacher1);
        student2.getTeachers().add(teacher2);
        teacher1.getStudents().add(student1);
        teacher1.getStudents().add(student2);
        teacher2.getStudents().add(student1);
        teacher2.getStudents().add(student2);
        // 保存
        session.save(student1);
        session.save(student2);
        session.save(teacher1);
        session.save(teacher2);
        // --------------------------------------------
    }

    @Test  // 获取,可以获取到关联的对方
    public void testGet() throws Exception {
        // --------------------------------------------
        // 获取一方,并显示另一方信息
        Teacher teacher = (Teacher) session.get(Teacher.class, 3L);
        System.out.println(teacher);
        System.out.println(teacher.getStudents());
        // --------------------------------------------
    }

    @Test  // 解除关联关系
    public void testRemoveRelation() throws Exception {
        // --------------------------------------------
        // 如果inverse=false就可以解除,如果为true就不可以解除;
        Teacher teacher = (Teacher) session.get(Teacher.class, 3L);
        teacher.getStudents().clear();   // 直接清空集合值;
        // --------------------------------------------
    }

    @Test  // 删除对象,对关联对象的影响
    public void testDelete() throws Exception {
        // --------------------------------------------
        // a, 如果没有关联的对方:能删除;
        // b, 如果有关联的对方且inverse=false,由于可以维护关联关系,他就会先删除关联关系,再删除自己;
        // c, 如果有关联的对方且inverse=true,由于不能维护关联关系,所以会直接执行删除自己,就会有异常;
        Teacher teacher = (Teacher) session.get(Teacher.class, 9L);
        session.delete(teacher);
        // --------------------------------------------
    }
}

>> 对象的状态、session的方法

~ 对象的状态:

  • 临时状态:与数据库没有对应,跟Session没有关联;一般是新new出的对象;
  • 持久化状态:对象在Session的管理之中,最终会有对应的数据库记录;
    特点:1,有OID; 2,对对象的修改会同步到数据库;
  • 游离状态:数据库中有对应记录,但对象不在Session管理之中,修改此状态对象时数据库不会有变化;
  • 删除状态:执行了delete()后的对象;

~ 对象状态的转换:

这里写图片描述

  • save():把临时状态变为持久化状态(交给Sessioin管理);
  • update():把游离状态变为持久化状态;
    在更新时,对象不存在就报错;
  • saveOrUpdate():把临时或游离状态转为持久化状态;
    在更新时,对象不存在就报错;
  • delete():把持久化或游离转为删除状态;
    如果删除的对象不存在,就会抛异常;
  • get():获取数据,是持久化状态;【会马上执行sql语句】
    如果数据不存在,就返回null ,不会报错;
  • load():获取数据,是持久化状态;

~ session中的方法分类:

  • 操作实体对象的
    • save():特殊,主键值由数据库生成(除了UUID)的时候立刻执行SQL语句,其他都是在flush时执行SQL语句;
    • update()
    • saveOrUpdate()
    • delete()
  • 操作缓存的
    • clear()
    • evict()
    • flush():马上执行所有SQL语句,默认在commit时执行,不会清楚缓存;
  • 查询实体对象的
    • get():会马上执行sql语句;
    • load()
    • createQuery()
    • createCriteria()

get、load方法区别:
这里写图片描述

~ Session一级缓存:

session里面有个集合,这个集合称之为session的一级缓存,它会引用要管理的对象,统一的做一些操作,对象发生变化会同步到数据库中;

save、update、get等方法执行多次,会只操作一次;这时因为save之后把对象交给session管理,再给session同一个对象会显示这个对象已经有了,同一个对象就会把后面的忽略掉;

~ session 方法详解

// 保存save():把临时状态变为持久化状态(交给Sessioin管理)
// 会生成:insert into ...
public void testSave() throws Exception {
    // --------------------------------------------
    User user = new User(); // 临时状态
    user.setName("test");   // 临时状态
    session.save(user);     // 变为了持久化状态 
(3) session.save(user);     // save执行多次,会只insert一次; 
(1) user.setName("李四");    // session未关闭,所以user是持久化状态,save之后数据库中是“李四”;
    // --------------------------------------------
    session.getTransaction().commit();
    session.close();
(2) user.setName("李四");    // session已经关闭,所以user是游离状态,save之后数据库中是“test”;
}

// 更新update():把游离状态变为持久化状态;
// 会生成:update ...
// 在更新时,对象不存在就报错;
public void testUpdate() throws Exception {
    // --------------------------------------------
    User user = (User) session.get(User.class, 1);
    System.out.println(user.getName());  // 持久化状态

(1) user.setName("newname2");  
    session.flush();   // 强制刷出到数据库,不写默认在commit时执行flush操作;
    session.clear();   // 清除Session中所有的对象,使对象变为游离状态;
    user.setName("newname3");
    session.flush();   // 在close之后,修改对象内容,再flush无效:没有更新语句;
//持久化状态下的对象发生改变,更新语句是在commit提交时执行的,在提交之前执行clear清空session中对象的方法之后,
//将对象赶出session的管理,此时对象是游离状态,对对象的改变不会同步到数据库;这里的clear方法之前、之后的两个
//set方法中对对象的修改不会同步到数据库,所以没有更新语句;
//在clear执行之前,修改对象内容,然后执行flush强制将对象的修改刷出到数据库,就会有更细语句; 

(2) session.evict(user);       // 清除Session中一个指定的对象,使对象变为游离状态;
    user.setName("newname3");  // 对游离状态的对象操作,不会同步到数据库中;
    session.update(user);      // 执行更新操作,将对象从游离状态转成持久化状态,将对象的修改同步到数据库;
    session.flush();           // 刷出到数据库
    // --------------------------------------------
    session.getTransaction().commit(); // 先flush,再commit
    session.close();    
}

// 保存或更新saveOrUpdate():把临时或游离状态转为持久化状态;
// 会生成:insert into 或 update ...
// 在更新时,对象不存在就报错;
// 本方法是根据id判断对象是什么状态的:如果id为原始值(对象的是null,原始类型数字是0)就是临时状态,
   如果不是原始值就是游离状态;   
public void testSaveOrUpdate() throws Exception {
    // --------------------------------------------
    User user = new User();  // 临时状态,总是执行保存操作;
    user.setId(3);           // 自己生成一个游离状态对象:修改id原始值,使之成为游离状态;
    user.setName("newName"); // 对游离状态的对象才会执行更新操作;
    session.saveOrUpdate(user);
    // --------------------------------------------
}

// 删除delete():把持久化或游离转为删除状态;
// 会生成:delete ...
// 如果删除的对象不存在,就会抛异常;
public void testDelete() throws Exception {
    // --------------------------------------------
(1) User user = (User) session.get(User.class, 2); // 持久化状态
(2) User user = new User();                        // 临时状态
    user.setId(300);                               // 游离状态(通过设置id,模拟一个游离状态的对象)  
    session.delete(user);   // 默认在commit时执行删除操作
    session.flush();        // flush使删除操作立即执行
    // --------------------------------------------
}

// 获取get():获取数据,是持久化状态;
// 会生成:select ... where id=?
// get后返回的是原始的真正的对象;
// 会马上执行sql语句;
// 如果数据不存在,就返回null,不会报错;    
public void testGet() throws Exception {
    // --------------------------------------------
    User user = (User) session.get(User.class, 5); // 持久化
    System.out.println(user.getClass());   // get后返回的是原始的真正的对象;
    System.out.println(user.getName());    // 立刻执行SQL语句
    // --------------------------------------------
}

// 类级别的懒加载load():获取数据,是持久化状态;
// 会生成:select ... where id=?
// load()后返回的是一个通过子类的方式动态生成的代理对象,先给一个代理对象来用,还有真正的对象;
// 要求类不能是final的,否则不能生成子类代理,就不能使用懒加载功能了;
// 让懒加载失效的方式:一、把实体写成final的;二、在hbm.xml中写<class ... lazy="false">【类级别的懒加载】
// 不会马上执行sql语句,而是在第1次使用非id或class属性时执行sql;
// 如果数据不存在,就抛异常:ObjectNotFoundException;
public void testLoad() throws Exception {
    // --------------------------------------------
    User user = (User) session.load(User.class, 5);
    System.out.println(user.getClass());   // 获取id、class属性时不会执行SQL语句;返回的是代理对象;
    System.out.println(user.getId());       
    System.out.println(user.getName());    // 首次使用非id、class属性时才会执行SQL语句,生成对象;
    // --------------------------------------------
}

// 大批量操作数据的时候,session不会自动的吧对象清除出来,会一直引用,这样会导致内存溢出;
// 解决办法:隔段时间清除一次,或者够多少对象就清除一次;
// 操作大量数据,要防止Session中对象过多而内存溢出;
public/* final */class User {
    private Integer id;
    private String name;
    private byte[] data = new byte[1024 * 1024 * 5];  // 5兆
}
public void testBatchSave() throws Exception {
    // --------------------------------------------
    for (int i = 0; i < 30; i++) {
        User user = new User();
        user.setName("测试");
        session.save(user);
        if (i % 10 == 0) {
            session.flush(); // 先刷出
            session.clear(); // 再清空
        }
    }
    // --------------------------------------------
}

public void test2() throws Exception {
    // --------------------------------------------
(1) User user = (User) session.get(User.class, 500); // 持久化
    User user = (User) session.get(User.class, 500);
    User user = (User) session.get(User.class, 500);

(2) User user = (User) session.get(User.class, 5);
    System.out.println(user.getName());
//id为500事务记录在数据库中没有,所以第一次get获取不到记录,第二次获取先在缓存中查找是否有记录,没有的话再从
//数据库查找,因为第一次没查到,所以缓存中灭有对应记录;所以最终结果是有3条select语句; 
    // session.clear();
    // user = (User) session.get(User.class, 5); // 持久化
    session.refresh(user); // 刷新Session缓存中对象的状态,即重新select一下
    System.out.println(user.getName());
//在第二个输出语句处打断点,在其执行之前,修改数据库中id为5的记录的名称,然后执行第二个输出,结果与第一次一样,
//并且只有一条select语句,这是因为第二次查询是从session缓存中查询的缓存记录;使用clear刷出缓存中记录,再重新
//获取,或者调用refresh方法刷新session缓存中对象的状态,这时会有两条select语句;但是两次获取结果还是都是数据
//库修改之前的数据,这时因为mysql默认事务隔离级别是可重复读;
//要实现在第二条输出语句执行前修改数据库记录,并且两次输出分别是旧、新数据,则需要clear/refresh、然后在主配置
//文件中修改mysql事务隔离级别为读已提交:<property name="connection.isolation">2</property> ; 
    // --------------------------------------------
}

~ SQL标准的事务隔离级别

这里写图片描述
在主配置文件中配置事务隔离级别<property name="connection.isolation">2</property>

SQL标准的四个事务隔离级别:

  • 读未提交 1:修改未提交,另一个事务也能读取到修改后的数据;
  • 读已提交 2:修改并且提交了,另一个事务才能读取到;
  • 可重复读 4:(Mysql默认隔离级别)在事务之内,重复查询,得到的都是事务开始时的状态,不管其他事务有没有修改;
  • 串行化 8(不可并发)

>> 映射 - 关联关系 - 实体类型的集合 - 一对一映射

这里写图片描述

~ 基于外键的方式:Person/IdCard.hbm.xml

<class name="Person" table="person">
    <id name="id"> <generator class="native"></generator> </id>
    <property name="name"/>
    <!-- idCard属性,IdCard类型;表达的是本类Person类与IdCard类的一对一关系;
         采用基于外键的一对一映射方式,本方无外键方;
         property-ref属性:写的是对方映射中外键列对应的属性名;  
     -->
    <one-to-one name="idCard" class="IdCard" property-ref="person"/>
</class>
// -------------------------------------------------------------------------
<class name="IdCard" table="idCard">
    <id name="id"> <generator class="native"></generator> </id>
    <property name="number"/>
    <!-- person属性,Person类型;表达的是本类IdCard类与Person类的一对一关系;
        采用基于外键的一对一映射方式,本方有外键方; 
    -->
    <many-to-one name="person" class="Person" column="personId" unique="true"></many-to-one>    
</class>
  • 保存:有关联关系;双方、单方(只能是有主键方)设置关联关系都能保存成功;
    有外键方设置关联关系:外键列有值;
    无外键方设置关联关系:外键列值为空;
  • 获取:可以获取到关联的对方;可以获取一方,并显示另一方信息;
  • 解除关联关系:一对一中,只能有外键方可以维护关联关系;
    从有外键方解除关系,可以;
    从无外键方解除关系,不可以;
  • 删除对象:对关联对象的影响;
    a, 如果没有关联的对方:能删除;
    b, 如果有关联的对方且可以维护关联关系(有外键方),他就会先删除关联关系,再删除自己;
    c, 如果有关联的对方且不能维护关联关系(无外键方),所以会直接执行删除自己,就会有异常;

~ 基于主键的方式:Person/IdCard.hbm.xml

主键值根据对方主键值来生成的;

<class name="Person" table="person2">
    <id name="id"><generator class="native"></generator></id>
    <property name="name"/>
    <!-- idCard属性,IdCard类型;表达的是本类Person类与IdCard类的一对一关系;
         采用基于主键的一对一映射方式,本方无外键方;
     -->
    <one-to-one name="idCard" class="IdCard"></one-to-one>      
</class>
// ------------------------------------------------------------------------
<class name="IdCard" table="idCard2">
    <id name="id">
    <!-- 当使用基于主键的一对一映射时,有外键方的主键生成策略一定要是foreign;
        参数property:生成主键值时所根据的对象属性;
     -->
        <generator class="foreign">
            <param name="property">person</param>
        </generator>
    </id>
    <property name="number"/>   
    <!-- person属性,Person类型;表达的是本类IdCard类与Person类的一对一关系;
         采用基于主键的一对一映射方式,本方有外键方;
         constrained属性:给主键添加外键约束; 
    -->
    <one-to-one name="person" class="Person" constrained="true"></one-to-one>       
</class>
  • 解除关联关系:使用基于主键的一对一映射方式:双方都不可以解除关联关系;
    从有外键方解除关系,不可以,因为主键 不能为null;
    从无外键方解除关系,不可以,因为无外键方不能维护关联关系;
  • 删除对象
    如果没有关联的对方:能删除;
    如果有关联的对方:因为会直接执行删除自己,所以无外键方会有异常,有外键方没有异常;

>> 映射 - 继承结构映射

这里写图片描述

~ 实体类 - Article/Topic/Reply

public class Article {
    private Integer id;
    private String title;
    private String content;
    private Date postTime;
}
public class Topic extends Article {
    private int type;  // 精华、置顶...
}
public class Reply extends Article {
    private int floor; // 楼层
}

~ 整个继承结构使用一张表的方式 - Article.hbm.xml

  • 一张表:只写一个映射文件,映射文件名与父类一样;
  • 每个列中都不能有非空约束;
  • 继承结构一个特性:获取父类,能拿到子类;
<class name="Article" table="article" discriminator-value="Aticle">
    <id name="id"><generator class="native"/></id>
    <property name="title"/>
    <property name="content" type="text" length="10000"/>
    <property name="postTime" type="timestamp"/>

    <!-- discriminator-value属性:用于鉴别是什么类型的一个列,表示这个值就是这个类;
         如果不写,默认为类的全限定名;-->
    <discriminator type="string" column="class_"></discriminator>

    <!-- 子类:Topic -->
    <subclass name="Topic" discriminator-value="Topic">
        <property name="type"></property>
    </subclass> 
    <!-- 子类:Reply -->
    <subclass name="Reply" discriminator-value="Reply">
        <property name="floor"></property>
    </subclass> 
</class>

~ 每个类一张表的方式 - Article.hbm.xml

<!-- 采用每个类一张表的方式,抽象类也对应表;每个表中之有当前类中的属性 -->
<class name="Article" table="article2">
    <id name="id"> <generator class="native" /> </id>
    <property name="title" />
    <property name="content" type="text" length="10000" />
    <property name="postTime" type="timestamp" />

    <!-- 子类:Topic -->
    <joined-subclass name="Topic" table="topic2">
        <key column="id"></key>
        <property name="type"></property>
    </joined-subclass>

    <!-- 子类:Reply -->
    <joined-subclass name="Reply" table="reply2">
        <key column="id"></key>
        <property name="floor"></property>
    </joined-subclass>
</class>

~ 每个具体类对应一张表 - Article.hbm.xml

<!-- 采用每个具体类一张表的方式,抽象类不对应表;
     abstract默认为false,设为true表示本类不对应表(类可以不是abstract的),这时就会忽略table属性;
-->
<class name="Article" abstract="false" table="article3">
    <id name="id">
    <!--当使用每个具体类一张表的方式时,主键生成策略不能是identity;因为在整个继承结构中,主键值不能重复-->
        <generator class="hilo">
            <param name="table">hi_value</param>
            <param name="column">next_value</param>
            <param name="max_lo">100</param>
        </generator>
    </id>
    <property name="title"/>
    <property name="content" type="text" length="10000"/>
    <property name="postTime" type="timestamp"/>

    <!-- 子类:Topic -->
    <union-subclass name="Topic" table="topic3">
        <property name="type"></property>
    </union-subclass>
    <!-- 子类:Reply -->
    <union-subclass name="Reply" table="reply3">
        <property name="floor"></property>
    </union-subclass>       
</class>

>> HQL

HQL(Hibernate Query Language):与SQL相似的一种数据库查询语言,SQL中的语法基本上都可以直接使用;

~ HQL与SQL区别:

  • SQL查询的是数据库中的表和表中的字段,HQL查询的是对象和对象中的属性;
  • SQL不区分大小写,HQL关键字不区分大小写,但是类名和属性区分大小写(因为java类名、属性区分大小写)
  • HQL里面SELECT可以省略;

~ Hibernate 提供的5种数据库查询方式

  • 导航对象图检索方式:根据已经加载的对象导航到其他对象;根据部门对象查询出员工对象;
  • OID检索方式:按照对象的OID来检索对象;get/load方法;
  • HQL检索方式:使用面向对象的HQL查询语言;【重点掌握!!】
  • QBC检索方式:使用QBC(Query By Criteria)API来检索对象;这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口;
  • 本地SQL检索方式:使用本地数据库的SQL查询语句;【不推荐】

~ Department/Employee.hbm(使用一对多映射例子)

<!-- auto-import属性:表示在HQL中写类的简单名称时,是否自动导入当前这个包 ;多个包中有同名类时只能有一个为true;
     即为true时,在HQL中可以写简单名称,表示当前这个类 ;【默认为true ;】
     当为false时,在HQL中就得写全限定名了 ;
 -->
<hibernate-mapping package="cn.itcast.k_query_hql" auto-import="true">
    <class name="Employee" table="employee">
        <id name="id"><generator class="native"></generator></id>
        <property name="name" type="string" column="name"/>
        <!-- department属性,表达的是本类与Department的多对一 -->
        <many-to-one name="department" class="Department" column="departmentId"></many-to-one>  
    </class>

    <!-- 定义命名的查询 -->
    <query name="queryByIdRange">
        <![CDATA[FROM Employee e WHERE e.id >= :idMin AND e.id <= :idMax]]>
    </query>    
</hibernate-mapping>

~ 测试类 - App

public class App {
    private static SessionFactory sessionFactory = new Configuration()......

    public void testHql() throws Exception {
        // --------------------------------------------
        String hql = null;     

        // 1,简单的查询
        hql = "FROM Employee";      // SELECT可以省略;表示查询整个对象,即所有列; 
                                    // Employee是类名不是表名,区分大小写;
                                    // xml文件中auto-import="false"时,类名必须写全限定名;
        hql = "FROM Employee AS e"; // AS使用别名:as关键字可省略,通常会带上别名,
                                    // 表中列名与关键字冲突时,需加上别名进行限定,否则会因关键字冲突报错;

        // 2,带上过滤条件的(可以使用别名):Where
        hql = "FROM Employee WHERE id<10";
        hql = "FROM Employee e WHERE e.id<10";   // 使用别名
        hql = "FROM Employee e WHERE e.id<10 AND/OR e.id>=5";  // 使用AND/OR

        // 3,带上排序条件的:Order By   
        hql = "FROM Employee e WHERE e.id<10 ORDER BY e.name";   // 默认升序
        hql = "FROM Employee e WHERE e.id<10 ORDER BY e.name DESC";  // DESC降序,ASC升序;
        hql = "FROM Employee e WHERE e.id<10 ORDER BY e.name DESC, id ASC"; // 先按名称降序;

        // 4,指定select子句(不可以使用select *)
        hql = "SELECT e FROM Employee e";   // 相当于"FROM Employee e"
        hql = "SELECT e.name FROM Employee e"; // 只查询一个列,返回的集合的元素类型就是这个属性的类型;
        hql = "SELECT e.id,e.name FROM Employee e";//查询多个列逗号隔开;返回的集合元素类型是Object数组;
        hql = "SELECT new Employee(e.id,e.name) FROM Employee e"; 
             // 可以使用new语法,指定把查询出的部分属性封装到对象中;要求类中要有无参构造方法和有参构造方法;

        // 5,执行查询,获得结果(list、uniqueResult、分页 )
        // Query query = session.createQuery("FROM Employee e WHERE id<3");
        // query.setFirstResult(0);
        // query.setMaxResults(10);
        // List list = query.list();  // 查询的结果是一个List集合
        Query query = session.createQuery("FROM Employee e WHERE id=3");
        Employee employee = (Employee) query.uniqueResult();
        System.out.println(employee);    //查询的结果是唯一的一个结果,当结果有多个,就会抛异常

        // 6,方法链
        List list = session.createQuery(//
                            "FROM Employee e")//
                            .setFirstResult(0)//
                            .setMaxResults(10)//
                            .list();

        // 显示结果、执行查询
        List list = session.createQuery(hql).list();
        for (Object obj : list) {
            if (obj.getClass().isArray()) {
                System.out.println(Arrays.toString((Object[]) obj));//查询部分列,转换成数组显示;
            } else {
                System.out.println(obj);     // 查询整个对象,直接输出对象,使用对象的toString方法;
            }
        }
        // --------------------------------------------
    }
    public void testHql_2() throws Exception {
        // --------------------------------------------
        String hql = null;

        // 1,聚集函数:count(), max(), min(), avg(), sum()
        hql = "SELECT COUNT(*) FROM Employee"; // 返回的结果是Long型的,表示记录总条数;
        hql = "SELECT min(id) FROM Employee";  // 返回的结果是id属性的类型,表示id最小值;
        Number result = (Number) session.createQuery(hql).uniqueResult();
        System.out.println(result);

        // 2,分组: Group By ... Having ; 能与聚集函数一起使用;
        hql = "SELECT e.name,COUNT(e.id) FROM Employee e WHERE id<9 GROUP BY e.name";
                // SELECT后面的属性必须跟GROUP BY后面的属性一样,即select后面的属性应该在每组内都一样;
                // 或者使用聚集函数COUNT(e.id),显示组中记录的数量;
        hql = "SELECT e.name,COUNT(e.id) FROM Employee e GROUP BY e.name HAVING count(e.id)>1";
                // 使用Having过滤,限制组中成员数量大一1的才显示;组成员只有一个的不显示;

        hql = "SELECT e.name,COUNT(e.id) AS c " +    //count 使用别名,AS不能省略;
                  "FROM Employee e " +    
                  "WHERE id<9 " +          //where在group by之前,限制哪些记录参与分组;
                  "GROUP BY e.name " + 
                  "HAVING count(e.id)>1 "+ //having在group by之后,限制参与分组的记录分组后,显示哪些分组; 
                  "ORDER BY c ASC";        // Order by 放在最后面;
                                     // 在orderby子句中可以使用列别名,在having子句中不能使用列别名;

        // 3,连接查询 / HQL是面向对象的查询 : inner、outer 关键字可省略;
        // inner join 内连接 :两边都有值
        hql = "SELECT e.id,e.name,d.name FROM Employee e INNER JOIN e.department d";
        // left outer join 左外连接 :左边有值的
        hql = "SELECT e.id,e.name,d.name FROM Employee e LEFT OUTER JOIN e.department d";
        // right outer join 右外连接 :右边有值的
        hql = "SELECT e.id,e.name,d.name FROM Employee e RIGHT JOIN e.department d";
        // 可以使用更方便的方法 : e.department.name
        hql = "SELECT e.id,e.name,e.department.name FROM Employee e";

        // 4,查询时使用参数
        // 方式一:使用'?'占位 【参数多的时候,一个一个指定容易乱】
        hql = "FROM Employee e WHERE id=?";
        hql = "FROM Employee e WHERE id BETWEEN ? AND ?";  // between包含左右边界
        List list = session.createQuery(hql)               //
                            .setParameter(0, 5)            // 设置参数,第1个参数的索引为0 ;
                            .setParameter(1, 15)           //
                            .list();
        // 方式二:使用变量名占位 【:变量名】,变量赋值顺序随意;
        hql = "FROM Employee e WHERE id BETWEEN :idMin AND :idMax";
        List list = session.createQuery(hql)           //
                            .setParameter("idMax", 15) //
                            .setParameter("idMin", 5)  //
                            .list();
        // 当参数是集合时,一定要使用setParameterList()设置参数值,并且只能使用变量占位的方式;
        hql = "FROM Employee e WHERE id IN (:ids)";
        List list = session.createQuery(hql)  //
                            .setParameterList("ids", new Object[] { 1, 2, 3, 5, 8, 100 })//
                            .list();

        // 5,使用命名查询:把SQL语句不硬编码到代码中,而是写在配置文件中;
        // queryByIdRange对应一个查询语句,这个查询语句一般写在与查询对象相关的对象的配置文件中;
        Query query = session.getNamedQuery("queryByIdRange");
        query.setParameter("idMin", 3);
        query.setParameter("idMax", 10);
        List list = query.list();       
// 写在Employee.hbm.xml文件中的SQL查询语句:
<!-- 定义命名的查询 -->
<query name="queryByIdRange">
    FROM Employee e WHERE e.id BETWEEN :idMin AND :idMax    
    FROM Employee e WHERE e.id &gt;= :idMin AND e.id &lt;= :idMax    // 特殊字符需要转义使用;
    <![CDATA[FROM Employee e WHERE e.id >= :idMin AND e.id <= :idMax]]> // 特殊字符直接使用;
</query>

        // 6,update与delete,直接更新数据库,不会通知Session缓存;
        int result = session.createQuery(                                 //
                            "UPDATE Employee e SET e.name=? WHERE id>15") //
                            .setParameter(0, "无名氏")                     //
                            .executeUpdate(); // 返回int型的结果,表示受影响的行数;
        int result = session.createQuery(                         //
                            "DELETE FROM Employee e WHERE id>15") // FROM可省略;
                            .executeUpdate();  
        // ----- 执行查询并显示结果......
        // --------------------------------------------
    }
    public void testHql_DML() throws Exception {         // DML数据操纵语言
        // --------------------------------------------
        Employee employee = (Employee) session.get(Employee.class, 13);
        System.out.println(employee.getName()); // 第一次显示名称

        int result = session.createQuery("UPDATE Employee e SET e.name=? WHERE id>10")//
                .setParameter(0, "无名氏3").executeUpdate();  

        session.refresh(employee);  // 在update或delete后,需要refresh(obj)一下以获取最新的状态;
        System.out.println(employee.getName());  // 第二次显示名称
        // --------------------------------------------
    }
}

~ QBC(Query By Criteria) 查询(了解)

public void testQBC() throws Exception {
    // --------------------------------------------
    // 创建Criteria对象
    Criteria criteria = session.createCriteria(Employee.class);
    // 增加过滤条件
    criteria.add(Restrictions.ge("id", 1));   // ge大于等于
    criteria.add(Restrictions.le("id", 5));   // le小于等于
    // 增加排序条件
    criteria.addOrder(Order.desc("name"));
    criteria.addOrder(Order.desc("id"));
    // 执行查询
    // criteria.setFirstResult(0);
    // criteria.setMaxResults(100);
    // criteria.uniqueResult();
    // criteria.list()
    List list = criteria.list();
    // 显示结果......
    // --------------------------------------------
}

>> 懒加载特性

懒加载:也叫延迟加载,不是在执行获取操作时马上生成SQL,而是在第一次使用时生成SQL;
所有关联关系属性里面都可以使用懒加载;只要对应另一个对象的集合都可以使用;

分成两种

  • 类级别的<class ... lazy="true/false">
  • 属性级别的
    <set/list/map/bag ... lazy="...">; 数组不可以使用懒加载,因为数组不能生成代理对象;
    <many-to-one ... lazy="...">
    <one-to-one ... lazy="...">

在使用懒加载特性时,可能会有LazyInitializationException异常:
原因: 真正的去获取数据时,Session已经没有了;
解决办法
(1)延迟session关闭时间,即在关闭之前使用get;
(2)在属性级别的lazy属性处关闭懒加载(false),使所有方法中get时都立即加载左右数据;
(3)Hibernate.initialize(…); // 只在这个方法中关闭懒加载;


>> c3p0数据库连接池

在生产环境下使用Hibernate,要提高性能、效率;其中一种方式就是使用数据库连接池,而不是像现在这样每次用就打开,不用就关掉数据库连接,因为在创建和销毁数据库连接时消耗好多时间;

Hibernate默认实现一个自己的简单的数据库连接池,里面初始化时默认只有一个连接,可以在主配置文件中修改数据库连接池的有关配置,来修改连接数量等;数据库连接池有关配置可以参考..\project\etc本地文件夹下的hibernate.properties文件;

使用专业的c3p0数据库连接池;


>> 二级缓存

使用一对多的例子;

这里写图片描述

~ 一、二级缓存原理

一、二级缓存原理:用id作为key,用对象作为value进行存储;
获取数据时,会先拿id去缓存里面根据id进行查找,所以不管是一级缓存,还是二级缓存,都是在使用OID的方式获取对象时才有效;比如getload方法,但是对于Query.list()默认不会使用缓存,哪怕写成where id=1也不会使用缓存;

class Session{    //伪代码
    private Map<Serializable, Object> cache; // 缓存,以键值对的形式存储在map集合里面;
    public Object get(.., id){
        Object obj = cache.get(id);     // 根据id到cache里面查找对象
        if(obj == null){                // 如果对象为空,说明缓存里面没有所需对象;
            obj = "select * from xxx whereid=?"; // 这时需要到数据库中查找该对象,并赋值给obj
            cache.put(id, obj);                  // 然后加到cache集合里面; 
        }
        return obj;                       // 最后返回所需对象;
    }
} 

~ 一级缓存与二级缓存

  • 一级缓存:session里面的缓存;【一次会话级别的缓存】
    • 针对一次连接所做的操作;一次连接的时间很短,一般一个请求做一个业务操作,一个业务操作可能操作一次或多次数据库,一个业务操作里面会用一个session,也可以说一个请求使用一个session,一个请求很快就响应完,所以session的范围是很短的,这就是一级缓存;所以一级缓存里面的对象真正起到的提高性能的作用不大,因为session关闭缓存就清空了;
    • 一级缓存最大的作用:不是用于提高性能,而是管理对象;session里面有个集合,管理对象的状态,将对象的变化更新到数据库中;
  • 二级缓存:整个sessionFactory级别的缓存;【应用程序级别的缓存】
    • 在整个应用程序里面整一个大缓存,所有session获取数据时先从大缓存里面取数据,而不是先从数据库里面取数据,大缓存里面没有所需数据,再去数据库里面取数据,从数据库中取出来的数据先缓存到大缓存中,再返回给session;二级缓存使得可以在应用程序的级别也可以共用一些缓存的对象;
    • 二级缓存默认不开启,因为不是所有数据都能使用缓存;例如财务相关的数据很敏感不能使用缓存,必须实时从数据库中获取数据;
    • 二级缓存分为类级别的缓存、集合级别的缓存、查询缓存:集合级别的缓存是指类里面的集合属性 使用的缓存;
    • 打开二级缓存(类级别、集合级别、查询缓存),需要在主配置文件或映射配置文件中进行配置;
    • 打开集合级别的缓存,需要同时打开集合里存放的元素的类型对应的 类的 缓存;(打开部门里面的员工集合的缓存,需要同时打开员工实体类的二级缓存),否则会在第二次查询部门的员工集合时,因为员工实体没有开启二级缓存,因而第一次查询部门的员工集合时查出的员工信息没有进行缓存,所以第二次查询部门的员工集合时在二级缓存里面没有找到对应的元素,但是由于部门的员工集合开启了集合级别的缓存,所以在第二次查询员工集合时,会按照集合的数量分别按照每个id一个一个进行一次查询,因而会出现更多的查询语句;
    • 所有的查询都会放到缓存里面,但是不一定所有查询都会用到缓存:使用HQL语句 [Query.list()] 查询数据,默认不使用二级缓存;

~ 二级缓存设置与使用

  • 设置要进行二级缓存的类有两种方法
    • (1)在主配置文件中使用<class-cache usage="二级缓存的读写属性" class="类的全限定名" />标签;【推荐使用,方便对使用二级缓存的类进行统一的管理】
    • (2)在具体的映射文件中设置当前的类使用二级缓存:<cache usage="read-write"/>
  • 对类里面的集合属性设置二级缓存的方法:
    • 在主配置文件中:
  • HQL语句使用二级缓存:
    • 我们更多的使用的查询条件,直接写HQL语句能不能缓存?不能,因为缓存需要使用id进行查询;
    • 对于HQL [Query.list()] 默认不会使用缓存,哪怕写成where id=1也不会使用缓存,若要使用缓存,则需要进行设置;
    • 在使用HQL方式的查询时,如果用iterate()方法,就会使用缓存;因为这个方法是先查询所有符合条件的id集合,再一个一个的按id查找数据,就能用上缓存了;但这个方法会有N+1次查询的问题,提升性能有限,不太常用;
      Iterator<Employee> iterator = session.createQuery("FROM Employee e WHERE e.id<8").iterate();
    • 使用迭代器的方法查询时,第一次查询时会先查询所有符合条件的id的值,然后按照id一个一个的查询,因为只有使用id的方式获取的时候才会使用缓存;在第二次查询的时候,也会先查询出所有符合条件的id,再根据id从缓存中获取数据;
    • 但是如果两次获取数据时id的取值范围不一样,比如第一次id<8,第二次id<10,那么id=8/9的两个数据二级缓存中没有,就会单独从数据库中查询,然后放入二级缓存中;

~ 在主配置文件中开启二级缓存

<!-- 使用二级缓存,默认是未打开的; -->
<!-- 缓存也有专业的缓存,跟连接池一样,可以使用集合实现一个简单的连接池,也可使用性能更好的专业的数据库连接池;
     指定要使用的外部专业的二级缓存的提供商,这也就打开了二级缓存;
<property name="cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
-->
<!-- 使用外部提供的更高级的缓存:需要添加额外的三个jar包(可根据console提示中信息看缺少那个jar包);
     更高级的缓存一般会有一些自己的配置,模板在Hibernate\...\project\etc\ehcache.xml 中,
     直接将配置文件拷贝到src目录下(跟主配置文件在同一目录),稍作修改即可;
 -->
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- 开启使用查询缓存;property标签需要放在class-cache标签前面 -->
<property name="cache.use_query_cache">true</property>
<!-- 开启二级缓存之后,默认所有类都没有使用二级缓存,需要手动指定需要使用二级缓存的实体类;
     有几个类使用二级缓存,就指定几个类;不指定类的话相当于打开但没有使用二级缓存,没效果;
     开启集合级别的缓存,需要同时开启集合元素类型对应的类的类级别缓存; 
     read-only:只读;缓存里面的数据不修改或有少量修改就用只读;设置为只读属性的缓存,数据若做修改就会发生异常;
     read-write:读写;缓存里面数据偶尔有变化,就用读写,经常变化就不使用缓存,关键数据(财务)也不使用缓存;
-->
<class-cache usage="read-write" class="cn.itcast.l_second_cache.Employee"/>   //类用全限定名
<class-cache usage="read-write" class="cn.itcast.l_second_cache.Department"/> //开启类级别缓存
<collection-cache usage="read-write" collection="cn.itcast.l_second_cache.Department.employees"/> 

~ 测试类 - App

public class App {
    private static SessionFactory sessionFactory = new Configuration()......

// 测试一级缓存(Session缓存,一次会话级别的缓存)
// 一个session里面获取两次,只有一个查询;若不开启二级缓存,两个session获取同一个对象会有两个查询;
// 因为session的缓存只在自己的session集合里面有效,跟另外一个session没有关系;这样对性能的提高帮助不大; 
// 打开二级缓存之后,两次session会从二级缓存里面获取同一个对象(普通属性,不包括集合属性)就会只有一个查询;
    public void testSessionCache() throws Exception {
        Session session1 = sessionFactory.openSession();   // 第1个Session
        session1.beginTransaction(); 
        Employee employee1 = (Employee) session1.get(Employee.class, 1); // 获取
        session1.getTransaction().commit();
        session1.close();

        Session session2 = sessionFactory......   // 第2个Session
    }

// 测试二级缓存(sessionFactory缓存,应用程序级别的缓存)【已开启二级缓存】
// 打开二级缓存之后,首先需要在主配置文件中手动指定需要使用二级缓存的实体类; 
// 查询类中集合属性的信息时,需要再开启集合级别的缓存;开启此级别缓存,需要同时开启集合元素类型对应的类的缓存;
// 开启二级缓存之后,使用两个session获取同一个对象(普通属性的值)时,因为这是二级缓存里面的同一个对象,
// 所以只有一个查询;但是如果查询类里面的集合属性的信息(部门里面的员工集合的信息)时,两个session会对集合
// 属性的信息进行两次查询,分别在两次使用集合信息之前进行查询;
//(因为对于集合属性的信息的获取,会采用懒加载的策略,在使用数据之前在进行查询)
// 因为类缓存了,但是类里面的所有其它员工对象都没有进行缓存;要想对集合数据也进行缓存,需要另外的设置指定;
    public void testSecondCache() throws Exception {
        Session session1 = sessionFactory.openSession();  // 第1个Session
        session1.beginTransaction();

        Department department1 = (Department) session1.get(Department.class, 1); // 获取
        System.out.println(department1.getName());
        System.out.println(department1.getEmployees());   // 获取集合属性的信息

        session1.getTransaction().commit();
        session1.close();
        // -----------------------------------------------------------------------------
        Session session2 = sessionFactory.......  // 第2个Session
    }

    @Test   // 测试查询缓存 : 当使用Query.list()时,默认不会使用二级缓存
    public void testQueryCache() throws Exception {
        Session session1 = sessionFactory.openSession();  // 第1个Session
        session1.beginTransaction();
        List<Employee> list = session1.createQuery("FROM Employee e WHERE e.id<10").list();
        System.out.println(list);
        Employee employee5 = (Employee) session.get(Employee.class, 5);
        System.out.println(employee5);
        session1.getTransaction().commit();
        session1.close();
        // ----------------------------------------------------------------------------------
        Session session2 = sessionFactory.......  // 第2个Session
    }

    // 测试查询缓存
    // 在使用HQL方式的查询时,如果用iterate()方法,就会使用缓存;
    // 因为这个方法是先查询所有符合条件的id集合,再一个一个的按id查找数据,就能用上缓存了;
    // 但这个方法会有N+1次查询的问题,提升性能有限,不太常用;
    public void testQueryCache2() throws Exception {
        Session session = sessionFactory.openSession();  // 第1个Session
        session.beginTransaction();

        Iterator<Employee> iterator = session.createQuery("FROM Employee e WHERE e.id<8").iterate();
        while (iterator.hasNext()) {
            Employee e = iterator.next();
            System.out.println(e);
        }
        session.getTransaction().commit();
        session.close();

        Session session2 = sessionFactory.......  // 第2个Session
    }
// 使用setCacheable()查询缓存:前提是两次session.createQuery("条件")中条件必须一样;
// 使用setCacheable()方法时,需要在两个session.createQuery方法后同时调用这个方法,
// 一个是把当前条件作为key存到缓存里面,第二个是以当前条件作为key去查询缓存;前提是两次条件必须一样;
// 查询缓存默认没开启,所以使用查询缓存时,需要在主配置文件中开启查询缓存:
// <property name="cache.use_query_cache">true</property>
    public void testQueryCache3() throws Exception {
        Session session = sessionFactory.openSession();   // 第1个Session
        session.beginTransaction();

        List list = session.createQuery("FROM Employee e WHERE e.id<18")//
                .setCacheable(true)   // 是否使用查询缓存,需要在hibernate.cfg.xml中开启查询缓存才行
                .list();
        System.out.println(list);
        session.getTransaction().commit();
        session.close();

        Session session2 = sessionFactory.......  // 第2个Session
    }
// 测试Update与 Delete格式的HQL语对二级缓存的影响
// 会让二级缓存中相关的数据失效,下次使用这些数据时会重新到数据库中加载
    public void testUpdateTimestampCache() throws Exception {
        Session session = sessionFactory.openSession();    // 第1个Session
        session.beginTransaction();
        // 先获取
        Employee employee = (Employee)session.get(Employee.class, 1);
        System.out.println(employee.getName());
        // 再直接更新DB
        session.createQuery("UPDATE Employee e SET e.name=? WHERE id=?")//
            .setParameter(0, "测试")//
            .setParameter(1, 1)//
            .executeUpdate();// 执行
        // 再显示这个员工的名称
        System.out.println(employee.getName());

        session.getTransaction().commit();
        session.close();

        Session session2 = sessionFactory.openSession();
        session2.beginTransaction();
        Employee employee2 = (Employee)session2.get(Employee.class, 1);
        System.out.println(employee2.getName());
        session2.getTransaction().commit();
        session2.close();
    }
}

>> 总结

~ 添加环境:

1,jar包
2,配置文件:hibernate.cfg.xmlxxx.hbm.xml

~ 使用Hibernate实现CRUD操作

// --- 准备
Configuration cfg = new Configuration().configure();        // hibernate.cfg.xml
SessionFactory sessionFactory = cfg.buildSessionFactory();  // 只需要一个
// --- 模板代码
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
    tx = session.beginTransaction();
    // 操作
    tx.commit();
}catch(Exception e){
    tx.rollback();
    throw e;
}finally{
    session.close();
}
// --- 操作
Session中的方法:
    save(Object)        --> insert into ..
    update(Object)      --> update ..
    saveOrUpdate(Object)
    delete(Object)      --> delete ..
    get(Class, id)      --> select ...
    createQuery(hql)    --> select ..

~ 集合映射

这里写图片描述
要说明的信息有:

  • 1,只要有集合,就一定有集合表<xxx name="" table="" order-by="">
  • 2,集合外键<key column="">
  • 3,集合元素<element type="" column="">
  • 4,对于List和数组,还需要多一个索引列<list-index column="">
  • 5,对于Map,还需要多一个key列<map-key type="" column="">

~ 关联关系映射

要说明的信息有什么

  • 一对多
    • 1,属性名
    • 2,集合外键
    • 3,关联的实体类型(one-to-many class=”“)
  • 多对一
    • 1,属性名
    • 2,外键列名
    • 3,关联的实体类型
  • 多对多
    • 1,属性名
    • 2,中间表
    • 3,集合外键:引用当前对象表主键值的那外外键
    • 4,关联的实体类型
    • 5,另一个外键:引用关联实体表主键的那个外键;
  • 一对一:(外键 + 唯一)
    • 基于外键的
      有外键方:<many-to-one name="obj" class=".." column=".." unique="true"/>
      无外键方:<one-to-one name=".." class=".." property-ref="obj"/>
    • 基于主键的
      有外键方:<one-to-one ...>
      无外键方:<one-to-one ...>
    • 操作
      在采用基于外键的方式时:只有有外键方可以维护关联关系;
      在采用基于主键的方式时:双方都不可以维护关系;

~ 一些重要的属性

  • inverse:
    • 是否放弃维护关联关系;
    • 默认是false,表示可以维护;
    • 实体类型的集合映射中可以使用(一对多、多对多);
  • sort:
    • 在内存中排序(类似于TreeSet)
    • 默认为unsorted,即不排序;
    • 在无序的集合映射中可以使用(List有序不能用,Set、Map能用)
  • order-by:
    • 使用数据库排序,即在SQL中增加orderby子句(类似于LinkedHashSet);
    • 默认不排序,这里指定的是sql语句中的orderby子句;
    • 在无序的集合映射中可以使用;
  • cascade:级联
    • 默认为none;
    • 在所有的关联关系映射中可以使用;
    • 常用的值:all, save-update, delete, none.

猜你喜欢

转载自blog.csdn.net/qq_37546891/article/details/81067479