Hibernate 00 冬眠不觉晓

Hibernate 00 冬眠不觉晓

Spring 4 好好复习之后, 在最后的Spring MVC之前开启了两条支线, 一条PgSQL已经初步看过了日常操作的部分, 现在就要开启第二条支线, 也就是Hibernate了. 在之前初学Hibernate的时候, 其实已经知道了Hibernate的那些注解和简单的开发,不过对于什么缓存之类

Spring 4 好好复习之后, 在最后的Spring MVC之前开启了两条支线, 一条PgSQL已经初步看过了日常操作的部分, 现在就要开启第二条支线, 也就是Hibernate了.

在之前初学Hibernate的时候, 其实已经知道了Hibernate的那些注解和简单的开发,不过对于什么缓存之类还不是很清楚,这次来仔细看一下吧.

  1. Hibernate相关资源
  2. 使用JPA API启动一个简单项目
  3. 使用Hibernate原生API

Hibernate相关资源

中文圈子里发现专门讲Hibernate5的书和资源很少, 不过想想也是, 反正都是要硬啃中文的, 目前找到的资源如下:

  1. Hibernate5 持久化教程, 这个是Hibernate教程的Git库.
  2. Hibernate Framework Tutorials, 这个是我自己搜索到的, 带数据库样例, 还算不错.
  3. Java Persistence with Hibernate, 2nd Edition, 2016年的教程, 有中文版但是看评论翻译的实在不怎么样, 那么就只能硬上了.
  4. Hibernate官网, 到这看文档准没错.
  5. Hibernate官网的用户手册, 列出来方便快速的看.

现在先用英文的Java Persistence with Hibernate, 2nd Edition看一遍. 这本书讲的是使用Hibernate的JPA, 所以会首先讲JPA的操作, 然后是原生Hibernate的操作.

使用JPA API启动一个简单项目

真是开卷有益, 上来讲一个JPA的实现, 由于没有单独折腾过JPA, 书上讲的又比较简略, 自己折腾了一番还真的有不少新收获.

首先JPA是一套规范, 在标准的JDK里并不包含JPA的实现, 只要安装了Hibernate, 就附带了一套JPA标准的接口, 在持久化的时候既可以选择JPA的API, 也可以选择Hibernate的原生API.

现在都采用maven项目了, 所以在IDEA里启动一个Maven achetype 是 quickstart的项目, 直接配置Maven就可以了, 除了Hibernate之外, 还需要JDBC驱动, 博客写的多就是好, 这里就可以找到Java连接PgSQL的方法.

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.9</version>
</dependency>

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>5.4.10.Final</version>
</dependency>

配置好之后, 先来看JPA的一个简单持久化程序.

JPA的配置需要一个 persistence.xml 文件, 对于maven项目, 路径是 src/main/resources/META-INF/persistence.xml. 对于打包后的文件, 就是classpath:/META-INF/persistence.xml.

这个文件的内容书上提供的是一个JTA数据源, 我查了一下, 是可以自行配置的. 内容如下:

<persistence
        version="2.1"
        xmlns="http://xmlns.jcp.org/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
            http://xmlns.jcp.org/xml/ns/persistence_2_1.xsd">

<!--    一个持久化单元, 相当于一个数据源, name属性需要在创建EntityManagerFactory的时候指定-->
    <persistence-unit name="HelloWorldPU">
<!--        持久化技术提供商, 这里不指定可以, 因为包含在Hibernate套件中, 如果有多个提供商, 就需要指定类-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!--        很重要, 是所有实体化类, 有几个就要用几个class属性指定-->
        <class>cc.conyli.model.helloworld.Message</class>

        <!--        表示除了指定的Entity类之外, 不自动扫描其他类-->
        <exclude-unlisted-classes>true</exclude-unlisted-classes>

        <!--        持久化单元的各个属性配置-->
        <properties>

