第二章: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 {
}
我们创建了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-mapping package="org.hibernate.tutorial.domain">
</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与数据库间的配置主要有三种方式,分别是使用property文件(即.properties),使用xml文件(即.cfg.xml)和使用代码配置,通常使用xml的形式配置。(Hibernate.cfg.xml)描述如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
<hibernate-configuration>
</hibernate-configuration>
文档开始配置数据库相关的信息,包括数据库驱动程序,数据库访问地址,数据库用户名和密码等,hsqldb是内嵌数据库,因此在无需单独安装和配置数据库的前提下,即可运行程序。dialect(方言)是指Hibernate产生哪种类型的sql与配置数据库交互,Hibernate是独立于其它数据库,由于数据库厂商间存在sql访问方式的差异,因此,需要选择特定的方言支配具体厂商和版本的数据库。
之后配置了c3p0数据库连接池。Hibernate自身没有提供对数据库连接池的应用版本的实现,因此,Hibernate常使用第三方的数据库连接池。c3p0是Hibernate最常使用的数据库连接池。实际上,如果不配置数据库连接池,那么Hibernate将提供一个简单的单线程的连接方式(
),这种方式仅在测试阶段使用,不能够满足实际应用的需求。
连接池配置中: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 {
}
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 {
}
通过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">
</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
12:15:09,437
12:15:09,437
12:15:09,453
12:15:09,765
12:15:09,765
12:15:14,437
12:15:17,531
12:15:17,687
12:15:17,703
12:15:17,718
12:15:17,718
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
Hibernate:
Hibernate:
Hibernate:
Hibernate:
hello world
good day