对象-关系映射(o/r映射)是许多软件开发项目的常见需求。数据持久化过程中所涉及到的活动是非常乏味且易于出错的。如果考虑到不可避免的需求变化,我们就面临很大的麻烦:数据存储结构必须与源代码保持同步。再加上移植性问题,事情就变得非常复杂。
而hibernate可以帮助我们轻松地在永久性存储介质中保存数据,而不需要在选择存储、安装或配置类型方面浪费太多精力。hibernate允许我们存储任何类型的对象,因此,应用程序不需要知道其数据将使用hibernate进行持久化。当然,这里提到的任何事情都可以逆向应用:现在从存储器获取已经准备好的对象是很平常的事情。更新和删除数据也是如此。
开始之前
在开始之前,您需要hibernate的发行版,可以在hibernate web站点(www.hibernate.org)上找到它。我们将使用2.0.3版本。对于数据库,我们将使用hypersonic sql 1.7.1版本,它可以在hsqldb.sourceforge.net上找到。 hibernate还支持许多开源或商业数据库,例如mysql、postgresql、oracle、db2等。对于受支持的任何数据库,安装这个教程都很简单。完整列表参见官方文档。
注意:如果您不希望类被持久化在数据库中(比如说,您只希望进行串行化),那么hibernate api为您提供了net.sf.hibernate.persister.entitypersister类和net.sf.hibernate.persister.classpersister接口。通过编写子类或实现它们,您可以编写自己的持久化类,并根据需要使用它们。
下载了所有必需的安装包后,我们必须设置测试环境。基本上,我们所需做的就是把下载的.jar文件放到classpath中。这包括hibernate发行版中的hibernate2.jar和hypersonic的lib/ 目录下的hsqldb.jar。hibernate还需要其他的几个库,这些库都可以在<hibernate-dist>/lib目录中找到。并不是该目录下的所有.jars文件都需要,但是如果您使用所有文件,也没有什么坏处。在我们开始研究hibernate之前,我们将首先定义我们的问题域。
注意:hibernate使用apache的commons-logging。它是一个智能工具,如果找到log4j,它就会默认地使用它。log4j是一个出色的日志记录库,我们将在这个教程中使用它。如果您还没有这个软件(您真的应该安装这个软件!),可以从log4j homepage下载,并将它添加到classpath中。使用hibernate团队所提供的示例log4j.properties,它可以在<hibernate-dist>/src目录下找到。
问题引入
每个开发人员都至少执行过一次类似的任务:创建一个订单,把一些产品放在其中,它就变成订单项,然后保存该订单。
我们使用这些简单的sql命令来设置数据库:
create table orders( id varchar not null primary key, order_date timestamp not null, price_total double not null) create table products( id varchar not null primary key, name varchar not null, price double not null, amount integer not null) create table order_items( id varchar not null primary key, order_id varchar not null, product_id varchar not null, amount integer not null, price double not null)
package test.hibernate; public class product { private string id; private string name; private double price; private int amount; public string getid() { return id; } public void setid(string string) { id = string; } // 默认的构造函数及其他 // 为了简洁起见,getter/setter方法没有显示 // ... } public string tostring() { return "[product] " + name + "(" + id + ") price=" + price + " amount=" + amount; } order
我们需要创建的下一个类是order,它甚至比product更简单:它只包含id、创建日期、总价格和该order所包括的orderitems的set。当然,还需要创建getter和setter方法以及默认的构造函数。
package test.hibernate; import java.util.date; import java.util.hashset; import java.util.set; public class order { private string id; private date date; private double pricetotal; private set orderitems = new hashset(); // 自动设置该order的创建时间 public order() { this.date = new date(); } public string getid() { return id; } public void setid(string string) { id = string; } // 为了简洁起见,其他getter/setter方法没有显示 // ... } package test.hibernate; public class orderitem { /** * 创建有效的订单项。自动设置订单项的价格,并更正产品的库存可用量 * * @param order 该订单项属于的订单 * @param product 该订单项为哪种产品而创建 * @param amount */ public orderitem(order order, product product, int amount) { this.order = order; this.product = product; this.amount = amount; product.setamount(product.getamount() - amount); this.price = product.getprice() * amount; } // 还需要默认的构造函数来保证hibernate工作 /** * 空构造函数遵循javabeans约定 * */ public orderitem() { // 空的默认构造函数 } // 字段 private string id; private product product; private order order; private string productid; private string orderid; private double price; private int amount; public string getid() { return id; } public string getproductid() { return product.getid(); } public string getorderid() { return order.getid(); } // 其他getter/setter方法没有显示 // ... file://显示该订单项的方便方式 public string tostring() { return "[orderitem] id=" + id + " amount=" + amount + " price=" + price + "(" + product + ")"; } } /** * 添加一项产品到订单中。产品自动成为一个订单项。 * pricetotal被自动更新。 * * @param p 添加到该订单的产品 * @param amount 添加的产品量 */ public void addproduct(product p, int amount) { orderitem orderitem = new orderitem(this, p, amount); this.pricetotal = this.pricetotal + p.getprice() * amount; this.orderitems.add(orderitem); } 创建和持久化product
现在我们终于用到hibernate了。使用的场景非常简单:
正如我们所看到的,这里没有提到jdbc、sql或任何类似的东西。非常令人振奋!下面的示例遵循了上面提到的步骤:
package test; import net.sf.hibernate.session; import net.sf.hibernate.sessionfactory; import net.sf.hibernate.transaction; import net.sf.hibernate.cfg.configuration; import test.hibernate.product; // 用法: // java test.insertproduct name amount price public class insertproduct { public static void main(string[] args) throws exception { // 1. 创建product对象 product p = new product(); p.setname(args[0]); p.setamount(integer.parseint(args[1])); p.setprice(double.parsedouble(args[2])); // 2. 启动hibernate configuration cfg = new configuration() .addclass(product.class); sessionfactory sf = cfg.buildsessionfactory(); // 3. 打开session session sess = sf.opensession(); // 4. 保存product,关闭session transaction t = sess.begintransaction(); sess.save(p); t.commit(); sess.close(); } } nov 23, 2003 9:05:50 am net.sf.hibernate.cfg.environment <clinit> info: hibernate 2.0.3 nov 23, 2003 9:05:50 am net.sf.hibernate.cfg.environment <clinit> info: hibernate.properties not found nov 23, 2003 9:05:50 am net.sf.hibernate.cfg.environment <clinit> info: using cglib reflection optimizer nov 23, 2003 9:05:50 am net.sf.hibernate.cfg.environment <clinit> info: jvm proxy support: true nov 23, 2003 9:05:50 am net.sf.hibernate.cfg.configuration addclass info: mapping resource: test/hibernate/product.hbm.xml exception in thread "main" net.sf.hibernate.mappingexception: resource: test/hibernate/product.hbm.xml not found at net.sf.hibernate.cfg.configuration.addclass(configuration.java:285) at test.findproductbyname.main(findproductbyname.java:24)
info: hibernate.properties not found and resource: test/hibernate/product.hbm.xml not found.
hibernate.connection.username=sa hibernate.connection.password= hibernate.connection.url=jdbc:hsqldb:/home/davor/hibernate/orders hibernate.connection.driver_class=org.hsqldb.jdbcdriver hibernate.dialect=net.sf.hibernate.dialect.hsqldialect
<?xml version="1.0" encoding="utf-8"?> <!doctype hibernate-mapping public "-//hibernate/hibernate mapping dtd//en" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="test.hibernate.product" table="products"> <id name="id" type="string" unsaved-value="null"> <column name="id" sql-type="char(32)" not-null="true"/> <generator class="uuid.hex"/> </id> <property name="name"> <column name="name" sql-type="char(255)" not-null="true"/> </property> <property name="price"> <column name="price" sql-type="double" not-null="true"/> </property> <property name="amount"> <column name="amount" sql-type="integer" not-null="true"/> </property> </class> </hibernate-mapping>
<generator class="uuid.hex"/>元素乍一看不太好理解。但是知道了它是<id>的一个子元素后,它的作用就很明显了:由于应用程序不知道它的数据如何被持久化(我们一直这么说),我们需要一个没有任何业务含义的代理键帮助hibernate操纵对象。新创建的products没有那个id,hibernate将为我们创建它们。我们选择使用uuid字符串,但它提供了许多id生成器(顺序的、限定范围的,甚至是用户指派的,等等),而且还可以编写自己的id生成器。
现在,创建(复制、粘贴)product.hbm.xml的内容,并把文件和test.hibernate.product类放到同一个包内(例如,放置product.java文件的目录),重新运行java test.insertproduct milk 100 1.99命令。现在我们看到更多的日志以及...没有其他东西了!它运行正常吗?在session sess = sf.opensession(); 前和sess.close()后添加system.out.println(p),看一看produc出了什么问题。重新运行程序。您将看到类似于如下内容的日志输出(id数字肯定会不同的):
[product] milk(null) price=1.99 amount=100 [product] milk(40288081f907f42900f907f448460001) price=1.99 amount=100
hibernate为我们创建了product的id!让我们看一下product是否存储到了数据库中。执行select * from products,数据库返回类似于以下内容的输出:
id |name |price |amount | 40288081f907f42900f907f448460001|milk |1.99 |100 |
查找和加载产品
查找和加载已经持久化的对象在hibernate中非常简单。使用它的查询语言,我们可以很容易地通过id、名称或其他属性获取一个对象(或对象集)。我们能够获取完整的对象或它的一部分属性。hibernate将处理余下的工作,最后,我们将拥有相当有用的对象层次体系。我们来看一下test.findproductbyname类。
package test; import java.util.list; import net.sf.hibernate.hibernate; import net.sf.hibernate.session; import net.sf.hibernate.sessionfactory; import net.sf.hibernate.cfg.configuration; import test.hibernate.product; // 用法: // java test.findproductbyname name public class findproductbyname { public static void main(string[] args) throws exception { // 执行的查询 string query = "select product from product " + "in class test.hibernate.product " + "where product.name=:name"; // 搜索的内容 string name = args[0]; // 初始化 configuration cfg = new configuration() .addclass(product.class); sessionfactory sf = cfg.buildsessionfactory(); // 打开会话 session sess = sf.opensession(); // 搜索并返回 list list = sess.find(query, name, hibernate.string); if (list.size() == 0) { system.out.println("no products named " + name); system.exit(0); } product p = (product) list.get(0); sess.close(); system.out.println("found product: " + p); } }
执行java test.findproductbyname milk,查看显示在控制台中的内容。
注意:查询是区分大小写的,所以搜索小写的milk将不会返回任何结果。使用lower()或upper()sql函数来启用不区分大小写的搜索。在这种情况下,我们会在查询字符串中使用where lower(product.name)=lower(:name)。关于查询的详细内容,请参见文档。此外,如果不希望显示所有的info日志信息,可以修改log4j.properties文件,将日志等级设置为warn。
更新和删除产品
到现在为止,您应该对hibernate的工作方式有了一个基本的了解,因此我们将缩短冗长的示例,只显示重要的部分。
为了在单个事务中将所有产品的价格提高10%,我们可以编写如下的内容:
double percentage = double.parsedouble(args[0])/100; sess = sf.opensession(); transaction t = sess.begintransaction(); // 列表包含产品 iterator iter = list.iterator(); while (iter.hasnext()) { product p = (product) iter.next(); p.setprice(p.getprice() * (1 + percentage)); sess.saveorupdate(p); } t.commit(); sess.close(); orders,orderitems
有时一个一个地操纵对象确实可行,但是我们希望能够级联加载和更新。现在我们来看如何做到这一点。
我们需要同时检查order和orderitem。就如前面所提到的,我们添加一项product到一个order中,它将变成一个orderitem。order在内部保存一个orderitem集。我们希望保存order,让hibernate来做其他工作:保存orderitem和更新所添加的product的可用库存(数量)。听起来很复杂,但实际上非常简单。hibernate知道如何处理一对一、一对多、多对一和多对多方式中的相关对象。我们将从映射文件开始。
order.hbm.xml
<?xml version="1.0" encoding="utf-8"?> <!doctype hibernate-mapping public "-//hibernate/hibernate mapping dtd//en" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="test.hibernate.order" table="orders"> <id name="id" type="string" unsaved-value="null" > <column name="id" sql-type="char(32)" not-null="true"/> <generator class="uuid.hex"/> </id> <property name="date"> <column name="order_date" sql-type="datetime" not-null="true"/> </property> <property name="pricetotal"> <column name="price_total" sql-type="double" not-null="true"/> </property> <set name="orderitems" table="order_items" inverse="true" cascade="all"> <key column="order_id" /> <one-to-many class="test.hibernate.orderitem" /> </set> </class> </hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?> <!doctype hibernate-mapping public "-//hibernate/hibernate mapping dtd//en" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class name="test.hibernate.orderitem" table="order_items"> <id name="id" type="string" unsaved-value="null" > <column name="id" sql-type="char(32)" not-null="true"/> <generator class="uuid.hex"/> </id> <property name="orderid" insert="false" update="false"> <column name="order_id" sql-type="char(32)" not-null="true"/> </property> <property name="productid" insert="false" update="false"> <column name="product_id" sql-type="char(32)" not-null="true"/> </property> <property name="amount"> <column name="amount" sql-type="int" not-null="true"/> </property> <property name="price"> <column name="price" sql-type="double" not-null="true"/> </property> <many-to-one name="order" class="test.hibernate.order" column="order_id" /> <many-to-one name="product" class="test.hibernate.product" cascade="save-update" column="product_id"/> </class> </hibernate-mapping>
用法示例
创建一个订单。在该示例中,我们创建并持久化一个订单。反复运行这个示例,查看产品数量在每次成功创建订单后如何变化。
// ... configuration cfg = new configuration() .addclass(product.class) .addclass(order.class) .addclass(orderitem.class); // ... order order = new order(); order.addproduct(milk, 3); order.addproduct(coffee, 5); // ... sess = sf.opensession(); transaction t = sess.begintransaction(); sess.save(order); t.commit(); sess.close(); system.out.println(order); // ...
// ... string query = "select o from o " + "in class test.hibernate.order " + "where o.pricetotal > :pricetotallower " + "and o.pricetotal < :pricetotalupper"; // ... query q = sess.createquery(query); q.setdouble("pricetotallower", double.parsedouble(args[0])); q.setdouble("pricetotalupper", double.parsedouble(args[1])); list list = q.list(); // ... sess.close(); // ... // ... string query = "select o from o " + "in class test.hibernate.order " + "where o.pricetotal > :pricetotallower " + "and o.pricetotal < :pricetotalupper"; transaction tx = sess.begintransaction(); sess.delete(query, new object[]{new double(args[0]), new double(args[1])}, new type[]{hibernate.double, hibernate.double} ); tx.commit(); sess.close(); 新闻热点
疑难解答
图片精选