[toc]
Hibernate它是一个轻量级的jdbc封装,也就是说,我们可以使用hibernate来完成原来我们使用jdbc完成操作,就是与数据库的交互操作。它是在dao层去使用的。
对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping)。简单说,我们使用orm可以将我们的对象与我们的类去进行映射,使的我们可以去操作对象就完成对表的操作。
一、Hibernate文件目录结构
documentation目录:存放hibernate的相关文件与API
lib目录:存放hibernate编译和运行所依赖的jar包,其中required子目录下包含了运行hibernate项目必须的jar包
project目录:存放hibernate各种相关的源代码与资源.
在lib/required目录中,包含必需的jar包
快速搭建项目需要导入的包
导入lib/required下所有的jar 导入数据库的驱动jar包 日志相关jar包 3 将hibernate/project/etc/log4j.properties文件导入到工程src下
二、Hibernate的配置详解
Hibernate中我们使用时主要有两种配置文件
核心配置文件 hibernate.cfg.xml
对于hibernate的核心配置文件它有两种方式:
- hibernate.cfg.xml
- hibernate.properties
我们在开发中使用比较多的是hibernate.cfg.xml这种方式,原因它的配置能力更强,易于修改 我们主要学习的是hibernate.cfg.xml配置
- 可以加载数据库相关信息
- hibernate相关配置
- 加载映射配置文件
<session-factory>
<!-- 配置关于数据库连接的四个项 driverClass url username password -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3366/hibernateTest</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123</property>
<!-- 可以将向数据库发送的sql显示出来 -->
<property name="hibernate.show_sql">true</property>
<!-- 格式化sql -->
<property name="hibernate.format_sql">true</property>
<!-- hibernate的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 用于设置事务提交方式 -->
<property name="hibernate.connection.autocommit">false</property>
<!-- 配置hibernate的映射文件所在位置 -->
<mapping resource="com/lbb/hibernate/domain/Customer.hbm.xml" />
</session-factory>
复制代码
对于hibernate.cfg.xml配置文件中的内容可以参考hibernate/project/etc/hibernate.properties的配置 4. 配置<property name="hibernate.hbm2ddl.auto">update</property>
属性后,我们可以进行表的自动创建
- Create-drop 每次都会创建一个新的表,执行完成后删除。一般在测试中使用
- Create 每次都会创建一个新的表,一般是在测试中使用
- update 如果数据库中有表,不创建,没有表创建,如果映射不匹配,会自动更新表结构(只能添加)
- validate 只会使用存在的表,并且会对映射关系进行校验.
映射配置文件 xxx.hbm.xml
映射配置文件它的名称是类名.hbm.xml,它一般放置在实体类所在的包下。 这个配置文件的主要作用是建立表与类的映射关系。
<hibernate-mapping package="cn.itheima.domain">
<!-- name属性它是实体类的全名 table 表的名称 catalog 数据库名称 -->
<class name="Customer" table="t_customer"
catalog="hibernateTest">
<!-- id它是用于描述主键 -->
<id name="id" column="id" type="int"> <!-- java数据类型 -->
<!-- 主键生成策略 -->
<generator class="native"></generator>
</id>
<!-- 使用property来描述属性与字段的对应关系 -->
<property name="name" column="name" length="20" type="string"></property> <!-- hibernate数据类型 -->
<property name="address">
<column name="address" length="50" sql-type="varchar(50)"></column> <!-- sql数据类型 -->
</property>
<property name="sex" column="sex" length="20"></property>
</class>
</hibernate-mapping>
复制代码
- 统一声明包名,这样在
<class>
中就不需要写类的全名.<hibernate-mapping package="cn.itheima.domain">
- 关于
<class>
标签配置 name属性:类的全名称 table 表的名称,可以省略,这时表的名称就与类名一致 catalog属性:数据库名称 可以省略.如果省略,参考核心配置文件中url路径中的库名称 - 关于
<id>
标签 首先它必须存在。<id>
是用于建立类中的属性与表中的主键映射。 name 类中的属性名称 column 表中的主键名称 column它也可以省略,这时列名就与类中属性名称一致 length 字段长度 type属性 指定类型<generator>
它主要是描述主键生成策略. - 关于
<property>
标签 它是描述类中属性与表中非主键的映射关系
关于hibernate的映射文件中类型问题
对于type属性它的取值,可以有三种:
- java中的数据类型
- hibernate中的数据类型
- SQL的数据类型
三、Hibernate的执行原理和常用API
hibernate工作原理
- 通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件。
- 由hibernate.cfg.xml中的
<mappingresource="com/xx/User.hbm.xml"/>
读取解析映射信息。 - 通过config.buildSessionFactory();//得到sessionFactory。
- sessionFactory.openSession();//得到session。
- session.beginTransaction();//开启事务。
- persistent operate;
- session.getTransaction().commit();//提交事务
- 关闭session;
- 关闭sessionFactory;
Hibernate的核心类和接口一共有6个
分别为:Session、SessionFactory、Transaction、Query、Criteria和Configuration。这6个核心类和接口在任何开发中都会用到
// 保存一个Customer
@Test
public void saveCustomerTest() {
// 使用hibernate的api来完成将customer信息保存到mysql中操作
Configuration config = new Configuration().configure(); // 加载hibernate.cfg.xml
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession(); // 相当于得到一个Connection。
// 开启事务
Transaction transaction = session.beginTransaction();
// 操作
Customer c = new Customer();
c.setName("张三");
c.setAddress("北京");
c.setSex("男");
session.save(c);
// 事务提交
transaction.commit();
session.close();
sessionFactory.close();
}
复制代码
Configuration
它主要是用于加载hibernate配置
Configuration config=new Configuration().config(); 主要加载src下的hibernate.cfg.xml
Configuration config=new Configuration();主要加载的src下的hibernate.properties
Configuration config=new Configuration().config(核心配置文件名称);加载指定的名称的配置文件
问题:我们是在hibernate.cfg.xml文件中有xxx.hbm.xml文件的位置。如果我们使用的是hibernate.properties这种核心配置,它如何加载映射配置?
Configuration config=new Configuration();//主要加载的src下的hibernate.properties
// 手动加载映射
// config.addResource("cn/itheima/domain/Customer.hbm.xml"); 直接加载映射配置文件
// config.addClass(Customer.class); //这种方式它会直接在实体类所在包下查找规范映射配置文件
复制代码
SessionFactory
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
SessionFactory它不是轻量级的,不要频繁创建关闭它。在一个项目中有一个SessionFactory就可以,通过SessionFactory来获取Session进行操作。
public class HibernateUtils {
private static Configuration config;
private static SessionFactory sessionFactory;
static{
config=new Configuration().configure();
sessionFactory=config.buildSessionFactory();
}
// 是从连接池中获取一个连接
public static Session openSession(){
return sessionFactory.openSession();
}
// 获取一个与线程绑定的Session
public static Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
}
复制代码
SessionFactory内部还维护了一个连接池,如果我们要想使用c3p0连接池,应该怎样处理?
-
我们要导入c3p0的相关jar包 在hibernate/lib/options下有关于c3p0连接池jar包
-
在hibernate.cfg.xml文件中配置c3p0连接 可以查看etc/hibernate.properties中关于c3p0的配置
<!-- 设置连接提供者 -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- c3p0连接池的配置 -->
<property name="hibernate.c3p0.max_size">20</property> <!-- 最大连接池 -->
<property name="hibernate.c3p0.min_size">5</property> <!-- 最小连接数 -->
<property name="hibernate.c3p0.timeout">120</property> <!-- 超时 -->
<property name="hibernate.c3p0.idle_test_period">3000</property> <!-- 空闲连接 -->
复制代码
Session
Session接口负责执行被持久化对象的CRUD操作(CRUD的任务是完成与数据库的交流,包含了很多常见的SQL语句)。但需要注意的是Session对象是非线程安全的。
问题:我们如何解决session的安全问题?
我们只需要在方法内部来使用Session就可以。
问题:Session如何获取到?
SessionFactory.openSession() ; 相当于直接通过SessionFactory创建一个新的Session,使用完成后要手动调用close来关闭。
SessionFactory.getCurrentSession(); 获取一个与线程绑定的Session,当我们提交或事务回滚后会自动关闭。
Session常用的方法
save 保存对象 update 修改操作 delete删除 get/load 根据id进行查询 savenOrUpdate 执行save或update操作 createQuery()获取一个Query对象 createSQLQUery()获取一个可以操作sql的SQLQuery对象 createCriteria() 获取一个Criteria它可以完成条件查询
Transaction
Transaction接口主要用于管理事务,它是hibernate的事务接口,对底层的事务进行了封装。使用它可以进行事务操作。 commit 事务提交 rollback 事务回滚
问题:如果在程序中没有开启事务,是否存在事务? 有事务,session的每一个操作就会开启一个事务。
默认情况下事务是不会自动提交的。
<!-- 用于设置事务提交方式 -->
<property name="hibernate.connection.autocommit">false</property>
复制代码
Query(重点)
Query接口让你方便地对数据库及持久对象进行查询,它可以有两种表达方式:HQL语言或本地数据库的SQL语句。Query经常被用来绑定查询参数、限制查询记录数量,并最终执行查询操作。
通过Query主要完成查询操作. 我们通过Query可以执行hql语句. Query query=Session.createQuery(hql);
下面这个可以执行sql语句 SQLQUery sqlQuery=Session.createSQLQuery(sql); SQLQuery是Query的子.
使用hql完成查询所有操作
@Test
public void test1() {
Session session = HibernateUtils.openSession();
Query query = session.createQuery("from Customer");// from后面是类名
List<Customer> list = query.list();
System.out.println(list);
session.close();
}
复制代码
分页查询
// 分页查询 一页显示10条 要得到第二页数据
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.createQuery("from Customer");
query.setFirstResult(10);
query.setMaxResults(10);
List list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
复制代码
查询指定列信息 Select name ,address from Customer; 得到的是List<Object[]>
结果 要想得到List结果
- 在Customer类中生成以name,address为参数的构造,注意,无参数构造也要有。
- Select new Customer(name,address) from Customer;
// 查询指定列信息
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.createQuery("select name,address from Customer");
List<Object[]> list = query.list();
System.out.println(list);
Query query2 = session.createQuery("select new Customer(name,address) from Customer");
List list2 = query2.list();
System.out.println(list2);
session.getTransaction().commit();
session.close();
}
复制代码
条件查询
无名称参数 from Customer where name=? 对其进行赋值 query.setParameter(0,”张三”)
有名称参数 from Customer where name=:myname; 对其进行赋值 query.setParameter(“myname”,”李四”);
如果查询结果可以保证就是唯一 的,我们可以使用 query. uniqueResult()来得到一个单独对象.
// 条件查询
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// Query query = session.createQuery("select new Customer(name,address) from Customer where name = ?");
Query query = session.createQuery("from Customer where name = ?");
query.setParameter(0, "姓名0");
Customer customer = (Customer) query.uniqueResult();
System.out.println(customer);
Query query2 = session.createQuery("from Customer where name = :myname");
query2.setParameter("myname", "姓名0");
Customer customer2 = (Customer) query.uniqueResult();
System.out.println(customer2);
session.getTransaction().commit();
session.close();
}
复制代码
执行本地SQL 要想执行本地sql
SQLQuery sqlQuery=session.createSqlQuery(String sql);
使用addEntity方法来将结果封装到指定的对象中,如果不封装,得到的是List<Object[]>
如果sql中有参数,我们使用setParameter方法完成参数传递。
如果结果就是一个可以使用uniqueResult()来得到一个单独对象。
// 执行本地sql----查询全部
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 执行select * from t_customer;
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer");
// List<Object[]> list = sqlQuery.list();
// System.out.println(list);
// 想要将结果封装到Customer对象中
sqlQuery.addEntity(Customer.class);
List<Customer> list = sqlQuery.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 执行本地sql----条件查询
@Test
public void test7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 执行select * from t_customer where name=?;
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer where name=?");
// 对参数进行赋值
sqlQuery.setParameter(0, "姓名1");
// 想要将结果封装到Customer对象中
sqlQuery.addEntity(Customer.class);
// List<Customer> list = sqlQuery.list();
// System.out.println(list);
Customer c = (Customer) sqlQuery.uniqueResult();
System.out.println(c);
session.getTransaction().commit();
session.close();
}
复制代码
Criteria
Criteria接口与Query接口非常类似,允许创建并执行面向对象的标准化查询。值得注意的是Criteria接口也是轻量级的,它不能在Session之外使用。
首先我想使用Criteria,必须得到Criteria Criteria criteria=Session.createCriteria()
查询所有操作 Session.createCriteria(实体类.class)得到一个Criteria对象,调用list查询所有
分页操作与query的方法一样 setFirstResult() setMaxResults()
条件查询 criteria.add(Restrictions.eq(“name”,”xxxx”)); criteria.add(Restrictions.or(Restricitons.eq(),Restrictions.list()…..))
我们使用Criteria可以更加面向对象去操作,它非常适合进行多条件组合查询。
@Test
public void test8() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Customer.class);
// 查询所有
// List list = criteria.list();
// System.out.println(list);
// 分页查询
// criteria.setFirstResult(10);
// criteria.setMaxResults(10);
// List list2 = criteria.list();
// System.out.println(list2);
// 多条件查询
// 1.查询name='姓名1'
// criteria.add(Restrictions.eq("name","姓名1"));
// Customer customer = (Customer) criteria.uniqueResult();
// System.out.println(customer);
// 2.查询address='上海'
// criteria.add(Restrictions.eq("address", "上海"));
// Customer customer = (Customer) criteria.uniqueResult();
// System.out.println(customer);
// criteria.add(Restrictions.eq("name","姓名1"));
// criteria.add(Restrictions.eq("address", "上海"));
// Customer customer = (Customer) criteria.uniqueResult();
// System.out.println(customer);
// List list = criteria.list();
// System.out.println(list);
//查询name='姓名1' 或者 address='上海'
criteria.add(Restrictions.or(Restrictions.eq("name", "姓名1"),Restrictions.eq("address","上海")));
List<Customer> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
复制代码
四、Hibernate持久化类与主键生成策略
Hibernate持久化类
什么是持久化类?
持久化类Persistent Object (PO)其实就相当于PO=POJO+hbm映射配置。
hibernate中的PO编写规则
- 必须提供一个无参数的public构造方法
- 所有属性要private ,对外提供public 的get/set方法
- 在PO类必须提供一个标识属性,让它与数据库中的主键对应,我们管这个属性叫OID
- PO类中的属性尽量使用基本数据类型的包装类. Int-Integer double--Double float-Float
- PO类它不能使用final修饰符
OID作用
OID指的是与数据库中表的主键对应的属性。
Hibernate框架它是通过OID来区分不同的PO对象,如果在内存中有两个相同的OID对象,那么hibernate认为它们是同一个对象。
为什么PO类属性它要使用包装类型
使用基本数据类型是没有办法去描述不存在概念,如果使用包装类型,它就是一个对象,对于对象它的默认值是null。
PO类不可以使用final修饰?(hibernate中的get/load方法的区别)
-
get直接得到了一个持久化类型对象,它就是立即查询操作。 load它得到的是持久化类开的代理类型对象(子类对象)。它默认采用了一种延迟策略来查询数据。
-
get方法在查询时,如果不存在返回null。 load方法在查询时,如果 不存在,会产生异常ObjectNotFoundException。
Hibernate主键生成策略
Hibernate中定义的主键类型包括:自然主键和代理主键。
自然主键:具有业务含义 字段 作为主键,比如:学号、身份证号。
代理主键:不具有业务含义 字段作为主键(例如 自增id),比如:mysql自增主键,oracle序列生成的主键、uuid()方法生成的唯一序列串。建议:企业开发中使用代理主键!
主键生成器 | 描述 |
---|---|
increment | 代理主键。由hibernate维护一个变量,每次生成主键时自动以递增。问题:如果有多个应用访问一个数据库,由于每个应用维护自己的主键,所以此时主键可能冲突。建议不采用。 优点:可以方便跨平台。缺点:不适合高并发访问 |
identity | 代理主键。由底层数据库生成表识符。条件是数据库支持自动增长数据类型。比如:mysql的自增主键,oracle不支持主键自动生成。如果数据库支持自增建议采用。 优点:由底层数据库维护,和hibernate无关。缺点:只能对支持自动增长的数据库有效,例如mysql |
sequence | 代理主键。Hibernate根据底层数据库序列生成标识符。条件是数据库支持序列。比如oracle的序列。如果数据库支持序列建议采用。 优点:由底层数据库维护,和hibernate无关。缺点:数据库必须支持sequence方案例如oracle。 |
native | 代理主键。根据底层数据库对自动来选择identity、sequence、hilo.由于生成主键策略的控制权由hibernate控制,所以不建议采用。 优点:在项目中如果存在多个数据库时使用。缺点:效率比较低。 |
uuid | 代理主键。Hibernate采用128bit位的UUID算法来生成标识符。该算法能够在网络环境中生成唯一的字符串标识符。此策略可以保证生成主键的唯一性,并且提供了最好的数据库插入性能和数据库平台的无关性。建议采用。 优点:与数据库无关,方便数据库移植,效率高,不访问数据库就可以直接生成主键值,并且它能保证唯一性。缺点:uuid长度大(32位),占用空间比较大,对应数据库中类型 char varchar |
assigned | 自然主键。由java程序负责生成标识符。不建议采用。尽量在操作中避免手动对主键操作 |
Hibernate持久化对象状态
持久化对象三种状态
-
瞬时态:也叫做临时态或自由态,它一般指我们new出来的对象,它不存在OID,与hibernate session无关联,在数据库中也无记录。它使用完成后,会被jvm直接回收掉,它只是用于信息携带。 简单说:无OID 与数据库中的信息无关联,不在session管理范围内。
-
持久态:在hibernate session管理范围内,它具有持久化标识OID它的特点,在事务未提交前一直是持久态,当它发生改变时,hibernate是可以检测到的。 简单说:有OID 由session管理,在数据库中有可能有,也有可有没有。
-
托管态:也叫做游离态或离线态,它是指持久态对象失去了与session的关联,托管态对象它存在OID,在数据库中有可能存在,也有可能不存在。 对于托管态对象,它发生改变时hibernet不能检测到。
持久化类三种状态切换
判断持久化类对象三种状态:
- 是否有OID
- 判断是否与session关联
-
瞬时态(new 出来的) 瞬时------持久 save saveOrUpdate 瞬时-----脱管(游离) 手动设置oid
-
.持久态 它是由session管理 持久-------瞬时 delete() 被删除后持久化对象不在建议使用 持久-----脱管 注意:session它的缓存就是所说的一级缓存 evict(清除一级缓存 中指定的一个对象) clear(清空一级缓存) close(关闭,清空一级缓存)
-
.脱管态 (它是无法直接获取) 脱管-----瞬时 直接将oid删除 脱管----持久 update saveOrUpdate lock(过时)
五、Hibernate一级缓存
Hibernate的一级缓存就是指session缓存。在Session——EventSource——SessionImpl实现类中,有如下属性
private transient ActionQueue actionQueue;
private transient StatefulPersistenceContext persistenceContext;
复制代码
actionQueue它是一个行列队列,它主要记录crud操作的相关信息。 persistenceContext它是持久化上下文,它其实是真正缓存。
在session中定义了一系列的集合来存储数据,它们构成session缓存。只要session没有关闭,它就会一直存在。当我们通过hibernate中的session提供的一些API例如 save get update等进行操作时,就会将持久化对象保存到session中,当下一次在去查询缓存中具有的对象(OID值来判断),就不会去从数据库查询,而是直接从缓存中获取。Hibernate的一级缓存存在的目的就是为了减少对数据库访问。
持久化对象具有自动更新数据库能力
一级缓存常用API
一级缓存特点
- 当我们通过session的save,update saveOrupdate进行操作时,如果一级缓存中没有对象,会将这些对象从数据库中查询到,存储到一级缓存。
- 当我们通过session的load,get,Query的list等方法进行操作时,会先判断一级缓存中是否存在,如果没有才会从数据库获取,并且将查询的数据存储到一级缓存中。
- 当调用session的close方法时,session缓存清空。 clear 清空一级缓存. evict 清空一级缓存中指定的一个对象。 refresh重新查询数据库,用数据库中信息来更新一级缓存与快照
Hibernate常用API-Session补充
update
- udpate操作它主要是针对于脱管对象,持久对象具有自动更新能力。
- Update操作时,如果对象是一个脱管对象,可以操作,它会将脱管对象转换成持久对象在操作。
- 如果在session中出现相同的oid两个对象,会产生异常。
- 脱管对象的oid如果在数据表中不存在,会报异常。
所以:在操作中,建议我们通过持久化对象来直接修改其操作。
saveOrUpdate
如果对象是一个瞬时对象 --------执行save操作 如果对象是一个脱管对象---------执行update 如果是一个持久对象-------直接返回
delete
删除一个脱管对象,与session关联,在删除。注意:如果执行delete操作,先删除一级缓存,在删除数据库中的数据。删除后对象id变为空,对象变为瞬时态
六、Hibernate关联映射--数据对象三种关系
Hibernate框架基于ORM设计思想,它将关系型数据库中的表与我们java中的类进行映射,一个对象就对应着表中的一条记录,而表中的字段对应着类中的属性。数据库中表与表之间存在着三种关系,也就是系统设计中的三种实体关系。
一对多
实体类创建
public class Customer {
private Integer id; // 主键
private String name; // 姓名
// 描述客户可以有多个订单
private Set<Order> orders = new HashSet<Order>();
public class Order {
private Integer id;
private Double money;
private String receiverInfo; // 收货地址
// 订单与客户关联
private Customer c; // 描述订单属于某一个客户
复制代码
Hbm映射文件编写
<!-- 一个客户关联多个订单 -->
<set name="orders" inverse="false" cascade="all">
<key column="cid" />
<one-to-many class="cn.itheima.oneToMany.Order" />
</set>
<!-- 使用set来描述在一的一方中关联的多 Set<Order>,
它的name属性就是set集合的名称
key:它主要描述关联的多的一方产生的外键名称,注意要与多的一方定义的外键名称相同
one-to-many 描述集合中的类型 -->
<!-- 多对一 -->
<many-to-one fetch="join" lazy="false" name="c" class="cn.itheima.oneToMany.Customer" column="cid" cascade="save-update">
</many-to-one>
<!--
name属性它描述的是Order类中的一的一方的属性名称 Customer c;
class 代表一的一方的类型
column 描述的是一对多,在多的一方产生的外键的名称 c_customer_id
-->
复制代码
使用级联操作,设置cascade=save-update
那么在保存一方的同时可以同时保存另一方。
我们在开发中要配置双向关联配置。---------可以通过任意一方来操作对方 在操作代码,尽量来要进行单向关联。------可以尽量资源浪费。 在双向关联中,会存在多余的update语句。我们可以使用inverse
属性来设置,双向关联时由哪一方来维护表与表之间的关系。
Inverse它的值如果为true代表,由对方来维护外键。 Inverse它的值如果为false代表,由本方来维护外键。 原则:inverse设置在主表的一方,外键在哪一个表中,我们就让哪一方来维护外键。
对象导航
cascade总结
使用cascade可以完成级联操作 它可常用取值
- none这是一个默认值
- save-update,当我们配置它时,底层使用save update或save-update完成操作,级联保存临时对象,如果是游离对象,会执行update.
- delete 级联删除
- delete-ophan 删除与当前对象解除关系的对象。
- all 它包含了save-update delete操作
- all-delete-orphan 它包信了delete-orphan与all操作
cascade与inverse有什么区别 cascade它是完成级联操作 Inverse它只有在双向关联情况下有作用,它来指定由哪一方维护外键。
七、Hibernate注解开发
PO类注解配置
- @Entity 声明一个实体
- @Table来描述类与表对应
- @Id来声明一个主键
- @GenerateValue 用它来声明一个主键生成策略 默认情况下相当于native 可以选择的主键生成策略 AUTO IDENTITY SEQUENCE
- @Column来定义列 注意:对于PO类中所有属性,如果你不写注解,默认情况下也会在表中生成对应的列。 列的名称就是属性的名称
- @Temporal来声明日期类型 可以选择 TemporalType.DATA 只有年月日
TemporalType.TIME 只有小时分钟秒 TemporalType.TIMESTAMP 有年月日小时分钟秒 - @Transient设定类的属性不在表中映射
@Entity // 定义了一个实体
@Table(name = "t_book", catalog = "hibernateTest")
public class Book {
@Id // 主键
// @GeneratedValue //native
@GeneratedValue(strategy = GenerationType.IDENTITY) // identity
private Integer id; // 主键
@Column(name = "c_name", length = 30, nullable = true)
private String name;
@Temporal(TemporalType.TIMESTAMP) // 是用来定义日期类型
private Date publicationDate; // 出版日期
@Type(type="double")
private Double price; // 价格 如果没有添加注解,也会自动的生成在表中
@Transient
private String msg; // 现在这个属性不想生成在表中
复制代码
如果我们主键生成策略想使用UUID类型。
@Id
@GenericGenerator(name = "myuuid", strategy = "uuid")
@GeneratedValue(generator = "myuuid")
private String id;
复制代码
对于我们以上讲解的关于属性配置的注解,我们也可以在其对应的getXxx方法去使用。我们最终需要在hibernate.cfg.xml文件中将我们类中的注解配置引用生效。<mapping class="cn.itheima.oneToMany.Customer" /> <mapping class="cn.itheima.oneToMany.Order" />
一对多(多对一)
// 描述客户可以有多个订单
/*
* targetEntity相当于<one-to-many class="">
* mappedBy相当于inverse=true
*/
@OneToMany(targetEntity=Order.class,mappedBy="c",orphanRemoval=true)
@Cascade(org.hibernate.annotations.CascadeType.DELETE)
private Set<Order> orders = new HashSet<Order>();
// 订单与客户关联
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "c_customer_id") // 指定外键列
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private Customer c; // 描述订单属于某一个客户
复制代码
对于这个示例我们需要在Customer中配置cascade操作,save-update 第一种方式,可以使用JPA提供的注解。在@OneToMany中cascade=CascadeType.ALL
第二种方式:可以使用hibernate提供的注解.如上代码。
扩展:关于hibernate注解@Cascade中的DELETE_ORPHAN过时,我们在@OneToMany中添加orphanRemoval=true
解决
Hibernate关联映射-多对多
使用@ManyToMany来配置多对多,只需要在一端配置中间表,另一端使用mappedBy表示放置外键维护权。
创建PO类 描述学生与老师.
@ManyToMany(targetEntity = Student.class, mappedBy = "teachers") // 代表由对方来维护外键
@Cascade(CascadeType.ALL)
private Set<Student> students = new HashSet<Student>();
@ManyToMany(targetEntity = Teacher.class)
// 使用JoinTabl来描述中间表,并描述中间表中外键与Student,Teacher的映射关系
// joinColumns它是用来描述Student与中间表中的映射关系
// inverseJoinColums它是用来描述Teacher与中间表中的映射关系
@JoinTable(name = "s_t", joinColumns = {
@JoinColumn(name = "c_student_id", referencedColumnName = "id") }, inverseJoinColumns = {
@JoinColumn(name = "c_teacher_id", referencedColumnName = "id") })
@Cascade(CascadeType.ALL)
private Set<Teacher> teachers = new HashSet<Teacher>();
复制代码
Hibernate关联映射-一对一
一对一操作有两种映射方式:
- 在任意一方添加外键
- 主键映射
创建PO类 以人与身份证号为例
@OneToOne(targetEntity = IDCard.class, mappedBy = "user")
private IDCard idCard;
@OneToOne(/*targetEntity = User.class*/)
@JoinColumn(name = "c_user_id")
@Cascade(CascadeType.SAVE_UPDATE)
private User user;
复制代码
八、Hibernate检索方式概述
对数据库操作中,最常用的是select.使用hibernate如何select操作。
- 导航对象图检索方式,根据已加载的对象导航到其它对象
- OID检索方式,按照对象的OID来检索对象
- HQL检索方式,使用面向对象的HQL查询语言
- QBC检索方式,使用QBC(Query by Criteria)API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口
- 本地SQL检索方式,使用本地数据库的SQL查询语句
导航对象图检索方式
Customer c=session.get(Customer.class,2); c.getOrders().size() 通过在hibernate中进行映射关系,在hibernate操作时,可以通过导航方式得到 其关联的持久化对象信息。
OID检索方式
Session.get(Customer.class,3); Session.load(Order.class,1); Hibernate中通过get/load方法查询指定的对象,要通过OID来查询。
HQL
HQL是我们在hibernate中是常用的一种检索方式。 HQL(Hibernate Query Language)提供更加丰富灵活、更为强大的查询能力 因此Hibernate将HQL查询方式立为官方推荐的标准查询方式,HQL查询在涵盖Criteria查询的所有功能的前提下,提供了类似标准SQL语 句的查询方式,同时也提供了更加面向对象的封装。完整的HQL语句形式如下: Select/update/delete…… from …… where …… group by …… having …… order by …… asc/desc 其中的update/delete为Hibernate3中所新添加的功能,可见HQL查询非常类似于标准SQL查询。 基本步骤:
- 得到Session
- 编写HQL语句
- 通过session.createQuery(hql)创建一个Query对象
- 为Query对象设置条件参数
- 执行list查询所有,它反胃的是List集合 uniqueResut()返回一个查询结果。
PO类
@Entity
@Table(name = "t_customer")
@NamedQuery(name = "myHql", query = "from Customer")
@SqlResultSetMapping(name = "customerSetMapping", entities = { @EntityResult(entityClass = Customer.class, fields = {
@FieldResult(name = "id", column = "id"), @FieldResult(name = "name", column = "name") }) })
@NamedNativeQuery(name = "findCustomer", query = "select * from t_customer", resultSetMapping = "customerSetMapping")
@Proxy(lazy = true)
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; // 主键
private String name; // 姓名
@OneToMany(targetEntity = Order.class, mappedBy = "c")
private Set<Order> orders = new HashSet<Order>();
@Entity
@Table(name = "t_order")
@NamedQuery(name="findOrderByCustomer",query="from Order where c=:c")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收货地址
// 订单与客户关联
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "c_customer_id")
@Cascade(CascadeType.SAVE_UPDATE)
private Customer c;
复制代码
public class HQLTest {
// 命名查询
@Test
public void test9() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.我要查询张龙这个客户的订单
Customer c = session.get(Customer.class, 1);
Query query = session.getNamedQuery("findOrderByCustomer"); // from Order where c=:c
// 2.现在hql它的参数是一个实体
List<Order> list = query.setEntity("c", c).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 命名查询
@Test
public void test8() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.getNamedQuery("myHql");
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 投影查询
@Test
public void test7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.查询出所有的Customer的name
// String hql = "select name from Customer";
// List list = session.createQuery(hql).list();
// System.out.println(list); // [张龙, 张三丰]
// 如果只查询一个列,得到的结果List<Object>
// 2.查询所有的Customer的id,name
// String hql = "select id,name from Customer";
// List<Object[]> list = session.createQuery(hql).list();
// for(Object[] objs:list){
// for(Object obj:objs){
// System.out.print(obj+" ");
// }
// System.out.println();
// }
// 如果是查询多列,得到的结果是List<Object[]>
// 3.使用投影将查询的结果封装到Customer对象
String hql = "select new Customer(id,name) from Customer"; // 必须在PO类中提供对应的构造方法
List<Customer> cs = session.createQuery(hql).list();
System.out.println(cs);
session.getTransaction().commit();
session.close();
}
// 分组统计操作
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 统计操作----统计一共有多少订单 count
// String hql="select count(*) from Order";
// Object count = session.createQuery(hql).uniqueResult();
// System.out.println(count);
// 分组统计----每一个人的订单总价
String hql = "select sum(money) from Order group by c";
List list = session.createQuery(hql).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 分页检索
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.createQuery("from Order");
// 每页显示6条件 ,我们要得到第二页数据
query.setFirstResult((2 - 1) * 6); // 设定开始位置
query.setMaxResults(6); // 设置条数
List<Order> list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 条件查询
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.根据位置来绑定参数
// 1.1创建hql
// String hql = "from Order where money>? ";
// 1.2.执行hql
// List<Order> list = session.createQuery(hql).setParameter(0,
// 2000d).list();
// 可以使用例如setString() setDouble这样的方法去添加参数,参数的序号是从0开始.
// 2.根据名称来绑定
// 1.1创建hql
String hql = "from Order where money>:mymoney ";
// 1.2.执行hql
List<Order> list = session.createQuery(hql).setParameter("mymoney", 2000d).list();
// 可以使用例如setString() setDouble这样的方法去添加参数
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 排序检索--//查询订单,根据订单的价格进行排序
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.定义hql
String hql = "from Order order by money desc"; // desc 降序 默认是asc 升序
// 2.执行hql查询订单,根据价格进行排序
List<Order> list = session.createQuery(hql).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 基本检索
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.编写HQL
String hql = "from Customer"; // from是关键字,后面是类名,关键字是不区分大小写,但是类名是区分
// 2.通过session.createQuery(hql)
// Query query = session.createQuery(hql);
// 3.通过list方法得到数据
// List<Customer> list = query.list();
List<Customer> list = session.createQuery(hql).list();
System.out.println(list.get(0));
session.getTransaction().commit();
session.close();
}
// 准备数据(2个Customer,每一个Customer有10个Order)
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 操作
Customer c = new Customer();
c.setName("张三丰");
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setMoney(2000d + i * 10);
order.setReceiverInfo("上海");
order.setC(c);
session.save(order);
}
session.getTransaction().commit();
session.close();
}
}
复制代码
QBC
QBC(query by criteria),它是一种更加面向对象的检索方式。 QBC步骤
- 通过Session得到一个Criteria对象 session.createCriteria()
- 设定条件 Criterion实例 它的获取可以通过Restrictions类提供静态。 Criteria的add方法用于添加查询条件
- 调用list进行查询 criterfia.list.
public class QBCTest {
// 离线的检索
@Test
public void test6() {
// 1.得到一个DetachedCriteria
DetachedCriteria dc = DetachedCriteria.forClass(Customer.class);
dc.add(Restrictions.like("name", "张_"));
// 2.生成Criteria执行操作
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = dc.getExecutableCriteria(session);
List<Customer> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 统计检索
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.统计订单总数
Criteria criteria = session.createCriteria(Order.class);
// Object obj =
// criteria.setProjection(Projections.rowCount()).uniqueResult();
// //统计总行数 count(id)
// System.out.println(obj);
// 2.订单的总价格----分组统计根据客户
// criteria.setProjection(Projections.sum("money")); //统计总金额
criteria.setProjection(
Projections.projectionList().add(Projections.sum("money")).add(Projections.groupProperty("c")));
List<Object[]> list = criteria.list(); // 在这个集合中保存的是Object[money的统计信息,客户信息]
for (Object[] objs : list) {
for (Object obj : objs) {
System.out.println(obj);
}
}
session.getTransaction().commit();
session.close();
}
// 分页检索
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Order.class);
criteria.setFirstResult((2 - 1) * 6);
criteria.setMaxResults(6);
List<Order> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 条件检索
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.查询名称叫张某客户 张_
Criteria criteria = session.createCriteria(Customer.class);
Criterion like = Restrictions.like("name", "张_"); // 其它的条件 lt < gt > le
// <= ge>= eq==
criteria.add(like);// 添加条件
Customer c = (Customer) criteria.uniqueResult();
System.out.println(c);
// 2.查询订单价格在1050以上的,并且它的客户是张某
Criteria cri = session.createCriteria(Order.class);
SimpleExpression lt = Restrictions.gt("money", 1050d); // >1050
SimpleExpression eq = Restrictions.eq("c", c); // 与c客户相同
LogicalExpression and = Restrictions.and(lt, eq);
cri.add(and);
// List<Order> orders =
// cri.add(Restrictions.and(Restrictions.gt("money", 1050d),
// Restrictions.eq("c", c))).list();
List<Order> orders = cri.list();
System.out.println(orders);
session.getTransaction().commit();
session.close();
}
// 排序检索
@Test
public void test2() {
// 查询订单信息 根据订单的价格进行排序
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Order.class);
// 指定排序
// criteria.addOrder(org.hibernate.criterion.Order.desc("money")); // 降序
criteria.addOrder(org.hibernate.criterion.Order.asc("money")); // 升序
List<Order> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
// 基本检索
@Test
public void test1() {
// 查询所有Customer
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 1.得到一个Criteria对象
Criteria criteria = session.createCriteria(Customer.class);
// 2.调用list方法
List<Customer> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
}
复制代码
本地SQL
//测试本地sql命名查询
@Test
public void test2(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.getNamedQuery("findCustomer");
List list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
复制代码
多表操作
HQL多表操作
Hql多表操作分类
- 交叉连接
- 内连接 a) 显示内连接 b) 隐式内连接
c) 迫切内连接
- 外连接 左外连接
迫切左外连接
右外连接注意:在hibernate中有迫切连接的概念,而sql中没有。
//hql多表查询
public class HQLJoinTest {
// 演示迫切左外连接
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 注意:fetch不可以与单独条件的with一起使用
List<Customer> list = session
.createQuery("select distinct c from Customer c left outer join fetch c.orders where c.id=1").list(); // 左外连接
for (Customer c : list) {
System.out.println(c);
}
session.getTransaction().commit();
session.close();
}
// 演示外连接
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Object[]> list = session.createQuery("from Customer c left outer join c.orders").list(); // 左外连接
for (Object[] objs : list) {
for (Object obj : objs) {
System.out.print(obj + "\t");
}
System.out.println();
}
session.getTransaction().commit();
session.close();
}
// 迫切内连接
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 迫切内连接 inner join fetch 注意:使用迫切连接结果可能出现重复,所以要使用distinct来去重复
String hql = "select distinct c from Customer c inner join fetch c.orders";
// 底层也是执行的inner join 只不过结果封装到对象中。
Query query = session.createQuery(hql);
List<Customer> list = query.list(); // 结果是List<>,集合中装入的From后面的对象。
for (Customer o : list) {
System.out.println(o);
}
session.getTransaction().commit();
session.close();
}
// 测试隐式内连接
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// sql连接 select * from t_customer,t_order where 条件
String hql = "from Order o where o.c.id=1";
Query query = session.createQuery(hql);
List list = query.list();
System.out.println(list);
/*
String hql = "from Customer c,Order o where c.id=1 and c.id = o.customer.id";
Query query = session.createQuery(hql);
List<Object[]> list = query.list();
for (Object[] objs : list) {
for (Object obj : objs) {
System.out.print(obj + "\t");
}
System.out.println();
}
*/
session.getTransaction().commit();
session.close();
}
// 测试显示内连接
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// sql连接 select * from t_customer inner join t_order on 条件
String hql = "from Customer c inner join c.orders with c.id=1";
Query query = session.createQuery(hql);
List<Object[]> list = query.list(); // 结果是List<Object[]>
// 而Object[]中装入的是Customer与Order对象。
for (Object[] objs : list) {
for (Object obj : objs) {
System.out.print(obj + "\t");
}
System.out.println();
}
session.getTransaction().commit();
session.close();
}
}
复制代码
九、Hibernate事务管理
Hibernate中设置事务隔离级别
在hibernate.cfg.xml文件中配置
<!-- 设置事务隔离级别 -->
<property name="hibernate.connection.isolation ">4</property>
复制代码
它可取的值有 1 2 4 8
1.代表的事务隔离级别为READ UNCOMMITTED 2.代表的事务隔离级别为READ COMMITTED 4.代表的事务隔离级别为 REPEATABLE READ 8.代表的事务隔离级别为 SERIALIZABLE
Hibernate中session管理
Hibernate提供了三种管理session的方式,在实际开发中我们一般使用的是前两种。
- Session对象的生命周期与本地线程绑定(ThreadLocal)
- Session对象的生命周期与JTA事务绑定(分布式事务管理)
- Hibernate委托程序来管理Session的生命周期
本地线程绑定Session
- 需要在hibernate.cfg.xml文件配置
- 在获取session时不要在使用openSession而是使用getCurrentSession()方法。
<property name="hibernate.current_session_context_class">thread</property>
Session currentSession = sessionFactory.getCurrentSession();
复制代码
使用getCurrentSession获取的与线程绑定的session对象,在事务关闭时,session对象也会close,简单说,就不需要我们在手动close。
十、Hibernate优化方案
HQL优化
1.使用参数绑定 2.尽量少使用NOT 3.尽量使用where来替换having 4.减少对表的查询 5.使用表的别名 6.实体的更新与删除
检索策略
延迟加载
延迟加载 是hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
load方法采用的策略延迟加载、 get方法采用的策略立即加载。
检索策略分为两种:
- 类级别检索
- 关联级别检索
类级别检索
类级别检索是通过session直接检索某一类对应的数据,例如 Customer c=session.load(Customer.class,1) Session.createQuery(“from Order”)
类级别检索策略分为立即检索与延迟检索,默认是延迟检索,类级别的检索策略可以通过<class>
元素的lazy属性来设置 ,默认值是true。如果将lazy设置为false,代表类级别检索也使用立即检索。这时load与get就一样,都是立即检索。
关联级别检索
查询到某个对象,获得其关联的对象或属性,这种称为关联级别检索,例如 c.getOrders().size() c.getName() 对于关联级别检索我们就要研究其检索策略(抓取策略)
抓取策略
抓取策略介绍
指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。
注解配置抓取策略
@Entity
@Table(name = "t_customer")
@Proxy(lazy = false)
@BatchSize(size=3)
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; // 主键
private String name; // 姓名
@OneToMany(targetEntity = Order.class, mappedBy = "c")
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
@BatchSize(size=3)
private Set<Order> orders = new HashSet<Order>();
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Double money;
private String receiverInfo; // 收货地址
// 订单与客户关联
@ManyToOne(targetEntity = Customer.class)
@JoinColumn(name = "c_customer_id")
@Cascade(CascadeType.SAVE_UPDATE)
@Fetch(FetchMode.JOIN)
@LazyToOne(LazyToOneOption.FALSE)
private Customer c;
复制代码
set上的fetch与lazy
set上的fetch与lazy它主要是用于设置关联的集合信息的抓取策略。根据一的一方去查询多的一方 Fetch可取值有:
- SELECT 多条简单的sql (默认值)
- JOIN 采用迫切左外连接。如果fetch选择的是join方案,那么lazy它会失效。
- SUBSELECT 将生成子查询的SQL
lazy可取值有:
- TURE 延迟检索 (默认值)
- FALSE 立即检索
- EXTRA 加强延迟检索(及其懒惰)
One的上的fetch与lazy
在多的一方如何查询一的主方信息。例如:获取到一个订单对象,要查询客户信息。
Fetch可取值
- select 默认值,代表发送一条或多条简单的select语句
- join 发送一条迫切左外连接。如果fetch值为join,那么lazy失效。
lazy可取值
- false 不采用延迟加载
- proxy 默认值 是否采用延迟,需要另一方的类级别延迟策略来决定
- no-proxy 不用研究
批量抓取
我们在查询多个对象的关联对象时,可以采用批量抓取方式来对程序进行优化。
要想实现批量抓取,可以在配置文件中 batch-size属性来设置 ,可以使用注解 @BatchSize(size=4)。
无论是根据哪一方来查询别一方,在进行批量抓取时,都是在父方来设置,如果是要查询子信息,那么我们是在上来设置batch-size,如果是从子方来查询父方, 也是在父方设置在设置batch-size。