<!--            以javax.persistence开头的是JPA的配置属性, 以hibernate开头的是hibernate的属性-->

            <!--            这个设置表示JPA启动的时候去DROP对应表格, 这样每次都是全新数据-->
            <property
                    name="javax.persistence.schema-generation.database.action"
                    value="drop-and-create"/>

            <!--            配置数据源-->
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://106.54.215.164:5432/mydb"/>
            <property name="javax.persistence.jdbc.user" value="postgres"/>
            <property name="javax.persistence.jdbc.password" value="fflym0709"/>

            <!--            hibernate的配置, 开启显示SQL-->
            <property name="hibernate.show_sql" value="true"/>
            <!--            将单行的SQL改成多行显示, 提高可读性-->
            <property name="hibernate.format_sql" value="true"/>
            <!--            在SQL之前显示 /* ... */ 的注释, 解释改行语句或者显示原来的查询字符串-->
            <property name="hibernate.use_sql_comments" value="true"/>

        </properties>
    </persistence-unit>
</persistence>

然后就是Java代码了, 其实想想也知道, 先创建持久化类, 然后也会创建一个XXX工厂, 从工厂中获得一个会话对象, 开启事务然后进行操作, 最后提交事务.

持久类如下:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Message {

    @Id
    @GeneratedValue
    private Long id;

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", text='" + text + '\'' +
                '}';
    }
}

这个没什么好说的, 不写@Column的域会对应到同名的id和text列(这里还没说带下划线如何匹配), 唯一就是要观察@GeneratedValue这个注解在PostgreSQL中的作用.

之后就可以来进行操作:

import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class HelloWorldJPA {

    @Test
    public void testStart() {

        //创建会话工厂, 线程安全, 可以被共享
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("HelloWorldPU");

        //获取当前线程绑定的会话对象
        EntityManager em = emf.createEntityManager();

        //获取事务对象并启动事务
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        //创建新对象
        Message message = new Message();
        message.setText("Hello World!");

        //持久化
        em.persist(message);

        //提交事务
        tx.commit();

        //关闭会话
        em.close();
    }
}

这个套路很熟悉, 启动之后, 日志中Hibernate开始启动, 毕竟是Hibernate提供的JPA实现, 之后注意观察输出:

Hibernate:
    drop table if exists Message cascade

Hibernate:
    drop sequence if exists hibernate_sequence

Hibernate: create sequence hibernate_sequence start 1 increment 1

Hibernate:

    create table Message (
       id int8 not null,
        text varchar(255),
        primary key (id)
    )

Hibernate:
    select
        nextval ('hibernate_sequence')

Hibernate:
    /* insert cc.conyli.model.helloworld.Message
        */ insert
        into
            Message
            (text, id)
        values
            (?, ?)

可以看到Hibernate首先会检查表和序列存在与否, 然后重新创建表和序列, 之后先从序列中取值, 然后插入数据. 用PgADmin可以查看新的序列和表.

然后是查和修改:

@Test
public void test2() {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("HelloWorldPU");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    tx.begin();

    Message message = new Message();
    message.setText("Hello World!");
    em.persist(message);

    tx.commit();

    tx.begin();

    List<Message> messages = em.createQuery("select m from Message m").getResultList();

    assertEquals(messages.size(), 1);
    assertEquals(messages.get(0).getText(), "Hello World!");

    messages.get(0).setText("A new World!");

    tx.commit();

    messages.get(0).setText("After commit!");
}

可以看到最后的结果是"A new World!", 在关闭事务之后, 就这个对象就和会话断开了关联. 这是一大特点, 即默认情况下, 选出的结果还与会话关联, 修改都会反映到事务提交之后的数据库内部.

橙色的部分是Java Persistence Query Language, 即JPQL, Java持久化查询语句. JPQL中都使用的是类名和属性名, 因为在映射关系中已经将类和属性映射到了表和列上.

这样的一大好处就是, 如果类和属性映射到别的表和列上, 上边所有的代码都完全不用更改, 一样可以发挥作用.

使用Hibernate原生API

与EntityManagerFactory相对应的是org.hibernate.SessionFactory, 同样也是线程安全, 每个应用共享一个就可以了.

使用Hibernate的好处是可以更详尽的进行设置, 仅仅通过persistence.xml中的设置是无法完全设置Hibernate的一些配置的.

来看看创建SessionFactory的方法, 首先需要创建src/main/resources/hibernate.cfg.xml, 也就是classpath之下, 内容如下:

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

<hibernate-configuration>

<!--    会话工厂设置-->
    <session-factory>

    <!--    配置Session上下文环境, 必须要写-->
        <property name="hibernate.current_session_context_class">org.hibernate.context.internal.ThreadLocalSessionContext</property>

    <!--        配置数据库连接-->
        <property name="connection.driver_class">org.postgresql.Driver</property>
        <property name="connection.url">jdbc:postgresql://106.54.215.164:5432/mydb</property>
        <property name="connection.username">postgres</property>
        <property name="connection.password">fflym0709</property>
