Hibernate入门01 - 配置文件
Hibernate可以使用XML或属性档案来配置SessionFactory,预设的配置文件名称为hibernate.cfg.xml或hibernate.properties。
上一个主题中所示范的为使用XML文件的方式,一个XML文件的例子如下:
hibernate.cfg.xml
<?xml version='1.0' encoding='big5'?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 显示实际操作数据库时的SQL -->
<property name="show_sql">true</property>
<!-- SQL方言,这边设定的是MySQL -->
<property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
<!-- JDBC驱动程序 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- JDBC URL -->
<property name="connection.url">jdbc:mysql://localhost/HibernateTest</property>
<!-- 数据库使用者 -->
<property name="connection.username">caterpillar</property>
<!-- 数据库密码 -->
<property name="connection.password">123456</property>
<!-- 对象与数据库表格映像文件 -->
<mapping resource="onlyfun/caterpillar/User.hbm.xml"/>
<mapping resource="onlyfun/caterpillar/Item.hbm.xml"/>
</session-factory>
</hibernate-configuration>
使用XML文件进行配置时,可以在当中指定对象与数据库表格的映像文件位置,XML配置文件的位置必须在CLASSPATH的设定中,例如单机执行时主 程序的位置,或是Web程序的WEB-INF/classes中,我们使用下面的方式来读入XML文件以配置Hibernate:
SessionFactory sf = new Configuration().configure().buildSessionFactory();
Configuration表示Java对象与数据库表格映像的集合,并用于之后建立SessionFactory,之后Configuration就不再有作用。预设的XML文件名称是hibernate.cfg.xml,您也可以指定文件的名称,例如:
SessionFactory sf = new Configuration()
.configure("db.cfg.xml")
.buildSessionFactory();
除了使用XML文件进行配置,我们也可以使用属性档案进行配置,文件名称是hibernate.properties,一个例子如下:
hibernate.properties
hibernate.show_sql = true
hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/HibernateTest
hibernate.connection.username = caterpillar
hibernate.connection.password = 123456
hibernate.properties的位置必须在CLASSPATH的设定中,例如单机执行时主程序的位置,或是Web程序的WEB-INF/classes中,而为了要取得对象至数据库表格的映像文件,我们必须在程序中如下加载:
Configuration cfg = new Configuration()
.addClass(onlyfun.caterpillar.User.class)
.addClass(onlyfun.caterpillar.Item.class);
这么一来,程序会自动加载onlyfun/caterpillar/User.hbm.xml与onlyfun/caterpillar/Item.hbm.xml,完成Hibernate配置之后,我们可以如下取得SessionFactory:
SessionFactory sessions = cfg.buildSessionFactory();
其它更多有关Hibernate配置的细节,您可以查看Hibernate参考手册。
Hibernate入门02 - 提供JDBC连接
如果需要的话,您可以自行提供JDBC连接对象给Hibernate使用,而无需透过配置文件设定JDBC来源,一个最简单的例子如下:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/HibernateTest?user=root&password=";
java.sql.Connection conn = DriverManager.getConnection(url);
SessionFactory sessionFactory = cfg.buildSessionFactory();
Session session = sessionFactory.openSession(conn);
当然您也可以透过属性文件hibernate.properties来配置JDBC来源,例如:
hibernate.properties
hibernate.show_sql = true
hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/HibernateTest
hibernate.connection.username = caterpillar
hibernate.connection.password = 123456
如果是透过XML文件hibernate.cfg.xml则是如下进行配置:
hibernate.cfg.xml
<?xml version='1.0' encoding='big5'?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 显示实际操作数据库时的SQL -->
<property name="show_sql">true</property>
<!-- SQL方言,这边设定的是MySQL -->
<property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
<!-- JDBC驱动程序 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- JDBC URL -->
<property name="connection.url">jdbc:mysql://localhost/HibernateTest</property>
<!-- 数据库使用者 -->
<property name="connection.username">caterpillar</property>
<!-- 数据库密码 -->
<property name="connection.password">123456</property>
<!-- 对象与数据库表格映像文件 -->
<mapping resource="User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Hibernate在数据库连接池的使用上是可选的,您可以使用C3P0连接池,当您的属性文件中含有hibernate.c3p0.*的配置时,就会 自动启用C3P0连接池,而您的CLASSPATH中必须包括c3p0-
hibernate.properties
hibernate.show_sql = true
hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://localhost/HibernateTest
hibernate.connection.username = root
hibernate.connection.password =
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
如果是使用hibernate.cfg.xml配置C3P0连接池的例子如下:
hibernate.cfg.xml
<?xml version='1.0' encoding='big5'?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 显示实际操作数据库时的SQL -->
<property name="show_sql">true</property>
<!-- SQL方言,这边设定的是MySQL -->
<property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
<!-- JDBC驱动程序 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- JDBC URL -->
<property name="connection.url">jdbc:mysql://localhost/HibernateTest</property>
<!-- 数据库使用者 -->
<property name="connection.username">root</property>
<!-- 数据库密码 -->
<property name="connection.password"></property>
<property name="c3p0.min_size">5</property>
<property name="c3p0.max_size">20</property>
<property name="c3p0.timeout">1800</property>
<property name="c3p0.max_statements">50</property>
<!-- 对象与数据库表格映像文件 -->
<mapping resource="User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
您也可以使用Proxool或DBCP连接池,只要在配置文件中配置hibernate.proxool.或hibernate.dbcp. 等相关选项,这可以在hibernate的etc目录中找hibernate.properties中的配置例子来参考,当然要记得在CLASSPATH 中加入相关的jar档案。
如果您使用Tomcat的话,您也可以透过它提供的DBCP连接池来取得连接,您可以先参考这边的文章来设定Tomcat的DBCP连接池:
DBCP连接池设定
设定好容器提供的DBCP连接池之后,您只要在配置文件中加入connection.datasource属性,例如在hibernate.cfg.xml中加入:
hibernate.cfg.xml
<property name="connection.datasource">java:comp/env/jdbc/dbname</property>
如果是在hibernate.properties中的话,则加入:
hibernate.properties
hibernate.connection.datasource = java:comp/env/jdbc/dbname
Hibernate入门03 - 基本数据查询
使用Hibernate进行数据查询是一件简单的事,Java程序设计人员可以使用对象操作的方式来进行数据查询,查询时使用一种类似SQL的HQL (Hibernate Query Language)来设定查询的条件,与SQL不同的是,HQL是具备对象导向的继承、多型等特性的语言。
直接使用范例来看看如何使用Hibernate进行数据库查询,在这之前,请先照之前介绍过的主题在数据库中新增几笔数据:
在Hibernate中新增资料
查询数据时,我们所使用的是Session的find()方法,并在当中指定HQL设定查询条件,查询的结果会装载在List对象中传回,您所需要的是将它们一一取出,一个最简单的例子如下:
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
}
}
find()中的"from User"即HQL,User指的是User类别,藉由映射文件,它将会查询USER表格中的数据,相当于SQL中的SELECT * FROM USER,实际上我们的User类别是位于onlyfun.caterpillar下,Hibernate会自动看看import中的package名称与类别名称是否符合,您也可以直接指定package名称,例如:
session.find("from onlyfun.caterpillar.User");
这个程序的运行结果可能是这样的:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
caterpillar
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
上面所介绍的查询是最简单的,只是从数据表中查询所有的数据,Hibernate所查询得回的数据,是以对象的方式传回,以符合程序中操作的需要,我们也可以限定一些查询条件,并只传回我们指定的字段,例如:
List names = session.find("select user.name from User as user where age = 25");
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
在find()中的HQL示范了条件限定的查询,User as user为User类别取了别名,所以我们就可以使用user.name来指定表格传回字段,where相当于SQL中的WHERE子句,我们限定查询 age等于25的数据,这次查询的数据只有一个字段,而型态是String,所以传回的List内容都是String对象,一个运行的例子如下:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.name as x0_0_ from USER user0_ where (age=25 )
name: momor
name: Bush
如果要传回两个以上的字段,也不是什么问题,直接来看个例子:
List results = session.find("select user.name, user.age from User as user where sex = 'F'");
for (ListIterator iterator = results.listIterator(); iterator.hasNext(); ) {
Object[] rows = (Object[]) iterator.next();
String name = (String) rows[0];
Integer age = (Integer) rows[1];
System.out.println("name: " + name + "\n\t" + age);
}
从上面的程序中不难看出,传回两个以上字段时,每一次ListIterator会以Object数组的方式传回一笔数据,我们只要指定数组索引,并转换为适当的型态,即可取得数据,一个查询的结果如下:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.name as x0_0_, user0_.age as x1_0_ from USER user0_ where (sex='F' )
name: momor
25
name: Becky
35
您也可以在HQL中使用一些函数来进行结果统计,例如:
List results = session.find("select count(*), avg(user.age) from User as user");
ListIterator iterator = results.listIterator();
Object[] rows = (Object[]) iterator.next();
System.out.println("资料笔数: " + rows[0] + "\n平均年龄: " + rows[1]);
一个查询的结果如下所示:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select count(*) as x0_0_, avg(user0_.age) as x1_0_ from USER user0_
资料笔数: 4
平均年龄: 28.25
这边我们先介绍的是一些简单的查询动作,将来有机会的话,再介绍一些进阶的查询,如果您有想要先认识一些HQL,可以看看参考手册的第11章,当中对于HQL有详细的说明。
Hibernate入门04 - Query接口
除了直接使用find()方法并配合HQL来进行查询之外,我们还可以透过 net.sf.hibernate.Query接口的实例来进行查询,透过Query接口,您可以先设定查询参数,之后透过setXXX()等方法,将指定的参数值填入,而不用每次都撰写完整的HQL,直接来看个例子:
Query query = session.createQuery("select user.name from User as user where user.age = ? and user.sex = ?");
query.setInteger(0, 25);
query.setCharacter(1, 'M');
List names = query.list();
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
在设定参数值时,必须依照 ? 所设定的顺序,并使用对应型态的setXXX()方法,一个执行的例子如下:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.name as x0_0_ from USER user0_ where (user0_.age=? )and(user0_.sex=? )
name: Bush
您可以使用命名参数(Named Parameter)来取代这个方法,这可以不用依照特定的顺序来设定参数值,并拥有较好的可读性,直接来看个例子:
Query query = session.createQuery("select user.name from User as user where user.age = :age and user.sex = :sex");
query.setInteger("age", 25);
query.setCharacter("sex", 'M');
List names = query.list();
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
设定命名参数时,在建立Query时先使用:后跟着参数名,之后我们就可以在setXXX()方法中直接指定参数名来设定参数值,而不用依照特定的顺序。
我们也可以将HQL撰写在程序之外,以避免硬编码(hard code)在程序之中,在需要修改HQL时就很方便,在*.hbm.xml中使用<query/>标签,并在<![CDATA[与]] >之间撰写HQL,撰写的位置是在</hibernate-mapping>之前,例如:
User.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" type="string">
<column name="user_id" sql-type="char(32)" />
<generator class="uuid.hex"/>
</id>
<property name="name" type="string" not-null="true">
<column name="name" length="16" not-null="true"/>
</property>
<property name="sex" type="char"/>
<property name="age" type="int"/>
</class>
<query name="onlyfun.caterpillar.queryUser">
<![CDATA[
select user.name from User as user where user.age = :age and user.sex = :sex
]]>
</query>
</hibernate-mapping>
<query>的name属性用来设定查询外部HQL时的名称依据,使用的例子如下:
Query query = session.getNamedQuery("onlyfun.caterpillar.queryUser");
query.setInteger("age", 25);
query.setCharacter("sex", 'M');
List names = query.list();
for (ListIterator iterator = names.listIterator(); iterator.hasNext(); ) {
String name = (String) iterator.next();
System.out.println("name: " + name);
}
更多有关于Hibernate查询的操作,您可以查看参考手册的第九章内容。
Hibernate入门05 - 更新,删除数据
如果您是在同一个Session中取出数据并想要马上进行更新,则只要先查询并取出对象,透过setXXX()方法设定好新的值,然后呼叫session.flush()即可在同一个Session中更新指定的数据,例如:
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
User updated = null;
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
if(updated == null)
updated = user;
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
updated.setName("justin");
session.flush();
users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
}
}
这个程序会显示数据表中的所有数据,并将数据表中的第一笔数据更新,一个执行的结果如下:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
caterpillar
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate: update USER set name=?, sex=?, age=? where user_id=?
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
justin
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
如果您开启了一个Session,从数据表中取出数据显示到使用者接口上,之后关闭Session,当使用者在接口上操作完毕并按下储存时,这时您要重新开启一个Session,使用update()方法将对象中的数据更新至对应的数据表中,一个例子如下:
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
// 关闭这个Session
session.close();
User updated = null;
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
if(updated == null)
updated = user;
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
// 使用者作一些操作,之后储存
updated.setName("caterpillar");
// 开启一个新的Session
session = sessionFactory.openSession();
// 更新数据
session.update(updated);
users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
}
}
这个程序执行的结果范例如下,您可以看看实际上执行了哪些SQL:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
justin
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate: update USER set name=?, sex=?, age=? where user_id=?
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
caterpillar
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate提供了一个saveOrUpdate()方法,为数据的储存或更新提供了一个统一的操作接口,藉由定义映像文件时,设定<id>标签的unsaved-value来决定什么是新的值必需,什么是已有的值必须更新:
User.hbm.xml
<id name="id" type="string" unsaved-value="null">
<column name="user_id" sql-type="char(32)" />
<generator class="uuid.hex"/>
</id>
unsaved-value
可以设定的值包括:
§ any - 总是储存
§ none - 总是更新
§ null - id为null时储存(预设)
§ valid - id为null或是指定值时储存
这样设定之后,您可以使用session.saveOrUpdate(updated);来取代上一个程序的session.update(updated);方法。
如果要删除数据,只要使用delete()方法即可,直接看个例子。
HibernateTest.java
import onlyfun.caterpillar.*;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;
import java.util.*;
public class HibernateTest {
public static void main(String[] args) throws HibernateException {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
List users = session.find("from User");
User updated = null;
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
if(updated == null)
updated = user;
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
session.delete(updated);
users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
}
}
}
一个执行的结果范例如下:
log4j:WARN No appenders could be found for logger (net.sf.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
justin
Age: 28
Sex: M
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate: delete from USER where user_id=?
Hibernate: select user0_.user_id as user_id, user0_.name as name, user0_.sex as sex, user0_.age as age from USER user0_
momor
Age: 25
Sex: F
Bush
Age: 25
Sex: M
Becky
Age: 35
Sex: F
Hibernate对于数据的更新、删除等动作,是依懒id值来判定,如果您已知id值,则可以使用load()方法来加载资料,例如:
User user = (User) session.load(User.class, id);
更多有关于Hibernate资料更新操作的说明,您可以看看参考手册的第九章内容。
Hibernate入门06 - 继承映射1
如果应用程序中的对象有继承的关系,我们可以有三种策略将这种关系映像至数据表上。
最简单的方式就是给每个对象一个表格,如果父类别User中有field1、field2两个属性,其表格USER有FIELD1、FIELD2与之对 应,而子类别SubUser若继承了父类别的field1、field2属性,表格中SUBUSER中也要拥有FIELD1、FIELD2与之对应,这种方法的好处只有映射上的方便,很显然的,父类与子类共有的属性,会变成在数据库表格中重复的字段,而且很难实现多型操作,建议只有在不需要多型操作时使 用,要执行这种映射,为每一个子类别撰写一个映射文件就是了,没什么特别的设定。
第二种方式是将所有继承同一父类别的对象储存在同一个表格中,表格中使用识别字段来表示某一列(row)是属于某个子类别或父类别,这种方式方便执行多型操作,而且兼具效能上的考量,在这个主题中我们将先说明这个方法。
我们先来看看我们撰写的类别与继承关系,首先是父类别:
User.java
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private String password;
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPassword() {
return password;
}
public void setId(String string) {
id = string;
}
public void setName(String string) {
name = string;
}
public void setPassword(String password) {
this.password = password;
}
}
再来是继承User类别的两个子类别,首先是PowerUser类别:
PowerUser.java
package onlyfun.caterpillar;
public class PowerUser extends User {
private int level;
private String otherOfPower;
public int getLevel() {
return level;
}
public String getOtherOfPower() {
return otherOfPower;
}
public void setLevel(int level) {
this.level = level;
}
public void setOtherOfPower(String otherOfPower) {
this.otherOfPower = otherOfPower;
}
}
下面是继承User类别的GuestUser类别:
GuestUser.java
package onlyfun.caterpillar;
public class GuestUser extends User {
private String otherOfGuest;
public String getOtherOfGuest() {
return otherOfGuest;
}
public void setOtherOfGuest(String otherOfGuest) {
this.otherOfGuest = otherOfGuest;
}
}
映射文件中该如何撰写,由于这些类别将映像至同一个表格,我们使用discriminator作为每个类别记录在表格中的识别,先直接看看映像文件如何撰写:
User.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER" discriminator-value="ParentUser">
<id name="id" type="string" unsaved-value="null">
<column name="ID" sql-type="char(32)"/>
<generator class="uuid.hex"/>
</id>
<discriminator column="DISCRIMINATOR_USERTYPE" type="string"/>
<property name="name" type="string" not-null="true">
<column name="NAME" length="16" not-null="true"/>
</property>
<property name="password" type="string" not-null="true">
<column name="PASSWORD" length="16" not-null="true"/>
</property>
<subclass name="onlyfun.caterpillar.PowerUser" discriminator-value="POWER">
<property name="level" type="integer" column="POWERUSER_LEVEL"/>
<property name="otherOfPower" type="string" column="POWER_OTHER"/>
</subclass>
<subclass name="onlyfun.caterpillar.GuestUser" discriminator-value="GUEST">
<property name="otherOfGuest" type="string" column="GUEST_OTHER"/>
</subclass>
</class>
</hibernate-mapping>
在表格中,我们增加一个字段DISCRIMINATOR_USERTYPE来记录储存的类别是属于User、PowerUser或是 GuestUser的记录,如果该字段是ParentUser,则表示该笔数据是User类别,如果是POWER,表示是PowerUser的记录,如果是GUEST,表示是GuestUser的记录,在映像子类别时,使用<subclass>指明映像的子类别以及其 discriminator-value。
我们可以在数据库中建立数据表格如下:
create table USER (
ID char(32) not null,
DISCRIMINATOR_USERTYPE varchar(255) not null,
NAME varchar(16) not null,
PASSWORD varchar(16) not null,
POWERUSER_LEVEL integer,
POWER_OTHER varchar(255),
GUEST_OTHER varchar(255),
primary key (ID)
);
您可以将资料表的建立工作,透过SchemaExportTask来自动建立,您可以参考这篇介绍:
使用SchemaExportTask
假设我们在程序中如下储存数据的话:
PowerUser pu = new PowerUser();
pu.setName("caterpillar");
pu.setPassword("123456");
pu.setLevel(1);
pu.setOtherOfPower("PowerUser's field");
GuestUser gu = new GuestUser();
gu.setName("momor");
gu.setPassword("654321");
gu.setOtherOfGuest("GuestUser's field");
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(pu);
session.save(gu);
tx.commit();
session.close();
则资料表中将会有以下的内容(没有显示ID字段):
+------------------------+-------------+----------+-----------------+-------------------+-------------------+
| DISCRIMINATOR_USERTYPE | NAME | PASSWORD | POWERUSER_LEVEL | POWER_OTHER | GUEST_OTHER |
+------------------------+-------------+----------+-----------------+-------------------+-------------------+
| POWER | caterpillar | 123456 | 1 | PowerUser's field | NULL |
| GUEST | momor | 654321 | NULL | NULL | GuestUser's field |
+------------------------+-------------+----------+-----------------+-------------------+-------------------+
您可以观察实际的储存方式,注意DISCRIMINATOR_USERTYPE字段,它用以标示该列属于哪一个类别的数据,如果要查询数据的话,例如查询所有PowerUser的数据,我们只要如下进行:
Session session = sessionFactory.openSession();
List users = session.find("from PowerUser");
session.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
PowerUser user = (PowerUser) iterator.next();
System.out.println(user.getName() +
"\n\tPassword: " + user.getPassword());
System.out.println("\tPower: " + user.getOtherOfPower() +
"\n\tLevel: " + user.getLevel());
}
您可以观察Hibernate真正所执行的SQL的内容,看看where就知道它如何查询PowerUser的数据:
{code:borderStyle=solid}select poweruser0_.ID as ID,
poweruser0_.POWERUSER_LEVEL as POWERUSE5_,
poweruser0_.POWER_OTHER as POWER_OT6_,
poweruser0_.NAME as NAME,
poweruser0_.PASSWORD as PASSWORD
from USER poweruser0_ where poweruser0_.DISCRIMINATOR_USERTYPE='POWER';
使用session.find("from GuestUser");就可以查询GuestUser的数据,您也可以取回所有User型态的数据,例如:
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"\n\tPassword: " + user.getPassword());
if(user instanceof PowerUser)
System.out.println("\tPower: " + ((PowerUser)user).getOtherOfPower() +
"\n\tLevel: " + ((PowerUser)user).getLevel());
else
System.out.println("\tGuest: " + ((GuestUser)user).getOtherOfGuest());
}
Hibernate可以使用父类别来取得所有的子类别数据,我们知道所有的Java类别都继承自Object,所以如果您使用session.find("from java.lang.Object");,就将会取回数据库中所有表格的数据。
有关于继承关系映射的第三种作法,将留待下一个主题说明。
Hibernate入门07 - 继承映射2
接续上一个主题,我们来看看继承关系映像的第三种方式,我们给予父类别与每个子类别一个表格,与第一个方法不同的是,父类别映像的表格与子类别映像的表格共享相同的主键值,父类别表格只记录本身的属性,如果要查询的是子类别,则透过外键参考从父类别表格中取得继承而来的属性数据。
直接以图片说明会比较容易理解,我们使用前一个主题中的User、PowerUser与GuestUser类别作说明,类别继承图如下:
其映像至数据库表格的关系如下:
其中POWER_USER与GUEST_USER表格的主键值将与USER表格的主键值相同,POWER_USER_ID与GUEST_USER_ID作为一个外键参考,以取得父类别映像表格的NAME与PASSWORD数据。
在映射文件中要实现这种映像,我们使用<joined-subclass>卷标,并使用<key>卷标指定子类别表格与父类别表格共享的主键值,映像文件的撰写方式如下:
User.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" type="string" unsaved-value="null">
<column name="USER_ID"/>
<generator class="uuid.hex"/>
</id>
<property name="name" type="string" not-null="true">
<column name="NAME" length="16" not-null="true"/>
</property>
<property name="password" type="string" not-null="true">
<column name="PASSWORD" length="16" not-null="true"/>
</property>
<joined-subclass name="onlyfun.caterpillar.PowerUser" table="POWER_USER">
<key column="POWER_USER_ID"/>
<property name="level" type="int" column="POWER_USER_LEVEL"/>
<property name="otherOfPower" type="string" column="POWER_OTHER"/>
</joined-subclass>
<joined-subclass name="onlyfun.caterpillar.GuestUser" table="GUEST_USER">
<key column="GUEST_USER_ID"/>
<property name="otherOfGuest" type="string" column="GUEST_OTHER"/>
</joined-subclass>
</class>
</hibernate-mapping>
您可以自行建立数据库与表格,当然使用SchemaExportTask帮您自动建立表格是更方便的,下面是SchemaExportTask自动建立表格时所使用的SQL:
[schemaexport] alter table GUEST_USER drop constraint FKB
[schemaexport] alter table POWER_USER drop constraint FK
[schemaexport] drop table if exists USER;
[schemaexport] drop table if exists GUEST_USER;
[schemaexport] drop table if exists POWER_USER;
[schemaexport] create table USER (
[schemaexport] USER_ID varchar(255) not null,
[schemaexport] NAME varchar(16) not null,
[schemaexport] PASSWORD varchar(16) not null,
[schemaexport] primary key (USER_ID)
[schemaexport] );
[schemaexport] create table GUEST_USER (
[schemaexport] GUEST_USER_ID varchar(255) not null,
[schemaexport] GUEST_OTHER varchar(255),
[schemaexport] primary key (GUEST_USER_ID)
[schemaexport] );
[schemaexport] create table POWER_USER (
[schemaexport] POWER_USER_ID varchar(255) not null,
[schemaexport] POWER_USER_LEVEL integer,
[schemaexport] POWER_OTHER varchar(255),
[schemaexport] primary key (POWER_USER_ID)
[schemaexport] );
[schemaexport] alter table GUEST_USER add index FKB
add constraint FKB
[schemaexport] alter table POWER_USER add index FK
add constraint FK
至于在Java程序的撰写方面,您可以直接使用前一个主题中所写的测试程序(您可以看到,Hibernate将程序撰写与数据库处理的细节分开了),假设我们使用上一个主题的测试程序新增了两笔数据,则数据库表格的结果如下:
mysql> select * from user;
+----------------------------------+-------------+----------+
| USER_ID | NAME | PASSWORD |
+----------------------------------+-------------+----------+
| 297e3dbdff0af72900ff0af72d4b0001 | caterpillar | 123456 |
| 297e3dbdff0af72900ff0af72d4b0002 | momor | 654321 |
+----------------------------------+-------------+----------+
2 rows in set (0.05 sec)
mysql> select * from power_user;
+----------------------------------+------------------+-------------------+
| POWER_USER_ID | POWER_USER_LEVEL | POWER_OTHER |
+----------------------------------+------------------+-------------------+
| 297e3dbdff0af72900ff0af72d4b0001 | 1 | PowerUser's field |
+----------------------------------+------------------+-------------------+
1 row in set (0.00 sec)
mysql> select * from guest_user;
+----------------------------------+-------------------+
| GUEST_USER_ID | GUEST_OTHER |
+----------------------------------+-------------------+
| 297e3dbdff0af72900ff0af72d4b0002 | GuestUser's field |
+----------------------------------+-------------------+
1 row in set (0.00 sec)
了解一下储存数据至数据库时所使用的SQL语句有助于您了解底层的运作方式,新增两笔数据的SQL如下:
Hibernate: insert into USER (NAME, PASSWORD, USER_ID) values (?, ?, ?)
Hibernate: insert into POWER_USER (POWER_USER_LEVEL, POWER_OTHER, POWER_USER_ID) values (?, ?, ?)
Hibernate: insert into USER (NAME, PASSWORD, USER_ID) values (?, ?, ?)
Hibernate: insert into GUEST_USER (GUEST_OTHER, GUEST_USER_ID) values (?, ?)
有兴趣的话,也可以看一下查询数据时的SQL语句,我们可以看到实际上使用inner join来查询数据,以下是查询PowerUser所使用的SQL:
select poweruser0_.POWER_USER_ID as USER_ID,
poweruser0_.POWER_USER_LEVEL as POWER_US2_1_,
poweruser0_.POWER_OTHER as POWER_OT3_1_,
poweruser0__1_.NAME as NAME0_,
poweruser0__1_.PASSWORD as PASSWORD0_
from POWER_USER poweruser0_
inner join USER poweruser0__1_ on poweruser0_.POWER_USER_ID=poweruser0__1_.USER_ID
Hibernate入门8 - Component 映射
考虑这么一个对象类别:
User.java
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private char sex;
private int age;
private String address;
........
}
我们建立一个数据库表格与这个类别作对应:
create table USER (
ID char(32) not null,
NAME varchar(16) not null,
SEX char(1),
AGE integer,
ADDRESS varchar(255) not null,
primary key (ID)
);
这样的对应,您可以依照之前所介绍过的主题来撰写映像文件达成,然而现在我们想要将address属性以一个自订对象Email表示,以便我们将这个自 订对象当作辅助对象,在适当的时候,我们可以直接呼叫Email对象的sendMail()方法,也就是说,我们的User类别变成:
User.java
package onlyfun.caterpillar;
public class User {
private String id;
private String name;
private char sex;
private int age;
private Email email;
public int getAge() {
return age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public char getSex() {
return sex;
}
public Email getEmail() {
return email;
}
public void setAge(int i) {
age = i;
}
public void setId(String string) {
id = string;
}
public void setName(String string) {
name = string;
}
public void setSex(char c) {
sex = c;
}
public void setEmail(Email email) {
this.email = email;
}
}
而我们新增的Email类别设计如下:
Email.java
package onlyfun.caterpillar;
public class Email {
private String address;
public void setAddress(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
public void sendMail() {
System.out.println("send mail to: " + address);
}
}
实际上,我们只是将使用者的Email信息抽取出来,并独立为一个辅助对象,这是为了在应用程序中操作方便而设计的,而实际上在数据库表格中的信息并没有增加,我们不用改变数据表的内容。
显然的,如果这么设计,对象与数据表并不是一个对映一个,对象的数量将会比数据表来的多,对象的设计粒度比数据表来的细,为了完成对象与数据表的对应,在 Hibernate中使用<component>标签来进行设定,我们的User.hbm.xml映射文件撰写如下:
User.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" type="string" unsaved-value="null">
<column name="ID" sql-type="char(32)"/>
<generator class="uuid.hex"/>
</id>
<property name="name" type="string" not-null="true">
<column name="NAME" length="16" not-null="true"/>
</property>
<property name="sex" type="char" column="SEX"/>
<property name="age" type="int" column="AGE"/>
<component name="email" class="onlyfun.caterpillar.Email">
<property name="address" type="string" column="ADDRESS" not-null="true"/>
</component>
</class>
</hibernate-mapping>
这个映射文件与之前的映射文件撰写相比,主要是多了红色的<component>部份,将Email类别当作User类别的一个组件(Component)。
接下来对对象的操作并没有什么太大的差别,例如下面的程序片段示范如何储存数据:
User user = new User();
user.setName("caterpillar");
user.setSex('M');
user.setAge(28);
Email email = new Email();
email.setAddress("justin@caterpillar.onlyfun.net");
user.setEmail(email);
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user);
tx.commit();
session.close();
下面的程序片段则示范如何查询数据:
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName() +
"\n\tAge: " + user.getAge() +
"\n\tSex: " + user.getSex());
user.getEmail().sendMail();
}
关于Component进一步的设定说明,可以看看参考手册的
Hibernate入门9 - Set 映射
这个主题介绍如果在对象中包括集合对象,像是使用HashSet来包括其它对象时,该如何进行对象与数据表的映像,像Set这样的集合,可以包括所有的Java对象,这边先介绍当Set中包括的对象没有实体(Entiy)时的映像方式。
(简单的说,也就是所包括的对象没有对象识别(identity)值,没有数据库层次上的识别值之表格与之对应的对象,只是纯綷的值型态(value type)对象,关于Entity与value type的说明,可以看看参考手册
假设我们有一个User类别,当中除了名称属性之外,另一个就是使用者的电子邮件地址,同一个使用者可能有多个不同的邮件地址,所以我们在 User类别中使用Set对象来加以记录,在这边我们使用String来记录每一笔邮件地址,为了不允许重复的邮件地址记录,所以我们使用Set对象,我们的User类别如下:
User.java
package onlyfun.caterpillar;
import java.util.HashSet;
import java.util.Set;
public class User {
private long id;
private String name;
private Set addrs = new HashSet();
public Set getAddrs() {
return addrs;
}
public void setAddrs(Set addrs) {
this.addrs = addrs;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addAddress(String addr) {
addrs.add(addr);
}
}
addAddress()方法是为了加入一笔一笔的邮件地址而另外增加的,我们也可以在外部设定好Set对象,再使用setAddrs()方法设定给User对象,在映像文件上,为了进行Set的映像,我们使用<set>标签来进行设定,如下所示:
User.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" type="long" unsaved-value="null">
<column name="USER_ID"/>
<generator class="increment"/>
</id>
<property name="name" type="string" not-null="true">
<column name="NAME" length="16" not-null="true"/>
</property>
<set name="addrs" table="ADDRS">
<key column="USER_ID"/>
<element type="string" column="ADDRESS" not-null="true"/>
</set>
</class>
</hibernate-mapping>
从映射文件中我们可以看到,我们使用另一个表格ADDRS来记录Set中真正记录的对象,为了表明ADDRS中的每一 笔数据是属于哪一个USER 的,我们透过ADDRS的外键USER_ID参考至USER的USER_ID,ADDRS的USER_ID与USER_ID的内容将会是相同的,而 <element>中设定Set所包括的对象之型态,以及它将记录在哪一个字段中。
假设我们使用下面的程序来储存User的数据:
User user1 = new User();
user1.setName("caterpillar");
user1.addAddress("caterpillar@caterpillar.onlyfun.net");
user1.addAddress("justin@caterpillar.onlyfun.net");
user1.addAddress("justin@fake.com");
User user2 = new User();
user2.setName("momor");
user2.addAddress("momor@caterpillar.onlyfun.net");
user2.addAddress("momor@fake.com");
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.save(user1);
session.save(user2);
tx.commit();
session.close();
实际上在数据库中的USER与ADDRS表格的内容将如下:
mysql> select * from user;
+---------+-------------+
| USER_ID | NAME |
+---------+-------------+
| 1 | caterpillar |
| 2 | momor |
+---------+-------------+
2 rows in set (0.00 sec)
mysql> select * from addrs;
+---------+-------------------------------------+
| USER_ID | ADDRESS |
+---------+-------------------------------------+
| 1 | caterpillar@caterpillar.onlyfun.net |
| 1 | justin@caterpillar.onlyfun.net |
| 1 | justin@fake.com |
| 2 | momor@caterpillar.onlyfun.net |
| 2 | momor@fake.com |
+---------+-------------------------------------+
5 rows in set (0.00 sec)
下面的程序则简单的示范如何取出数据:
Session session = sessionFactory.openSession();
List users = session.find("from User");
session.close();
sessionFactory.close();
for (ListIterator iterator = users.listIterator(); iterator.hasNext(); ) {
User user = (User) iterator.next();
System.out.println(user.getName());
Object[] addrs = user.getAddrs().toArray();
for(int i = 0; i < addrs.length; i++) {
System.out.println("\taddress " + (i+1) + ": " + addrs[i]);
}
}