JavaMuse

2007年3月13日 #

Unit-testing Hibernate with HsqlDB

The Motivation

I've used lots of methods to transform data between databases and object code. From hand-coded SQL to JDO to EJB. I've never found a method I liked particularly well. This distaste has become especially acute since adopting test-driven development (TDD) as a guiding philosophy.

Unit-testing should have as few barriers as possible. For relational databases those barriers range from external dependencies (is the database running?) to speed to keeping the relational schema synchronized with your object model. For these reasons it is vital to keep database access code away from the core object model and to test as much as possible without touching a real database.

This has often led me to one of two patterns. The first is externalizing all data access to domain objects and their relationships to separate classes or interfaces. These are typically data store objects that can retrieve, edit, delete and add domain entities. This is the easiest to mock-out for unit-testing, but tends to leave your domain model objects as data-only objects with little or no related behavior. Ideally access to child records would be directly from the parent object rather than handing the parent object to some third-party class to determine the children.

The other method has been to have the domain objects have access to an interface into the data-mapping layer a la Martin Fowler’s Data Mapper pattern. This has the advantage of pushing object relationships inside the domain model where the object-relational interface can be expressed once. Classes that use the domain model are unaware of the persistence mechanism because it is internalized into the domain model itself. This keeps your code focused on the business problem you are trying to solve and less about the object-relational mapping mechanism.

My current project involves crunching a number of baseball statistics and running simulations with the data. Since the data was already in a relational database it was a chance for me to explore the Hibernate object-relational mapping system. I have been very impressed with Hibernate, but I ran into the problem was trying to insert a layer of indirection while using Hibernate as my data mapper for unit-testing. The extra layer was so flimsy that it felt embarrassing to write it. The real deployed version was simply a pass-through to a Hibernate-specific implementation. Even worse, the mock versions had more complexity in them than the real "production" version simply because they didn't have some of the basic object storage and mapping that came with Hibernate.

I also had enough complex Hibernate query usage that I wanted to unit-test this significat portion of the application. However, testing against a ‘live’ database is a bad idea, because it almost invariably introduces a maintenance nightmare. In addition, since tests are best when they are independent from each other, using the same obvious primary keys in test fixture data means you have to create code to clean the database before each test case, which is a real problem when lots of relationships are involved

By using HSQLDB and Hibernate's powerful schema-generation tool I was able to unit-test the mapping layer of the application and find numerous bugs in my object queries I would not have found as easily by manual testing. With the techniques outlines below I was able unit-test my entire application during development with no compromises in test coverage.

Setting up HSQLDB

I used version 1.7.3.0 of HSQLDB. To use an in-memory version of the database you need to invoke the static loader for the org.hsqldb.jdbcDriver. Then when you get a JDBC connection you use JDBC url such as jdbc:hsqldb:mem:yourdb where 'yourdb' is the name of the in-memory database you want to use.

