导航

Hibernate入门(一)

Posted on 2007-06-08 13:45  lirengang  阅读(623)  评论(0编辑  收藏  举报

Hibernate入门01 - 配置文件

      Hibernate可以使用XML或属性档案来配置SessionFactory,预设的配置文件名称为hibernate.cfg.xmlhibernate.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.xmlonlyfun/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-0.8.4.5.jar,属性文件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 = 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>


 您也可以使用ProxoolDBCP连接池,只要在配置文件中配置hibernate.proxool.hibernate.dbcp. 等相关选项,这可以在hibernateetc目录中找hibernate.properties中的配置例子来参考,当然要记得在CLASSPATH 中加入相关的jar档案。
如果您使用Tomcat的话,您也可以透过它提供的DBCP连接池来取得连接,您可以先参考这边的文章来设定TomcatDBCP连接池:
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程序设计人员可以使用对象操作的方式来进行数据查询,查询时使用一种类似SQLHQL Hibernate Query Language)来设定查询的条件,与SQL不同的是,HQL是具备对象导向的继承、多型等特性的语言。
 直接使用范例来看看如何使用Hibernate进行数据库查询,在这之前,请先照之前介绍过的主题在数据库中新增几笔数据:
Hibernate中新增资料
 查询数据时,我们所使用的是Sessionfind()方法,并在当中指定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"HQLUser指的是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 userUser类别取了别名,所以我们就可以使用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 - idnull时储存(预设)

§       valid - idnull或是指定值时储存

 这样设定之后,您可以使用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中有field1field2两个属性,其表格USERFIELD1FIELD2与之对 应,而子类别SubUser若继承了父类别的field1field2属性,表格中SUBUSER中也要拥有FIELD1FIELD2与之对应,这种方法的好处只有映射上的方便,很显然的,父类与子类共有的属性,会变成在数据库表格中重复的字段,而且很难实现多型操作,建议只有在不需要多型操作时使 用,要执行这种映射,为每一个子类别撰写一个映射文件就是了,没什么特别的设定。
 第二种方式是将所有继承同一父类别的对象储存在同一个表格中,表格中使用识别字段来表示某一列(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来记录储存的类别是属于UserPowerUser或是 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


      接续上一个主题,我们来看看继承关系映像的第三种方式,我们给予父类别与每个子类别一个表格,与第一个方法不同的是,父类别映像的表格与子类别映像的表格共享相同的主键值,父类别表格只记录本身的属性,如果要查询的是子类别,则透过外键参考从父类别表格中取得继承而来的属性数据。
 直接以图片说明会比较容易理解,我们使用前一个主题中的UserPowerUserGuestUser类别作说明,类别继承图如下:

 其映像至数据库表格的关系如下:

 其中POWER_USERGUEST_USER表格的主键值将与USER表格的主键值相同,POWER_USER_IDGUEST_USER_ID作为一个外键参考,以取得父类别映像表格的NAMEPASSWORD数据。
 在映射文件中要实现这种映像,我们使用<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 FKB62739F25ED19688;

[schemaexport] alter table POWER_USER drop constraint FK38F5D2E586CA74B5;

[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 FKB62739F25ED19688 (GUEST_USER_ID),

add constraint FKB62739F25ED19688 foreign key (GUEST_USER_ID) references USER (USER_ID);

[schemaexport] alter table POWER_USER add index FK38F5D2E586CA74B5 (POWER_USER_ID),

add constraint FK38F5D2E586CA74B5 foreign key (POWER_USER_ID) references USER (USER_ID);


 至于在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进一步的设定说明,可以看看参考手册的5.1.12内容。

 

 

 

Hibernate入门9 - Set 映射

      这个主题介绍如果在对象中包括集合对象,像是使用HashSet来包括其它对象时,该如何进行对象与数据表的映像,像Set这样的集合,可以包括所有的Java对象,这边先介绍当Set中包括的对象没有实体(Entiy)时的映像方式。
 (简单的说,也就是所包括的对象没有对象识别(identity)值,没有数据库层次上的识别值之表格与之对应的对象,只是纯綷的值型态(value type)对象,关于Entityvalue type的说明,可以看看参考手册5.2.1或是Hibernate in Action的第六章。)
 假设我们有一个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参考至USERUSER_IDADDRSUSER_IDUSER_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();


 实际上在数据库中的USERADDRS表格的内容将如下:

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]);  

            }

        }