Hibernate是一个开源的、面向java环境的对象/关系数据库映射工具。Object/Relational Mapping(对象/关系数据库映射)表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。其方法就是通过创建对象/关系映射配置文件(XML文件),在实体类(Entity)和数据库表之间建立对应关系,其中,对象属性对应表字段;对象之间的依赖关系,对应数据表之间的主、外键约束。采用Hibernate 作为持久层技术的最大的好处在于:可以完全以面向对象的方式进行系统分析、系统设计。面向对象的分析和面向对象的设计才最接近于程序员的自然思维。
有两种方法创建对象/关系映射,一种是先定义实体类及其依赖关系,并据此编写对象/关系映射文件,然后编程生成对应的数据库表结构;另一种是先创建数据库表结构以及表之间的主外键约束,再利用开发工具(如MyEclipse)反向生成实体类及对象/关系映射配置文件。先介绍第一种方法:
1、编写PO(Persist Object)类
下图显示了本系统PO的类图。
如图所示,根据要实现的功能,系统的模型Model 实现类有四个: Category,News, NewsReview 和User,它们都是普通的JavaBean ,下面是这四个基本Persisent Object 类要实现的功能
• News: 封装了一条消息。包括标题、内容、发布时间及发布人等。
• Category: 封装了一个消息分类。
• User: 封装了一个用户的信息。
• NewsReview: 封装了一条消息评论。
其中, News 有一个Category 类型的成员变量及User 类型的成员变量;NewsReview有一个News 类型的成员变量和一个User 类型的成员变量。为了能够使用双向关联(Hibernete 的映射功能), Category 有一个集合型成员变量,用于存放与这个Category 对象有关联的News 对象;同样News 也有一个集合型成员变量,用于存放与这个News 对象有关联的NewsReview 对象。实际上,持久化就是通过成员变量来映射关系数据库里的l-N 和N-N 的关系。
需要指出的是,四个类都继承自BaseObject类。下面是BaseObject类的代码:
//将父类声明为abstract 类
package mxh.model;
import java.io.Serializable;
public abstract class BaseObject implements Serializable {
public abstract String toString();
public abstract boolean equals(Object o);
public abstract int hanshCode();
}
父类BaseObject 是一个抽象类,定义了三个抽象方法toStringO ,equalsO 和hashCodeO,这三个方法是Hibernate 推荐持久化对象时重写的。
让其他类继承这个抽象类只是一个可选的写法,至少直接让其他类重写equalsO和hashCodeO方法来实现接口Serializable也是可以的。
推荐实现Serializable 接口时,最好重写equalsO 和hashCodeO 方法。
下面具体来看News 类。
package mxh.model;
import java.util.Date;
import java.util.Set;
import org.apache.commons.lang.builder.ToStringBuilder;
public class News extends BaseObject {
private Long id;// pk,required
private String title;// required
private String content;// required
private User poster;// fk,required
private Date postDate;// required
private Date lastModifyDate;// required
private Category category;// required
private Set newsReviews;
public News() {
}
public Set getNewsReviews() {
return newsReviews;
}
public void setNewsReviews(Set newsReviews) {
this.newsReviews = newsReviews;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Date getLastModifyDate() {
return lastModifyDate;
}
public void setLastModifyDate(Date lastModifyDate) {
this.lastModifyDate = lastModifyDate;
}
public Date getPostDate() {
return postDate;
}
public void setPostDate(Date postDate) {
this.postDate = postDate;
}
public User getPoster() {
return poster;
}
public void setPoster(User poster) {
this.poster = poster;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
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 boolean equals(Object object) {
if (!(object instanceof News)) {
return false;
}
News rhs = (News) object;
return this.poster.equals(rhs.getPoster()) && this.postDate.equals(rhs.getPostDate());
//* return new EqualsBuilder().append(this.newsReviews, rhs.newsReviews)
//* .append(this.title, rhs.title).append(this.category,
//* rhs.category).append(this.content, rhs.content).append(
//* this.postDate, rhs.postDate).append( this.lastModifyDate,
//* rhs.lastModifyDate).append( this.id, rhs.id).append(this.poster,
//* rhs.poster) .isEquals();
}
public String toString() {
return new ToStringBuilder(this).append("id", this.id).append("title",this.title)
.append("postDate", this.postDate).append("content",this.content)
.append("lastModifyDate", this.lastModifyDate).append("poster", this.poster)
.append("category", this.category).append("newsReviews",this.newsReviews)
.toString();
}
public int hanshCode() {
return this.poster.hashCode() + this.postDate.hashCode();
//* return new HashCodeBuilder(1595611275, -1477459617).append(
//* this.newsReviews).append(this.title).append(this.category)
//* .append(this.content).append(this.postDate).append(
//* this.lastModifyDate).append(this.id)
//* .append(this.poster).toHashCode();
}
}
各个属性的含义(后面的映射配置文件会有进一步探讨):每个News 对象就相当于数据库表里的一条记录,其中id 属性映射的是记录的主键: title 与content分别是消息的标题和内容;postDate 是发布时间;lastModifyDate 是最后评论时间;category则是News 关联(在数据库里通过外键关联)的Category对象; newsReviews则是News 对象关联的所有NewsReview 对象。
2、编写PO 的映射配置文件
仅有一个POJO 是无法完成数据库的持久化操作的,还必须为POJO 增加映射文件。当POlO 增加映射文件后,可以完成O/R Mapping ,从而在某个特定对象的管理下完成数据库访问。
因此必须为这个POlO 配上一个映射文件,通常将这个映射文件命名为:类名.hbm.xml ,并与这个类放置在同一目录下。News.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="mxh.model.News" table="news">
<id name="id" column="id" unsaved-value="null">
<generator class="increment">
</generator>
</id>
<!-- N--1关联, category_id为Category表(主键)在News表中的外键名。如果在Category表中不引
用News对象(Set--News对象列表),Hibernate称之为单向关系映射。很明显本系统的Category
和News是双向关系映射(后面将列出Category的映射配置)。实际上,与单向映射的单向访问相
比,双向映射只是在双方类定义中都设置了互访接口,无论单向还是双向,两个表之间的主外键
关系总是单向的。如果非得设置双向的主外键关系,那只能通过增加连接表来设置,让连接表与
双方建立外键关系,这就成为Hibernate所称的N--N关联 -->
<many-to-one name="category" class="mxh.model.Category" column="category_id"
not-null="true">
</many-to-one>
<property name="lastModifyDate" column="last_modify_date" not-null="true">
</property>
<property name="postDate" column="post_date" not-null="true">
</property>
<many-to-one name="poster" column="username" not-null="true">
</many-to-one>
<property name="content" column="content" length="3000" not-null="true">
</property>
<property name="title" column="title" length="50" not-null="true">
</property>
<!-- 与NewsReviews的1--N双向关系映射(NewsReviews配置文件要相应设置many-to-one映射)-->
<set name="newsReviews" lazy="false" inverse="true" cascade="all-delete-orphan">
<key>
<column name="news_id" />
</key>
<one-to-many class="mxh.model.NewsReview" />
</set>
</class>
</hibernate-mapping>
下面是Category.hbm.xml映射文件的代码:
......
<hibernate-mapping>
<class name="mxh.model.Category" table="category">
<id name="id" column="id" unsaved-value="null">
<generator class="increment">
</generator>
</id>
<property name="name" column="name" length="50" not-null="true">
</property>
<!-- 与News的双向关系映射 one-to-many association to News -->
<set name="news" lazy="false" inverse="true" cascade="all-delete-orphan">
<key>
<column name="category_id" />
</key>
<one-to-many class="mxh.model.News" />
</set>
</class>
</hibernate-mapping>
映射文件根元素hibernate-mapping 下面可以有多个class 子元素,即在一个映射文件里可以配置多个映射对象,但为了清晰起见,建议为每个类单独写一个映射配置文件。
class 的name 属性就是要映射的对象类;table 是数据库里对应的表名;class 下面的子元素就是这个类的属性并与数据库里的宇段相对应。
id 用于唯一标识该对象,称为标识属性。其中name 属性是类里面的属性名; column是数据库的对应宇段。另外, id 还有generator 子元素,用于指定主键生成方式,这里用的是自动增长生成(increment) 策略,其他方式请参照Hibernate 文档。
3、使用Hibernate配置数据库连接并创建和查询数据表
完成后可以通过配置Hibernate 的属性(数据库连接驱动,数据库方言,登录用户名与密码等)自动生成数据库的表。首先来看hibernate.cfg.xml文件内容(为工程添加hibernate功能支持组件时自动生成配置框架)。
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!-- Generated by MyEclipse Hibernate Tools. -->
<hibernate-configuration>
<session-factory>
<property name="connection.username">root</property>
<property name="connection.url">jdbc:mysql://localhost:3306/newsboard</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="myeclipse.connection.profile">newsboard</property>
<property name="connection.password">root</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<mapping resource="mxh/model/User.hbm.xml"/>
<mapping resource="mxh/model/Category.hbm.xml"/>
<mapping resource="mxh/model/News.hbm.xml"/>
<mapping resource="mxh/model/NewsReview.hbm.xml"/>
</session-factory>
</hibernate-configuration>
注意:除了手工编写以外,开发者还有一些工具可以帮助生成映射配直文件。例如,Xdoclet 就可以用来生成Hibernate 和Struts 等配置文件,只需在编写源代码时在适当的地方加上注释,再用Xdoclet 生成即可。
完成映射配置后,可以使用如下程序实现数据库表以及主外键约束关系的创建。
package mxh.model;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public class SchemaToDb {
public static void main(String[] args) {
//使用src目录下的hibernate.cfg.xml默认配置文件,如果使用不同位置或不同的(数据库)连接配
//置文件,请在configure("")中加载
Configuration cfg = new Configuration().configure();
SchemaExport export = new SchemaExport(cfg);
export.create(true, true);
}
}
运行该程序,可以在MySql数据库服务器中创建上述四个PO类所对应的四个数据库表及其主外键约束。由此,我们还可以创建利用Hibernate的SessionFactory创建会话的通用工具程序。其代码如下:
package mxh.dataoperationutility;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
public static final SessionFactory sessionFactory;
static
{
try
{
//采用默认的hibernate.cfg.xml来启动一个Configuration的实例
Configuration configuration=new Configuration().configure();
//由Configuration的实例来创建一个SessionFactory实例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal并不是线程本地化的实现,而是线程局部变量。也就是说每个使用该变量的线程都必须为
//该变量提供一个副本,每个线程改变该变量的值仅仅是改变该副本的值,而不会影响其他线程的该变量
//的值.
//ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,因此不再需要对线程同步
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//如果该线程还没有Session,则创建一个新的Session
if (s == null)
{
s = sessionFactory.openSession();
//将获得的Session变量存储在ThreadLocal变量session里
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
下面是利用该工具插入和查询数据的示例程序,Sql语句仅供参考:
package mxh.dataoperationutility;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;
import mxh.model.*;
public class HibernateSqlTest
{
public static void main(String[] args) throws Exception {
HibernateSqlTest mgr=new HibernateSqlTest();
//mgr.insertData();
mgr.queryData();
HibernateUtil.sessionFactory.close();
}
private void queryData(){
Session session = HibernateUtil.currentSession();
// 通过命名参数解决汉字乱码问题,又可避免sql注入攻击
List newsList=session.createQuery("from News nw where nw.poster.username = 'j2ee' "
+"and nw.category.name = :cname").setString("cname","社会新闻").list();
for (Iterator it = newsList.iterator() ; it.hasNext(); )
{
News nw = ( News )it.next();
System.out.println(nw.getContent());
}
List newsreList=session.createQuery("from NewsReview nr where nr.news.category.name in
(select ca.name from Category ca where ca.id < 3) ").list();
for (Iterator it = newsreList.iterator() ; it.hasNext(); )
{
NewsReview nwr = ( NewsReview )it.next();
System.out.println(nwr.getContent());
}
}
private void insertData()throws Exception
{
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();
User user1=new User();
user1.setUsername("j2ee");
user1.setPassword("123");
User user2=new User();
user2.setUsername("mysql");
user2.setPassword("12345");
//使用persist报错“detached entity passed to persist: null”,
//原因可能是persist是把托管对象持久化(与数据表关联),不执行insert,
//而新对象不生成标识属性,不能持久化,只能保存(执行insert),当然事务中
//如果有错可以回滚。请注意save、persist、update、merge方法的区别。
//session.persist(user1);
session.save(user1);
session.save(user2);
Category category1=new Category();
category1.setName("社会新闻");
Category category2=new Category();
category2.setName("军事新闻");
session.save(category1);
session.save(category2);
News news1=new News();
//SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//Date d = sdf.parse("2004-10-03");
news1.setCategory(category1);
news1.setTitle("第一条新闻");
news1.setContent("第一条新闻的内容");
news1.setPoster(user1);
news1.setPostDate(new Date());
news1.setLastModifyDate(new Date());
News news2=new News();
news2.setCategory(category1);
news2.setTitle("第二条新闻");
news2.setContent("第二条新闻的内容");
news2.setPoster(user1);
news2.setPostDate(new Date());
news2.setLastModifyDate(new Date());
session.persist(news1);
session.persist(news2);
NewsReview newsreview1=new NewsReview();
newsreview1.setNews(news1);
newsreview1.setContent("第一条社会新闻的第一条评论");
newsreview1.setPoster(user1);
newsreview1.setPostDate(new Date());
newsreview1.setLastModifyDate(new Date());
NewsReview newsreview2=new NewsReview();
newsreview2.setNews(news1);
newsreview2.setContent("第一条社会新闻的第二条评论");
newsreview2.setPoster(user1);
newsreview2.setPostDate(new Date());
newsreview2.setLastModifyDate(new Date());
session.persist(newsreview1);
session.persist(newsreview2);
tx.commit();
HibernateUtil.closeSession();
}
}
前述各文件在MyEclipse中的路径如下图所示: