读书笔记:Hibernate基本用法(JavaEE 企业应用实战,3rd)

李刚的书写的很晦涩,不知道那么多人捧是为什么……对照着官方Document看了一下,基本就是把那文档翻译过来了,只不过裁剪了一些废话。

但是很多翻译的都不明白。

以下是结合书中第五章《Hibernate基本用法》和官方文档中所述,学习所得。

1、Hibernate的第一个例子。

使用Hibernate ORM框架开发的程序一般是四个部分组成:

  • POJO(代表一个实体)
  • 实体的hbm映射
  • Hibernate总体配置(数据库连接等)
  • Java代码,用于驱动ORM。

POJO类

符合Java的POJO规范,必须有无参数的构造函数,可选有setter和getter,不是必须的,因为Hibernate可以用反射搞定。

package com.coder4;

public class News {
	private Integer id;
	private String title;
	private String content;

	public News() {

	}

	public Integer getId() {
		return id;
	}

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

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
}

hbm映射文件

该文件指名POJO和数据库的映射关系:POJO中哪些域,要对应到,数据库的哪些字段、表中。

文件命名规范:类名.hbm.xml,例如这里是News.hbm.xml。其实可以多个Class的映射关系写到一个文件中,但不便于维护,因此不推荐。

News.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
                                   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.coder4">
	<class name="com.coder4.News" table="tbl_news">
		<id name="id" type="java.lang.Integer">
			<column name="id" />
			<generator class="identity" />
		</id>
		<property name="title" type="java.lang.String" />
		<property name="content" type="java.lang.String" />
	</class>
</hibernate-mapping>

从上述文件可以很清楚的看到:

  • 一个POJO对应一个<class>配置标签,name与类全名保持一致。
  • 普通(Java8大内置类型)可以用<id>或<property>表示
  • <id>是数据库中的主键(物理主键,一般无BI或逻辑含义。)
  • <id>或<property>的name属性为POJO的属性名。
  • 默认的,数据库中列名与name一致,如果想更改,可以指定column树形,或者在<property>下追加<column>孩子结点。
  • <id>下的<generator>可以指定该字段自动递增的方式,这个是数据库中很常见的功能了。

Hibernate配置文件

Hibernate配置文件的默认文件名是hibernate.cfg.xml,当类Configuration对象调用configure()方法时,会自动加载这个配置文件。

<?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 name="">
		<!-- DB & Connection -->
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.password">123456</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>
		<property name="hibernate.connection.username">liheyuan</property>
		<!-- Dialect and hbm2ddl -->
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="hbm2ddl.auto">update</property>
		<!-- C3P0 -->
		<property name="hibernate.c3p0.max_size">40</property>
		<property name="hibernate.c3p0.min_size">5</property>
		<property name="hibernate.c3p0.max_statements">100</property>
		<property name="hibernate.c3p0.timeout">1800</property>
		<property name="hibernate.c3p0.idle_test_period">600</property>
		<property name="hibernate.c3p0.acquire_increment">2</property>
		<property name="hibernate.c3p0.validate">true</property>
		<!-- Mapping -->
		<mapping resource="com/coder4/News.hbm.xml" />
	</session-factory>
</hibernate-configuration>

上述配置由4部分组成:

  • 数据库连接配置:hibernate.connection.*
  • 数据库方言:dialect,是否要自动创建数据库中的表:hbm2ddl.auto,如果是update,将自动创建,Session结束时不删除表。如果是create,每次执行时会重新建一个新表(在新建之前删除)。如果是create-drop,自动创建,且session结束后删除表。
  • c3p0,Hibernate推荐使用的数据库连接池之一,也可以用proxool。
  • <mapping>标签指定了引入哪些之前定义的POJO映射文件类,例如这里,我们引入了News.hbm.xml。

除了这些,在hibernate.cfg.xml中,还可以指定Cache、Transection(事务)、调试、抓取量等选项。

操作代码:

依赖的其他lib有:

c3p0-0.9.1.jar
hibernate3.jar
slf4j-api-1.6.1.jar
slf4j-nop-1.6.1.jar
dom4j-1.6.1.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
mysql-connector-java-5.1.22-bin.jar
commons-collections-3.1.jar
antlr-2.7.6.jar
javassist-3.12.0.GA.jar
jta-1.1.jar

具体代码

package com.coder4;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class NewsManager {
	public static void main(String[] args) {
		// Load config, default hibernate.cfg.xml
		Configuration conf = new Configuration().configure();
		// Get SessionFactory
		SessionFactory sf = conf.buildSessionFactory();
		// Session
		Session sess = sf.openSession();
		// Transection
		Transaction tx = sess.beginTransaction();
		News n = new News();
		n.setTitle("计算所 标题");
		n.setContent("计算所 内容内容");
		// Save & commit
		sess.save(n);
		tx.commit();
		// Close Transection & SessionFactory
		sess.close();
		sf.close();
	}
}