<!--    配置方言, 所谓方言就是类型转换的规则-->
        <property name="dialect">org.hibernate.dialect.PostgreSQL10Dialect</property>
<!--    三个和对应JPA中一样的配置-->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="use_sql_comments">true</property>
<!--    类似于JPA的启动时先DROP再CREATE的操作-->
        <property name="hibernate.hbm2ddl.auto">create-drop</property>
    <!--    Entity类, 有多个就要配置多个-->
        <mapping class="cc.conyli.model.helloworld.Message"/>
    </session-factory>

</hibernate-configuration>

这个配置文件在效果上和JPA的配置文件完全相同, 将其中的配置都写了进来. 在使用IDEA编辑的时候, 可以发现有很多配置可供选择, 比JPA标准要多很多, 这是使用原生Hibernate的一大好处.

其中的hibernate.current_session_context_class比较特殊, 如果不使用JTA, 一般配置成thread, 会用ThreadLocal来隔离线程取得的会话对象.

之后就可以来创建SessionFactory了, 快速根据hibernate.cfg.xml创建的方法如下:

SessionFactory sessionFactory = new MetadataSources(
        new StandardServiceRegistryBuilder()
                .configure("hibernate.cfg.xml").build()
).buildMetadata().buildSessionFactory();

拆开来说, 其实分为三步:

  1. 使用StandardServiceRegistryBuilder创建一个配置对象ServiceRegistry
  2. 使用MetadataSources, 构造参数为ServiceRegistry对象, 然后创建一个ORM元数据类型Metadata对象
  3. 使用Metadata对象创建SessionFactory

第一步, StandardServiceRegistryBuilder可以根据一个配置文件, 也可以加载一个Properties文件, 还可以手工进行配置, 最后调用.build()方法创建一个ServiceRegistry对象:

StandardServiceRegistryBuilder serviceRegistryBuilder =
        new StandardServiceRegistryBuilder();

serviceRegistryBuilder
        .applySetting("hibernate.connection.datasource", "myDS")
        .applySetting("hibernate.format_sql", "true")
        .applySetting("hibernate.use_sql_comments", "true")
        .applySetting("hibernate.hbm2ddl.auto", "create-drop");

ServiceRegistry serviceRegistry = serviceRegistryBuilder.build();

第二步, 使用MetadataSources对象, 加载配置对象, 然后可以添加Entity类:

MetadataSources metadataSources = new MetadataSources(serviceRegistry);
metadataSources.addAnnotatedClass(cc.conyli.model.helloworld.Message.class);

最后一步, 创建SessionFactory对象:

MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder();
Metadata metadata = metadataBuilder.build();
assertEquals(metadata.getEntityBindings().size(), 1);
SessionFactory sessionFactory = metadata.buildSessionFactory();

我自己尝试着完全用这个方法, 没有启动成功Hibernate, 还是用xml文件方便一些.

查询, 修改的代码如下:

@Test
public void test() {
    SessionFactory sessionFactory = new MetadataSources(
            new StandardServiceRegistryBuilder()
                    .configure("hibernate.cfg.xml").build()
    ).buildMetadata().buildSessionFactory();

    //获取会话对象, 等于EntityManager
    Session session = sessionFactory.getCurrentSession();

    //启动事务
    Transaction tx = session.getTransaction();
    tx.begin();

    //写入
    Message message = new Message();
    message.setText("Hello World!");
    session.persist(message);

    //查询
    List<Message> messages = session.createQuery("SELECT m FROM Message m", Message.class).list();

    assertEquals(messages.size(), 1);
    assertEquals(messages.get(0).getText(), "Hello World!");

    messages.get(0).setText("Hibernate good!");

    tx.commit();

    System.out.println(messages);
}

可以看到这里的查询语句比起JPA的还可以传类型进去, 就不用强制转换. 与JPA一样, 在事务没有提交前, 不显式分离的话, messages及其中对象与会话依然关联, 修改内容后提交事务, 修改的内容也会写入到数据库中.

这样就看书外加自己摸索出了名副其实的JPA with Hibernate.

LICENSED UNDER CC BY-NC-SA 4.0
Comment