1 什么是Hibernate
Hibernate是ORM框架,能够建立面向对象的模型和关系数据库数据模型之间的映射。使Java开发人员只需要操纵Java对象,而无需编写SQL语句,就可以将Java对象持久化到数据库中。ORM是Object-Relation Mapping的简称,对象-关系映射,指的是负责Java对象的持久化,封装数据访问细节。Hibernate位于分层架构的持久层,封装数据访问的细节。持久层的解决方案有JDBC,iBatis,Hibernate,Spring jdbcTemplate,EJB的CMP等等。
优势:
1)开发简洁。如果使用jdbc那么数据插入的话您要构造PreparedStatement或者构造SQL,查询的话您要处理ResultSet,这些代码很啰嗦,而使用Hibernate的话代码更加清晰简洁,Hibernate会自动解析配置文件生成相应的SQL语句。
2)跨数据库。对于一个特定的项目,对数据库移植性通常要求不高,一般选定一个数据库后不会轻易更改。但是对于一个通用的中间件产品来讲通常要求能够支持多种数据库,例如您要开发一个工作流产品,那么您面对的客户用哪种数据库产品都有可能,这就要求您开发的工作流引擎支持多种数据库,具备跨数据库特性。
劣势:
1)复杂的查询。对于复杂的查询通常要编写针对性的SQL,已达到最优化的效能,这时如果依靠ORM工具自动生成的SQL就不太现实了。
2 Hibernate之helloworld
下面以学生,班级,课程,这几个实体对象及对象之间的关系来简单的体验Hibernate。
helloworld程序介绍简单的课程的增、删、改、查。学生和班级的关系是N:1,即一个学生只能属于一个班级,一个班级可以有多个学生。学生和课程的关系是M:N,即一个学生可以学习多门课程,一个可能也可以有多个学生来学。
数据库脚本如下:
create table clazz(id int not null AUTO_INCREMENT,name varchar(20) not null,primary key(id))
create table student(id int not null AUTO_INCREMENT,name varchar(20) not null,clazzid int not null,primary key(id));
create table course(id int not null AUTO_INCREMENT,name varchar(20) not null ,primary key(id));
create table student_course(studentid int not null,courseid int not null,primary key(studentid,courseid));
先来最简单的单表操作吧。
Hibernate开发依赖的最小包列表:
|
antlr.jar cglib-full.jar asm.jar asm-attrs.jar commons-collections.jar commons-logging.jar ehcache.jar hibernate3.jar jta.jar dom4j.jar log4j.jar |
主配置文件:
hibernate.cfg.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//HIbernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/demo</property>
<property name="connection.username">root</property>
<property name="connection.password">frank1234</property>
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="show_sql">true</property>
<mapping resource="course.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Course对象:
public class Course implements Serializable {
public Course(){}
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString(){
return "id="+id+",name="+name;
}
}
course配置文件:
course.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//HIbernate/HibernateMapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="org.frank1234.hibernate.helloworld.Course" table="course">
<id name="id" column="id">
<generator class="identity"/>
</id>
<property name="name" />
</class>
</hibernate-mapping>
这个有4个要说明的地方:
1)需要将这个配置文件,添加到hibernate.cfg.xml中,否则Hibernate不认Course对象。
抛出异常: org.hibernate.MappingException: Unknown entity: org.frank1234.hibernate.helloworld.Course
2)<generator class="identity"/>表示使用数据库的自增字段。
3)由于Course对象和数据库course表的字段名称和类型一样,所以可以直接写成<property name="name" />
4)可以给hibernate-mapping添加属性package,这样后面的class属性就可以省去包路径了。
获取Session公共类:
HibernateUtil对象:
public class HiberanteUtil {
public static SessionFactory sessionFactory = null;
static{
try{
sessionFactory = new Configuration().configure().buildSessionFactory();
}catch(Exception e){
e.printStackTrace();
}
}
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException{
Session s = (Session) session.get();
if(s == null){
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException{
Session s = (Session)session.get();
if(s != null)
s.close();
session.set(null);
}
}
1)其中SessionFactory是重量级的,创建和销毁都会消耗较大,他要读取配置文件信息,加载到缓存中,一般一个数据库一个,启动的时候加载一次就可以了。
2)Session对象是非线程安全的,是轻量级的,所以一个线程要创建一个Session对象,主要的增删改查接口都在Session对象中。
测试类:
CourseMain :
public class CourseMain {
public static void main(String[] args) throws Exception{
save();
// delete();
// update();
// list();
}
private static void save(){
Course course = new Course();
course.setName("语文");
Session session = HiberanteUtil.currentSession();
Transaction tx = session.beginTransaction();
try{
tx.begin();
session.save(course);
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
HiberanteUtil.closeSession();
}
}
private static void delete(){
Course course = new Course();
course.setId(3);
Session session = HiberanteUtil.currentSession();
Transaction tx = session.beginTransaction();
try{
tx.begin();
session.delete(course);
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
HiberanteUtil.closeSession();
}
}
private static void update(){
Course course = new Course();
course.setId(5);
course.setName("英语");
Session session = HiberanteUtil.currentSession();
Transaction tx = session.beginTransaction();
try{
tx.begin();
session.update(course);
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
HiberanteUtil.closeSession();
}
}
private static void list(){
Session session = HiberanteUtil.currentSession();
try{
// List list = session.createQuery("from Course").list();
// for(int i =0;i<list.size();i++){
// System.out.println(list.get(i));
// }
// Course course = (Course)session.load(Course.class, 5);
// System.out.println(course);
// Course course = (Course) session.get(Course.class,5);
// System.out.println(course);
}catch(Exception e){
e.printStackTrace();
}finally {
HiberanteUtil.closeSession();
}
}
}
值得注意的有2个:
1)最后一定要记得关闭Session。
2)查询获取的几种方式。
3 1:N关联
笔者觉得这个必须得理解好,理解好了它M:N就很容易理解了。
班级和学生是1:N关系。
Clazz对象:
public class Clazz implements Serializable {
private int id;
private String name;
private Set<Student> students = new HashSet<Student>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
public String toString(){
return "id="+id+",name="+name;
}
}
clazz中有private Set<Student> students = new HashSet<Student>();属性。
Student对象:
public class Student implements Serializable {
public Student(){}
private int id;
private String name;
private Clazz clazz;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
public String toString(){
return "id="+id+",name="+name;
}
}
Student对象有 private Clazz clazz;属性,这是一个双向的关联关系。
但是数据库中只在Student表中保存了到Clazz表的clazzid字段,这点同对象模型不大一样,对象模型可以是多对多,一对多,甚至可以是双向的多对多,但是在数据库中只有单向的多对一。
clazz配置文件:
clazz.hbm.xml:
<hibernate-mapping>
<class name="org.frank1234.hibernate.helloworld.onetomany.Clazz" table="clazz">
<id name="id" >
<generator class="identity"/>
</id>
<property name="name"/>
<set name="students" table="student" >
<key column="clazzid"/>
<one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>
</class>
</hibernate-mapping>
由于Clazz对象有一个到Student对象的Set属性。是1:N关系,
所以clazz.hbm.xml配置文件要配置如下属性:
<set name="students" table="student" >
<key column="clazzid"/>
<one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>
Student配置文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//HIbernate/HibernateMapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="org.frank1234.hibernate.helloworld.onetomany.Student" table="student">
<id name="id" column="id">
<generator class="identity"/>
</id>
<property name="name" />
<many-to-one name="clazz" class="org.frank1234.hibernate.helloworld.onetomany.Clazz" column="clazzid" />
</class>
</hibernate-mapping>
因为Student对象有一个到Clazz对象的属性,所以其中要添加如下属性:
<many-to-one name="clazz" class="org.frank1234.hibernate.helloworld.onetomany.Clazz" column="clazzid" />
保存的单元测试类:
public class OnetoManyMain {
public static void main(String[] args) throws Exception{
save();
}
private static void save() throws Exception{
Student student1 = new Student();
student1.setName("小红");
Student student2 = new Student();
student2.setName("小兰");
Clazz clazz = new Clazz();
clazz.setName("小班");
//既然设置的是双向关联,就同时更新对象的状态,解除关系的时候也一样,同时解除
student1.setClazz(clazz);
student2.setClazz(clazz);
clazz.getStudents().add(student1);
clazz.getStudents().add(student2);
Session session = HiberanteUtil.currentSession();
Transaction tx = session.beginTransaction();
try{
session.save(clazz);
session.save(student1);
session.save(student2);
tx.commit();
}catch (Exception e){
e.printStackTrace();
tx.rollback();
}finally {
HiberanteUtil.closeSession();
}
}
}
输出为:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: update student set clazzid=? where id=?
Hibernate: update student set clazzid=? where id=?
几点说明:
1)由上面的输出可见,执行了insert 还执行了update多执行了一次,这样对性能必然有较大影响,在clazz.hbm.xml中设置上
inverse="true"。
<set name="students" table="student" inverse="true" >
<key column="clazzid"/>
<one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>
变为了如下输出,没有了update:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)
inverse="true"的含义是Clazz端的关联只是Student端关联的镜像,当Hibernate检测到持久化对象Clazz和Student对象均发生变化时,仅按照Student对象的变化来更新数据库。
2)级联保存和更新。
注释掉测试类的如下部分。
session.save(clazz);
// session.save(student1);
// session.save(student2);
如果配置文件不变,则输出:
Hibernate: insert into clazz (name) values (?)
如果将clazz.hbm.xml设置为:
<set name="students" table="student" inverse="true"
cascade="save-update" >
<key column="clazzid"/>
<one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>
那么输出为:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)
表示保存Clazz对象的同时也保存它关联的Student对象。
注释掉测试类的如下部分:
// session.save(clazz);
session.save(student1);
session.save(student2);
如果不修改配置文件会抛出如下异常:
Hibernate: insert into student (name, clazzid) values (?, ?)
2015-01-03 17:19:30,200: Column 'clazzid' cannot be null
因为没有保存clazz,而student表的字段clazzid不允许为空。
如果将student.hbm.xml也加上cascade="save-update"
<many-to-one name="clazz" class="org.frank1234.hibernate.helloworld.onetomany.Clazz" column="clazzid" cascade="save-update"/>
就可以正确输出:
Hibernate: insert into clazz (name) values (?)
Hibernate: insert into student (name, clazzid) values (?, ?)
Hibernate: insert into student (name, clazzid) values (?, ?)
哪个配置文件设置了cascade就可以对保存哪个对象的时候检查它的关联对象。
3)级联删除。
如果将clazz.hbm.xml配置上级联删除。
<set name="students" table="student" inverse="true" cascade="save-update,
delete" >
<key column="clazzid"/>
<one-to-many class="org.frank1234.hibernate.helloworld.onetomany.Student"/>
</set>
测试代码:
Clazz clazz16 = (Clazz)session.load(Clazz.class,16);
session.delete(clazz16);
输出:
Hibernate: select clazz0_.id as id2_0_, clazz0_.name as name2_0_ from clazz clazz0_ where clazz0_.id=?
Hibernate: select students0_.clazzid as clazzid1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.clazzid as clazzid1_0_ from student students0_ where students0_.clazzid=?
Hibernate: delete from student where id=?
Hibernate: delete from student where id=?
Hibernate: delete from clazz where id=?
Hibernate生成的查询SQL还真够奇葩的。
但是如果你仅仅创建了一个Clazz对象,则级联删除是不生效的。
例如
Clazz clazz17 = new Clazz();
clazz17.setId(17);
session.delete(clazz17);
输出:
Hibernate: delete from clazz where id=?
Student表并不做删除。
这些规则咋看起来挺复杂的,其实理解了以后也不难。
4 M:N关联
M:N 同1:N类似,这里就不贴代码了。
关键配置文件如下:
<set name="courses"
table="student_course"
>
<key column="studentid"/>
<many-to-many
column="courseid"
class="org.frank1234.hibernate.helloworld.Course"/>
</set>