Since I'm using Hibernate (3.0 beta 4), I hardly ever need to touch real-live JDBC objects. Instead I can let Hibernate do the heavy lifting for me--including automatically creating the database schema from my Hibernate mapping files. Since Hibernate creates its own connection pool it will automatically load the HSQLDB JDBC driver based on the configuration code lives in a class called TestSchema. Below is the static initializer for the class.

 1 public class TestSchema {
 2 
 3     static {
 4         Configuration config = new Configuration().
 5             setProperty("hibernate.dialect""org.hibernate.dialect.HSQLDialect").
 6             setProperty("hibernate.connection.driver_class""org.hsqldb.jdbcDriver").
 7             setProperty("hibernate.connection.url""jdbc:hsqldb:mem:baseball").
 8             setProperty("hibernate.connection.username""sa").
 9             setProperty("hibernate.connection.password""").
10             setProperty("hibernate.connection.pool_size""1").
11             setProperty("hibernate.connection.autocommit""true").
12             setProperty("hibernate.cache.provider_class""org.hibernate.cache.HashtableCacheProvider").
13             setProperty("hibernate.hbm2ddl.auto""create-drop").
14             setProperty("hibernate.show_sql""true").
15             addClass(Player.class).
16             addClass(BattingStint.class).
17             addClass(FieldingStint.class).
18             addClass(PitchingStint.class);
19 
20         HibernateUtil.setSessionFactory(config.buildSessionFactory());
21     }
22 

Hibernate provides a number of different ways to configure the framework, including programmatic configuration. The code above sets up the connection pool. Note that the user name 'sa' is required to use HSQLDB's in-memory database. Also be sure to specify a blank as the password. To enable Hibernate's automatic schema generation set the hibernate.hbm2ddl.auto property to 'create-drop'.

Testing In Practice

My project is crunching a bunch of baseball statistics so I add the four classes that I'm mapping ( Player, PitchingStint, BattingStint and FieldingStint). Finally I create a Hibernate SessionFactory and insert it into the HibernateUtil class which simply provides a single access method for my entire application for Hibernate sessions. The code for the HibernateUtil is below:

 1 import org.hibernate.*;
 2 import org.hibernate.cfg.Configuration;
 3 
 4 public class HibernateUtil {
 5 
 6     private static SessionFactory factory;
 7 
 8     public static synchronized Session getSession() {
 9         if (factory == null) {
10             factory = new Configuration().configure().buildSessionFactory();
11         }
12         return factory.openSession();
13     }
14 
15     public static void setSessionFactory(SessionFactory factory) {
16         HibernateUtil.factory = factory;
17     }
18 }
19 

Since all of my code (production code as well as unit-tests) get their Hibernate sessions from the HibernateUtil I can configure it in one place. For unit-tests the first bit of code to access the TestSchema class will invoke the static initializer which will setup Hibernate and inject the test SessionFactory into the HibernateUtil. For production code the SessionFactory will be initialized lazily using the standard hibernate.cfg.xml configuration mechanism.

So what does this look like in the unit-tests? Below is a snippet of a test that checks the the logic for determining what positions a player is eligible to play at for a fantasy baseball league:

 1  public void testGetEligiblePositions() throws Exception {
 2         Player player = new Player("playerId");
 3         TestSchema.addPlayer(player);
 4 
 5         FieldingStint stint1 = new FieldingStint("playerId"2004"SEA", Position.CATCHER);
 6         stint1.setGames(20);
 7         TestSchema.addFieldingStint(stint1);
 8 
 9         Set<Position> positions = player.getEligiblePositions(2004);
10         assertEquals(1, positions.size());
11         assertTrue(positions.contains(Position.CATCHER));
12     }
13 

I first create a new Player instance and add it to the TestSchema via the addPlayer() method. This step must occur first because the FieldingStint class has a foreign-key relationship to the Player class. If I didn't add this instance first I would get a foreign-key constraint violation when I try to add the FieldingStint. Once the test-fixture is in place I can test the getEligiblePositions() method to see that it retrieves the correct data. Below is the code for the addPlayer() method in the TestSchema. You will notice that Hibernate is used instead of bare-metal JDBC code:
 1 public static void addPlayer(Player player) {
 2         if (player.getPlayerId() == null) {
 3             throw new IllegalArgumentException("No primary key specified");
 4         }
 5 
 6         Session session = HibernateUtil.getSession();
 7         Transaction transaction = session.beginTransaction();
 8         try {
 9             session.save(player, player.getPlayerId());
10             transaction.commit();
11         }
12         finally {
13             session.close();
14         }
15     }
16 

One of the most important things in unit-testing is to keep your test-cases isolated. Since this method still involves a database, you need a way to clean your database prior to each test case. I have four tables in my schema so I wrote a reset() method on the TestSchema that removes all rows from the tables using JDBC. Note because HSQLDB knows about foreign keys, the order in which the tables are deleted is important. Here is the code:

 1 public static void reset() throws SchemaException {
 2         Session session = HibernateUtil.getSession();
 3         try {
 4             Connection connection = session.connection();
 5             try {
 6                 Statement statement = connection.createStatement();
 7                 try {
 8                     statement.executeUpdate("delete from Batting");
 9                     statement.executeUpdate("delete from Fielding");
10                     statement.executeUpdate("delete from Pitching");
11                     statement.executeUpdate("delete from Player");
12                     connection.commit();
13                 }
14                 finally {
15                     statement.close();
16                 }
17             }
18             catch (HibernateException e) {
19                 connection.rollback();
20                 throw new SchemaException(e);
21             }
22             catch (SQLException e) {
23                 connection.rollback();
24                 throw new SchemaException(e);
25             }
26         }
27         catch (SQLException e) {
28             throw new SchemaException(e);
29         }
30         finally {
31             session.close();
32         }
33     }
34 

When bulk deletes become finalized in Hibernate 3.0 we should able to remove this last bit of direct JDBC from our application. Until then we have to get a Connection and issue direct SQL to the database.

Be sure not to close your Connection, closing the Session is sufficient for resource cleanup. Out of habits developed from writing lots of hand-crafted JDBC code, the first version closed the JDBC Connection. Since I configured Hibernate to create a connection pool with only one Connection I completely torpedoed any tests after the first one.Be sure to watch out for this!

Since you can never be sure what state the database may be in when your test class is running (imagine running all of your test cases), you should include database cleanup in your setUp() methods like so:

1 public void setUp() throws Exception {
2         TestSchema.reset();
3     }
4 

Conclusion

Being able to test against a real-live RDBMS without all of the hassles of trying to run tests against your deployed database is essential, even when working with sophisticated O/R mappers like Hibernate. The example I showed here is not exclusive to Hibernate and could probably be made to work with JDO or TopLink, though Hibernate makes this kind of testing particularly easy since it has a built-in schema generation tool. With a setup like the one described above you don't ever have to leave the comfort of your IDE and still have extensive test coverage over your code.

posted @ 2007-03-13 14:05 满山红叶 阅读(327) | 评论 (0)编辑 收藏

HSQLDB文档

一 什么是HSQLDB
HSQLDB具有以下特点:
l          是一个开放源代码的JAVA 数据库
l          具有标准的SQL 语法和JAVA 接口
l          HSQLDB 可以自由使用和分发
l          非常简洁和快速的
l          具有内存数据库,独立数据库和C/S 数据库三种方式
l          可是在APPLET 中使用
更多的细节:
l          索引可以被创建和自动使用
l          支持事务处理
l          允许表关联
l          完整性引用和约束
l          支持JAVA存储过程和函数
l          数据库可以生成SQL脚本
l          使用用户名,密码,访问权限等安全机制
l          可以被JAVA1.1和JAVA2编译
建立在HypersonicSQL基础上的HSQLDB,是一个通用目的的数据库,非常的小,而且易于安装和使用。可以用于APPLETS中 ,测试中,应用系统中。
由于提供了标准SQL和JDBC接口,HSQLDB可以方便的和其他数据库之间进行数据转换。
HSQLDB的当前最新版本是1.7.1,以压缩包的形式提供,包括可以使用的JAR文件,文档,源代码,测试程序,例子等。
操作模式介绍
HSQLDB有两种操作模式:
l          进程内模式(只用在同一个JVM里的应用程序才可以访问数据库)
l          C/S模式(多个计算机/系统可以访问同一个数据库)
 
进程内访问模式
进程内访问模式也就是独立模式。这里的独立模式是相对于C/S模式(客户端程序访问数据库服务器)而言的。这里,数据库和应用程序运行在同一个JVM下。这个时候的数据库实际上就是相当于被应用程序调用的代码库。程序和数据库通过通用的JDBC调用进行通讯,但是这种调用是内部调用,不需要通过网络通讯。
在这个模式下,同一时间一个数据库只能有一个应用程序访问,否则,就要使用C/S模式(允许多个JVM或者计算机在同一时间访问同一个数据库)。
 
这种模式下的JDBC的URL如下:
jdbc:hsqldb:test
这里,test是数据库文件名。另一个例子(WINDOWS系统下):
				jdbc:hsqldb:c:\db\test 
		
 
 
C/S 访问模式
这种模式下数据库和应用程序不是运行在同一个JVM进程下,而是有自己独立的进程或者是独立的机器。 不需要客户端程序进入服务器的文件系统。这种模式下的数据库操作模式和一些大的数据库(比如SQL SERVER,ORACLE等)没什么区别的。可以在INTERNET或者INTRANET。
HSQLDB除了有自己的访问协议,还支持标准的HTTP协议,从而可以穿越防火墙或者代理服务器来访问数据库。
In all Server modes the actual database file name is specified in the Java command that starts the server. This can be the dot "." for all-in-memory operation or the path for the database name
 
服务器模式一共有三种:SERVER,WEBSERVER和SERVLET。
 
l          SERVER
这种模式下的通讯协议是建立在TCP/IP基础上的HSQL专有协议。每个客户端都有一个独立的连接。这种模式的响应速度是非常快的,如果使用C/S模式,应该更多的采用这种服务模式。
这种模式下的JDBC URL是:
jdbc:hsqldb:hsql://hsqldbsrv
这里,hsqldbsrv是机器名称。如果一台机器上跑多个服务器,需要指定端口,例如:jdbc:hsqldb:hsql://hsqldbsrv:9002,如果是本地计算机,则使用localhost:jdbc:hsqldb:hsql://localhost。
 
l          WEBSERVER
有些时候,由于防火墙或者代理服务器的存在,需要使用HTTP协议进行通讯,系统提供一个小而简单的WEBSERVER用来相应针对数据库的查询,例如:
				jdbc:hsqldb:http://websrv
		
 
l          SERVLET
这种模式和WEBSERVER模式很类似,数据库运行在一个SERVLET里,而SERVLET可以运行在几乎所有的WEBSERVER里。而且和JAVA SERVLETE API兼容(测试环境是J2DK2.1)。这是通过网络直接访问的。如果你的SERVLET不能直接访问这个数据库,就不要使用这种模式。
 
全内存访问(All-In-Memory )模式
所谓全内存访问模式,就是所有的数据(包括索引和记录)都保存在主内存里。这意味着数据库的大小是受到内存大小的限制的(不能超过内存的大小)。支持这种模式的原因是:
l          在非日志模式下,这种模式稍微快些
l          可以在APPLET下使用
l          用来存储临时数据(应用系统的数据缓存)All-In-Memory
JDBC URL如下:
				jdbc:hsqldb:. 
		
 
内存和硬盘结合访问模式
 
在这种模式下,数据库的改变会写入到硬盘中,这就意味着在数据库启动时,内存里的表会根据他们的数据重新创建。或者说,可以创建表来保存数据,在访问数据库时,只有少量记录时保存在内存里的。可以在创建的时候使用''''CREATE CACHED TABLE''''来代替''''CREATE TABLE''''。从而支持大表(这些表的记录相对于内存来说太大了)。被缓存的表的索引也可以保存到硬盘中。因此,数据库的大小就可以不受到内存大小的限制。进入缓存表要比从内存表里获取数据要慢些。从1.7.0版本开始,支持第三种模式:数据可以存储在文本文件(如CSV格式的文件)中。对应的语句时:''''CREATE TEXT TABLE''''。
在关闭数据库前,当前状态会被保存到磁盘中。缓存表中的数据会被保存到一个单独的文件中。启动HSQLDB时,数据库从磁盘中载入数据(SQL脚本被执行),如果数据库被毁坏(比如使用Ctrl+C或者断电),数据也不会丢失。这是因为当下次数据库重新启动时,它使用脚本恢复到最近一次(有脚本文件的那次)的状态。
 
 
混合绑定模式
所有的模式都可以在一个程序里使用,系统可以在统一时间使用这四种模式,去连接四种不同的数据库,例如:
				c1=DriverManager.getConnection("jdbc:hsqldb:.","sa",""); 
		
				c2=DriverManager.getConnection("jdbc:hsqldb:test","sa","");
		
				c3=DriverManager.getConnection("jdbc:hsqldb:http://dbserver","sa",""); 
		
				c4=DriverManager.getConnection("jdbc:hsqldb:hsql://dbserver","sa","");
		
在这个例子中,四个连接被打开:
c1是内存数据库;c2打开的是本地数据库test;c3使用http协议连接dbserver数据库;c4也是连接dbserver机器,但是使用的是更快的hsql协议。这里的限制就是:只有一个进程内的全内存进程是可用的。
 
比较
每种模式或配置都有不同的细节和好坏两个方面:
l          事务处理
对于webserver和servlet模式而言,由于HTTP协议是无状态的,因此,每个查询数据库都建立新的连接。每次查询都需要发送用户名和密码到数据库中,然后建立一个新的连接,同时也建立一个新的事务(因为事务是绑定到连接中的)。可以使用’cookies’,但是现在还没有实现。
l          并发访问
SERVER模式允许系统和管理工具(比如DatabaseManager同时访问数据库)。
l          数据库性能优化因素
内存数据库不需要访问系统,因此是最快的。其他模式的数据库需要访问文件系统,每个INSERT/UPDATE/DELETE操作都要保存到磁盘中,因此速度慢些。如果select和delete查询命中了缓存表的信息,则速度几乎和内存表速度一样快,否则就要慢许多(因为要和操作系统的文件系统交互)。
l          每个statement的传输时间
在SERVER模式,每个statement都需要通过TCP/IP协议传送到服务端,然后将结果返回到客户端。而webserver和servlet模式则需要更多的时间,因为每次statement都需要重新建立连接。相对照的,进程内模式则是在一个系统内部传送数据,就快多了。
 
l          以APPLET方式运行
这就是全内存操作。

posted @ 2007-03-13 14:01 满山红叶 阅读(250) | 评论 (0)编辑 收藏

2007年2月28日 #

vimtutor 乱码

iconv -f gb2312 /usr/share/vim/vim70/tutor/tutor.zh.euc -t utf-8 > tmp.txt
mv tmp.txt /usr/share/vim/vim70/tutor/tutor.zh.euc

posted @ 2007-02-28 19:06 满山红叶 阅读(207) | 评论 (0)编辑 收藏

2007年2月17日 #

ubuntu 6.10 edgy 安装与配置

1、设置网络,上网安装和更新:
sudo pppoeconf
(打开连接:pon dsl-provider; 关闭连接:poff dsl-provider)
2、设置安装源apt:
先备份原有的安装源文件:sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
修改安装源:sudo vim /etc/apt/sources.list 用下面的源全部代替原来的(电信用户)
deb http://ubuntu.cn99.com/ubuntu/ edgy main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ edgy-security main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ edgy-updates main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ edgy-proposed main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ edgy-backports main restricted universe multiverse
deb-src http://ubuntu.cn99.com/ubuntu/ edgy main restricted universe multiverse
deb-src http://ubuntu.cn99.com/ubuntu/ edgy-security main restricted universe multiverse
deb-src http://ubuntu.cn99.com/ubuntu/ edgy-updates main restricted universe multiverse
deb-src http://ubuntu.cn99.com/ubuntu/ edgy-proposed main restricted universe multiverse
deb-src http://ubuntu.cn99.com/ubuntu/ edgy-backports main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu-cn/ edgy main restricted universe multiverse
3、
更新源和更新系统:
sudo apt-get update
sudo apt-get dist-upgrade
4、
安装开发环境:
本人想学习一下Linux下的C,C++程序开发,这几天一直在研究Linux下的C语言编译环境的建立,应为新装好的Ubuntu里面无法编译最简单的C语言,所以要配置一番,这几天也有一点心得,写下来和大家一起学习。
原来我以为安装配置非常麻烦,但是在新立得的帮助下很快就能配置好。
我先安装了一个新的Ubuntu6.10,然后按照Wiki里的帮助先配置好了源、输入法、中文环境等。然后开始来配置编译环境。
(1)配置GCC
刚装好的GCC什么都不能编译,因为没有一些必须的头文件,所以要安装build-essential,安装了这个包会安装上g++,libc6-dev,linux-libc-dev,libstdc++6-4.1-dev等好多必须的软件和头文件。
代码:
sudo apt-get install build-essentia
安装完成后写一个C语言程序testc.c测试一下。
代码:
#include<stdio.h>
int main()
{
   printf("Hello Ubuntu!\n");
   return 0;
}
编译
$ gcc testc.c -o testc
$ ./testc
显示
Hello Ubuntu!
(2)安装GTK环境
安装GTK环境只要安装一个 gnome- core-devel就可以了,里面集成了很多其他的包。除此之外还要转一些其他的东西,如libglib2.0-doc、 libgtk2.0-doc帮助文档,devhelp帮助文档查看,glade-gnome、glade-common、glade-doc图形界面设计 等。
代码:

sudo apt-get install gnome-core-devel
sudo apt-get install libglib2.0-doc libgtk2.0-doc
sudo apt-get install devhelp
sudo apt-get install glade-gnome glade-common glade-doc
安装完成后我们也同样做个测试程序
代码:

#include<gtk/gtk.h>
void hello(GtkWidget *widget,gpointer data)
{
g_print("Hello Ubuntu!\n");
}
gint delete_event(GtkWidget *widget,GdkEvent *event,gpointer data)
{
g_print ("delete event occurred\n");
return(TRUE);
}
void destroy(GtkWidget *widget,gpointer data)
{
gtk_main_quit();
}
int main( int argc, char *argv[] )
{
GtkWidget *window;
GtkWidget *button;
gtk_init (
&argc&argv);
window=gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT(window),"delete_event",GTK_SIGNAL_FUNC(delete_event),NULL);
gtk_signal_connect (GTK_OBJECT (window), "destroy",GTK_SIGNAL_FUNC (destroy), NULL);
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
button = gtk_button_new_with_label ("Hello Ubuntu!");
gtk_signal_connect (GTK_OBJECT (button), "clicked",GTK_SIGNAL_FUNC (hello), NULL);
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",GTK_SIGNAL_FUNC (gtk_widget_destroy),GTK_OBJECT (window));
gtk_container_add (GTK_CONTAINER (window), button);
gtk_widget_show (button);
gtk_widget_show (window);   /*显示一个窗口*/
gtk_main();   /*进入主循环*/
return(0);
}
用下面命令编译运行 
$ gcc gtkhello.c -o gtktest `pkg-config --cflags --libs gtk+-2.0`
$ ./gtktest 
5、
java 开发环境:
 sudo apt-get install sun-java5-jdk
