zoukankan      html  css  js  c++  java
  • Hibernate入门

    1.Hibernate是什么

    Hibernate是一款优秀的持久化ORM框架,解决持久化操作,使得程序员可以从编写繁复的JDBC工作中解放出来,专注于业务,提高程序员开发效率。并且具有可靠的移植性,降低了系统耦合度。

    2.Hibernate入门案例

    源码地址:https://github.com/zhongyushi-git/hibernate-collection.git。下载代码后,示例代码在hibernate-maven文件夹下。 

    1)新建一个普通的maven项目

    2)导入依赖,本文采用hibernate5版本进行说明

     <dependencies>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-agroal</artifactId>
                <version>5.4.30.Final</version>
                <type>pom</type>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.18</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>

    3)配置hibernate.cfg.xml

    新建一个资源目录resources,在下面新建hibernate.cfg.xml,内容如下

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
    <hibernate-configuration>
        <session-factory>
            <property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
            <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="connection.username">root</property>
            <property name="connection.password">zys123456</property>
            <!-- 方言-->
            <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
         <!-- 指定数据库的生成方式,update是当表存在时插入数据,表不存在时先创建表再插入数据-->
         <property name="hibernate.hbm2ddl.auto">update</property>
    </session-factory> </hibernate-configuration>

    上述<session-factory>中指定了数据库的基本信息,包括url,driverClass,用户名及密码等。

    4)新建实体类User

    package com.zxh.entity;
    
    import lombok.Data;
    
    @Data
    public class User {
        private Integer id;
    
        private String name;
    
        private String password;
    }

    5)创建数据库及表

    create databse hebarnate;

    6)在User类同级目录下新建User.hbm.xml用于进行映射

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate mapping DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    
    <!--数据库映射,package指定了实体类所在的包名-->
    <hibernate-mapping package="com.zxh.entity">
        <!--表映射,name表示对象名,table表示表名-->
        <class name="User" table="t_user">
            <!--主键,native是自增,assigned是不指定,自定义-->
           <id name="id">
               <generator class="native"></generator>
           </id>
            <!--其他属性映射,name对象的属性,column表示数据库表的字段名称,当name和column时可省略-->
            <property name="name" column="name"></property>
            <property name="password"></property>
        </class>
    </hibernate-mapping>

    7)在hibernate.cfg.xml指定orm映射,指定hbm.xml位置

    <!-- orm映射文件-->
    <mapping resource="com/zxh/entity/User.hbm.xml"/>

    8)在pom.xml中读取配置文件

    <build>
            <!--读取配置文件-->
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering></resource>
            </resources>
        </build>

    9)添加测试类并进行测试

     @Test
        public void test1() {
            //初始化注册服务对象
            final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                    .configure()//默认加载hibernate.cfg .xmL配置文件,如果配置文件名称被修改,configure("修改的名字")
                    .build();
            //从元信息获取session工厂
            SessionFactory sessionFactory = new MetadataSources(registry)
                    .buildMetadata()
                    .buildSessionFactory();
            //从工厂创建session连接
            Session session = sessionFactory.openSession();
            //开启事务
            Transaction tx = session.beginTransaction();
            //创建实例
            User user = new User();
            user.setName("zhangsan");
            user.setPassword("123");
            session.save(user);
            //提交事务
            tx.commit();
            //关闭
            session.close();
    
        }

    执行测试方法成功后查看数据库,发现自动创建了表t_user并插入了一条数据。上述示例是添加数据,另外还有修改、删除和查询。

    10)修改数据

    添加一个测试方法,来修改上面添加的信息

    @Test
        public void test2(){
            //初始化注册服务对象
            final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                    .configure()//默认加载hibernate.cfg .xmL配置文件,如果配置文件名称被修改,configure("修改的名字")
                    .build();
            //从元信息获取session工厂
            SessionFactory sessionFactory = new MetadataSources(registry)
                    .buildMetadata()
                    .buildSessionFactory();
            //从工厂创建session连接
            Session session = sessionFactory.openSession();
            //获取事务
            Transaction tx = session.beginTransaction();
            //创建实例
            User user = new User();
            user.setId(1);
            user.setName("王海红");
            user.setPassword("9999");
            //提交事务
            session.update(user);
            tx.commit();
            //关闭session
            session.close();
        }

    由代码可以看出,修改数据和添加数据是类型的,只是使用的方法不一样。

    11)查询数据

    添加一个测试方法,来查询上面修改的信息

       @Test
        public void test3(){
            //初始化注册服务对象
            final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                    .configure()//默认加载hibernate.cfg .xmL配置文件,如果配置文件名称被修改,configure("修改的名字")
                    .build();
            //从元信息获取session工厂
            SessionFactory sessionFactory = new MetadataSources(registry)
                    .buildMetadata()
                    .buildSessionFactory();
            //从工厂创建session连接
            Session session = sessionFactory.openSession();
    
            User user = session.get(User.class, 1);
            System.out.println(user);
    
            //关闭session
            session.close();
        }

    这里查询是根据主键查询的单挑数据。在 Hibernate 中,除了使用 get() 方法加载数据以外,还可以使用 load() 方法加载数据,它们都能将数据从数据库中取出。两者的区别是使用 get() 方法加载数据时,如果指定的记录不存在,则返回 null;而使用 load() 方法加载数据时,如果指定的记录不存在,则会报出 ObjectNotFoundException 异常。

    12)删除数据

    添加一个测试方法,来删除上面修改的信息

     @Test
        public void test4(){
            //初始化注册服务对象
            final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                    .configure()//默认加载hibernate.cfg .xmL配置文件,如果配置文件名称被修改,configure("修改的名字")
                    .build();
            //从元信息获取session工厂
            SessionFactory sessionFactory = new MetadataSources(registry)
                    .buildMetadata()
                    .buildSessionFactory();
            //从工厂创建session连接
            Session session = sessionFactory.openSession();
            //获取事务
            Transaction tx = session.beginTransaction();
            //创建实例进行查询
            User user = session.get(User.class, 1);
            //删除
            session.delete(user);
            //提交事务
            tx.commit();
            //关闭session
            session.close();
        }

    由代码可以看出,在删除时先根据条件去查询,查询后根据结果进行删除。

    3.hibernate.cfg.xml介绍

    3.1Hibernate配置文件

    Hibernate中配置主要分为两种:

    1)一种包含了Hibernate与数据库的基本连接信息,在Hibernate工作的初始阶段,这些信息被先后加载到Configuration和SessionFactory实例。(如hibernate.cfg.xml,说明见本章节)

    2)另一种包含了Hibernate的基本映射信息,即系统中每一个类与其对应的数据库表之间的关联信息,在Hibernate工作的初始阶段,这些信息通过hibernate.cfg.xml的mapping节点被加载到Configuration和SessionFactory实例。(如User.hbm.xml,说明见下一章节)

    3.2常用配置属性

    配置文件在入门案例中,其常用属性说明如下表:

    名   称描   述
    hibernate.dialect 操作数据库方言
    hibernate.connection.driver_class 连接数据库驱动程序
    hibernate.connection.url 连接数据库 URL
    hibernate.connection.username 数据库用户名
    hibernate.connection.password 数据库密码
    hibernate.show_sql 在控制台输出 SQL 语句,true时打印,默认是false
    hibernate.format_sql 格式化控制台输出的 SQL 语句,true时格式化,默认是false
    hibernate.hbm2ddl.auto 当 SessionFactory 创建时是否根据映射文件自动验证表结构或 自动创建、自动更新数据库表结构。推荐使用update参数
    hibernate.connection.autocommit 事务是否自动提交

    属性说明后下面进行详细的说明。

    3.3数据库更新方式

    在入门案例中,hibernate.cfg.xml中配置的hibernate.hbm2ddl.auto是update方式,其参数的作用是用于自动创建表或更新数据等。除此之外还有其他的几种方式,见下表

    方式 说明
    update 第一次加载时,数据库是没有表的,会先创建表,把数据插入,后面表存在时直接插入数据。推荐使用
    create

    每次执行前都先把原有数据表删除,然后创建该表,会导致数据库表数据丢失。不使用

    create-drop

    每次执行前都先把原有数据表删除,然后创建该表。关闭SessionFactory时,将删除掉数据库。不使用

    validate

    每次加载hibernate时,会验证创建数据库表结构,如果不一致就抛出异常,但是会插入新值。只会和数据库中的表进行比较,不会创建新表。不建议使用

    3.4打印sql日志

    在进行数据的操作时,我们并没有写sql语句,当想查看其执行的sql时,也是可以开启的。只需要的配置文件中开启sql并格式化sql即可,代码如下:

    <!--打印sql-->
    <property name="show_sql">true</property>
    <!--格式化sql-->
    <property name="format_sql">true</property>

    截图如下

     再执行测试的方法,又添加了一条数据,控制台打印如下图

    4.*.hbm.xml介绍

    用于向 Hibernate 提供对象持久化到关系型数据库中的相关信息,每个映射文件的结构基本相同。通常和对象在同一目录下,前缀也一样,如User.hbm.xml,见入门案例配置。其首先进行了 xml 声明,然后定义了映射文件的 dtd 信息,然后后面是Hibernate 映射的具体配置。

    4.1<hibernate-mapping> 元素

    是映射文件的根元素,它所定义的属性在映射文件的所有节点都有效。其元素所包含的常用属性及其含义说明如下表所示。

    属性名是否必须说   明
    package 为映射文件中的类指定一个包前缀,用于非全限定类名
    schema 指定数据库 schema 名
    catalog 指定数据库 catalog 名
    default-access 否 

    指定 Hibernate 用于访问属性时所使用的策略,默认为 property。

    当 default-access="property" 时,使用 getter 和 setter 方法访问成员变量;当 default-access = "field"时,使用反射访问成员变量

    default-cascade 否  指定默认的级联样式,默认为空
    default-lazy 指定 Hibernate 默认所采用的延迟加载策略,默认为 true

    在User.hbm.xml中,使用package指定了实体类所在的包路径。

    4.2<class> 元素

    主要用于指定持久化类和数据表的映射关系,它是 XML 配置文件中的主要配置内容。其常用属性及其含义说明如下表 所示。

    属性名是否必须说   明
    name 持久化类或接口的全限定名。如果未定义该属性,则 Hibernate 将 该映射视为非 POJO 实体的映射
    table 持久化类对应的数据库表名,默认为持久化类的非限定类名
    catalog 数据库 catalog 名称,如果指定该属性,则会覆盖 hibernate-mapping 元素中指定的 catalog 属性值
    lazy 指定是否使用延迟加载

    在User.hbm.xml中,使用name指定了实体类是User,table指定了表名是t_user。

    4.3<id>元素

    用于设定持久化类的主键的映射,其常用属性及其含义说明如下表 所示。

    属性名是否必须说   明
    name 标识持久化类主键标识
    type 持久化类中标识属性的数据类型。如果没有为某个属性显式设定映射类型,Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型,然后自动使用与之对应的默认 Hibernate 映射类型。若指定是引用数据类型,可省略。
    column 设置标识属性所映射的数据列的列名(主键字段的名字),若列名与对象的属性名称一致,则可省略
    access 指定 Hibernate 对标识属性的访问策略,默认为 property。若此处指定了该属性,则会覆盖 <hibemate-mapping> 元素中指定的 default-access 属性

    除了上面4个元素以外,<id> 元素还可以包含一个子元素 <generator>。<generator> 元素指定了主键的生成方式。

    对于不同的关系型数据库和业务应用来说,其主键的生成方式往往也不同,有的是依赖数据库自增字段生成主键,有的是按照具体的应用逻辑决定,通过 <generator> 元素就可以指定这些不同的实现方式。这些实现方式在 Hibernate 中,又称为主键生成策略。常用的策略有两种,一种是native,它是根据不同的数据库实现主键的自增;另一种是assigned,根据用户的需要自定义主键的值,也就是说在插入数据时需要指定主键的值;如果不指定 id 元素的 generator 属性,则默认使用assigned主键生成策略。

    在User.hbm.xml中,id使用的是native,即使用数据库的自增特性,这里以mysql为例说明。

    4.4<property>元素

    <class> 元素内可以包含多个 <property> 子元素,它表示持久化类的其他属性和数据表中非主键字段的映射关系。常用属性及其含义说明如下表所示。

    属性名是否必须说   明
    name 持久化类属性的名称,以小写字母开头
    column 数据表字段名(若列名与对象的属性名称一致,则可省略)
    type 数据表的字段类型(若指定是引用数据类型,可省略。)
    length 数据表字段定义的长度(未指定则使用数据库的默认长度)
    lazy 指定当持久化类的实例首次被访问时,是否对该属性使用延迟加载,其默认值是 false
    unique 是否对映射列产生一个唯一性约束。常在产生 DDL 语句或创建数据库对象时使用
    not-null   是否允许映射列为空

    5.整合log4j

    当需要根据自己的需求打印日志时,可使用log4j进行自定义。在使用时,需先把配置文件hibernate.cfg.xml中的开启sql打印和格式化sql代码注释。

    1)导入依赖

      <!--添加s1f4j依赖-->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.25</version>
            </dependency>
            <!--添加s1f4j-log4j转换包-->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.7.25</version>
            </dependency>
            <!--添加log4j依赖-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>

    2)新建log4j.properties

    #控制台处理类
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    #控制台输出源布局layout
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd} %d{ABSOLUTE} %5p %c{1}:%L - %m%n
    #文件处理类
    log4j.appender.file=org.apache.log4j.FileAppender
    log4j.appender.file.File=D:/test/hibernate.log
    #文件输出源布局layout
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd} %d{ABSOLUTE} %5p %c{1}:%L - %m%n
    # 记录器 输出源 布局
    log4j.rootLogger=warn,stdout,file
    #记录hibernate参数
    log4j.logger.org.hibernate.SQL=debug
    #记录JDBC参数
    log4j.logger.org.hibernate.type=info
    #记录执行sQL的DDL语句
    1og4j.logger.org.hibernate.tool.hbm2ddl=debug

    3)测试。再次执行测试方法,看到有打印的信息。

    5.Hibernate生命周期

    5.1对象的状态

    Hibernate中对象有三种状态:瞬时状态(Transient)、持久状态(Persistent)、游离状态(Detached)。

    1)瞬时状态:刚刚使用new语句创建,还没有被持久化,不处于Session的缓存中。处于临时状态的Java对象被称为临时对象。Session中没有,数据库中没有。

    2)持久化状态:已经被持久化,加入到Session的缓存中。处于持久化状态的Java对象被称为持久化对象。Session中有,数据库中有。

    3)游离状态:已经被持久化,但不处于Session的缓存中。处于游离状态的Java对象被称为游离对象。Session中没有,数据库中有。

    6.一级缓存

    6.1概述

    Hibernate 中的缓存分为一级缓存和二级缓存,这两个级别的缓存都位于持久化层,并且存储的都是数据库数据的备份。其中一级缓存是 Hibernate 的内置缓存,也叫做session缓存,属于事务范围的缓存,这一级别的缓存由 Hibernate 管理,一般情况下无须进行干预。其作用是减少数据库的访问次数。

    hibernate在查询数据时,首先会使用对象的 OID 值在 Hibernate 的一级缓存中查找,如果找到匹配的对象,则直接将该对象从一级缓存中取出使用;如果没有找到匹配的对象,则会去数据库中查询对应的数据。当从数据库中查询到所需数据时,该数据信息会同步存储到一级缓存中,从而在下次查询同一个OID时就可直接从缓存中获取,不需要再次查询数据库。

    6.2特点

    1)当调用 Session 接口的 load()、get() 方法,以及 Query 接口的 list()、iterator() 方法时,会判断缓存中是否存在该对象,有则返回,不会查询数据库,如果缓存中没有要查询的对象,则再去数据库中查询对应对象,并添加到一级缓存中。

    2)当应用程序调用 Session 接口的 save()、update()、saveOrUpdate() 时,如果 Session 缓存中没有相应的对象,则 Hibernate 就会自动把从数据库中查询到的相应对象信息加入到一级缓存中。

    3)当调用 Session 的 close() 方法时,Session 缓存会被清空。

    4)Session 能够在某些情况下,按照缓存中对象的变化,执行相关的 SQL 语句同步更新数据库,这一过程被称为刷出缓存(flush)。那么刷出缓存的几种情况如下:

    A:当应用程序调用 Transaction 的 commit() 方法时,该方法先刷出缓存(调用 session.flush() 方法),然后再向数据库提交事务(调用 commit() 方法)

    B:当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态。

    C:调用 Session 的 flush() 方法。

    6.3一级缓存示例

    1)为了使用方便即代码的简洁,把创建session的代码封装为一个工具类

    package com.zxh.util;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.boot.MetadataSources;
    import org.hibernate.boot.registry.StandardServiceRegistry;
    import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
    
    /**
     * 工具类,用来创建session
     */
    public class HibernateUtils {
    
        public static final StandardServiceRegistry registry;
        public static final SessionFactory sessionFactory;
    
        static {
            registry = new StandardServiceRegistryBuilder().configure().build();
            sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
        }
    
        public static Session getSession() {
            return sessionFactory.openSession();
        }
    }

    2)添加一个测试方法,对一条数据进行两次查询

        @Test
        public void test5(){
            Session session = HibernateUtils.getSession();
            User user = session.get(User.class, 2);
            System.out.println(user);
            User user2 = session.get(User.class, 2);
            System.out.println(user2);
            //关闭session
            session.close();
        }

    控制台的打印结果如下图,根据下图可以看出,只执行了一次查询,第二次查询直接从缓存中获取了。

    6.4快照技术

    Hibernate 向一级缓存中存入数据的同时,还会复制一份数据存入 Hibernate 快照中。当调用 commit() 方法时,会清理一级缓存中的数据操作,同时会检测一级缓存中的数据和快照区的数据是否相同。如果不同,则会执行 update() 方法,将一级缓存的数据同步到数据库中,并更新快照区;反之,则不会执行 update() 方法。快照的作用就是保证缓存中的数据与数据库的数据保持一致。

    且先看下面的示例:

        @Test
        public void test6() {
            Session session = HibernateUtils.getSession();
            User user = new User();
            user.setName("李焕英");
            user.setPassword("123456");
            //向一级缓存中存入session对象
            session.save(user);
            //重新设置值
            user.setPassword("000000");
            //提交事务
            session.beginTransaction().commit();
            //关闭session
            session.close();
        }

    运行的日志如下:

    插入的数据查询如下:

    从这个sql的执行日志来看,首先把数据插入进去,然后根据id把password进行了修改,最终的数据便是数据库查询到的数据。其实这里就用到了快照技术。当设置对象后调用session.save()时,会把在一级缓存中和快照中各存一份User对象数据,此时两者的数据是一样的;但在提交事务之前,把User对象的password属性修改了,数据会同步到快照中;在提交事务时,先根据一级缓存的数据进行插入操作,于此同时会对比快照中的数据与一级缓存中的数据是否相同,由于两者的数据不同,就会执行update语句把一级缓存的数据更新并修改数据库的数据。

    6.5一级缓存常用操作

    1)刷出flush

    一级缓存刷出功能是指在调用 Session 的 flush() 方法时让Session执行一些必须的SQL语句来把内存中的对象的状态同步到JDBC中,也就会执行update语句。在提交事务前,Hibernate 程序会默认先执行 flush() 方法。

    且看代码:

        @Test
        public void test7() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            User user = session.get(User.class, 8);
            //重新设置值
            user.setPassword("9999");
            //执行刷出操作
            session.flush();
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    2)清除clear

    在调用 Session 的 clear() 方法时,会清除缓存中的数据。

    且看代码:

        @Test
        public void test8() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            User user = session.get(User.class, 8);
            //重新设置值
            user.setPassword("55555");
            //清除缓存
            session.clear();
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    执行后发现控制台只打印了查询的语句,并没有执行update语句,数据库的数据也没有发生变化。原因是在提交事务前,清除了缓存中的数据,就不会执行修改操作。

    如果将上述方法中的 session.clear() 方法更改为 session.evict(user)方法,也可以实现同样的效果。这两个方法的区别是:clear() 方法是清空一级缓存中所有的数据,而 evict() 方法是清除一级缓存中的某一个对象。

    3)刷新refresh

    在调用 Session 的 refresh() 方法时,会重新查询数据库,并更新 Hibernate 快照区和一级缓存中的数据,让两者的数据保持一致。

    且看代码:

        @Test
        public void test9() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            User user = session.get(User.class, 8);
            //重新设置值
            user.setPassword("55555");
            //清除缓存
            session.refresh(user);
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    日志打印截图:

    可以看出执行了两次的查询。第一次查询是指定的查询,第二次查询是由refresh执行的查询。查询后查看数据库,发现数据并没有发生变化,原因是在执行refresh后,会重新查询数据,并把数据给一级缓存和快照各一份,尽管在此之前快照中的值发生了变化,但此时仍然会被最新查询的数据覆盖,password由55555变成原来的9999。

    7.二级缓存

    7.1概述

    二级缓存是SessionFactory 级别的缓存,它是属于进程范围的缓存,这一级别的缓存可以进行配置和更改,以及动态地加载和卸载,它是由 SessionFactory 负责管理的。

    在访问指定的对象时,首先从一级缓存中查找,找到就直接使用,找不到则转到二级缓存中查找(必须配置和启用二级缓存)。如果在二级缓存中找到,就直接使用,否则会查询数据库,并将查询结果根据对象的 ID 放到一级缓存和二级缓存中。

    7.2二级缓存分类

    外置缓存(二级缓存)的物理介质可以是内存或硬盘,其分类如下表:

    名称 说明
    Class Cache Region 类级别的缓存。用于存储 PO(实体)对象
    Collection Cache Region 集合级别的缓存。用于存储集合数据
    Query Cache Region 查询缓存。缓存一些常用查询语句的查询结果
    Update Timestamps 更新时间戳缓存。该区域存放了与查询结果相关的表在进行插入、更新或删除操作的时间戳,Hibernate 通过更新时间戳缓存区域判断被缓存的查询结果是否过期

    7.3整合EHCache插件实现二级缓存

    1)导入依赖

           <!-- 二级缓存 EHcache -->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-ehcache</artifactId>
                <version>5.4.30.Final</version>
            </dependency>    

    由于本文采用的是hibernate5,那么ehcache也要采用相同的版本才行,否则执行会报错,这是一个大坑!其他版本开启缓存的方式略有差别,请注意!

    2)开启二级缓存

    在核心配置文件中开启缓存,并指定使用缓存的类

            <!-- 开启二级缓存-->
            <property name="hibernate.cache.use_second_level_cache">true</property>
            <!-- 开启查询缓存-->
            <property name="hibernate.cache.use_query_cache">true</property>
            <property name="hibernate.cache.region.factory_class">
                org.hibernate.cache.ehcache.EhCacheRegionFactory
            </property>
            <!-- orm映射文件-->
            <mapping resource="com/zxh/entity/User.hbm.xml"/>
            <class-cache usage="read-write" class="com.zxh.entity.User"/>

    上述标红的配置实际上在起那么已经存在了,这里只是说明其位置。也就是说<mapping>标签必须放到<property>标签后面,<class-cache> 标签必须放在 <mapping> 标签的后面。

    <class-cache> 标签用于指定将哪些数据存储到二级缓存中,其中 usage 属性表示指定缓存策略。

    3)创建测试类

       @Test
        public void test10() {
            //开启第一个Session对象
            Session session1 = HibernateUtils.getSession();
            // 开启第一个事务
            Transaction tx1 = session1.beginTransaction();
            // 获取对象
            User p1 = session1.get(User.class, 8);
            User p2 = session1.get(User.class, 8);
            // 第一次比较对象是否相同
            System.out.println(p1 == p2);
            // 提交事务
            tx1.commit();
            //session1对象关闭,一级缓存被清理
            session1.close();
    
            // 开启第二个Session对象
            Session session2 = HibernateUtils.getSession();
            // 开启第二个事务
            Transaction tx2 = session2.beginTransaction();
            // 获取对象
            User p3 = session2.get(User.class, 8);
            // 第二次比较
            System.out.println(p1 == p3);
            User p4 = session2.get(User.class, 8);
            // 第三次比较
            System.out.println(p3 == p4);
            // 提交事务2
            tx2.commit();
            // session2关闭
            session2.close();
    
        }

    执行后控制台只执行了一次查询,打印的结果分别是true,false,true。

    4)代码执行分析

    第一步:从第一个 Session 中获取 p1 对象时,由于一级缓存和二级缓存中没有相应的数据,需要从数据库中查询,所以执行了一次查询。

    第二步:查询出 p1 对象后,p1 对象会保存到一级缓存和二级缓存中。在获取p2对象时,Session 并没有关闭,由于一级缓存中已存在对应的数据,则直接从缓存中获取,并不会去数据库查询;由于 p1 和 p2 对象都保存在一级缓存中,而且指向的是同一实体对象,所以p1和p2是一样的。

    第三步:提交事务 tx1并关闭 session1,此时一级缓存中的数据会被清除。

    第四步:开启第二个 Session 和事务,获取 p3 对象,此时 p3 对象是从二级缓存中获取的。取出后,二级缓存会将数据同步到一级缓存中,这时 p3 对象又在一级缓存中存在了。

    第五步:二级缓存中存储的都是对象的散装数据,它们会重新 new 出一个新的对象,所以p1和p3指向的地址不同。

    第六步:获取 p4 对象时,Session 并没有关闭,由于一级缓存中已存在对应的数据,则直接从缓存中获取,故p3和p4是一样的,其原理同p1与p2。

    8.Hibernate多表操作

    在前面的操作中,都是对单表进行操作。除此之外,还有多表的关联关系,它们的关系分别有一对一、一对多、多对多。

    8.1一对多映射

    8.1.1一对多关系

    一对多映射关系是由“多”的一方指向“一”的一方。在表示“多”的一方的数据表中增加一个外键,指向“一”的一方的数据表的主键,“一”的一方称为主表,而“多”的一方称为从表。

    比如班级和学生关系,一个班级中有多个学生,但是一个学生只属于一个班级,就属于一对多的关系(一个班级对应多个学生)

    8.1.2 实战演练

    1)新建班级类Clazz

    package com.zxh.entity;
    
    import lombok.Data;
    import lombok.experimental.Accessors;
    
    import java.util.HashSet;
    import java.util.Set;
    
    @Data
    @Accessors(chain = true)
    public class Clazz {
    
        private Integer id;
    
        private String name;
    
        private Set<Student> students = new HashSet<>();
    
    }

    其中 students 是一个集合对象,用于存储一个班级的学生。

    2)新建学生类Student

    package com.zxh.entity;
    
    import lombok.Data;
    import lombok.experimental.Accessors;
    
    @Data
    @Accessors(chain = true)
    public class Student {
    
        private Integer id;
    
        private String name;
    
        private Clazz clazz;
    
    
    }

    其中 clazz是一个 Clazz类型的对象,用于表示该学生属于某一个班级。

    3)新建Clazz.hbm.xml

    在Clazz同级目录下新建Clazz.hbm.xml,内容如下:

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate mapping DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    
    <!--数据库映射,package指定了实体类所在的包名-->
    <hibernate-mapping package="com.zxh.entity">
        <!--表映射,name表示对象名,table表示表名-->
        <class name="Clazz" table="t_clazz">
            <!--主键自增-->
           <id name="id">
               <generator class="native"></generator>
           </id>
            <property name="name" length="100"></property>
            <!-- 一对多的关系使用set集合映射 -->
            <set name="students">
                <!-- 指定映外键的列名 -->
                <key column="cid" foreign-key="cid"/>
                <!-- 指定映射的类,指向多的一方 -->
                <one-to-many class="Student"/>
            </set>
        </class>
    </hibernate-mapping>

    在一对多的关系中,使用<set>标签进行关系的映射。其name表示对象的属性名,里面包含两个标签。<key>用于指定外键,colmun指定外键的列名,foreign-key指定外键名称(若不指定,则使用默认策略生成外键名称,若指定,主键名称 不能重复)。<one-to-many> 标签描述持久化类的一对多关联,指向多的一方的类。

    4)新建Student.hbm.xml

    在Student同级目录下新建Student.hbm.xml,内容如下:

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-mapping PUBLIC
            "-//Hibernate/Hibernate mapping DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
    
    <!--数据库映射,package指定了实体类所在的包名-->
    <hibernate-mapping package="com.zxh.entity">
        <!--表映射,name表示对象名,table表示表名-->
        <class name="Student" table="t_student">
            <!--主键自增-->
           <id name="id">
               <generator class="native"></generator>
           </id>
            <property name="name" length="50"></property>
            <!-- 多对一关系映射,指向一的一方 -->
            <many-to-one name="clazz" class="Clazz" column="cid"></many-to-one>
        </class>
    </hibernate-mapping>

    <many-to-one> 标签定义了三个属性,分别是 name、class 和 column 属性。其中,name 属性表示 Student 类中的 clazz属性名称,class 属性表示指定映射的类,column 属性表示表中的外键类名。需要注意的是,该 column 属性与 Clazz.hbm.xml 映射文件的 <key> 标签的 column 属性要保持一致。

    5)核心配置文件指定xml位置

    在hibernate.cfg.xml配置

    <mapping resource="com/zxh/entity/Clazz.hbm.xml"/>
    <mapping resource="com/zxh/entity/Student.hbm.xml"/>

    6)创建测试方法

    为了代码的结构清晰,这里再新建一个测试类MyTest2,添加一个测试方法

       @Test
        public void test1() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            Clazz clazz = new Clazz();
            clazz.setName("计算机1班");
            //学生属于某个班级
            Student student1 = new Student().setName("蔡敏敏").setClazz(clazz);
            Student student2 = new Student().setName("李明").setClazz(clazz);
            //班级里有多个学生
            clazz.getStudents().add(student1);
            clazz.getStudents().add(student2);
            //保存数据
            session.save(clazz);
            session.save(student1);
            session.save(student2);
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    运行测试方法,会发现发生了异常,如下图:

    根据打印的结果来看,表已经创建成功了,在插入数据时出现的异常。这也是一个坑,原因是在实体类上使用了lombok的@Data注解,其toString()方法会去调用本类属性的其他对象的toString()方法。Clazz调用Student的toString(),Studen又调用Clazz的toString(),这样就陷入了死循环,导致栈溢出。解决办法是不使用@Data注解,改使用@Getter和@Setter注解。当两个实体都注解修改后,删除两个表,再次运行,发现执行成功,如下图:

    数据库的数据如下图:

                

     如果需要使用上述两个对象的toString()方法,那么只需要给其加上@toString注解即可。这样使用三个注解来替换@Data可以完美的解决上述的异常。

    8.2多对多映射

    8.2.1多对多关系

    多对多的关系都会产生一张中间表。比如学生和学科关系,一个学生可以选择多个学科,一个学科可以被多个学生选择。

    8.2.2 实战演练

    1)新建学生类Stud

    为了和一对多的代码进行区分,这里学生类使用Stud

    package com.zxh.entity;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import lombok.experimental.Accessors;
    
    import java.util.HashSet;
    import java.util.Set;
    
    @Setter
    @Getter
    @ToString
    @Accessors(chain = true)
    public class Stud {
    
        private Integer id;
    
        private String name;
    
        private Set<Course> courses = new HashSet<>();
    }

    其中 courses是一个集合对象,用于存储选择的学科。

    2)新建学科类Course

    package com.zxh.entity;

    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    import lombok.experimental.Accessors;

    import java.util.HashSet;
    import java.util.Set;

    @Setter
    @Getter
    @ToString
    @Accessors(chain = true)
    public class Course {

    private Integer id;

    private String name;

    private Set<Stud> students = new HashSet<>();
    }

    其中 students是一个集合对象,用于存储选择学科的学生。

    3)新建Stud.hbm.xml

    在Stud同级目录下新建Stud.hbm.xml,内容如下:

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate mapping DTD//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

    <!--数据库映射,package指定了实体类所在的包名-->
    <hibernate-mapping package="com.zxh.entity">
    <!--表映射,name表示对象名,table表示表名-->
    <class name="Stud" table="t_stud">
    <!--主键自增-->
    <id name="id">
    <generator class="native"></generator>
    </id>
    <property name="name" ></property>
    <!-- 多对多的关系使用set集合映射 -->
    <set name="courses" table="stu_course">
    <!-- 指定映外键的列名 -->
    <key column="sid"/>
    <!-- 指定映射的类,指向指向对方 -->
    <many-to-many class="Course" column="cid"/>
    </set>
    </class>
    </hibernate-mapping>

    对比一对多的关系,<set>标签中多了一个table属性,用来指定中间表的表名称。<many-to-many> 标签表示两个持久化类多对多的关联关系,其中 column 属性指定 course 表在中间表中的外键名称。Stud指向Course类,其在中间表的列名是cid。

    4)新建Course.hbm.xml

    在Course同级目录下新建Course.hbm.xml,内容如下:

    <?xml version='1.0' encoding='utf-8'?>
    <!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate mapping DTD//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

    <!--数据库映射,package指定了实体类所在的包名-->
    <hibernate-mapping package="com.zxh.entity">
    <!--表映射,name表示对象名,table表示表名-->
    <class name="Course" table="t_course">
    <!--主键自增-->
    <id name="id">
    <generator class="native"></generator>
    </id>
    <property name="name" ></property>
    <!-- 多对多的关系使用set集合映射 -->
    <set name="students" table="stu_course">
    <!-- 指定映外键的列名 -->
    <key column="cid"/>
    <!-- 指定映射的类,指向对方 -->
    <many-to-many class="Stud" column="sid"/>
    </set>
    </class>
    </hibernate-mapping>

    <set>标签中table属性用来指定中间表的表名称。<many-to-many> 标签表示两个持久化类多对多的关联关系,其中 column 属性指定 Stud表在中间表中的外键名称。Course指向Stud类,其在中间表的列名是sid。

    5)核心配置文件指定xml位置

    在hibernate.cfg.xml配置

    <mapping resource="com/zxh/entity/Stud.hbm.xml"/>
    <mapping resource="com/zxh/entity/Course.hbm.xml"/>

    6)创建测试方法

    在测试类MyTest2中添加一个测试方法

     @Test
        public void test2() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
    
            //学生
            Stud student1 = new Stud().setName("蔡敏敏");
            Stud student2 = new Stud().setName("李明");
            //学科
            Course course = new Course().setName("java 基础");
            Course course2 = new Course().setName("NySQL 基础");
    
            //学生关联科目
            student1.getCourses().add(course);
            student1.getCourses().add(course2);
    
            student2.getCourses().add(course);
            student2.getCourses().add(course2);
            //保存数据
            session.save(course);
            session.save(course2);
            session.save(student1);
            session.save(student2);
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    运行测试方法,执行成功,如下图

     数据库数据如下图:

                     

     上述测试代码中,首先创建两个学生对象和两门课程对象,然后用学生对科目进行关联,这是多对多的单向关联。

    8.3反转(inverse)

    8.3.1概述

    在映射文件的 <set> 标签中,inverse(反转)属性的作用是控制关联的双方由哪一方管理关联关系。

    inverse 属性值是 boolean 类型的,当取值为 false(默认值)时,表示由当前这一方管理双方的关联关系,如果双方 inverse 属性都为 false 时,双方将同时管理关联关系;取值为 true 时,表示当前一方放弃控制权,由对方管理双方的关联关系。

    通常情况下,在一对多关联关系中,会将“一”的一方的 inverse 属性取值为 true,即由“多”的一方维护关联关系,否则会产生多余的 SQL 语句;而在多对多的关联关系中,任意设置一方的 inverse 属性为 true 即可。

    8.3.2实战演练

    1)新建一个测试方法test3,对test2方法添加课程对学生的关联

      @Test
        public void test3() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
    
            //学生
            Stud student1 = new Stud().setName("张三丰");
            Stud student2 = new Stud().setName("赵虎义");
            //学科
            Course course = new Course().setName("redis 基础");
            Course course2 = new Course().setName("java框架");
    
            //学生关联科目
            student1.getCourses().add(course);
            student1.getCourses().add(course2);
    
            student2.getCourses().add(course);
            student2.getCourses().add(course2);
    
            //科目关联学生
            course.getStudents().add(student1);
            course.getStudents().add(student2);
    
            course2.getStudents().add(student1);
            course2.getStudents().add(student2);
    
            //保存数据
            session.save(course);
            session.save(course2);
            session.save(student1);
            session.save(student2);
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    2)执行后代码报错,显示主键重复。这是因为在双向关联中会产生一张中间表,关联双方都向中间表插入了数据。

    通常情况下,这种问题有两种解决方案:第一种是进行单向关联(即最初的代码test2方法中的代码),第二种是在一方的映射文件中,将 <set> 标签的 inverse 属性设置为 true。

    3)在 Course.hbm.xml 映射文件的 <set> 标签中,添加 inverse 属性,并将其属性值设置为 true

    再次执行test3方法,运行成功,数据正常插入进去。

    4)注意点:inverse 只对 <set>、<one-to-many> 和 <many-to-many> 标签有效,对 <many-to-one> 和 <one-to-one> 标签无效。

    8.4级联(cascade)

    8.4.1概述

    级联操作是指当主控方执行任意操作时,其关联对象也执行相同的操作,保持同步。在映射文件的 <set> 标签中有个 cascade 属性,该属性用于设置是否对关联对象采用级联操作。其常用属性如下表所示:

    属性值描   述
    save-update 在执行 save、update 或 saveOrUpdate 吋进行关联操作
    delete 在执行 delete 时进行关联操作
    delete-orphan 删除所有和当前解除关联关系的对象
    all 所有情况下均进行关联操作,但不包含 delete-orphan 的操作
    all-delete-orphan 所有情况下均进行关联操作
    none 所有情况下均不进行关联操作,这是默认值

    一对多的关系还是以班级和学生进行说明,新建一个测试类MyTest3,下面的级联操作均在此测试类进行。

    8.4.2一对多级联添加

    1)新建一个方法test1

        @Test
        public void test1(){
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            // 创建一个班级
            Clazz clazz = new Clazz();
            clazz.setName("计算机二班");
            // 创建一个学生
            Student student = new Student();
            student.setName("王五");
            clazz.getStudents().add(student);
            // 班级关联学生
            session.save(clazz);
            session.getTransaction().commit();
            session.close();
        }

    2)修改Clazz.hbm.xml

    把<set>标签的cascade 的属性值设置为 save-update

    3)运行测试方法,日志截图

    查看数据库,数据如下:

                

    对比上述8.1.2小节的代码可以看出,这里的测试方法并没有写保存学生的代码,由级联操作自动去执行。

    8.4.3一对多级联删除

    在班级和学生的关联关系中,如果使用级联删除了班级,那么该班级对应的学生也会被删除。

    1)先看不使用级联删除班级信息的效果。新建test2,用于删除班级信息

        @Test
        public void test2(){
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            Clazz clazz = session.get(Clazz.class,3);
            session.delete(clazz);
            session.getTransaction().commit();
            session.close();
        }

    2)执行后日志截图如下。从日志可以看出,在删除时,先修改了学生的外键值为null,再删除了班级信息。数据库查询也是如此,也就是说删除看班级信息,但对应的学生信息并没有删除,只是将其班级id置为null而已。

    3)如果需要在删除班级的同时删除学生信息,就需要使用级联操作。只需要在 Clazz.hbm.xml 映射文件的 <set> 标签中,将 cascade 属性值设置为 delete 即可。在设置之前,先执行test1方法把数据再次插入进去,然后把cascade 值设置为delete,

    4)在插入数据后,根据id修改test2方法中的查询条件(现在插入的班级id是4),再次执行test2方法,日志截图:

    执行后对应的数据已经删除成功了。看到日志发现,它还是先把学生的外键置为null,然后根据主键id进行删除。

    8.4.4孤儿删除

    孤儿的意思是如果解除了外键的关联关系,那么没有外键的一方就是孤儿。要想删除这些数据,也是可以的,那么这种删除就叫孤儿删除。

    1)先修改cascade="save-update"执行test1方法把数据再次插入进去

    2)新建测试方法test3,先查询数据,然后解除关联关系,再删除班级信息

       @Test
        public void test3() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            Clazz clazz = session.get(Clazz.class, 6);
            Student student = session.get(Student.class, 3);
            //解除关联关系
            clazz.getStudents().remove(student);
            session.delete(clazz);
            session.getTransaction().commit();
            session.close();
        }

    3)在 Clazz.hbm.xml 映射文件的 <set> 标签中,将 cascade 属性值设置为 delete-orphan

    4)运行test3方法,日志截图如下:

    数据也正常被删除了,达到了预定的效果。

    8.4.5多对多级联添加

    前面已经熟悉了一对多的级联添加,那么多对多的级添加也是同样的道理,直接贴代码。

    1)测试方法test4

        @Test
        public void test4() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
    
            //学生
            Stud student1 = new Stud().setName("蔡敏敏");
            Stud student2 = new Stud().setName("李明");
            //学科
            Course course = new Course().setName("java 基础");
            Course course2 = new Course().setName("NySQL 基础");
    
            //学生关联科目
            student1.getCourses().add(course);
            student1.getCourses().add(course2);
    
            student2.getCourses().add(course);
            student2.getCourses().add(course2);
            //保存数据
            session.save(student1);
            session.save(student2);
    
            //提交事务
            session.getTransaction().commit();
            //关闭session
            session.close();
        }

    2)修改Stud.hbm.xml的属性cascade="save-update"

    3)运行测试方法,日志截图如下,数据添加成功:

    8.4.6多对多级联删除

    多对多级联删除和一对多的级联删除类似,直接贴代码:

    1)测试方法test5

        @Test
        public void test5() {
            Session session = HibernateUtils.getSession();
            session.beginTransaction();
            Stud stud = session.get(Stud.class, 2);
            session.delete(stud);
            session.getTransaction().commit();
            session.close();
        }

    2)修改Stud.hbm.xml的属性cascade="delete"

    3)运行测试方法,日志截图如下,数据删除成功:

    9.Hibernate的5种检索方式

    9.1导航对象图检索

    导航对象图检索方式是根据已经加载的对象,导航到其他对象,利用的是类与类之间的关系检索对象。

    Student student = session.get(Student.class, 4);
    Clazz clazz = student.getClazz();

    这种方式不常用。

    9.2 OID 检索

    OID 检索方式是指按照对象的 OID 检索对象。它使用 Session 对象的 get() 和 load() 方法加载某一条记录所对应的对象,其使用的前提是需要事先知道 OID 的值。

    Student student = session.get(Student.class, 4);

    9.3 HQL 检索

    HQL(Hibernate Query Language)是 Hibernate 查询语言的简称,它是一种面向对象的查询语言,与 SQL 查询语言有些类似,但它使用的是类、对象和属性的概念,而没有表和字段的概念。

    9.3.1 HQL 检索语法

    select/update/delete...]from...[where...][group by...][having...][order by...][asc/desc]
    

    HQL 查询与 SQL 查询非常类似,通常情况下,当检索表中的所有数据时,查询语句中可以省略 select 关键字。这种查询使用createQuery()执行,数据使用list()方法获取。

    String hql="from User";
    

    需要注意的是,语句中 User 表示类名,而不是表名,因此需要区分大小写。新建测试类HQLQuery,本小节的测试方法在里面创建。

    9.3.2指定别名

    指定别名和sql中的as一样,对字段或表名指定别名。下面的案例对表名指定别名:

    @Test
    public void test1(){
    Session session = HibernateUtils.getSession();
    String sql ="from User t where t.name='lisi'";
    Query query = session.createQuery(sql);
    List<User> list = query.list();
    System.out.println(list);
    session.close();
    }

    这里查询出了所有的字段信息,有时候想只查询部分字段信息,就需要使用投影查询。

    9.3.3投影查询

    投影查询就是只查询一部分字段,把数据库的字段与实体类的字段进行投影。

        @Test
        public void test2(){
            Session session = HibernateUtils.getSession();
            String sql ="select t.id,t.name from User t";
            Query query = session.createQuery(sql);
            List<Object[]> list = query.list();
            Iterator<Object[]> iter = list.iterator();
            while (iter.hasNext()) {
                Object[] objs = iter.next();
                System.out.println(objs[0] + " 	" + objs[1]);
            }
            session.close();
        }

    上述语句只查询了用户的id和姓名,这就属于只查询部分字段。那么对于这种情况,查询返回的一个数组类型的集合,集合的大小代表查询的数据条数,数组的大小代表查询的字段个数。数组中的元素都是按照查询的语句字段的顺序来的,不同的字段对应不同的位置。如上述objs[0]代表字段id,objs[1]代表字段name,这是因为在select后id是第一位,name是第二位。

    查询的结果如下图:

    使用这种查询,麻烦之处在于对查询结果的处理,必须按照顺序进行一一处理。

    9.3.4动态实例查询

    动态实例查询将检索出来的数据重新封装到一个实体的实例中,提高了检索效率。

        @Test
        public void test3(){
            Session session = HibernateUtils.getSession();
            String sql ="select new User (t.id,t.name) from User t";
            Query query = session.createQuery(sql);
            List<User> list = query.list();
            System.out.println(list);
            session.close();
        }

    由于这里创建了对象User,那么就需要给User对象添加部分参数构造函数(无参构造默认已存在),否则会报错:

    执行测试方法,数据查询成功,已经被封装到User对象中。需要注意的就是要给对应的类添加部分参数构造。也就是说,要查询哪几个参数,就需要用这几个参数创建一个有参构造。

    9.3.5条件查询

    Hibernate条件查询分为按参数位置查询和按参数名称查询。

    1)按参数位置查询

    按参数位置查询时,需要在 HQL 语句中使用“?”定义参数的位置,然后通过 Query 对象的 setXxx() 方法为其赋值,为参数赋值的常用方法如下表

    方法名说   明
    setString() 给映射类型为 String 的参数赋值
    setDate() 给映射类型为 Date 的参数赋值
    setTime() 给映射类型为 Date 的参数赋值
    setDouble() 给映射类型为 double 的参数赋值
    setBoolean() 给映射类型为 boolean 的参数赋值
    setInteger() 给映射类型为 int 的参数赋值
    setParameter() 给任意类型的参数赋值

    新建一个测试方法test4,使用like关键字进行模糊查询:

    @Test
    public void test4(){
    Session session = HibernateUtils.getSession();
    //使用?作为占位符,后面需要指定所在的位置索引
    String sql ="from User t where t.name like ?0 and t.password like ?1";
    Query query = session.createQuery(sql);
    //根据位置设置查询条件,位置表示的是?所在的索引位置,如第一个后面是0
    query.setString(0,"%zhang%").setString(1,"%12%");
    List<User> list = query.list();
    System.out.println(list);
    session.close();
    }

    在设置占位符时,使用?作为占位符,后面需要指定所在的位置索引,如第一个位置就是"?0"。设置参数时,要根据索引指定参数的位置。这种方式必须指定索引,只要一个索引写错了,就无法查询成功。

    2)按参数名称查询

    按参数名字查询时,需要在 HQL 语句中定义命名参数,命名参数是“:”和自定义参数名的组合。

    新建一个测试方法test5,使用like关键字进行模糊查询:

        @Test
        public void test5(){
            Session session = HibernateUtils.getSession();
            //使用:命名参数
            String sql ="from User t where t.name like :name1 and t.password like :pwd";
            Query query = session.createQuery(sql);
            //根据参数名称设置参数
            query.setParameter("name1","%zhang%").setParameter("pwd","%12%");
            List<User> list = query.list();
            System.out.println(list);
            session.close();
        }

    这种方式和上面按照参数位置查询的结果是一样的。

    3)Hibernate支持的常用运算符 

    类   型HQL 运算符
    比较运算 >,<,=,>=,<=,<>,!=
    逻辑运算 and,or,not
    模式匹配  like
    范围运算 in,not in,between,not between
    其他运算符 is null,is not null,is empty,is not empty等

    9.3.6分页查询

    Hibernate的分页查询使用Query接口,使用setFirstResult(int index)表示查询的起始索引值,使用setMaxResult(int size)表示每次查询的条数。

        @Test
        public void test6(){
            Session session = HibernateUtils.getSession();
            String sql ="from User";
            Query query = session.createQuery(sql);
            //设置查询的起始索引
            query.setFirstResult(2);
            //设置查询的条数
            query.setMaxResults(10);
            List<User> list = query.list();
            System.out.println(list);
            session.close();
        }

    上述代码查询的是从索引2开始的10条数据。那么在实际情况中,会传入当前页码和分页条数,需要对其进行转换。例如查询第m页的n条数据,那么分页条件设置如下:

    query.setFirstResult((m-1)*n);
    query.setMaxResults(n);

    9.4 QBC 检索

    QBC(Query By Criteria),它主要由 Criteria 接口、Criterion 接口和 Expression 类组成,并且支持在运行时动态生成查询语句。

    新建测试类QBCQuery,本小节的测试方法在里面创建。

    9.4.1组合查询

    组合查询是指通过 Restrictions 工具类的相应方法动态地构造查询条件,并将查询条件加入 Criteria 对象,从而实现查询功能。

    新建测试方法test1:

        @Test
        public void test1(){
            Session session = HibernateUtils.getSession();
            Criteria criteria = session.createCriteria(User.class);
            // 设定查询条件
            LogicalExpression expression = Restrictions.or(Restrictions.like("name", "%li%"), Restrictions.eq("id", 3));
            // 添加查询条件
            criteria.add(expression);
            // 执行查询,返回查询结果
            List<User> list = criteria.list();
            System.out.println(list);
            session.close();
        }

    上述代码查询的条件是:查询name包含"li"或id=3的用户信息。先设置查询的条件,把查询的条件放到Criteria 中去执行,获取查询的结果。条件的设置上述用到了or、eq和like,其常用的条件如下表:

    方法名说   明
    Restrictions.eq 等于
    Restrictions.like 对应 SQL 的 like 子句
    Restrictions.or or关系
    Restrictions.and and 关系
    Restrictions.allEq 使用 Map 和 key/value 进行多个等于的比较
    Restrictions.gt  大于 >
    Restrictions.ge 大于等于 >=
    Restrictions.lt 小于
    Restrictions.le 小于等于 <=
    Restrictions.between 对应 SQL 的 between 子句
    Restrictions.in 对应 SQL 的 IN 子句
    Restrictions.sqlRestriction SQL 限定查询

     9.4.2分页查询

    QBC也可以实现分页查询。在 Criteria 对象中,通过设置setFirstResult(int index)和setMaxResult(int size)进行分页查询。

        @Test
        public void test2(){
            Session session = HibernateUtils.getSession();
            Criteria criteria = session.createCriteria(User.class);
            // 设定分页条件
            criteria.setFirstResult(2);
            criteria.setMaxResults(10);
            // 执行查询,返回查询结果
            List<User> list = criteria.list();
            System.out.println(list);
            session.close();
        }

    设置的参数和HQL的参数说明是一样的,就不多说。

    9.5本地 SQL 检索

    本地 SQL 检索方式就是使用本地数据库的 SQL 查询语句进行查询。

    新建测试类LocalQuery,本小节的测试方法在里面创建。

    9.5.1不指定类型查询(查询结果类型是Object)

        @Test
        public void test1() {
            Session session = HibernateUtils.getSession();
            SQLQuery sqlQuery = session.createSQLQuery("select * from t_user");
            List queryList = sqlQuery.list();
            System.out.println(queryList);
            session.close();
        }

    通过这种查询得到的只是对象的地址,无法得到具体的数据。不推荐使用!

    9.5.2将查询结果封装为对象

    在查询时添加要封装的对象即可

        @Test
        public void test2() {
            Session session = HibernateUtils.getSession();
            SQLQuery sqlQuery = session.createSQLQuery("select * from t_user");
            sqlQuery.addEntity(User.class);
            List<Object> queryList = sqlQuery.list();
            System.out.println(queryList);
            session.close();
        }

    这样查询出来的数据就是对象类型,可以看到具体的内容,方便使用。

    9.5.3动态参数查询

    本身sql检索也可以使用动态参数的格式进行查询,用法和HQL的动态参数查询类似,这里只举例说明

        @Test
        public void test3() {
            Session session = HibernateUtils.getSession();
            SQLQuery sqlQuery = session.createSQLQuery("select * from t_user where name=:name");
            //设置封装对象
            sqlQuery.addEntity(User.class);
            //指定参数
            sqlQuery.setString("name","zhangsan111");
            List<Object> queryList = sqlQuery.list();
            System.out.println(queryList);
            session.close();
        }

    10.Hibernate事务操作

    10.1事务配置

    Hibernate事务配置有两种方式,推荐使用第二种方式。

    1)使用代码操作管理事务

    开启事务:Transaction tx = session.beginTransaction();

    提交事务:tx.commit();

    回滚事务:tx.rollback();

    2)在核心配置文件中配置事务

    <!--使用本地事务-->
    <property name= "hibernate.current_session_context_class"> thread</property>
    <!--设置事务隔离级别-->
    <property name= "hibernate.connection.isolation">2</property>

      

    就是这么简单,你学废了吗?感觉有用的话,给笔者点个赞吧 !
  • 相关阅读:
    【2018.05.05 C与C++基础】C++中的自动废料收集:概念与问题引入
    【2018.04.27 C与C++基础】关于switch-case及if-else的效率问题
    【2018.04.19 ROS机器人操作系统】机器人控制:运动规划、路径规划及轨迹规划简介之一
    March 11th, 2018 Week 11th Sunday
    March 10th, 2018 Week 10th Saturday
    March 09th, 2018 Week 10th Friday
    March 08th, 2018 Week 10th Thursday
    March 07th, 2018 Week 10th Wednesday
    ubantu之Git使用
    AMS分析 -- 启动过程
  • 原文地址:https://www.cnblogs.com/zys2019/p/14629935.html
Copyright © 2011-2022 走看看