2、Hibernate基本结构。

Hibernate的核心目的:将用户从原始的JDBC代码堆砌中解放出来,以面向对象的方式操纵数据库。

Hibernate的几大关键对象:

  • SessionFactory:线程安全,一个应用一个就够,它是Session的工厂。
  • Session:单线程(非线程安全),底层对应一个JDBC连接,业务逻辑的数据持久化、访问都需要通过Session完成。它是Transaction的工厂。
  • Transaction:事务,对应数据库中的原子操作。Hibernate的事务集成了JDBC、JTA、COBRA的事务概念(后几个都是在分布式系统中的事务)。
  • TransactionFactory:生成Transaction的实际工厂,在Hibernate中被内部封装了,一般Session.beginTransaction()就可以了,无须直接访问这个类。
  • ConnectionProvider:在Hibernate中的地位,类似于JDBC中的DriverManager,实际也是对它的封装。
  • 持久化对象=POJO+与之关联(管理它的)Session。

此外,持久化对象的状态非常重要,主要有三个:

  • 瞬态:刚new出来的,还没有与Session关联。
  • 持久化状态:通过Sesision的save、load等,与Session绑定在一起的状态,此时可进行持久化或读取操作。
  • 脱管:注意是不是托,当持久化状态的对象,Session关闭,或通过API解除绑定后,变成这个状态。

3、hibernate.cfg.xml(Hibernate配置文件)详解

暂略

4、深入持久化对象

暂略

5、XXX.hbm.xml(Hibernate映射文件)详解

映射文件的结构一般如下:

<hibernate-mapping ......>
	<class ......>
	<class ......>
	<class ......>
</hibernate-mapping ......>

根<hibernate-mapping>的重要属性有:

  • schema、catalog:指定schema、catalog,不知道做什么用的……
  • default-cascade:级联风格,默认为none,不级联。
  • default-access:property,需要getter和setter方法访问属性,默认是这个。field,通过反射,直接获取属性名。
  • default-lazy:控制延迟加载,默认true,也建议不关闭。
  • package:指定包名,用于本文件中没有写完整包名路径的类。
  • auto-import:是否允许使用非全限定类名,和上面的package共用。

一个<hibernate-mapping>元素可以对应多个<class>元素,即一个文件中可以配置多个持久化类。但一般建议每个类一个文件,存在Class同目录下、命名为类名.hbm.xml,方便管理与后期维护。

<class>元素的可选属性为:

  • table:存储这类持久化对象的表名
  • discriminator-value:当有子类继承<subclass>时,可以用此属性区分父类还是子类。
  • mutable:持久化对象是否可改变,true或false。
  • proxy:用于替换lazy-load时的代理类。
  • dynamic-update、dynamic-insert:指定用于插入记录的insert、update语句是否在运行时动态生成。默认为false,如果开启true,生成sql时间可能会延长。如果开启,建议同时配合version的锁策略。
  • select-before-update:很明显了,在更新之前是否需要select,默认为false,即update总执行。如果设置成true,则会先select,并比对数据库中状态与POJO是否一致,一样就不更新了。
  • polumorphism:当使用<union-subclass>的继承类时配置。
  • where:附加过滤条件,不管是load()还是get(),只要试图加载,都会应用这个条件。
  • batch-size:抓取实例时,每批数据抓取数量。
  • optimistic-lock:乐观锁定策略,默认version。
  • subselect:映射只读实体,类似视图。

<class>下面的孩子,常见的为<id>和<property>,前者对应数据库中的主键,后者为非主键的普通属性。

映射(数据库)主键

<id name="id" column="_id" type="java.lang.Integer">
    <generator class="identity" />
</id>

如上所示,<id>表示了一个主键,name为主键在POJO中的属性名,column是主键在DB中的列名。type是主键类型。

<id>内置的generator是自动生成主键的策略。生成策略分为很多种:increment、identity、sequence、hilo、甚至uuid等。

但若采用uuid,POJO的字段类型需要为String。

映射(数据库)普通属性

如下是3个比较典型的普通属性配置:

<property name="title" type="java.lang.String" not-null="true" />
<property name="content" type="java.lang.String" />
<property name="type" column="news_type" index="index_news_type"
			type="java.lang.Integer" />