安装完毕之后,选择默认 java:
sudo update-alternatives --config java 
然后配置环境变量:
 sudo vim /etc/environment 
在其中添加如下两行:
CLASSPATH=/usr/lib/jvm/java-1.5.0-sun/lib
JAVA_HOME=/usr/lib/jvm/java-1.5.0-sun

posted @ 2007-02-17 14:34 满山红叶 阅读(637) | 评论 (16)编辑 收藏

2007年2月10日 #

Hello,World in Hibernate 入门

本文介绍如何用hibernate写一个hello world 程序,以及说明Hibernate下的对象的识别。
1、配置文件hibernate.cfg.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 " > com.mysql.jdbc.Driver </ property >
        
< property name = " connection.url " > jdbc:mysql: // localhost:3306/eg</property>
         < property name = " connection.username " > root </ property >
        
< property name = " connection.password " > 4864452 </ property >

        
<!--  JDBC connection pool (use the built - in)  -->
        
< property name = " connection.pool_size " > 1 </ property >

        
<!--  SQL dialect  -->
        
< property name = " dialect " > org.hibernate.dialect.MySQLInnoDBDialect </ property >

        
<!--  Enable Hibernate ' s automatic session context management -->
         < property name = " current_session_context_class " > thread </ property >

        
<!--  Disable the second - level cache   -->
        
