Hibernate从入门到精通
1. Hibernate框架简述
Hibernate框架简化了java应用程序与数据库交互的开发。Hibernate是一个开源,轻量级的ORM(对象关系映射) 工具。了解更多Hibernate,访问Hibernate官网。
ORM 工具简化了数据创建,数据处理和数据访问。它是将对象映射到数据库中存储的数据(表)的编程技术。
Hibernate框架的优点:
- 开源和轻量级: Hibernate框架是根据LGPL许可证和轻量级的开源工具。
- 快速性能: Hibernate框架的性能很快,因为缓存在Hibernate框架内部使用。hibernate框架中有两种类型的缓存:一级缓存和二级缓存。一级缓存默认是启用的。
- 数据库独立查询: HQL(Hibernate查询语言)是面向对象的SQL版本。 它生成数据库独立查询。 所以你不需要编写数据库特定的查询语句。 在Hibernate之前,如果项目更改了数据库,我们需要更改SQL查询,从而导致维护变得非常复杂。
- 自动创建表: Hibernate框架提供了自动创建数据库表的功能。 因此,无需手动在数据库中创建表。
- 简化复杂连接: 在hibernate框架中可轻松获取多个表中的数据。
- 提供查询统计和数据库状态: Hibernate支持查询缓存,并提供有关查询和数据库状态的统计信息。
2. Hibernate体系结构
Hibernate架构包括许多对象持久对象,会话工厂,事务工厂,连接工厂,会话,事务等。
Hibernate框架使用许多对象会话工厂,会话,事务等以及现有的Java API,如JDBC(Java数据库连接),JTA(Java事务API)和JNDI(Java命名目录接口)。
Hibernate体系结构的要素如下:
会话工厂(SessionFactory)
SessionFactory是ConnectionProvider的会话和客户端工厂。它拥有数据的二级缓存(可选)。org.hibernate.SessionFactory接口提供了工厂方法来获取Session的对象。
会话(Session)
Session对象提供应用程序和存储在数据库中的数据之间的接口。它是一个短生命周期的对象并包装JDBC连接。它是事务,查询和标准的工厂。 它拥有一级缓存(强制性)数据。org.hibernate.Session接口提供插入,更新和删除对象的方法。它还提供了事务,查询和标准的工厂方法。
事务(Transaction)
事务对象指定工作的原子单位,它是一个可选项。org.hibernate.Transaction接口提供事务管理的方法。
连接提供者(ConnectionProvider)
它是一个JDBC连接工厂。它从DriverManager或DataSource抽象出来的应用程序。它是一个可选项。
事务工厂(TransactionFactory)
它是一个事务工厂,是一个可选项。
3. Hibernate简单示例
3.1 创建一个Maven项目,目录结构如下:
3.2 在pom.xml里添加要依赖的jar配置
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hibernate.version>5.2.1.Final</hibernate.version>
</properties>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
3.3 创建表结构(如果这里不创建,hibernate可以自动生成表结构)
3.4 创建实体类HUser
public class HUser implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.5 创建实体类的映射文件Huser.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="angelia.hibernate.model.HUser" table="huser">
<id name="id" type="java.lang.Integer">
<column name="hid" />
<generator class="increment" />
</id>
<property name="name" type="string">
<column name="hname" length="20" not-null="true" unique="true" />
</property>
</class>
</hibernate-mapping>
3.6 创建配置文件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>
<!-- <property name="connection.driver_class">com.mysql.jdbc.Driver</property> -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<!-- &需要转义,即& -->
<property name="connection.url">jdbc:mysql://localhost:3306/hibernatetest?useSSL=false&serverTimezone=UTC</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<!-- dialect:配置数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- show_sql:操作数据库时,会向控制台打印sql语句 -->
<property name="show_sql">true</property>
<!-- format_sql:打印sql语句前,会将sql先格式化 -->
<property name="format_sql">true</property>
<!-- hbm2ddl.auto:是否自动生成表结构 -->
<property name="hbm2ddl.auto">update</property>
<mapping resource="Huser.hbm.xml"></mapping>
</session-factory>
</hibernate-configuration>
3.7 创建管理Session的辅助类
public class HibernateUtil {
private static SessionFactory sessionFactory;
static {
sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* Session是由SessionFactory负责创建的,而SessionFactory的实现是线程安全的,多个并发的线程可以同时访问一 个SessionFactory并从中获取Session实例,
* 而Session不是线程安全的。Session中包含了数 据库操作相关的状态信息,如果多个线程同时使用一个Session实例进行CRUD,就可能导致数据存取的混乱。
* java.lang.ThreadLocal,在编写多线程程序时提供了一种解决方案。
* 线程局部变量(ThreadLocal)就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
* 从线程的角度看,就好像每 一个线程都完全拥有一个该变量。 ThreadLocal这个类本身不是代表线程要访问的变量,这个类的成员变量才是。
* JDK1.5给ThreadLocal加了泛型功能,即是 ThreadLocal,这个泛型T即是要线程的本地变量。线程通过ThreadLocal的get和set方法去访问这个变量T。
* ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
*/
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
public static Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get();
if (session == null) {
session = sessionFactory.openSession();
threadLocal.set(session);
}
return session;
}
public static void closeSession() throws HibernateException {
Session session = (Session) threadLocal.get();
if (session != null)
session.close();
threadLocal.set(null);
}
public static void shutdown() {
getSessionFactory().close();
}
}
3.8 创建测试类
public class HibernateTest {
private Session session;
private Transaction tx;
@Before
public void before() {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
}
@Test
public void test() {
try {
HUser hUser = new HUser();
hUser.setName("hibernate demo");
session.save(hUser);// 这里操作的是java对象
tx.commit();
System.out.println("保存成功!");
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
System.out.println("保存失败!");
} finally {
HibernateUtil.closeSession();
}
}
@After
public void after() {
HibernateUtil.shutdown();
}
}
4. Hibernate注解
由上一个章节可以看出每一个实体类,都需要有一个以*.hbm.xml(*即类名)命名的映射文件,如果实体类很多,那么会有很多映射文件,这样很不友好。而Hibernate注解可以解决这个问题。如@Entity,@Id,@Table,@Column等。
下面四个常用注解及解释:
@Entity注释将此类标记为实体。
@Table注释指定要保留此实体的数据的表名。 如果不使用@Table注释,hibernate将使用类名作为表名称bydefault。
@Id注释标记此实体的标识符。
@Column注释指定此属性或字段的列的详细信息。如果未指定@Column注释,则属性名称将用作列名称bydefault。
Hibernate注解基于JPA 2规范,并支持所有功能。所有JPA注释都在javax.persistence.*包中定义。Hibernate EntityManager实现由JPA规范定义的接口和生命周期。
4.1 修改实体类HUser
@Entity
@Table(name = "huser")
public class HUser implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
/**
* @Id 定义为数据库的主键ID
* 建议不要在属性上引入注解,因为属性是private的,如果引入注解会破坏其封装特性,所以建议在getter方法上加入注解。
* @GeneratedValue ID的生成策略为自动生成
*/
@Id
@Column(name = "hid")
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "hname", length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4.2 修改配置文件hibernate.cfg.xml
<!-- 基于annotation的配置 -->
<mapping class="angelia.hibernate.model.HUser" />
<!-- 基于hbm.xml配置文件 -->
<!-- <mapping resource="Huser.hbm.xml"></mapping> -->
5. Hibernate生命周期
Hibernate中的一个对象存在于以下四个状态之中的一种:
- 短暂(Transient)
- 持久(Persistent)
- Removed
- Detached
以上几个状态在下面图中解释:
下面来看这几个状态的流转说明 -
当从一个实体创建一个新的Java对象时,该对象处于“短暂”状态。 Hibernate不知道它的存在,因为它独立于Hibernate的管理。
如果使用方法:get,load或find获取实体对象,则将获得一个等同于数据库中的1条记录的对象。 此对象处于Persistent状态。 它由Hibernate管理。
会话调用方法:save,saveOrUpdate和persist。 合并将短暂(Transient)对象置于Hibernate的管理之下,此对象转为持久化(Persistent)状态。 在使用的具体情况下,它向数据库插入或更新数据。
Session调用evict(..)或clear(),以便从处于Hibernate管理状态的对象处于关闭状态,并且这些对象处于分离(Detached)的状态。
使用update(..),saveOrUpdate(..),merge(..)将有助于重新连接分离对象。 在具体情况下,它会向数据库中创建更新或插入数据。 对象转回持久化(Persistent)状态。
Session调用方法:remove(..),delete(..)删除除记录并持久化对象。
6. Hibernate集合映射
可以在Hibernate中映射持久类的集合元素。 您需要从以下类型之一声明持久类中的集合类型:
java.util.List
java.util.Set
java.util.SortedSet
java.util.Map
java.util.SortedMap
java.util.Collection
org.hibernate.usertype.UserCollectionType
有很多<class>元素的子元素用来映射集合。 它们是<list>,<bag>,<set>和<map>。
key元素
<key>元素用于根据原始身份在连接的表中定义外键。 外键元素默认为空。 所以对于不可空的外键,需要指定not-null属性。key元素的属性是column,on-delete,property-ref,not-null,update和unique。
索引集合
集合元素可以分为两种形式:索引和非索引。List 和 Map集合都可被索引,而集合和行集合是非索引的。这里,索引收集 List 和 Map需要一个额外的元素<index>。 <index>元素用于标识类型。
集合元素
集合元素可以具有值或实体引用(另一个类对象)。可以使用4个元素之一。element,component-element,one-to-many,many-to-many。
element 和 component-element 用于正常值,例如string,int等,而一对多和多对多用于映射实体引用。但是如果集合存储实体引用(另一个类对象),我们需要定义<one-to-many>或<many-to-many>元素。
6.1 集合映射中的映射列表(List)示例
List中存储字符串值而不是实体引用的示例,这就是为什么要在列表元素中使用element标签而不是one-to-many标签的元素。
持久化类
public class Article {
private int id;
private String title;
private String content;
private List<String> comments;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public List<String> getComments() {
return comments;
}
public void setComments(List<String> comments) {
this.comments = comments;
}
}
持久化类的映射文件
<hibernate-mapping>
<class name="angelia.hibernate.model.Article" table="article">
<id name="id">
<generator class="increment"></generator>
</id>
<property name="title"></property>
<property name="content"></property>
<list name="comments" table="comment">
<key column="cid"></key>
<index column="type"></index>
<element column="comment" type="string"></element>
</list>
</class>
</hibernate-mapping>
更新hibernate.hnm.xml配置文件
<mapping resource="Article.hbm.xml" />
存储数据类
@Test
public void test() {
try {
ArrayList<String> comments = new ArrayList<String>();
comments.add("文章写的真好~");
comments.add("文章写的棒棒哒~");
Article article = new Article();
article.setTitle("Hibernate入门到精通");
article.setContent("Hibernate入门到精通的内容是。。。");
article.setComments(comments);
session.persist(article);
tx.commit();
Query<Article> query = session.createQuery("from Article");
List<Article> articles = query.list();
Iterator<Article> itr = articles.iterator();
while (itr.hasNext()) {
Article art = itr.next();
System.out.println("Article Title: " + art.getTitle());
List<String> cmts = art.getComments();
Iterator<String> itr2 = cmts.iterator();
while (itr2.hasNext()) {
System.out.println(itr2.next());
}
}
session.close();
System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}
}
如果持久化类具有包含实体引用的列表(List)对象,则需要使用一对多关联来映射列表元素。
在这里,我们使用博客发表文章应用场景,在博客上一篇文章有多个评论。
在这种情况下,一篇文章可以有多个评论,每个评论可能有自己的信息,这就是为什么在持久化类中使用列表(包含Comment类的引用)来表示一系列评论。
持久化类
public class Article {
private int id;
private String title;
private String content;
private List<Comment> comments;
//getters and setters
}
public class Comment {
private int id;
private String commentcontent;
private String postedBy;
//getters and setters
}
持久化类的映射文件Article.hbm.xml和Comment.hbm.xml
<hibernate-mapping>
<class name="angelia.hibernate.model.Article" table="article">
<id name="id">
<generator class="increment"></generator>
</id>
<property name="title"></property>
<property name="content"></property>
<!-- <list name="comments" table="comment"> -->
<list name="comments" cascade="all">
<key column="cid" not-null="true"></key>
<index column="type"></index>
<!-- <element column="comment" type="string"></element> -->
<one-to-many class="angelia.hibernate.model.Comment" />
</list>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="angelia.hibernate.model.Comment" table="comment">
<id name="id">
<generator class="increment"></generator>
</id>
<property name="commentcontent"></property>
<property name="postedBy"></property>
</class>
</hibernate-mapping>
配置文件hibernate.cfg.xml
<mapping resource="Article.hbm.xml" />
<mapping resource="Comment.hbm.xml" />
存储数据的类
@Test
public void test() {
try {
ArrayList<Comment> comments = new ArrayList<Comment>();
Comment comment1 = new Comment();
comment1.setCommentcontent("文章写的真好~");
comment1.setPostedBy("Angelia头号粉!!!");
Comment comment2 = new Comment();
comment2.setCommentcontent("文章写的棒棒哒~");
comment2.setPostedBy("Angelia二号粉!!!");
comments.add(comment1);
comments.add(comment2);
Article article = new Article();
article.setTitle("Hibernate入门到精通");
article.setContent("Hibernate入门到精通的内容是。。。");
article.setComments(comments);
session.persist(article);
tx.commit();
Query<Article> query = session.createQuery("from Article");
List<Article> articles = query.list();
Iterator<Article> itr = articles.iterator();
while (itr.hasNext()) {
Article art = itr.next();
System.out.println("Article Title: " + art.getTitle());
List<Comment> cmts = art.getComments();
Iterator<Comment> itr2 = cmts.iterator();
while (itr2.hasNext()) {
System.out.println(itr2.next());
}
}
session.close();
System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}
}
6.2 若持久类有List对象,我们可以通过列表或者bag元素在映射文件中映射。这个包(bag)就像List一样,但它不需要索引元素。
<bag name="comments" table="comment">
<key column="cid"></key>
<element column="comments" type="string"></element>
</bag>
<bag name="comments" cascade="all">
<key column="cid" not-null="true"></key>
<one-to-many class="angelia.hibernate.model.Comment" />
</bag>
请注意,bag不是基于索引的,而list是基于索引的。
6.3 若持久类具有Set对象,可以在映射文件中使用set元素映射Set集合。set元素不需要索引元素。List和Set之间的区别是: Set只存储唯一的值。
<set name="comments" table="comment">
<key column="cid"></key>
<element column="comments" type="string"></element>
</set>
<!-- <list name="comments" table="comment"> -->
<set name="comments" cascade="all">
<key column="cid" not-null="true"></key>
<!-- <index column="type"></index> -->
<!-- <element column="comment" type="string"></element> -->
<one-to-many class="angelia.hibernate.model.Comment" />
</set>
6.4 Hibernate允许我们将Map元素与RDBMS进行映射。我们知道,List和Map是基于索引的集合。 在map的情况下,索引列作为键,元素列用作值。
持久化类
public class Article {
private int id;
private String title;
private String content;
private Map<String, String> comments;
//getters and setters
}
持久化类的映射文件Article.hbm.xml
<hibernate-mapping>
<class name="angelia.hibernate.model.Article" table="article">
<id name="id">
<generator class="increment"></generator>
</id>
<property name="title"></property>
<property name="content"></property>
<map name="comments" table="comments" cascade="all">
<key column="cid"></key>
<index column="commentcontent" type="string"></index>
<element column="postedBy" type="string"></element>
</map>
</class>
</hibernate-mapping>
存储数据的类
@Test
public void test() {
try {
HashMap<String, String> comments = new HashMap<String, String>();
comments.put("文章写的真好~", "Angelia的头号粉丝");
comments.put("文章写的棒棒哒~", "Angelia的二号粉丝");
Article article = new Article();
article.setTitle("Hibernate入门到精通");
article.setContent("Hibernate入门到精通的内容是。。。");
article.setComments(comments);
session.persist(article);
Query query = session.createQuery("from Article ");
List<Article> list = query.list();
Iterator<Article> iterator = list.iterator();
while (iterator.hasNext()) {
Article art = iterator.next();
System.out.println("Article title:" + art.getTitle());
Map<String, String> map = art.getComments();
Set<Map.Entry<String, String>> set = map.entrySet();
Iterator<Map.Entry<String, String>> itercomment = set.iterator();
while (itercomment.hasNext()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) itercomment.next();
System.out.println("Comment name:" + entry.getKey());
System.out.println("Comment posted by:" + entry.getValue());
}
}
session.close();
System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}
}
6.5 双向映射
持久化类
public class Article {
private int id;
private String title;
private String content;
private Set<Comment> comments;
//getters and setters
}
public class Comment {
private int id;
private String commentcontent;
private String postedBy;
private Article article;
//getters and setters
}
持久化类的映射文件Article.hbm.xml和Comment.hbm.xml
<hibernate-mapping>
<class name="angelia.hibernate.model.Article" table="article">
<id name="id">
<generator class="increment"></generator>
</id>
<property name="title"></property>
<property name="content"></property>
<set name="comments" cascade="save-update" inverse="true" lazy="true" fetch="select">
<key column="aid" not-null="true"></key>
<one-to-many class="angelia.hibernate.model.Comment" />
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="angelia.hibernate.model.Comment" table="comment">
<id name="id">
<generator class="increment"></generator>
</id>
<property name="commentcontent"></property>
<property name="postedBy"></property>
<many-to-one name="article" cascade="save-update" class="angelia.hibernate.model.Article">
<column name="aid" not-null="true" />
</many-to-one>
</class>
</hibernate-mapping>
上一篇: #define和const区别
下一篇: 浅谈define和const的区别