1、环境
开发环境:
Eclipse Java EE IDE for Web Developers.
Version: 2018-09 (4.9.0)
Build id: 20180917-1800
Java运行时环境:Java SE 18.3,构建号10.0.2
mysql server版本号:8.0.15
本例不使用Eclipse的Hibernate插件(不好用)。
2、创建测试用数据库
CREATE DATABASE 'bookstore';
3、创建项目
在Eclipse中依次打开菜单File=>New=>Maven Project
注意勾选红框内的选项,这里不需要使用Maven提供的项目原型。
创建项目。
修改项目pom文件如下:
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhangdb.hibernate</groupId>
<artifactId>HibernateHelloExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.1.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>10</source>
<target>10</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
说明如下:
hibernate-core:hibernate构架核心jar包,5.4.1.Final版本发布于2019年1月份,现在是最新版本。
mysql-connector-java:mysql JDBC驱动。版本号为8.0.15,与我的mysql服务器版本一致。注意要避免出现服务器版本与驱动版本不一致的情况,否则会出现各种问题。
jaxb-api、jaxb-core、jaxb-impl、activation四个包,Hibernate用来解析xml配置文件。在旧版Java SE环境中,这四个包是默认包含的,在最新版本中,好像是从Java SE 9开始,需要手动引入。
build部分:与自己的Java环境一致就行。
修改完pom文件后,执行Maven的Update Project命令,让Maven载入相关的软件包。
至此环境装备完毕。
4、配置Hibernate
在src/main/resources目录下添加名为hibernate.cfg.xml文件,修改其内容为:
<?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>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://192.168.56.101:3306/bookstore?useSSL=false</property>
<property name="connection.username">billy</property>
<property name="connection.password">123456</property>
<property name="show_sql">true</property>
</session-factory>
</hibernate-configuration>
注意connection.driver_class的值,因为我使用的mysql版本是8.0.15,它的驱动是com.mysql.cj.jdbc.Driver,有些旧版本为com.mysql.jdbc.Driver。另外connection.url中useSSL必需设置成false,否则默认使用SSL连接。看其它人的网文,好像有什么时区的问题,需要在connection.url中特别指定,我没有遇到这个问题。
此时配置文件中只包含与mysql连接相关信息,不包括具体的表信息以及表与实体之间的映射信息,当然以上是最小配置,全部可用的配置需要查看Hibernate的官方文档,特别一点的属性show_sql设置成true,作用是将执行的SQL语句打印到控制台,方便看Hibernate到底做了什么事情。
从session-factory的内容看,类似与JDBC的connection配置。Hibernate从这里读取配置文件并创建与mysql服务器的连接池,在这里写一个类,专门用来完成此工作。
在项目中创建package:
选中Package com.zhangdb.hibernate点右键创建类 SessionFactoryManager,修改后如下:
package com.zhangdb.hibernate;
import java.util.List;
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;
public class SessionFactoryManager {
private SessionFactory sessionFactory = null;
public void initSessionFactory() {
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
try {
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
} catch (Exception ex) {
System.out.println(ex);
StandardServiceRegistryBuilder.destroy(registry);
}
}
public void exitSessionFactory() {
if ((sessionFactory != null) && (!sessionFactory.isClosed())) {
sessionFactory.close();
}
sessionFactory = null;
}
public Session openSession() {
if ((sessionFactory != null) && (!sessionFactory.isClosed())) {
return sessionFactory.openSession();
} else {
// throw exception
return null;
}
}
public static void main(String[] args) {
SessionFactoryManager sessionFactoryManager = new SessionFactoryManager();
// 初始化session factory
sessionFactoryManager.initSessionFactory();
// 打开session
Session session = sessionFactoryManager.openSession();
// 执行show tables命令
@SuppressWarnings("unchecked")
List<String> tables = session.createSQLQuery("show tables").list();
for (String item : tables) {
System.out.println(item);
}
// 关闭session
session.close();
// 关闭session factory
sessionFactoryManager.exitSessionFactory();
}
}
方法initSessionFactory用来初始化Hibernate数据源,从class path查找配置文件然后初始化,与以前的JDBC初始化差不多。 Hibernate的版本一直在演进,现在大版本号来到了5。不同版本的Hibernate它的session factory初始化方法可能不一样,另外还有其它第三方也提供的初始化的方法,所以这里的初始化代码可能与一些网文、教材上不一样,无需奇怪。有一点要引起重视,你用的是那个版本的Hibernate,你就去官网上查看相应版本的初始化方法,不要用版本不匹配的初始化方法,也不要用第三方提供的初始方法,否则会出问题。关于session factory的初始化,Hibernate官网专门有一个topic,参考这里:http://docs.jboss.org/hibernate/orm/5.4/topical/html_single/bootstrap/NativeBootstrapping.html。另外http://docs.jboss.org/hibernate/orm/5.4/quickstart/html_single/里一个最简单的实现,我这段代码就是从这个地方抄的。
刚才说了,这个配置文件是最小化配置,可以看到,里边并没有连接池的相关配置,比如最小连接、最大连接、连接不够用了怎么办等。这种情况下,Hibernate会使用内置的连接池管理器,但是要注意,这个内置连接池管理器非常简单,而且还有bugger,它存在的目的是开发环境下的测试。关于Hibernate连接池的管理问题也是一个单独的topic,我打算另外写一篇文章来研究这个问题。
方法exitSessionFactory的作用是关闭数据源连接。
方法openSession的作用是打开一个session,session代表与数据源的一个交互过程,既然它是session,就是一个会话,那么它就是有状态的,有开始也要有结束,所以用完了session要及时关掉。
然后就是main方法,这个主要用来测试,注释中有解释。注意其中的List<String> tables = session.createSQLQuery("show tables").list();这句话。createSQLQuery方法的作用是创建标准的SQL查询语句,session还有一个方法叫作createQuery,这个方法的作用是创建Hibernate特有的查询语句,就是所谓的HQL,它的语法与标准SQL不一样,当然Hibernate最后根据所使用的JDBC驱动类型以及配置文件中指定的方言(此例中没有指定,使用默认)、以及实体类中的注解,翻译成标准的SQL,最后执行。
总之,目前看来,Hibernate即能执行标准SQL也能执行HQL,使用那种用session中的方法作区别。
另一个与JDBC不同的地方就是List<String> tables = session.createSQLQuery("show tables").list();的返回值类型。在JDBC中,返回类型是ResultSet,需要我们编写代码解析,而在Hibernate中,返回的是List类型。
在控制台中确认一下结果,此时数据库中没有创建表,所以没有返回表名称,但是证明通过Hibernate已经能连接到数据库并能执行SQL语句。
5、创建表以及相应实体类
首先在数据库bookstore中创建book表:
USE 'bookstore';
CREATE TABLE `book` (
`book_id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(128) NOT NULL,
`author` varchar(45) NOT NULL,
`price` float NOT NULL,
PRIMARY KEY (`book_id`),
UNIQUE KEY `book_id_UNIQUE` (`book_id`),
UNIQUE KEY `title_UNIQUE` (`title`)
) ENGINE=InnoDB;
选中Package com.zhangdb.hibernate点右键创建类Book,修改后如下:
package com.zhangdb.hibernate;
public class Book {
private long id;
private String title;
private String author;
private float price;
public Book() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return "Book [id=" + id + ", title=" + title + ", author=" + author + ", price=" + price + "]";
}
}
Hibernate通过注解(也可以通过配置文件)将实体类与数据库中的表映射起来。这里用到的注解本身是Java的一种规范,java有自己的实现,Hibernate也有它自己的实现,而且Hibernate的注解不仅实现了java规范,还有额外的功能。在一些比较老的教材中,会告诉你为了使用Hibernate实现的注解,需要将hibernate-annotations、hibernate-entitymanager等包引入项目。在新版本的Hibernate中,这些都不用了。首先hibernate-annotations这个包从2010年起就不更新了,可能是官方觉得这东西没什么用处,直接使用java自带的标准注解就可以。另外hibernate-entitymanager这个包也不要了,它被合并到hibernate-core包中了。
接下来引入包含注解的包import
javax.persistence。
然后再添加注解,修改后内容如下:
import javax.persistence.*;
@Entity
@Table(name = "book")
public class Book {
private long id;
private String title;
private String author;
private float price;
public Book() {
}
@Id
@Column(name = "book_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Column(name = "title")
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Column(name = "author")
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Column(name = "price")
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
关于注解可查看官方文档,总结一下就是这个实例类通过注解的形式,说明它与表book映射,并且成员的getter方法的注解说明了类成员与表中列的对应关系。Hibernate正是根据这里定义的映射关系,以及我们提供的简单的SQL\HQL语句,来翻译成复杂的SQL语句,并用查询到的结果填充我们自己定义的实体类。当然,这里只是简单的示例,实际应用中实体类与表的映射关系很复杂,这个以后再研究。
除了映射关系,这个实体类与JDBC中的实体类就没有其它区别了,它只是一个数据的载体,不涉及到具体操作。
接下来修改hibernate.cfg.xml配置文件,让它知道Book是类是用来进行映射的,只需要加入一句话:<
mapping
class
="com.zhangdb.hibernate.Book"
/>,修改以后内容如下:
<?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>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://192.168.56.101:3306/bookstore?useSSL=false</property>
<property name="connection.username">billy</property>
<property name="connection.password">******</property>
<property name="show_sql">true</property>
<mapping class="com.zhangdb.hibernate.Book" />
</session-factory>
</hibernate-configuration>
6、为Book类创建Dao层
Book类的Dao层可以定得很复杂,这里的话只是创建简单实现CRUD的Dao层,主要看下Hibernate与JDBC在实现上有什么不同。
选中Package com.zhangdb.hibernate点右键创建类BookCRUDDao类,修改后如下:
package com.zhangdb.hibernate;
import org.hibernate.Session;
public class BookCRUDDao {
private SessionFactoryManager sessionFactoryManager = null;
public SessionFactoryManager getSessionFactoryManager() {
return sessionFactoryManager;
}
public void setSessionFactoryManager(SessionFactoryManager sessionFactoryManager) {
this.sessionFactoryManager = sessionFactoryManager;
}
public void clearSessionFactoryManager() {
sessionFactoryManager = null;
}
public void create(Book book) {
Session session = sessionFactoryManager.openSession();
session.beginTransaction();
// 持久化临时对象
session.save(book);
session.getTransaction().commit();
session.close();
}
public Book read(long bookId) {
Session session = sessionFactoryManager.openSession();
Book book = session.get(Book.class, bookId);
session.close();
return book;
}
public void update(Book book) {
Session session = sessionFactoryManager.openSession();
session.beginTransaction();
session.update(book);
session.getTransaction().commit();
session.close();
}
protected void delete(long bookId) {
Book book = new Book();
book.setId(bookId);
Session session = sessionFactoryManager.openSession();
session.beginTransaction();
session.delete(book);
session.getTransaction().commit();
session.close();
}
public static class TestBookCRUDDaoCreate {
public static void main(String[] args) {
SessionFactoryManager sessionFactoryManager = new SessionFactoryManager();
// 初始化session factory
sessionFactoryManager.initSessionFactory();
// 创建Book的Dao层
BookCRUDDao bookCRUDDao = new BookCRUDDao();
// 设置sessionFactoryManager
bookCRUDDao.setSessionFactoryManager(sessionFactoryManager);
Book book = new Book();
for (int i = 0; i < 10; i++) {
book.setTitle("Effective Java" + i);
book.setAuthor("Joshua Bloch");
book.setPrice(32.59f);
bookCRUDDao.create(book);
}
// 解除对sessionFactoryManager的引用
bookCRUDDao.clearSessionFactoryManager();
// 关闭session factory
sessionFactoryManager.exitSessionFactory();
}
}
public static class TestBookCRUDDaoRead {
public static void main(String[] args) {
SessionFactoryManager sessionFactoryManager = new SessionFactoryManager();
// 初始化session factory
sessionFactoryManager.initSessionFactory();
// 创建Book的Dao层
BookCRUDDao bookCRUDDao = new BookCRUDDao();
// 设置sessionFactoryManager
bookCRUDDao.setSessionFactoryManager(sessionFactoryManager);
for (int i = 0; i < 10; i++) {
System.out.println(bookCRUDDao.read(i + 1));
}
// 解除对sessionFactoryManager的引用
bookCRUDDao.clearSessionFactoryManager();
// 关闭session factory
sessionFactoryManager.exitSessionFactory();
}
}
public static class TestBookCRUDUpdate {
public static void main(String[] args) {
SessionFactoryManager sessionFactoryManager = new SessionFactoryManager();
// 初始化session factory
sessionFactoryManager.initSessionFactory();
// 创建Book的Dao层
BookCRUDDao bookCRUDDao = new BookCRUDDao();
// 设置sessionFactoryManager
bookCRUDDao.setSessionFactoryManager(sessionFactoryManager);
Book book = new Book();
for (int i = 0; i < 10; i++) {
book.setId(i + 1);
book.setTitle("Effective C++" + i);
book.setAuthor("Joshua Bloch");
book.setPrice(22.59f);
bookCRUDDao.update(book);
;
}
// 解除对sessionFactoryManager的引用
bookCRUDDao.clearSessionFactoryManager();
// 关闭session factory
sessionFactoryManager.exitSessionFactory();
}
public static class TestBookCRUDDelete {
public static void main(String[] args) {
SessionFactoryManager sessionFactoryManager = new SessionFactoryManager();
// 初始化session factory
sessionFactoryManager.initSessionFactory();
// 创建Book的Dao层
BookCRUDDao bookCRUDDao = new BookCRUDDao();
// 设置sessionFactoryManager
bookCRUDDao.setSessionFactoryManager(sessionFactoryManager);
for (int i = 0; i < 10; i++) {
bookCRUDDao.delete(i + 1);
}
// 解除对sessionFactoryManager的引用
bookCRUDDao.clearSessionFactoryManager();
// 关闭session factory
sessionFactoryManager.exitSessionFactory();
}
}
}
}
在上边的类中,提供了create、read、update、delete四个方法,分别对应Book类的CRUD操作,并提供了四个静态内部类,充当测试床,可分别运行它们,然后通过命令行查看一下数据库,确认结果是否是正确的。
在控制台中,可以看到Hibernate到底做了什么事情,本质上Hibernate的低层还是JDBC。
通过以上的测试可以确定一些事情,实体类及其注解非常重要,Hibernate根据其中确定的映射关系(实体类与表、实体类成员与表中的列等),能够自动生成SQL语句,最重要的是它能自动填充实体类,而非像JDBC中需要我们自己解析返回的结果,大大简化了代码量。
另外Hibernate相当于充当了虚拟数据库的功能,隐藏低层数据库的实现细节。如果把Mysql数据库换成其它产品,则只需要修改一下配置就可实现,不用改代码。
以上示例很简单,没有涉及复杂查询,只需要调用正确的方法,不用写任何一句SQL,返回的结果也由Hibernate自动解析,非常方便。
对于复杂查询,如多表查询、级联查询等,仍然需要写查询语句,不过是HQL而非SQL,同时要定义复杂的实体类并通过注解指明各种复杂的映射关系。