< property name = " cache.provider_class " > org.hibernate.cache.NoCacheProvider </ 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 = " eg/Message.hbm.xml " />
        
        
    
</ session - factory >

</ hibernate - configuration >

2、源文件:

package  eg;

public   class  Message  {
    
private  Long id;
    
private  String text;
             
    
private  Message nextMessage;

    
public  Message()  {}
    
public  Message(String text) {
        
this .text = text;
    }

    
    
public   void  setId(Long id)  {
        
this .id  =  id; 
    }


    
public   void  setText(String text)  {
        
this .text  =  text; 
    }


    
public   void  setNextMessage(Message nextMessage)  {
        
this .nextMessage  =  nextMessage; 
    }

       
public  Long getId()  {
        
return  ( this .id); 
    }

       
public  String getText()  {
        
return  ( this .text); 
    }

       
public  Message getNextMessage()  {
        
return  ( this .nextMessage); 
    }
 
}

3、配置文件:Messsage.hbm.xml

<? xml version = " 1.0 "  encoding = " utf-8 " ?>

<! DOCTYPE hibernate - mapping PUBLIC
    
" -//Hibernate/Hibernate Mapping DTD 3.0//EN "  
    
" http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd " >

< hibernate - mapping >
    
< class
        name
=" eg.Message "
        table
