李刚的书写的很晦涩,不知道那么多人捧是为什么……对照着官方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>。