Spring 4 好好复习之后, 在最后的Spring MVC之前开启了两条支线, 一条PgSQL已经初步看过了日常操作的部分, 现在就要开启第二条支线, 也就是Hibernate了.
在之前初学Hibernate的时候, 其实已经知道了Hibernate的那些注解和简单的开发,不过对于什么缓存之类还不是很清楚,这次来仔细看一下吧.
Hibernate相关资源
中文圈子里发现专门讲Hibernate5的书和资源很少, 不过想想也是, 反正都是要硬啃中文的, 目前找到的资源如下:
- Hibernate5 持久化教程, 这个是Hibernate教程的Git库.
- Hibernate Framework Tutorials, 这个是我自己搜索到的, 带数据库样例, 还算不错.
- Java Persistence with Hibernate, 2nd Edition, 2016年的教程, 有中文版但是看评论翻译的实在不怎么样, 那么就只能硬上了.
- Hibernate官网, 到这看文档准没错.
- 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();
拆开来说, 其实分为三步:
- 使用StandardServiceRegistryBuilder创建一个配置对象ServiceRegistry
- 使用MetadataSources, 构造参数为ServiceRegistry对象, 然后创建一个ORM元数据类型Metadata对象
- 使用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.