= " MESSAGE "
    
>

        
< id
            name
= " id "
            column
= " MESSAGE_ID "
            type
= " java.lang.Long "
        
>
            
< generator  class = " native " />
                         
        
</ id >
        
< property
            name
= " text "
            type
= " java.lang.String "
            update
= " true "
            insert
= " true "
            column
= " MESSAGE_TEXT "
        
/>

        
< many - to - one
            name
= " nextMessage "
            
class = " jmuse.eg.Message "
            cascade
= " all "
            outer
- join = " auto "
            update
= " true "
            insert
= " true "
            column
= " NEXT_MESSAGE_ID "
        
/>

      
</ class >

</ hibernate - mapping >

4、最后的包结构:

+ lib 
     antlr.jar 
     cglib.jar 
     asm.jar 
     asm
- attrs.jars 
     commons
- collections.jar 
     commons
- logging.jar 
     hibernate3.jar 
     jta.jar 
     dom4j.jar 
     log4j.jar 
+src 
    
+ eg
       Message.java
       Message.hbm.xml
       TestMessage.java
     hibernate.cfg.ml
     log4j.properties

5、测试:

 session.beginTransaction();
        Message m
= new  Message( " hello,world " );
        session.save(m);
        session.getTransaction().commit();
        session.close();

 你会发现在数据库中有一条信息了,id=1
