Hibernate从入门到精通

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014010512/article/details/82425298

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可以自动生成表结构)

扫描二维码关注公众号,回复: 3234427 查看本文章

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>
		<!-- &需要转义,即&amp; -->
		<property name="connection.url">jdbc:mysql://localhost:3306/hibernatetest?useSSL=false&amp;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>

猜你喜欢

转载自blog.csdn.net/u014010512/article/details/82425298