hibernate官方入门教程
第一部分 - 第一个Hibernate程序 html
首先咱们将建立一个简单的控制台(console-based)Hibernate程序。咱们使用内置数据库(in-memory database) (HSQL DB),因此咱们没必要安装任何数据库服务器。 java
让咱们假设咱们但愿有一个小程序能够保存咱们但愿关注的事件(Event)和这些事件的信息。 (译者注:在本教程的后面部分,咱们将直接使用Event而不是它的中文翻译“事件”,以避免混淆。) web
咱们作的第一件事是创建咱们的开发目录,并把全部须要用到的Java库文件放进去。 从Hibernate网站的下载页面下载Hibernate分发版本。 解压缩包并把/lib下面的全部库文件放到咱们新的开发目录下面的/lib目录下面。 看起来就像这样: sql
. +lib antlr.jar cglib-full.jar asm.jar asm-attrs.jars commons-collections.jar commons-logging.jar ehcache.jar hibernate3.jar jta.jar dom4j.jar log4j.jar
This is the minimum set of required libraries (note that we also copied hibernate3.jar, the main archive) for Hibernate. See the README.txt file in the lib/ directory of the Hibernate distribution for more information about required and optional third-party libraries. (Actually, Log4j is not required but preferred by many developers.) 这个是Hibernate运行所须要的最小库文件集合(注意咱们也拷贝了Hibernate3.jar,这个是最重要的库)。 能够在Hibernate分发版本的lib/目录下查看README.txt,以获取更多关于所需和可选的第三方库文件信息 (事实上,Log4j并非必须的库文件可是许多开发者都喜欢用它)。 数据库
接下来咱们建立一个类,用来表明那些咱们但愿储存在数据库里面的event. apache
咱们的第一个持久化类是 一个简单的JavaBean class,带有一些简单的属性(property)。 让咱们来看一下代码: 编程
import java.util.Date; public class Event { private Long id; private String title; private Date date; Event() {} public Long getId() { return id; } private void setId(Long id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
你能够看到这个class对属性(property)的存取方法(getter and setter method) 使用标准的JavaBean命名约定,同时把内部字段(field)隐藏起来(private visibility)。 这个是个受推荐的设计方式,但并非必须这样作。 Hibernate也能够直接访问这些字段(field),而使用访问方法(accessor method)的好处是提供了程序重构的时候健壮性(robustness)。 小程序
id 属性(property) 为一个Event实例提供标识属性(identifier property)的值- 若是咱们但愿使用Hibernate的全部特性,那么咱们全部的持久性实体类(persistent entity class)(这里也包括一些次要依赖类) 都须要一个标识属性(identifier property)。而事实上,大多数应用程序(特别是web应用程序)都须要识别特定的对象,因此你应该 考虑使用标识属性而不是把它看成一种限制。然而,咱们一般不会直接操做一个对象的标识符(identifier), 所以标识符的setter方法应该被声明为私有的(private)。这样当一个对象被保存的时候,只有Hibernate能够为它分配标识符。 你会发现Hibernate能够直接访问被声明为public,private和protected等不一样级别访问控制的方法(accessor method)和字段(field)。 因此选择哪一种方式来访问属性是彻底取决于你,你可使你的选择与你的程序设计相吻合。 安全
全部的持久类(persistent classes)都要求有无参的构造器(no-argument constructor); 由于Hibernate必需要使用Java反射机制(Reflection)来实例化对象。构造器(constructor)的访问控制能够是私有的(private), 然而当生成运行时代理(runtime proxy)的时候将要求使用至少是package级别的访问控制,这样在没有字节码编入 (bytecode instrumentation)的状况下,从持久化类里获取数据会更有效率一些。 服务器
咱们把这个Java源代码文件放到咱们的开发目录下面一个叫作src的目录里。 这个目录如今应该看起来像这样:
. +lib <Hibernate and third-party libraries> +src Event.java
在下一步里,咱们将把这个持久类(persisten class)的信息通知Hibernate
Hibernate须要知道怎样去加载(load)和存储(store)咱们的持久化类的对象。这里正是Hibernate映射文件(mapping file)发挥做用的地方。 映射文件告诉Hibernate它应该访问数据库里面的哪一个表(table)和应该使用表里面的哪些字段(column)。
一个映射文件的基本结构看起来像这样:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> [...] </hibernate-mapping>
注意Hibernate的DTD是很是复杂的。 你能够在你的编辑器或者IDE里面使用它来自动提示并完成(auto-completion)那些用来映射的XML元素(element)和属性(attribute)。 你也能够用你的文本编辑器打开DTD-这是最简单的方式来浏览全部元素和参数,查看它们的缺省值以及它们的注释,以获得一个总体的概观。 同时也要注意Hibernate不会从web上面获取DTD文件,虽然XML里面的URL也许会建议它这样作,可是Hibernate会首先查看你的程序的classpath。 DTD文件被包括在hibernate3.jar,同时也在Hibernate分发版的src/路径下。
在之后的例子里面,咱们将经过省略DTD的声明来缩短代码长度。可是显然,在实际的程序中,DTD声明是必须的。
在两个hibernate-mapping标签(tag)中间, 咱们包含了一个 class元素(element)。全部的持久性实体类(persistent entity classes)(再次声明, 这里也包括那些依赖类,就是那些次要的实体)都须要一个这样的映射,来映射到咱们的SQL database。
<hibernate-mapping> <class name="Event" table="EVENTS"> </class> </hibernate-mapping>
咱们到如今为止作的一切是告诉Hibernate怎样从数据库表(table)EVENTS里持久化和 加载Event类的对象,每一个实例对应数据库里面的一行。如今咱们将继续讨论有关惟一标识属性(unique identifier property)的映射。 另外,咱们不但愿去考虑怎样产生这个标识属性,咱们将配置Hibernate的标识符生成策略(identifier generation strategy)来产生代用主键。
<hibernate-mapping> <class name="Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="increment"/> </id> </class> </hibernate-mapping>
id元素是标识属性(identifer property)的声明, name="id" 声明了Java属性(property)的名字 - Hibernate将使用getId()和setId()来访问它。 字段参数(column attribute)则告诉Hibernate咱们使用EVENTS表的哪一个字段做为主键。 嵌套的generator元素指定了标识符的生成策略 - 在这里咱们使用increment,这个是很是简单的在内存中直接生成数字的方法,多数用于测试(或教程)中。 Hibernate同时也支持使用数据库生成(database generated),全局惟一性(globally unique)和应用程序指定(application assigned) (或者你本身为任何已有策略所写的扩展) 这些方式来生成标识符。
最后咱们还必须在映射文件里面包括须要持久化属性的声明。缺省的状况下,类里面的属性都被视为非持久化的:
<hibernate-mapping> <class name="Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="increment"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> </class> </hibernate-mapping>
和id元素相似,property元素的name参数 告诉Hibernate使用哪一个getter和setter方法。
为何date属性的映射包括column参数,可是title却没有? 当没有设定column参数的时候,Hibernate缺省使用属性名做为字段(column)名。对于title,这样工做得很好。 然而,date在多数的数据库里,是一个保留关键字,因此咱们最好把它映射成另一个名字。
下一件有趣的事情是title属性缺乏一个type参数。 咱们声明并使用在映射文件里面的type,并不像咱们假想的那样,是Java data type, 同时也不是SQL database type。这些类型被称做Hibernate mapping types, 它们把数据类型从Java转换到SQL data types。若是映射的参数没有设置的话,Hibernate也将尝试去肯定正确的类型转换和它的映射类型。 在某些状况下这个自动检测(在Java class上使用反射机制)不会产生你所期待或者 须要的缺省值。这里有个例子是关于date属性。Hibernate没法知道这个属性应该被映射成下面这些类型中的哪个: SQL date,timestamp,time。 咱们经过声明属性映射timestamp来表示咱们但愿保存全部的关于日期和时间的信息。
这个映射文件(mapping file)应该被保存为Event.hbm.xml,和咱们的EventJava 源文件放在同一个目录下。映射文件的名字能够是任意的,然而hbm.xml已经成为Hibernate开发者社区的习惯性约定。 如今目录应该看起来像这样:
. +lib <Hibernate and third-party libraries> +src Event.java Event.hbm.xml
咱们继续进行Hibernate的主要配置。
咱们如今已经有了一个持久化类和它的映射文件,是时候配置Hibernate了。在咱们作这个以前,咱们须要一个数据库。 HSQL DB,一个java-based内嵌式SQL数据库(in-memory SQL Database),能够从HSQL DB的网站上下载。 实际上,你仅仅须要下载/lib/目录中的hsqldb.jar。把这个文件放在开发文件夹的lib/目录里面。
在开发目录下面建立一个叫作data的目录 - 这个是HSQL DB存储它的数据文件的地方。
Hibernate是你的程序里链接数据库的那个应用层,因此它须要链接用的信息。链接(connection)是经过一个也由咱们配置的JDBC链接池(connection pool)。 Hibernate的分发版里面包括了一些open source的链接池,可是咱们已经决定在这个教程里面使用内嵌式链接池。 若是你但愿使用一个产品级的第三方链接池软件,你必须拷贝所需的库文件去你的classpath并使用不一样的链接池设置。
为了配置Hibernate,咱们可使用一个简单的hibernate.properties文件, 或者一个稍微复杂的hibernate.cfg.xml,甚至能够彻底使用程序来配置Hibernate。 多数用户喜欢使用XML配置文件:
<?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"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:data/tutorial</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create</property> <mapping resource="Event.hbm.xml"/> </session-factory> </hibernate-configuration>
注意这个XML配置使用了一个不一样的DTD。咱们配置Hibernate的SessionFactory- 一个关联于特定数据库全局性的工厂(factory)。若是你要使用多个数据库,一般应该在多个配置文件中使用多个<session-factory> 进行配置(在更早的启动步骤中进行)。
最开始的4个property元素包含必要的JDBC链接信息。dialectproperty 代表Hibernate应该产生针对特定数据库语法的SQL语句。hbm2ddl.auto选项将自动生成数据库表定义(schema)- 直接插入数据库中。固然这个选项也能够被关闭(经过去除这个选项)或者经过Ant任务SchemaExport来把数据库表定义导入一个文件中进行优化。 最后,为持久化类加入映射文件。
把这个文件拷贝到源代码目录下面,这样它就位于classpath的root路径上。Hibernate在启动时会自动 在它的根目录开始寻找名为hibernate.cfg.xml的配置文件。
在这个教程里面,咱们将用Ant来编译程序。你必须先安装Ant-能够从Ant download page 下载它。怎样安装Ant不是这个教程的内容,请参考Ant manual。 当你安装完了Ant,咱们就能够开始建立编译脚本,它的文件名是build.xml,把它直接放在开发目录下面。
注意Ant的分发版一般功能都是不完整的(就像Ant FAQ里面说得那样),因此你经常不得不须要本身动手来完善Ant。 例如:若是你但愿在你的build文件里面使用JUnit功能。为了让JUnit任务被激活(这个教程里面咱们并不须要这个任务), 你必须拷贝junit.jar到ANT_HOME/lib目录下或者删除ANT_HOME/lib/ant-junit.jar这个插件。
一个基本的build文件看起来像这样
<project name="hibernate-tutorial" default="compile"> <property name="sourcedir" value="${basedir}/src"/> <property name="targetdir" value="${basedir}/bin"/> <property name="librarydir" value="${basedir}/lib"/> <path id="libraries"> <fileset dir="${librarydir}"> <include name="*.jar"/> </fileset> </path> <target name="clean"> <delete dir="${targetdir}"/> <mkdir dir="${targetdir}"/> </target> <target name="compile" depends="clean, copy-resources"> <javac srcdir="${sourcedir}" destdir="${targetdir}" classpathref="libraries"/> </target> <target name="copy-resources"> <copy todir="${targetdir}"> <fileset dir="${sourcedir}"> <exclude name="**/*.java"/> </fileset> </copy> </target> </project>
这个将告诉Ant把全部在lib目录下以.jar结尾的文件加入classpath中用来进行编译。 它也将把全部的非Java源代码文件,例如配置和Hibernate映射文件,拷贝到目标目录下。若是你如今运行Ant, 你将获得如下输出:
C:/hibernateTutorial/>ant Buildfile: build.xml copy-resources: [copy] Copying 2 files to C:/hibernateTutorial/bin compile: [javac] Compiling 1 source file to C:/hibernateTutorial/bin BUILD SUCCESSFUL Total time: 1 second
是时候来加载和储存一些Event对象了,可是首先咱们不得不完成一些基础的代码。 咱们必须启动Hibernate。这个启动过程包括建立一个全局性的SessoinFactory并把它储存在一个应用程序容易访问的地方。 SessionFactory能够建立并打开新的Session。 一个Session表明一个单线程的单元操做,SessionFactory则是一个线程安全的全局对象,只须要建立一次。
咱们将建立一个HibernateUtil帮助类(helper class)来负责启动Hibernate并使 操做Session变得容易。这个帮助类将使用被称为ThreadLocal Session 的模式来保证当前的单元操做和当前线程相关联。让咱们来看一眼它的实现:
import org.hibernate.*; import org.hibernate.cfg.*; public class HibernateUtil { public static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().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); } } public static final ThreadLocal session = new ThreadLocal(); public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // Open a new Session, if this thread has none yet if (s == null) { s = sessionFactory.openSession(); // Store it in the ThreadLocal variable session.set(s); } return s; } public static void closeSession() throws HibernateException { Session s = (Session) session.get(); if (s != null) s.close(); session.set(null); } }
这个类不只仅在它的静态初始化过程(仅当加载这个类的时候被JVM执行一次)中产生全局SessionFactory, 同时也有一个ThreadLocal变量来为当前线程保存Session。不论你什么时候 调用HibernateUtil.currentSession(),它老是返回同一个线程中的同一个Hibernate单元操做。 而一个HibernateUtil.closeSession()调用将终止当前线程相联系的那个单元操做。
在你使用这个帮助类以前,肯定你明白Java关于本地线程变量(thread-local variable)的概念。一个功能更增强大的 HibernateUtil帮助类能够在CaveatEmptorhttp://caveatemptor.hibernate.org/找到 -它同时也出如今书:《Hibernate in Action》中。注意当你把Hibernate部署在一个J2EE应用服务器上的时候,这个类不是必须的: 一个Session会自动绑定到当前的JTA事物上,你能够经过JNDI来查找SessionFactory。 若是你使用JBoss AS,Hibernate能够被部署成一个受管理的系统服务(system service)并自动绑定SessionFactory到JNDI上。
把HibernateUtil.java放在开发目录的源代码路径下面,与 Event.java放在一块儿:
. +lib <Hibernate and third-party libraries> +src Event.java Event.hbm.xml HibernateUtil.java hibernate.cfg.xml +data build.xml
再次编译这个程序不该该有问题。最后咱们须要配置一个日志系统 - Hibernate使用通用日志接口,这容许你在Log4j和 JDK 1.4 logging之间进行选择。多数开发者喜欢Log4j:从Hibernate的分发版(它在etc/目录下)拷贝 log4j.properties到你的src目录,与hibernate.cfg.xml.放在一块儿。 看一眼配置示例,你能够修改配置若是你但愿看到更多的输出信息。缺省状况下,只有Hibernate的启动信息会显示在标准输出上。
教程的基本框架完成了 - 如今咱们能够用Hibernate来作些真正的工做。
终于,咱们可使用Hibernate来加载和存储对象了。咱们编写一个带有main()方法 的EventManager类:
import org.hibernate.Transaction; import org.hibernate.Session; import java.util.Date; public class EventManager { public static void main(String[] args) { EventManager mgr = new EventManager(); if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } HibernateUtil.sessionFactory.close(); } }
咱们从命令行读入一些参数,若是第一个参数是"store",咱们建立并储存一个新的Event:
private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); tx.commit(); HibernateUtil.closeSession(); }
咱们建立一个新的Event对象并把它传递给Hibernate。Hibernate如今负责建立SQL并把 INSERT命令传给数据库。在运行它以前,让咱们花一点时间在Session和Transaction的处理代码上。
每一个Session是一个独立的单元操做。你会对咱们有另一个API:Transaction而感到惊奇。 这暗示一个单元操做能够拥有比一个单独的数据库事务更长的生命周期 - 想像在web应用程序中,一个单元操做跨越多个Http request/response循环 (例如一个建立对话框)。根据“应用程序用户眼中的单元操做”来切割事务是Hibernate的基本设计思想之一。咱们调用 一个长生命期的单元操做Application Transaction时,一般包装几个更生命期较短的数据库事务。 为了简化问题,在这个教程里咱们使用Session和Transaction之间是1对1关系的粒度(one-to-one granularity)。
Transaction.begin()和commit()都作些什么?rollback()在哪些状况下会产生错误? Hibernate的Transaction API 其实是可选的, 可是咱们一般会为了便利性和可移植性而使用它。 若是你宁肯本身处理数据库事务(例如,调用session.connection.commit()),经过直接和无管理的JDBC,这样将把代码绑定到一个特定的部署环境中去。 经过在Hibernate配置中设置Transaction工厂,你能够把你的持久化层部署在任何地方。 查看第 12 章 事务和并发了解更多关于事务处理和划分的信息。在这个例子中咱们也忽略任何异常处理和事务回滚。
为了第一次运行咱们的应用程序,咱们必须增长一个能够调用的target到Ant的build文件中。
<target name="run" depends="compile"> <java fork="true" classname="EventManager" classpathref="libraries"> <classpath path="${targetdir}"/> <arg value="${action}"/> </java> </target>
action参数的值是在经过命令行调用这个target的时候设置的:
C:/hibernateTutorial/>ant run -Daction=store
你应该会看到,编译结束之后,Hibernate根据你的配置启动,并产生一大堆的输出日志。在日志最后你会看到下面这行:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
这是Hibernate执行的INSERT命令,问号表明JDBC的待绑定参数。若是想要看到绑定参数的值或者减小日志的长度, 检查你在log4j.properties文件里的设置。
如今咱们想要列出全部已经被存储的event,因此咱们增长一个条件分支选项到main方法中去。
if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } else if (args[0].equals("list")) { List events = mgr.listEvents(); for (int i = 0; i < events.size(); i++) { Event theEvent = (Event) events.get(i); System.out.println("Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()); } }
咱们也增长一个新的listEvents()方法:
private List listEvents() { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); List result = session.createQuery("from Event").list(); tx.commit(); session.close(); return result; }
咱们在这里是用一个HQL(Hibernate Query Language-Hibernate查询语言)查询语句来从数据库中 加载全部存在的Event。Hibernate会生成正确的SQL,发送到数据库并使用查询到的数据来生成Event对象。 固然你也可使用HQL来建立更加复杂的查询。
若是你如今使用命令行参数-Daction=list来运行Ant,你会看到那些至今为止咱们储存的Event。 若是你是一直一步步的跟随这个教程进行的,你也许会吃惊这个并不能工做 - 结果永远为空。缘由是hbm2ddl.auto 打开了一个Hibernate的配置选项:这使得Hibernate会在每次运行的时候从新建立数据库。经过从配置里删除这个选项来禁止它。 运行了几回store以后,再运行list,你会看到结果出如今列表里。 另外,自动生成数据库表并导出在单元测试中是很是有用的。
咱们已经映射了一个持久化实体类到一个表上。让咱们在这个基础上增长一些类之间的关联性。 首先咱们往咱们程序里面增长人(people)的概念,并存储他们所参与的一个Event列表。 (译者注:与Event同样,咱们在后面的教程中将直接使用person来表示“人”而不是它的中文翻译)
最初的Person类是简单的:
public class Person { private Long id; private int age; private String firstname; private String lastname; Person() {} // Accessor methods for all properties, private setter for 'id' }
Create a new mapping file called Person.hbm.xml:
<hibernate-mapping> <class name="Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="increment"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> </class> </hibernate-mapping>
Finally, add the new mapping to Hibernate's configuration:
<mapping resource="Event.hbm.xml"/> <mapping resource="Person.hbm.xml"/>
咱们如今将在这两个实体类之间建立一个关联。显然,person能够参与一系列Event,而Event也有不一样的参加者(person)。 设计上面咱们须要考虑的问题是关联的方向(directionality),阶数(multiplicity)和集合(collection)的行为。
咱们将向Person类增长一组Event。这样咱们能够轻松的经过调用aPerson.getEvents()获得一个Person所参与的Event列表,而没必要执行一个显式的查询。咱们使用一个Java的集合类:一个Set,由于Set 不容许包括重复的元素并且排序和咱们无关。
目前为止咱们设计了一个单向的,在一端有许多值与之对应的关联,经过Set来实现。 让咱们为这个在Java类里编码并映射这个关联:
public class Person { private Set events = new HashSet(); public Set getEvents() { return events; } public void setEvents(Set events) { this.events = events; } }
在咱们映射这个关联以前,先考虑这个关联另一端。很显然的,咱们能够保持这个关联是单向的。若是咱们但愿这个关联是双向的, 咱们能够在Event里建立另一个集合,例如:anEvent.getParticipants()。 这是留给你的一个设计选项,可是从这个讨论中咱们能够很清楚的了解什么是关联的阶数(multiplicity):在这个关联的两端都是“多”。 咱们叫这个为:多对多(many-to-many)关联。所以,咱们使用Hibernate的many-to-many映射:
<class name="Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="increment"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> <set name="events" table="PERSON_EVENT"> <key column="PERSON_ID"/> <many-to-many column="EVENT_ID" class="Event"/> </set> </class>
Hibernate支持全部种类的集合映射,<set>是最广泛被使用的。对于多对多(many-to-many)关联(或者叫n:m实体关系), 须要一个用来储存关联的表(association table)。表里面的每一行表明从一个person到一个event的一个关联。 表名是由set元素的table属性值配置的。关联里面的标识字段名,person的一端,是 由<key>元素定义,event一端的字段名是由<many-to-many>元素的 column属性定义的。你也必须告诉Hibernate集合中对象的类(也就是位于这个集合所表明的关联另一端的类)。
这个映射的数据库表定义以下:
_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | |_____________| |__________________| | PERSON | | | | | |_____________| | *EVENT_ID | <--> | *EVENT_ID | | | | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | |_____________| | FIRSTNAME | | LASTNAME | |_____________|
让咱们把一些people和event放到EventManager的一个新方法中:
private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); aPerson.getEvents().add(anEvent); tx.commit(); HibernateUtil.closeSession(); }
在加载一个Person和一个Event以后,简单的使用普通的方法修改集合。 如你所见,没有显式的update()或者save(), Hibernate自动检测到集合已经被修改 并须要保存。这个叫作automatic dirty checking,你也能够尝试修改任何对象的name或者date的参数。 只要他们处于persistent状态,也就是被绑定在某个Hibernate Session上(例如:他们 刚刚在一个单元操做从被加载或者保存),Hibernate监视任何改变并在后台隐式执行SQL。同步内存状态和数据库的过程,一般只在 一个单元操做结束的时候发生,这个过程被叫作flushing。
你固然也能够在不一样的单元操做里面加载person和event。或者在一个Session之外修改一个 不是处在持久化(persistent)状态下的对象(若是该对象之前曾经被持久化,咱们称这个状态为脱管(detached))。 在程序里,看起来像下面这样:
private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); tx.commit(); HibernateUtil.closeSession(); aPerson.getEvents().add(anEvent); // aPerson is detached Session session2 = HibernateUtil.currentSession(); Transaction tx2 = session.beginTransaction(); session2.update(aPerson); // Reattachment of aPerson tx2.commit(); HibernateUtil.closeSession(); }
对update的调用使一个脱管对象(detached object)从新持久化,你能够说它被绑定到 一个新的单元操做上,因此任何你对它在脱管(detached)状态下所作的修改都会被保存到数据库里。
这个对咱们当前的情形不是颇有用,可是它是很是重要的概念,你能够把它设计进你本身的程序中。如今,加进一个新的 选项到EventManager的main方法中,并从命令行运行它来完成这个练习。若是你须要一个person和 一个event的标识符 - save()返回它。*******这最后一句看不明白
上面是一个关于两个同等地位的类间关联的例子,这是在两个实体之间。像前面所提到的那样,也存在其它的特别的类和类型,这些类和类型一般是“次要的”。 其中一些你已经看到过,好像int或者String。咱们称呼这些类为值类型(value type), 它们的实例依赖(depend)在某个特定的实体上。这些类型的实例没有本身的身份(identity),也不能在实体间共享 (好比两个person不能引用同一个firstname对象,即便他们有相同的名字)。固然,value types并不只仅在JDK中存在 (事实上,在一个Hibernate程序中,全部的JDK类都被视为值类型),你也能够写你本身的依赖类,例如Address, MonetaryAmount。
你也能够设计一个值类型的集合(collection of value types),这个在概念上与实体的集合有很大的不一样,可是在Java里面看起来几乎是同样的。
咱们把一个值类型对象的集合加入Person。咱们但愿保存email地址,因此咱们使用String, 而此次的集合类型又是Set:
private Set emailAddresses = new HashSet(); public Set getEmailAddresses() { return emailAddresses; } public void setEmailAddresses(Set emailAddresses) { this.emailAddresses = emailAddresses; }
Set的映射
<set name="emailAddresses" table="PERSON_EMAIL_ADDR"> <key column="PERSON_ID"/> <element type="string" column="EMAIL_ADDR"/> </set>
比较此次和较早先的映射,差异主要在element部分此次并无包括对其它实体类型的引用,而是使用一个元素类型是 String的集合(这里使用小写的名字是向你代表它是一个Hibernate的映射类型或者类型转换器)。 和之前同样,set的table参数决定用于集合的数据库表名。key元素 定义了在集合表中使用的外键。element元素的column参数定义实际保存String值 的字段名。
看一下修改后的数据库表定义。
_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | ___________________ |_____________| |__________________| | PERSON | | | | | | | |_____________| | PERSON_EMAIL_ADDR | | *EVENT_ID | <--> | *EVENT_ID | | | |___________________| | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | | *EMAIL_ADDR | |_____________| | FIRSTNAME | |___________________| | LASTNAME | |_____________|
你能够看到集合表(collection table)的主键其实是个复合主键,同时使用了2个字段。这也暗示了对于同一个 person不能有重复的email地址,这正是Java里面使用Set时候所须要的语义(Set里元素不能重复)。
你如今能够试着把元素加入这个集合,就像咱们在以前关联person和event的那样。Java里面的代码是相同的。
下面咱们将映射一个双向关联(bi-directional association)- 在Java里面让person和event能够从关联的 任何一端访问另外一端。固然,数据库表定义没有改变,咱们仍然须要多对多(many-to-many)的阶数(multiplicity)。一个关系型数据库要比网络编程语言 更加灵活,因此它并不须要任何像导航方向(navigation direction)的东西 - 数据能够用任何可能的方式进行查看和获取。
首先,把一个参与者(person)的集合加入Event类中:
private Set participants = new HashSet(); public Set getParticipants() { return participants; } public void setParticipants(Set participants) { this.participants = participants; }
在Event.hbm.xml里面也映射这个关联。
<set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="Person"/> </set>
如你所见,2个映射文件里都有一般的set映射。注意key和many-to-many 里面的字段名在两个映射文件中是交换的。这里最重要的不一样是Event映射文件里set元素的 inverse="true"参数。
这个表示Hibernate须要在两个实体间查找关联信息的时候,应该使用关联的另一端 - Person类。 这将会极大的帮助你理解双向关联是如何在咱们的两个实体间建立的。
首先,请牢记在心,Hibernate并不影响一般的Java语义。 在单向关联中,咱们是怎样在一个Person和一个Event之间建立联系的? 咱们把一个Event的实例加到一个Person类内的Event集合里。因此,显然若是咱们要让这个关联能够双向工做, 咱们须要在另一端作一样的事情 - 把Person加到一个Event类内的Person集合中。 这“在关联的两端设置联系”是绝对必要的并且你永远不该该忘记作它。
许多开发者经过建立管理关联的方法来保证正确的设置了关联的两端,好比在Person里:
protected Set getEvents() { return events; } protected void setEvents(Set events) { this.events = events; } public void addToEvent(Event event) { this.getEvents().add(event); event.getParticipants().add(this); } public void removeFromEvent(Event event) { this.getEvents().remove(event); event.getParticipants().remove(this); }
注意如今对于集合的get和set方法的访问控制级别是protected - 这容许在位于同一个包(package)中的类以及继承自这个类的子类 能够访问这些方法,可是禁止其它的直接外部访问,避免了集合的内容出现混乱。你应该尽量的在集合所对应的另一端也这样作。
inverse映射参数究竟表示什么呢?对于你和对于Java来讲,一个双向关联仅仅是在两端简单的设置引用。然而仅仅这样 Hibernate并无足够的信息去正确的产生INSERT和UPDATE语句(以免违反数据库约束), 因此Hibernate须要一些帮助来正确的处理双向关联。把关联的一端设置为inverse将告诉Hibernate忽略关联的 这一端,把这端当作是另一端的一个镜子(mirror)。这就是Hibernate所需的信息,Hibernate用它来处理如何把把 一个数据导航模型映射到关系数据库表定义。 你仅仅须要记住下面这个直观的规则:全部的双向关联须要有一端被设置为inverse。在一个一对多(one-to-many)关联中 它必须是表明多(many)的那端。而在多对多(many-to-many)关联中,你能够任意选取一端,两端之间并无差异。
这个教程覆盖了关于开发一个简单的Hibernate应用程序的几个基础方面。
若是你已经对Hibernate感到自信,继续浏览开发指南里你感兴趣的内容-那些会被问到的问题大可能是事务处理 (第 12 章 事务和并发), 抓取(fetch)的效率 (第 20 章 提高性能 ),或者API的使用 (第 11 章 与对象共事)和查询的特性(第 11.4 节 “查询”)。
不要忘记去Hibernate的网站查看更多(有针对性的)教程