Hibernate的对象识别是通过对象的Id 来识别的:
如:

我们刚才在数据库中保存了一个Id为1的Message对象。
如果:
session.beginTraction();
Message m
= new  Message();
m.setId(
new  Long( 1 ));
m.setText(
" This is another greeting " );
session.saveOrUpdate(m);
sesion.getTraction.commit();
你会发现Hibernate会自动的把刚才的对象进行更新,而不是保存一个新的对象。
这个例子有点极端,呵呵。能说明问题就可以了。

posted @ 2007-02-10 17:42 满山红叶 阅读(484) | 评论 (0)编辑 收藏

对象的存储和关系型数据库

ORM 的原则是将一个类映射到一张表上,然而对象和关系型数据库之间有一点的差异。
对象和对象之间的关系有一对一,一对多,多对多。这三个关系的关系的存储是有差异的。
1、一对一:我们可以用两张表分别表示两个类。他们之间的关系我们可以用主关键字或者外关键字来表示。然而这里有一个粒度的问题(The problem of granularity )。关于粒度问题见第4点。
2、一对多:我们也可以用两张表来表示两个类。他们的关系我们可以用外键来关联。
3、多对多:我们可以用两张表来表示这两个类。用第三张表来表示他们之间的关系。
4、关于问题的粒度:
      如:一个用户和一个地址之间的关系。当然我们可以用一对一的方法来解决。
