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.