<property name="full" column="news_full" type="java.lang.String"
			formula="(select concat(title, content) from tbl_news where tbl_news.id=id)" />
  • name:POJO中的属性名
  • type:字段类型类型
  • column:在DB中的列名
  • not-null:同数据库
  • index:指定该字段含索引、及索引的名称。
  • unique_key:配合index,若指定了,则将创建uniq-key-index索引。
  • length、scale、precision:当为字符串时,length控制CHAR长度。scale和precision则为double类型的精度。
  • formula:则该属性只不写DB只读,且读时由formula指定的SQL语句生成,比如这里拼接了两个字段。注意参数由不加数据库名约束的id,在Load时由同类型的传入,在这里参数为=id。此外,sql必须用()括起来!!

对应的读取驱动如下:

News n2 = (News) sess.load(News.class, 2);
System.out.println(n2.getFull());

自动生成字段

Hibernate也支持由数据库自动生成字段。

首先,要将字段的generated设置成insert或者always,这将使得通过Hibernate执行了update或者insert后,自动触发select,以映射最新的数据到POJO中,但此generated并不管怎么自动生成。

<property name="pubtime" generated="insert" not-null="true"
			type="timestamp">
    <column name="news_pubtime" sql-type="timestamp" default="CURRENT_TIMESTAMP" />
</property>
import java.sql.Timestamp;

private Timestamp pubtime;

	public Timestamp getPubtime() {
		return pubtime;
	}

	public void setPubtime(Timestamp pubtime) {
		this.pubtime = pubtime;
	}

具体的生成方式需要额外指定,例如可以是触发器,也可以是时间戳这种default值:通过子column,设置sql-type和default值,如上所示。

注意,一旦使用column,则必须指明sql-type,否则可能无法创建数据库。

6、映射集合属性

一个POJO中,可能出现集合属性,例如Map、List等。对应的含义可能是课程的成绩、一个人的昵称(假设有很多个,需要用List存~)

Hibernate支持了很多集合属性的映射,其tag对应如下:

  • <list>/<set>/<map>/<array>:对应Java内置的List、Set、Map接口和数组。
  • <bag>、<idbag>:实际对应了Java内置的Collection。

集合属性其实也是一个属性,和上面POJO的lastname等一样,因此继承了name、schema、lazy等属性,同时又有以下不同点:

  • table:因为需要存储在另一个表里。
  • cascade:指定是否级联。
  • order-by:是否排序,可选xxx asc/desc
  • 子标签<key>:在table中,需要有一个物理主键(例如自增的id,同News中的id)。
  • 对于list、map、array,除了上述物理主键,还需要有对应Collection的逻辑主键,例如Map的Key、List、Array的下标。分别用<map-key>、<list-index>等指定。

特别注意:映射Collection属性时,属性必须使用接口,如Set、List。而new时候才能使用ArrayList等。

映射List属性

假设Person中有一个属性,List<String>,如下:

package com.coder4;

import java.util.ArrayList;
import java.util.List;

public class Person {
	private Integer id;
	private String name;
	private Integer age;
	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	private List<String> schools = new ArrayList<String>();

	public Integer getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public List<String> getSchools() {
		return schools;
	}

	public void setSchools(List<String> schools) {
		this.schools = schools;
	}
}

一般来说,这个List要单独放在另外一张表中的,所以map文件如下:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Jan 3, 2013 11:12:25 PM by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
	<class name="com.coder4.Person" table="tbl_person">
		<id name="id" column="p_id" type="java.lang.Integer">
			<generator class="identity" />
		</id>
		<property name="age" column="p_age" type="java.lang.Integer" />
		<property name="name" column="p_name" type="java.lang.String" />
		<list name="schools" table="tbl_school">
			<key column="p_id" not-null="true" />
			<list-index column="s_order" />
			<element column="s_name" type="java.lang.String" />
		</list>
	</class>
</hibernate-mapping>

如上所示,Person存在tbl_person表中,School存在了tbl_school表中,两个表通过p_id这个外键连接在一起。

school表中,s_order是对应的s_name在schools的List中的下标。同时,该表是联合主键:p_id和s_order。因此,可以保证不同Person之间的school名即使有冲突,也可以正常存放,且顺序不会乱。

两个表的结构如下:

tbl_person:

mysql> describe tbl_person;
+--------+--------------+------+-----+---------+----------------+
| Field  | Type         | Null | Key | Default | Extra          |
+--------+--------------+------+-----+---------+----------------+
| p_id   | int(11)      | NO   | PRI | NULL    | auto_increment |
| p_age  | int(11)      | YES  |     | NULL    |                |
| p_name | varchar(255) | YES  |     | NULL    |                |
+--------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

tbl_school:

mysql> describe tbl_school;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| p_id    | int(11)      | NO   | PRI | NULL    |       |
| s_name  | varchar(255) | YES  |     | NULL    |       |
| s_order | int(11)      | NO   | PRI | NULL    |       |
+---------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

最后看驱动:

package com.coder4;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class PersonManager {
	public static void main(String[] args) {
		// Load config, default hibernate.cfg.xml
		Configuration conf = new Configuration().configure();
		// Get SessionFactory
		SessionFactory sf = conf.buildSessionFactory();
		// Session
		Session sess = sf.openSession();
		// Transection
		Transaction tx = sess.beginTransaction();
		Person p = new Person();
		p.setName("张三");
		p.setAge(29);
		p.getSchools().add("北京大学");
		p.getSchools().add("哈尔滨佛学院");
		// Save & commit
		sess.save(p);
		tx.commit();
		// Close Transection & SessionFactory
		sess.close();
		sf.close();
	}
}

映射数组属性

映射数组属性和List很相似,除了<list>换成了<array>,连下标映射字段都沿用了<list-index>

POJO如下:

package com.coder4;

import java.util.ArrayList;
import java.util.List;

public class Person {
	private Integer id;
	private String name;
	private Integer age;
	private String[] schools;

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public Integer getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String[] getSchools() {
		return schools;
	}

	public void setSchools(String[] schools) {
		this.schools = schools;
	}
}

Person.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Jan 3, 2013 11:12:25 PM by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
	<class name="com.coder4.Person" table="tbl_person">
		<id name="id" column="p_id" type="java.lang.Integer">
			<generator class="identity" />
		</id>
		<property name="age" column="p_age" type="java.lang.Integer" />
		<property name="name" column="p_name" type="java.lang.String" />
		<array name="schools" table="tbl_school">
			<key column="p_id" not-null="true" />
			<list-index column="s_order" />
			<element column="s_name" type="java.lang.String" />
		</array>
	</class>
</hibernate-mapping>

驱动:

package com.coder4;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class PersonManager {
	public static void main(String[] args) {
		// Load config, default hibernate.cfg.xml
		Configuration conf = new Configuration().configure();
		// Get SessionFactory
		SessionFactory sf = conf.buildSessionFactory();
		// Session
		Session sess = sf.openSession();
		// Transection
		Transaction tx = sess.beginTransaction();
		Person p = new Person();
		p.setName("张三");
		p.setAge(29);
		String[] schools = new String[] { "北京大学", "哈尔滨佛学院" };
		p.setSchools(schools);
		// Save & commit
		sess.save(p);
		tx.commit();
		// Close Transection & SessionFactory
		sess.close();
		sf.close();
	}
}

 映射Set属性

Set内无次序,但是仍然需要保证主键约束:按照Set的语义,每一个p_id对应的p_name,不能重复。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Jan 3, 2013 11:12:25 PM by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
	<class name="com.coder4.Person" table="tbl_person">
		<id name="id" column="p_id" type="java.lang.Integer">
			<generator class="identity" />
		</id>
		<property name="age" column="p_age" type="java.lang.Integer" />
		<property name="name" column="p_name" type="java.lang.String" />
		<set name="schools" table="tbl_school">
			<key column="p_id" not-null="true" />
			<element column="s_name" type="java.lang.String" not-null="true" />
		</set>
	</class>
</hibernate-mapping>

注意:p_name必须是not-null的,否则无法作为主键!

做好后的数据库结构如下,p_id和s_name联合主键:

mysql> describe tbl_school;
+--------+--------------+------+-----+---------+-------+
| Field  | Type         | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| p_id   | int(11)      | NO   | PRI | NULL    |       |
| s_name | varchar(255) | NO   | PRI | NULL    |       |
+--------+--------------+------+-----+---------+-------+

代码如下:

package com.coder4;

import java.util.HashSet;
import java.util.Set;

public class Person {
	private Integer id;
	private String name;
	private Integer age;
	private Set<String> schools = new HashSet<String>();

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public Integer getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Set<String> getSchools() {
		return schools;
	}

	public void setSchools(Set<String> schools) {
		this.schools = schools;
	}
}
package com.coder4;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class PersonManager {
	public static void main(String[] args) {
		// Load config, default hibernate.cfg.xml
		Configuration conf = new Configuration().configure();
		// Get SessionFactory
		SessionFactory sf = conf.buildSessionFactory();
		// Session
		Session sess = sf.openSession();
		// Transection
		Transaction tx = sess.beginTransaction();
		Person p = new Person();
		p.setName("张三");
		p.setAge(29);
		p.getSchools().add("北京大学");
		p.getSchools().add("哈尔滨佛学院");
		// Save & commit
		sess.save(p);
		tx.commit();
		// Close Transection & SessionFactory
		sess.close();
		sf.close();
	}
}

映射Collection属性

是的,这里指的是java.util.Collection,它对应的hbm标签是<bag>。

 

 

Leave a Reply

Your email address will not be published.