第二章:Hibernate的应用举例
本章使用Eclipse创建一个Maven项目的基础上,提供Hibernate的实际应用参考,在开始本章前,您应当安装了Eclipse,并在Eclipse上配置了Maven插件。按照如下步骤,您可以实现您的第一个Hibernate项目。
如果您没有Maven插件,那么创建一个普通的java项目,之后将提到的jar文件引入java路径即可。
2.1 使用XML形式配置Hibernate
本节将讨论使用XML文档的形式配置Hibernate,在下一小节讨论使用Annotation形式,最后讨论使用JAVAEE形式。
2.1.1 创建域模型(domain model)
域模型是指JAVA Model,即实体类。如下所示:
package org.example.ch02.hello;
public class Message {
private Long id;
private String text;
private Message nextMessage;
public Long getId() {
return id;
}
@SuppressWarnings("unused")
private void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Message getNextMessage() {
return nextMessage;
}
public void setNextMessage(Message nextMessage) {
this.nextMessage = nextMessage;
}
}
我们创建了Message类,设置了id属性,需要注意的是,根据上一章的理论,id应当是一个代理键(surrogate key),代理键与Message的内容无直接关系,由数据库系统自动生成,因此,将主键id的set方法设置为private。Message类的text属性,表示当前消息, nextMessage属性,表示下一条消息。从结构上看,多个Message不仅可以构成链式结构,而且,同一个Message可以是多个其它Message的直接后继消息,因此,可以构成比链式结构更复杂的结构。
2.1.2 创建Hibernate映射
通常,一个域模型对应一个Hibernate映射文件,域模型与其映射文件(.hbm.xml)放在同一个目录下。下面对Message类和数据库中的表进行映射,映射文件(Message.hbm.xml)描述如下:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="org.example.ch02.hello.Message" table="MESSAGE">
<id name="id"
column="MESSAGE_ID">
<generator class="increment"/>
</id>
<property name="text" column="MESSAGE_TEXT"/>
<many-to-one name="nextMessage"
cascade="all"
column="NEXT_MESSAGE_ID"
foreign-key="FK_NEXT_MESSAGE"/>
</class>
</hibernate-mapping>
映射文件的结构可以从Hibernate帮助文档中找到,这里Message类映射为MESSAGE表,并且分别将id属性映射为MESSAGE_ID字段,text属性映射为MESSAGE_TEXT字段,nextMessage属性比较复杂,使用了many-to-one关系,在MESSAGE表中使用NEXT_MESSAGE_ID字段表示,并创建了FK_NEXT_MESSAGE外键约束。需要注意的是,id指定了生成方式为递增,起始id为0.
2.1.3 Hibernate与数据库间的配置
Hibernate与数据库间的配置主要有三种方式,分别是使用property文件(即.properties),使用xml文件(即.cfg.xml)和使用代码配置,通常使用xml的形式配置。(Hibernate.cfg.xml)描述如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:data/message</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="c3p0.min_size">5</property>
<property name="c3p0.max_size">20</property>
<property name="c3p0.timeout">300</property>
<property name="c3p0.max_statements">50</property>
<property name="c3p0.idle_test_period">3000</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<mapping resource="org/example/ch02/hello/Message.hbm.xml"/>
</session-factory>
</hibernate-configuration>
文档开始配置数据库相关的信息,包括数据库驱动程序,数据库访问地址,数据库用户名和密码等,hsqldb是内嵌数据库,因此在无需单独安装和配置数据库的前提下,即可运行程序。dialect(方言)是指Hibernate产生哪种类型的sql与配置数据库交互,Hibernate是独立于其它数据库,由于数据库厂商间存在sql访问方式的差异,因此,需要选择特定的方言支配具体厂商和版本的数据库。
之后配置了c3p0数据库连接池。Hibernate自身没有提供对数据库连接池的应用版本的实现,因此,Hibernate常使用第三方的数据库连接池。c3p0是Hibernate最常使用的数据库连接池。实际上,如果不配置数据库连接池,那么Hibernate将提供一个简单的单线程的连接方式(
<!-- JDBC connection pool (use the built-in) -->
<!-- <property name="connection.pool_size">1</property> -->
),这种方式仅在测试阶段使用,不能够满足实际应用的需求。
连接池配置中:min_size是连接池中最小线程数目,max_size是最大线程数目。当数据库连接请求到来时,如果当前有空闲的线程,就直接利用空闲线程处理请求,如果当前无空闲线程,需要创建新线程处理当前请求,如果当前使用的线程数已经达到max_size,那么连接池将会拒绝新请求。创建和销毁线程的代价比较大,因此,能够重用线程是最好的选择(这个在java多线程中很常见),timeout指定了一个线程过期的时间(按分钟计算,当一个连接任务释放后,执行线程将被回收,暂时缓存在线程池内,当超过timeout时间后仍未被使用,并且线程池内的线程数目大于min_size时,这个线程将被释放)。max_statements表示最多缓存的statements的数目,创建statements的代价比较大,因此缓存部分statements有利于性能的优化。idle_test_period表示一个连接在指定时间内不被使用将变为自动无效。连接池如下图所示:
配置文件之后配置了Hibernate生成sql语句的输出形式,这些sql语句对性能调节和错误定位有重要作用。
hbm2dll.auto是Hibernate的工具,配置其用于生成相应的数据表。
最后指明了映射文件的位置。
2.1.4 Hibernate的SessionFactory
一般认为一个SessionFactory对应于一个数据库,SessionFactory是线程安全的,可以被多线程共享。创建一个SessionFactory的代价较大,因此,常见的情况是在应用程序开始时创建SessionFactory,其生命周期经历整个应用程序运行过程。一般情况下,一个应用程序需要控制多个数据库时需要创建多个SessionFactory。
package org.example.ch02.Control;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory sessionFactory;
static
{
sessionFactory = new Configuration().configure().buildSessionFactory();
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public static void shutDown()
{
sessionFactory.close();
}
}
Configuration在创建时,会寻找根目录下的hibernate.properties文件,分析Hibernate的配置属性,如果没有配置这个文件,则调用configure方法寻找Hibernate.cfg.xml配置文件,可以通过参数传入配置文件的位置,默认是在根目录下。如果找不到Hibernate.cfg.xml Hibernate将报错。使用buildSessionFactory方法创建SessionFactory。
现在可以理解,Hibernate通过Configuration的configure方法分析Hibernate.cfg.xml文件,获取数据库的连接信息、连接池的配置信息、映射文件的位置,之后分析处理对象和数据库的映射关系。
2.5 Hibernate的存储与查询
测试代码如下:
package org.example.ch02.hello;
import java.util.List;
import org.example.ch02.Control.HibernateUtil;
import org.hibernate.Session;
import org.junit.Test;
public class MessageTest {
@Test
public void hibernateTest()
{
Message message = new Message();
message.setText("hello world");
Message message2 = new Message();
message2.setText("good day!");
message.setNextMessage(message2);
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction().begin();
session.save(message);
session.getTransaction().commit();
session.close();
Session session2 = HibernateUtil.getSessionFactory().openSession();
session2.beginTransaction().begin();
@SuppressWarnings("unchecked")
List<Message> ms = session2.createQuery("from Message m").list();
for(Message m:ms)
{
System.out.println(m.getText());
}
session2.getTransaction().commit();
session2.close();
}
}
通过SessionFactory的openSession方法,可以创建并获取一个Session。Session不是线程安全的,一般一个请求过程对应一个Session。Session的创建和销毁代价较小。通过Session可以获得Transaction。上例中先存储了两个对象,之后使用查询方法查询出相应的对象。
这里涉及到一个叫方法链(method chaining)的概念:方法链是一种编程模式,通过在set或者save方法(普通的set和save方法返回值是void)返回传入对象,因此可以创建一个方法调用的链式结构。例如session2.createQuery("from Message m").list(); 这种方法在Java中并不提倡,因为其在调试等方面存在缺陷。
2.5 运行库配置
使用maven描述文件(pom.xml)描述如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example.hibernate.ch02</groupId>
<artifactId>HelloHibernate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.5.6-Final</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.2.0.Final</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>3.6.10.Final</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
在配置文件中使用了log4j和slf4j,用于Hibernate的日志输出,javassisit为Hibernate选择字节码类库,hsqldb是内嵌的数据库,hibernate的commons-annotation、annotation是hibernate需要使用的类库,c3p0是连接池。
之后是日志配置(log4j.properties):
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=warn, stdout
log4j.logger.org.hibernate.tool.hbm2ddl=debug
log4j.logger.org.hibernate=INFO
log4j.logger.org.hibernate.type=INFO
如果您没有maven,那么可以将上述的相应库下载后添加到java路径下。
2.6 执行
输出结果:
12:15:09,421 INFO Environment:570 - Hibernate 3.5.6-Final
12:15:09,437 INFO Environment:603 - hibernate.properties not found
12:15:09,437 INFO Environment:781 - Bytecode provider name : javassist
12:15:09,453 INFO Environment:662 - using JDK 1.4 java.sql.Timestamp handling
12:15:09,765 INFO Configuration:1518 - configuring from resource: /hibernate.cfg.xml
12:15:09,765 INFO Configuration:1495 - Configuration resource: /hibernate.cfg.xml
12:15:14,437 INFO Configuration:655 - Reading mappings from resource : org/example/ch02/hello/Message.hbm.xml
12:15:17,531 INFO HbmBinder:348 - Mapping class: org.example.ch02.hello.Message -> MESSAGE
12:15:17,687 INFO Configuration:1633 - Configured SessionFactory: null
12:15:17,703 INFO ConnectionProviderFactory:173 - Initializing connection provider: org.hibernate.connection.C3P0ConnectionProvider
12:15:17,718 INFO C3P0ConnectionProvider:103 - C3P0 using driver: org.hsqldb.jdbcDriver at URL: jdbc:hsqldb:data/message
12:15:17,718 INFO C3P0ConnectionProvider:104 - Connection properties: {user=sa, password=****}
12:15:19,750 DEBUG SchemaUpdate:203 - create table MESSAGE (MESSAGE_ID bigint not null, MESSAGE_TEXT varchar(255), NEXT_MESSAGE_ID bigint, primary key (MESSAGE_ID))
12:15:19,750 DEBUG SchemaUpdate:203 - alter table MESSAGE add constraint FK_NEXT_MESSAGE foreign key (NEXT_MESSAGE_ID) references MESSAGE
12:15:19,750 INFO SchemaUpdate:217 - schema update complete
Hibernate:
select
max(MESSAGE_ID)
from
MESSAGE
Hibernate:
insert
into
MESSAGE
(MESSAGE_TEXT, NEXT_MESSAGE_ID, MESSAGE_ID)
values
(?, ?, ?)
Hibernate:
insert
into
MESSAGE
(MESSAGE_TEXT, NEXT_MESSAGE_ID, MESSAGE_ID)
values
(?, ?, ?)
Hibernate:
select
message0_.MESSAGE_ID as MESSAGE1_0_,
message0_.MESSAGE_TEXT as MESSAGE2_0_,
message0_.NEXT_MESSAGE_ID as NEXT3_0_
from
MESSAGE message0_
hello world
good day