在没有学习Hibernate之前,我们一直都是用jdbc来连接数据库和操纵数据库。所以在刚接触Hibernate时,我们都有一个疑问,为什么要学Hibernate,jdbc不是挺好的吗?那么接下来就来讲讲jdbc的优缺点与为什么要用Hibernate吧。
JDBC
优点:
直接底层操作,提供了很简单、便捷的访问数据库的方法,跨平台性比较强。灵活性比较强,可以写很复杂的SQL语句。
缺点:
- 因为JAVA时面向对象的,JDBC没有做到使数据能够面向对象的编程,是程序员的是靠仍然停留在SQL语句上。
- 操作比较频繁,很多代码需要重复写很多次。
- 如果遇到批量操作,频繁与数据库进行交互,容易造成效率的下降。
为什么要用Hibernate
- Hibernate实现了面向对象的数据库编程
- Hibernate比起JDBC,在代码的书写上比较简单化
- Hibernate提出了缓存机制,这样可以使访问数据的效率提高很多
Hibernate项目所需的lib包
Hibernate配置文件中name里面的一些信息
Hibernate.connection.url 表示要链接的数据库地址
Hibernate.connection.driver_class 表示要链接的数据库的驱动类
Hibernate.connection.username 要连接的数据库的用户名
Hibernate.connection.password 要连接的数据库的密码
Hibernate.dialect 表示要使用的数据库的类型
org.hibernate.dialect.MySQL5Dialect mysql数据库
org.hibernate.dialect.Oracle9Dialect oracle数据库
org.hibernate.dialect.SQLServerDialect SQLServer数据库
hibernate.hbm2ddl.auto
validate:加载hibernate时验证创建表结构
update:加载hibernate时自动更新数据库结构,如果表存在不用创建,如果不存在就创建。
create:每一次加载hibernate时都创建表结构
create-drop:加载hibernate时创建,退出时删除
- Cnfiguration 类负责管理 Hibernate 的配置信息。包括如下内容:
- Hibernate运行的底层信息:数据库的URL、用户名、密码、JDBC驱动类,数据库Dialect,数据库连接池等(对应 hibernate.cfg.xml 文件)。
- 持久化类与数据表的映射关系(*.hbm.xml 文件)
- 创建 Configuration 的两种方式
- 属性文件(hibernate.properties):
- Configuration cfg = new Configuration();
- Xml文件(hibernate.cfg.xml)
- Configuration cfg = new Configuration().configure();
- 属性文件(hibernate.properties):
也可以利用 config.config 方法来指定配置文件所在的目录。
- Configuration对象根据当前的配置信息生成 SessionFactory 对象。SessionFactory 对象一旦构造完毕,即被赋予特定的配置信息(SessionFactory 对象中保存了当前的数据库配置信息和所有映射关系以及预定义的SQL语句。同时,SessionFactory还负责维护Hibernate的二级缓存)。
- Configuration cfg = new Configuration().configure();
- SessionFactory sf = cfg.buildSessionFactory();
- 是线程安全的。
- SessionFactory是生成Session的工厂:
- Session session = sf.openSession();
- 构造 SessionFactory 很消耗资源,一般情况下一个应用中只初始化一个 SessionFactory 对象。
- 在Hibernate中,Transaction tx = session.beginTransaction()相当于给数据库操作起事务。Session.commit()则为提交事务。
- l Hibernate的整个运行过程如下:
- 应用程序先调用Configuration类,该类读取Hibernate配置文件及映射文件中的信息,
- 并用这些信息生成一个SessionFactory对象,
- 然后从SessionFactory对象生成一个Session对象,
- 并用Session对象生成Transaction对象;
- 可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate()等方法对PO进行加载、保存、更新、删除、等操作;
- 在查询的情况下,可通过Session对象生成一个Query对象,然后利用Query对象执行查询操作;如果没有异常,Transaction对象将提交这些操作到数据库中。
接下来详细介绍Hibernate
ORM框架 ------hibernate
- 开源的持久层框架.
- ORM(Object/Relational Mapping)映射工具,建立面向对象的域模型和关系数据模型之间的映射.
- 连接java应用和数据库的中间件.
- 对JDBC进行封装,负责java对象的持久化.
- 在分层结构中处于持久化层,封装对数据库的访问细节,使业务逻辑层更专注于实现业务逻辑
Hibernate的优点:
- Hibernate对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。
- Hibernate是一个基于jdbc的主流持久化框架,是一个优秀的orm实现,它很大程度的简化了dao层编码工作。
- Hibernate使用java的反射机制,而不是字节码增强程序类实现透明性
- Hibernate的性能非常好,因为它是一个轻量级框架。映射的灵活性很出色。它支持很多关系型数据库,从一对一到多对多的各种复杂关系。
- Hibernate使数据库的操作完全面向对象。而不是从前的面向关系进行操作。
对象关系映射(Object/Relation Mapping)提供了概念性的、易于理解的模型化数据的方法。ORM方法论基于三个核心原则: 简单:以最基本的形式建模数据。 传达性:数据库结构被任何人都能理解的语言文档化。 精确性:基于数据模型创建正确标准化了的结构。 典型地,建模者通过收集来自那些熟悉应用程序但不熟练的数据建模者的人的信息开发信息模型。建模者必须能够用非技术企业专家可以理解的术语在概念层次上与数据结构进行通讯。建模者也必须能以简单的单元分析信息,对样本数据进行处理。ORM专门被设计为改进这种联系。
Hibernate的主键生成机制:
表示符生成器 | 描述 |
Increment | 由hibernate自动以递增的方式生成表识符,每次增量为1 |
Identity | 由底层数据库生成表识符。条件是数据库支持自动增长数据类型。 |
Sequence | Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。 |
Native | 根据底层数据库对自动生成表示符的能力来选择identity、sequence、hilo |
Uuid.hex | Hibernate采用128位的UUID算法来生成标识符。该算法 能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。 如果主键用字符类型,并且不代表任何含义。 |
assigned | 适用于自然主键。由java程序负责生成标识符。不能把setID()方法声明为 Private的。尽量避免使用自然主键。 |
increment 标识符生成器
- increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
- Hibernate 会先读取 NEWS 表中的主键的最大值, 而接下来向 NEWS 表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.(带走+1)
- 适用范围:
- 由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
- 适用于只有单个 Hibernate 应用进程访问同一个数据库的场合
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
identity 标识符生成器
- identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型(加1带走)
- 适用范围:
- 由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型. 支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
sequence 标识符生成器
- sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
- Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
- 适用范围:
- 由于 sequence 生成标识符的机制依赖于底层数据库系统的序列, 因此, 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2 Oracle 等
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
native 标识符生成器
- native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
- 适用范围:
- 由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
- OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
assigned 标识符生成器
- hibernate和底层数据库都不帮助你生成主键,也就是说得自己在程序中手动的设置主键的值。
- 适用范围:
- 主键有一定的含义,需要根据业务产生的情况。
Uuid标识符生成器
- Hibernate采用128位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符,这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间
- 使用范围:
- 主键是字符串,而且必须是唯一
持久化对象的状态
持久化对象有3种状态:
- 持久化状态
- 临时状态
- 游离状态
Session 的特定方法能使对象从一个状态转换到另一个状态
临时对象(transient)
- 在使用代理主键的情况下, OID 通常为 null
- 不处于 Session 的缓存中
- 在数据库中没有对应的记录
- 当通过new语句刚创建了一个java对象,它处于临时对象,此时不和数据库中的任何记录对应。
持久化对象(也叫”托管”)(Persist)
- OID 不为 null
- 位于 Session 缓存中
- 持久化对象和数据库中的相关记录对应
- Session 在清理缓存时, 会根据持久化对象的属性变化, 来同步更新数据库
- 在同一个 Session 实例的缓存中, 数据库表中的每条记录只对应唯一的持久化对象
- Session的save()方法把临时对象转变为持久化对象。
- Session的load()或get()方法返回的对象总是处于持久化对象。
- Session的update()、saveOrUpdate()和lock()方法使游离对象转变为持久化对象。
- 当一个持久化对象关联一个临时对象,在允许级联保存的情况下,Session在清理缓存时会把这个临时对象也转变为赤计划对象。
游离对象(也叫”脱管”)(Detached)
- OID 不为 null
- 不再处于 Session 的缓存中
- 一般情况需下, 游离对象是由持久化对象转变过来的, 因此在数据库中可能还存在与它对应的记录
Session使用以下方法可以使持久化对象转变成游离对象:
- 当调用Session的close()方法时,Session的缓存被清空,缓存中的所有持久化对象都变为游离对象。如果在应用程序中没有引用变量引用这些游离对象,它们就会结束生命周期。
- Session的evict()方法能够从缓存中删除一个持久化对象,时它比哪位游离状态。当Session的缓存中保存了大量的持久化对象,会消耗许多内存空间,为了提高性能,可以考虑调用evict()方法,从缓存中删除一些持久化对象。
测试hibernate中对象变化的状态:
程序代码 | 生命周期 | 状态 |
tx = session.beginTransaction(); Customer c = new Customer); | 开始生命周期 | 临时状态 |
Session.save(c) | 处于生命周期中 | 转变为持久化状态 |
Long id=c.getId(); c = null; Customer c2 = (Customer)session.load(Customer.class,id); tx.commit(); | 处于生命周期中 | 处于持久化状态 |
session.close(); | 处于生命周期中 | 转变为游离态 |
c2.getName(); | 处于生命周期中 | 处于游离态 |
c2 = null; | 结束生命周期 | 结束生命周期 |
对象状态转化图
对象状态的总结
操纵持久化对象-save()
- Session 的 save() 方法使一个临时对象转变为持久化对象
- Session 的 save() 方法完成以下操作:
- 把 News 对象加入到 Session 缓存中, 使它进入持久化状态
- 选用映射文件指定的标识符生成器, 为持久化对象分配唯一的 OID. 在使用代理主键的情况下, setId() 方法为 News 对象设置 OID 使无效的.
- 计划执行一条 insert 语句,把Customer对象当前的属性值组装到insert语句中
- Hibernate 通过持久化对象的 OID 来维持它和数据库相关记录的对应关系. 当 News 对象处于持久化状态时, 不允许程序随意修改它的 ID
操纵持久化对象-update()
- Session 的 update() 方法使一个游离对象转变为持久化对象, 并且计划执行一条 update 语句.
操纵持久化对象-saveOrupdate()
saveOrUpdate:
该方法同时包含save和update方法,如果参数是临时对象就用save方
法,如果是游离对象就用update方法,如果是持久化对象就直接返回。
如果参数是临时对象就用save方法
如果是游离对象就用update方法
如果是持久化对象就直接返回,不执行操作
映射一对多关联关系
单向关联
仅仅建立从Order到Customer的多对一关联,即仅仅在Order类中定义customer属性。或者仅仅建立从Customer到Order的一对多关联,即仅仅Customer类中定义orders集合。
单向 n-1 关联只需从 n 的一端可以访问 1 的一端
域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
Hibernate 使用 <many-to-one> 元素来映射多对一关联关系
保存操作
级联保存和更新
- 当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,会抛出TransientObjectException.如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
双向关联
双向 1-n 与 双向 n-1 是完全相同的两种情形
双向 1-n 需要在 1 的一端可以访问 n 的一端, 反之依然.
域模型:
从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
关系数据模型:
ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
建立一对多的双向关联关系
Hibernate使用set元素来映射一对多关联关系
级联保存和更新
当hibernate持久化一个临时对象时,在默认情况下,他不会自动持久化所关联的其他临时对象,会抛出TransientObjectException.如果设定set元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
级联删除
inverse属性
Inverse来源
在hibernate中通过对 inverse 属性的值决定是由双向关联的哪一方来维护表和表之间的关系. inverse=false 的为主动方,inverse=true 的为被动方, 由主动方负责维护关联关系
Inverse设值
在没有设置 inverse=true 的情况下,父子两边都维护父子关系
Inverse设值原则
在 1-n 关系中,将 n 方设为主控方将有助于性能改善
在 1-N 关系中,若将 1 方设为主控方 会额外多出 update 语句
在一的一方设值inverse为TRUE表明一的一方不维护其关系,这样就会发出一条update语句,这样效率也就提高了。
Inverse结论
1.在映射一对多的双向关联关系时,应该在one方把inverse属性设为true,
这可以提高性能。
2.在建立两个对象的关联时,应该同时修改关联两端的相应属性:
customer.getOrders().add(order);
order.setCustomer(customer);
这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码
不受Hibernate实现类的影响。同理,当删除双向关联的关系时,也应该修改
关联两端的对象的相应属性:
Customer.getOrders().remove(order);
Order.setCustomer(null);
级联删除
cascade属性
在数据库中对集合进行排序
<set> 元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序