然而这样是否合理?也许最好的方法是在数据库中增加一个新的数据类型Address(country state city zipcode etc.)。这样我们就很容易把这个粒度问题解决了。不幸的是,数据库是不允许自定义类型的(也许现在的数据库可以,鄙人不太清楚,呵呵)。Hibernate 对这个问题有了很好的解决。见以后的文章。
5、对象之间可以有继承的关系,这是数据库望洋兴叹的。Hibernate 对这个问题有了很好的解决。见以后的文章。
6、对象的身份识别问题。
    大家都知道,java中的对象识别是用equals()和haseCode()来实现。
   举例:

package  jmuse.eg;
/**
 *@hibernate.class table="MESSAGE"
 *
 
*/


public   class  Message  {
    
private  Long id;
    
private  String text;
    
private  String string;
    
public   void  setString(String s) {
        string
= s;
    }

    
/**
     *@hibernate.property 
     *
     
*/

     
public  String getString() {
        
return   this .string;
    }

    
    
private  Message nextMessage;
    
public  Message()  {}
    
public  Message(String text) {
        
this .text = text;
    }

    
    
public   void  setId(Long id)  {
        
this .id  =  id; 
    }


    
public   void  setText(String text)  {
        
this .text  =  text; 
    }


    
public   void  setNextMessage(Message nextMessage)  {
        
this .nextMessage  =  nextMessage; 
    }

    
/**
     *@hibernate.id column="MESSAGE_ID" generator-class="native"
     *
     
*/

    
public  Long getId()  {
        
return  ( this .id); 
    }

    
/**
     *@hibernate.property column="MESSAGE_TEXT"
     *
     
*/

    
public  String getText()  {
        
return  ( this .text); 
    }

    
/**
     *@hibernate.many-to-one column="NEXT_MESSAGE_ID" cascade="all"
     *
     
*/

    
public  Message getNextMessage()  {
        
return  ( this .nextMessage); 
    }

}

这样的类由于没有实现equals()和haseCode(),所以如果用下面的代码我们可以看到如下输出:

Message m1 = new  Message("Hello");
Message m2
= new  Message("Hello");
if (!(m2.equals(m1)) )log.info( " m1 and m2 are not idential " );
else  log.info( " m1 and m2 are identical " );

////////////// /out put //////////////
m1 and m2 are not idential

但是我们加上如下代码:

  public   boolean  equals(Object o) {
        
if ( this == o)  return   true ;
        
if ( ! (o  instanceof  Message)) 
                
return   false ;
        
final  Message m = (Message)o;
        
return   this .getText().equals(m.getText());
    }

    
public   int  hashCode() {
        
// return text==null?System.identityHashCode(this):text.hashCode();
         int  hase = 20 ;
        
        
return   20 * hase + getText().hashCode();
    }


//////////// out put //////////// /
m1 and m2 are identical

这就是java的对象识别。如果你将修改后的Message对象加入到java.util.Set中,不管你加多少,最后size()仍然为1。
 那么hibernate中的对象识别如何呢?见以后文章。呵呵。

 

posted @ 2007-02-10 14:44 满山红叶 阅读(315) | 评论 (0)编辑 收藏

仅列出标题