关于hibernate的见解
来源:互联网 发布:有哪些网络调查网站 编辑:程序博客网 时间:2024/05/22 11:35
Hibernate:
什么是hibernate:
Hibernate它是一个轻量级的jdbc封装,也就是说,我们可以使用hibernate来完成原来我们使用jdbc完成操作,就是与数据库的交互操作。它是在dao层去使用的。
对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping)
简单说,我们使用orm可以将我们的对象与我们的类去进行映射,使的我们可以去操作对象就完成对表的操作。
hibernate的学习重点:
掌握hibernate的基本配置-------搭建hibenate开发环境
掌握hibernate常用API---------如何使用hibedrnate框架进行开
掌握hibernate的关联映射--------解决表与表之间存在关系问题 1:n 1:1 m:n
掌握hibernate的检索方式-------就是掌握hibernate的查询
掌握hibernate的优化方式------提高hibernate效率
hibernate的快速入门:
下载hibernate;
***注意:高版本的需要jdk8才可以
hibernate目录结构:
documentation目录:存放hibernate的相关文件与API
lib目录:存放hibernate编译和运行所依赖的jar包,其中required子目录下包含了运行hibernate项目必须的jar包
project目录:存放hibernate各种相关的源代码与资源.
在lib/required目录中,包含的jar包
创建数据库跟表:
CREATE DATABASE hibernateTest;
USER hibernateTest;
CREATE TABLE t_customer(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
address VARCHAR(50)
)
创建实体类:
public class Customer
private int id;
private String name ;
private String address;
private String sex;
导入hibernate框架相关依赖jar包
导入lib/required下所有的jar
导入数据库的驱动jar包
日志相关jar包
将hibernate/project/etc/log4j.properties文件导入到工程src下.
hibernate的相关文件配置工作:
有两种:
xxx.hbm.xml 它主要是用于描述类与数据库中的表的映射关系.
hibernate.cfg.xml 它是hibernate框架核心配置文件。
映射配置文件:
位置:它要与实体类在同一个包下.
名称 :类名.hbm.xml
约束:
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- name是类的全名 table 是表名 catal是数据库名 -->
<class name="com.itheima.domain.Customer" table="t_customer"
catalog="hibernateTest">
<!-- 主键生成策略 -->
<id name="id" column="id">
<generator class="native"></generator>
</id>
<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>
核心配置文件:
它主要是hibernate框架所使用的,它主要包含了连接数据库相关信息,hibernate相关配置等。
位置:在src下创建一个hibernate.cfg.xml
约束:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置数据库链接的四个项 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernateTest</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</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>
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- 事务自动提交的设置 -->
<property name="hibernate.connection.autocommit">false</property>
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</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>
<!-- 配合映射文件所在的位置 -->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Hibernate快速 入门开发测试:
@Test
public void saveCustomerTest1(){
Customer customer = new Customer();
customer.setName("马老师");
customer.setAddress("中山桥");
//固定步骤 使用hibernate中的配置来保存数据到数据库中
Configuration configure = new Configuration().configure();
SessionFactory sessionFactory = configure.buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();//开启事务
session.save(customer);//保存操作
session.getTransaction().commit();;//提交事务
session.close();
sessionFactory.close();
}
Hibernate执行原理总结
hibernate工作原理:
1,通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件。
2,由hibernate.cfg.xml中的<mappingresource="com/xx/User.hbm.xml"/>读取解析映射信息。
3,通过config.buildSessionFactory();//得到sessionFactory。
4,sessionFactory.openSession();//得到session。
5,session.beginTransaction();//开启事务。
6,persistent operate;
7,session.getTransaction().commit();//提交事务
8,关闭session;
9,关闭sessionFactory;
Hibernate的配置详解
Hibernate中我们使用时主要有两种配置文件;
核心配置文件 hibernate.cfg.xml:
对于hibernate的核心配置文件它有两种方式:
1. hibernate.cfg.xml
2. hibernate.properties
我们在开发中使用比较多的是hibernate.cfg.xml这种方式,原因它的配置能力更强,易于修改
我们主要讲解的是hibernate.cfg.xml配置
* 可以加载数据库相关信息:
<!-- 配置数据库链接的四个项 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernateTest</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
* hibernate相关配置:
<!-- 可以将数据库发送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>
* 加载映射配置文件:
<!-- 配合映射文件所在的位置 -->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
对于hibernate.cfg.xml配置文件中的内容可以参考hibernate/project/etc/hibernate.properties的配置
*配置这个属性后,我们可以进行表的自动创建
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
Create-drop 每次都会创建一个新的表,执行完成后删除。一般在测试中使用
Create 每次都会创建一个新的表,一般是在测试中使用
update 如果数据库中有表,不创建,没有表创建,如果映射不匹配,会自动更新表结构(只能添加)
validate 只会使用存在的表,并且会对映射关系进行校验.
映射配置文件 xxx.hbm.xml
映射配置文件它的名称是类名.hbm.xml,它一般放置在实体类所在的包下。
这个配置文件的主要作用是建立表与类的映射关系。
统一声明包名,这样在<class>中就不需要写类的全名.
<hibernate-mapping package="com.itheima.domain">
关于<class>标签配置
name属性:类的全名称
table 表的名称,可以省略,这时表的名称就与类名一致
catalog属性:数据库名称 可以省略.如果省略,参考核心配置文件中url路径中的库名称
关于<id>标签
首先它必须存在。<id>是用于建立类中的属性与表中的主键映射。
name 类中的属性名称
column 表中的主键名称 column它也可以省略,这时列名就与类中属性名称一致
length 字段长度
type属性 指定类型
<generator>它主要是描述主键生成策略.
关于<property>标签
它是描述类中属性与表中非主键的映射关系
关于hibernate的映射文件中类型问题
对于type属性它的取值,可以有三种:
1. java中的数据类型
2. hibernate中的数据类型
3. SQL的数据类型
默认是hibernate中数据类型
Hibernate常用API;
Hibernate的核心类和接口一共有6个,分别为:Session,SessionFactory,
Transaction,Query,Criteria和Configuration。这6个核心类和接口在任何开发中都会用到
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这种核心配置,它如何加载映射配置?
//手动加载
//config.addResource("com/itheima/domain/Customer.hbm.xml");直接加载配置文件
//config.addClass(Customer.class);//这种方式会直接在实体类所在的包下查找规范映射配置文件
SessionFactory
首先SessionFactory它的获取是通过Configuration得到。
SessionFactory sessionFactory = configure.buildSessionFactory();
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。
这里用到了工厂模式。
需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,
当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
通过SessionFactory可以得到Session.
Session session = sessionFactory.openSession();
是从连接池中获取一个连接。
Session session = sessionFactory.getCurrentSession();
获取一个与线程绑定的Session.
SessionFactory它不是轻量级的,不要频繁创建关闭它。
在一个项目中有一个SessionFactory就可以,通过SessionFactory来获取Session进行操作。
问题:怎样可以保证在一个项目中所使用的SessionFactory是同一个哪?
可以写一个工具类
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();
}
}
SessionFactory内部还维护了一个连接池,如果我们要想使用c3p0连接池,应该怎样处理?
1. 我们要导入c3p0的相关jar包
在hibernate/lib/options下有关于c3p0连接池jar包
2. 在hibernate.cfg.xml文件中配置c3p0连接
可以查看etc/hibernate.properties中关于c3p0的配置
<!-- 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>
<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider</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 事务回滚
问题:如何获取一个Transaction对象
Session.beginTransaction();
问题:如果在程序中没有开启事务,是否存在事务?
有事务,session的每一个操作就会开启一个事务。
默认情况下事务是不会自动提交的。
<!-- 事务自动提交的设置 -->
<property name="hibernate.connection.autocommit">false</property>
不自动提交
<!-- 事务自动提交的设置 -->
<property name="hibernate.connection.autocommit">true</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();
session.beginTransaction();// 开启事务
Query query = session.createQuery("from Customer");
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();// 提交事务
session.close();
}
分页查询
@Test
public void Test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
Query query = session.createQuery("from Customer");
query.setFirstResult(10);
query.setMaxResults(10);
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();// 提交事务
session.close();
}
查询指定列信息
@Test
public void Test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
Query query = session.createQuery("select new Customer(name , address) from Customer where id<5");
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();// 提交事务
session.close();
}
Select name ,address from Customer; 得到的是List<Object[]>结果
要想得到List<Customer>结果
1. 在Customer类中生成以name,address为参数的构造,注意,无参数构造也要有。
2. Select new Customer(name,address) from Customer;
条件查询
可以使用where关键字
@Test
public void Test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
// 无名称参数
// Query query = session.createQuery("from Customer where name=?");
// query.setParameter(0, "梅剑华2");
// 有名称参数
Query query = session.createQuery("from Customer where name=:myname");
query.setParameter("myname", "梅剑华2");
// List<Customer> list = query.list();
// System.out.println(list);
// 如果保证查询结果唯一 也可以使用
Customer c = (Customer) query.uniqueResult();
System.out.println(c);
session.getTransaction().commit();// 提交事务
session.close();
}
无名称参数 from Customer where name=?
对其进行赋值 query.setParameter(0,”张三”)
有名称参数 from Customer where name=:myname;
对其进行赋值 query.setParameter(“myname”,”李四”);
如果查询结果可以保证就是唯一 的,我们可以使用
query. uniqueResult()来得到一个单独对象.
执行本地SQL
要想执行本地sql
SQLQuery sqlQuery=session.createSqlQuery(String sql);
使用addEntity方法来将结果封装到指定的对象中,如果不封装,得到的是List<Object>
如果sql中有参数,我们使用setParameter方法完成参数传递。
如果结果就是一个可以使用uniqueResult()来得到一个单独对象。
/**
* 使用sql语句查询所有
*
* @zhoujiaxuan
*/
@Test
public void Test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer");
// 指定封装的对象
sqlQuery.addEntity(Customer.class);
List<Customer> list = sqlQuery.list();
System.out.println(list);
// 提交事务
session.getTransaction().commit();
session.close();
}
/**
* 条件查询用sql
*
* @zhoujiaxuan
*/
@Test
public void Test7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer where name=?");
sqlQuery.addEntity(Customer.class);
sqlQuery.setParameter(0, "梅剑华2");
// 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()
@Test
public void Test8() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
Criteria criteria = session.createCriteria(Customer.class);
// 查询所有
// List<Customer> list = criteria.list();
// System.out.println(list);
// 分页查询
// criteria.setFirstResult(10);
// criteria.setMaxResults(10);
// 条件查询
// 查询姓名是梅剑华2的信息
// criteria.add(Restrictions.eq("name", "梅剑华2"));
// 查询地址时非洲的信息
// criteria.add(Restrictions.eq("address", "非洲"));
// Customer c = (Customer) criteria.uniqueResult();
// System.out.println(c);
// 查询姓名是梅剑华2或者地址时非洲的信息
criteria.add(Restrictions.or(Restrictions.eq("address", "非洲"),
Restrictions.eq("name", "梅剑华2")));
List<Customer> list = criteria.list();
System.out.println(list);
// 提交事务
session.getTransaction().commit();
session.close();
}
查询所有操作
Session.createCriteria(实体类.class)得到一个Criteria对象,调用list查询所有
分页操作与query的方法一样
setFirstResult() setMaxResults()
条件查询
criteria.add(Restrictions.eq(“name”,”xxxx”));
criteria.add(Restrictions.or(Restricitons.eq(),Restrictions.list()…..))
我们使用Criteria可以更加面向对象去操作,它非常适合进行多条件组合查询。
Hibernate持久化类与主键生成策略:
什么是持久化类?
Persistent Object (PO)
PO=POJO+hbm映射配置
对于hibernate中的PO编写规则:
1. 必须提供一个无参数的public构造方法
2. 所有属性要private ,对外提供public 的get/set方法
3. 在PO类必须提供一个标识属性,让它与数据库中的主键对应,我们管这个属性叫OID
4. PO类中的属性尽量使用基本数据类型的包装类.
Int-Integer double--Double float-Float
5. PO类它不能使用final修饰符
OID作用:
OID指的是与数据库中表的主键对应的属性。
Hibernate框架它是通过OID来区分不同的PO对象,如果在内存中有两个相同的OID对象,那么hibernate认为它们是同一个对象。
为什么PO类属性它要使用包装类型?
使用基本数据类型是没有办法去描述不存在概念,如果使用包装类型,它就是一个对象,对于对象它的默认值是null.
PO类不可以使用final修饰?(hibernate中的get/load方法的区别)
Get/load方法它们都是根据id去查询对象。
get直接得到了一个持久化类型对象,它就是立即查询操作
load它得到的是持久化类开的代理类型对象(子类对象)。它采用了一种延迟策略来查询数据。
get方法在查询时,如果不存在返回null
load方法在查询时,如果不存在,会产生异常 ObjectNotFoundException.
//持久化类
@Test
public void test1() {
Session session = HibernateUtils.openSession();
// Customer customer = session.get(Customer.class, 2);
// System.out.println(customer.getClass());//com.itheima.domain.Customer
// Customer customer = session.load(Customer.class, 2);
// System.out.println(customer.getClass());//com.itheima.domain.Customer_$$_jvstbeb_0
// Customer customer = session.get(Customer.class, 200);
// System.out.println(customer);//null
Customer customer = session.load(Customer.class, 200);
System.out.println(customer);// 异常
session.close();
}
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持久化对象状态
持久化对象三种状态
有三种:
1. 瞬时态:也叫做临时态或自由态,它一般指我们new出来的对象,它不存在OID,与hibernate session无关联,在数据库中也无记录。它使用完成后,会被jvm直接回收掉,它只是用于信息携带。
简单说:无OID 与数据库中的信息无关联,不在session管理范围内。
2. 持久态:在hibernate session管理范围内,它具有持久化标识OID它的特点,在事务未提交前一直是持久态,当它发生改变时,hibernate是可以检测到的。
简单说:有OID 由session管理,在数据库中有可能有,也有可有没有。
3. 托管态:也叫做游离态或离线态,它是指持久态对象失去了与session的关联,托管态对象它存在OID,在数据库中有可能存在,也有可能不存在。
对于托管态对象,它发生改变时hibernet不能检测到。
//持久化对象的三种状态
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer = new Customer();// 瞬时态
customer.setName("大司马");
customer.setAddress("wuhu");
customer.setSex("nan");
session.save(customer);// 持久态
session.getTransaction().commit();
session.close();
System.out.println(customer);// 托管态
}
持久化类三种状态切换
判断持久化类对象三种状态:
1. 是否有OID
2. 判断是否与session关联
1. 瞬时态(new 出来的)
瞬时------持久 save saveOrUpdate
瞬时-----脱管(游离) 手动设置oid
2. .持久态 它是由session管理
持久-------瞬时 delete() 被删除后持久化对象不在建议使用
持久-----脱管 注意:session它的缓存就是所说的一级缓存
evict(清除一级缓存 中指定的一个对象)
clear(清空一级缓存)
close(关闭,清空一级缓存)
3. .脱管态 (它是无法直接获取)
脱管-----瞬时 直接将oid删除
脱管----持久 update saveOrUpdate lock(过时)
Hibernate一级缓存
Hibernate的一级缓存就是指session缓存。
private transient ActionQueue;
private transient StatefulPersistenceContext persistentContext;
actionQueue它是一个行列队列,它主要记录crud操作的相关信息
persistenceContext它是持久化上下文,它其实是真正缓存。
在session中定义了一系列的集合来存储数据,它们构成session缓存。
只要session没有关闭,它就会一直存在。
当我们通过hibernate中的session提供的一些API例如 save get update等进行操作时,就会将持久化对象保存到session中,当下一次在去查询缓存中具有的对象(OID值来判断),
就不会去从数据库查询,而是直接从缓存中获取。
Hibernate的一级缓存存在的目的就是为了减少对数据库访问。
在hibernate中还有一个二级缓存,它是SessionFactory级别缓存。
//一级缓存的存在
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 1);
Customer cc = session.get(Customer.class, 1);
session.getTransaction().commit();
session.close();
}
持久化对象具有自动更新数据库能力
//持久化具有自动更新数据库的能力
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 1);
c.setName("TOM");
session.getTransaction().commit();
session.close();
}
为什么持久化对象具有自动更新数据库能力?
当事务提交,session关闭,向数据库发送请求是,会判断一级缓存中的数据是否与快照区一直,如果不一样,就会发送update语句
一级缓存常用API
一级缓存特点:
1. 当我们通过session的save,update saveOrupdate进行操作时,如果一级缓存中没有对象,会将这些对象从数据库中查询到,存储到一级缓存。
2. 当我们通过session的load,get,Query的list等方法进行操作时,会先判断一级缓存中是否存在,如果没有才会从数据库获取,并且将查询的数据存储到一级缓存中。
3. 当调用session的close方法时,session缓存清空。
clear 清空一级缓存.
evict 清空一级缓存中指定的一个对象。
refresh重新查询数据库,用数据库中信息来更新一级缓存与快照
public void test5(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();//存储数据到一级缓存
session.clear();//清空一级缓存
Customer c = session.get(Customer.class, 1);//先从一级缓存中获取,如果获取不到 则从数据库中查询
session.evict(c);//从一级缓存中删除某个对象
Customer cc = session.get(Customer.class, 1);
cc.setName("kkkkkk");
session.refresh(cc);//重新查询数据库 用数据库中的信息来更新我们的一级缓存 和快照
session.getTransaction().commit();
session.close();
}
Hibernate常用API-Session补充
update
udpate操作它主要是针对于脱管对象,持久对象具有自动更新能力。
问题1:如果我们直接操作的对象是一个脱管对象,执行update会出现什么情况?
Update操作时,如果对象是一个脱管对象,可以操作,它会将脱管对象转换成持久对象在操作
如果在session中出现相同的oid两个对象,会产生异常
问题2脱管对象的oid如果在数据表中不存在,会报异常?
所以:在操作中,建议我们通过持久化对象来直接修改其操作。
saveOrUpdate
如果对象是一个瞬时对象 --------执行save操作
如果对象是一个脱管对象---------执行update
如果是一个持久对象-------直接返回
delete
删除一个脱管对象,与session关联,在删除
注意:如果执行delete操作,先删除一级缓存,在删除数据库中的数据。
Hibernate关联映射--数据对象三种关系
Hibernate框架基于ORM设计思想,它将关系型数据库中的表与我们java中的类进行映射,一个对象就对应着表中的一条记录,而表中的字段对应着类中的属性。
数据库中表与表之间存在着三种关系,也就是系统设计中的三种实体关系
一对一
原则有两种:
1. 唯一外键对应:在任意一方添加外键来描述对应关系
2. 主键对应:一方的主键作为另一方的主键
比如说员工跟档案,一个员工对应一份档案
此时可以分别在两个表上加上外键指向对方的主键,也可以直接使用主键对应
Class Employee{
Private Archives archives;
}
Class Archives{
Private Employee employee;
}
一对多(多对一)
客户与订单之间一对多关系(多对一)
建表原则:在多的一方添加外键来描述关联关系
在客户表加上一个外键指向订单的主键来描述关系
Class Customer{
Private Set<Order> orders;
}
Class Order{
Private Customer c;
}
多对多
例如学生与老师
建表原则:通过一张中间表来描述其对应关系
中间表至少需要两个字段作为外键来分别指向多对多双方的主键
Class Student{
Set<Teacher> ts;
}
Class Teacher{
Set<Student> ss;
}
Hibernate关联映射--一对多(多对一);
我们以客户(Customer)与订单(Order)为例
实体类创建
订单
public class Order {
private Integer id;
private Double money;
private String receiverInfo;
private Customer c;
客户
public class Customer {
private Integer id;
private String name;
private Set<Order> order = new HashSet<Order>();
Hbm映射文件编写
Order.hbm.xml
<hibernate-mapping>
<class name="com.itheima.one2Many.Order" table="t_order" catalog="hibernateTest">
<id name="id" column="c_id">
<generator class="identity"></generator>
</id>
<property name="money" column="c_money" />
<property name="receiverInfo" column="c_receiverInfo" length="50" />
<many-to-one name="c" class="com.itheima.one2Many.Customer"
column="c_customer_id" cascade="save-update"></many-to-one>
</class>
</hibernate-mapping>
Customer.hbm.xml
<hibernate-mapping>
<class name="com.itheima.one2Many.Customer" table="t_customer"
catalog="hibernateTest">
<id name="id" column="c_id">
<generator class="identity"></generator>
</id>
<property name="name" column="c_name" length="20" />
<set name="order" cascade="save-update" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
</class>
</hibernate-mapping>
测试保存
/**
* 双向关联
*
* @zhoujiaxuan
*/
@Test
public void test() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
o1.setC(c);
o2.setC(c);
c.getOrder().add(o1);
c.getOrder().add(o2);
session.save(c);
session.save(o1);
session.save(o2);
session.getTransaction().commit();
session.close();
}
测试单向关联保存
***org.hibernate.TransientObjectException:
object references an unsaved transient instance - save the transient instance before flushing:
cn.itheima.oneToMany.Customer …………..
这个异常代表提一个持久化对象关联了一个瞬时对象。
我们可以使用级联操作来解决上述的问题.
我们现在要做的是保存订单时保存客户,需要在订单的hbm配置文件中修改
设置cascade=save-update 那么在保存订单时就可以自动将客户保存。
<many-to-one name="c" class="com.itheima.one2Many.Customer"
column="c_customer_id" cascade="save-update"></many-to-one>
如果我们要完成保存客户时,保存订单
<set name="order" cascade="save-update" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
/**
* 级联单项保存(保存订单自动保存客户)
*
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
o1.setC(c);
o2.setC(c);
session.save(o1);
session.save(o2);
session.getTransaction().commit();
session.close();
}
/**
* 级联保存(保存客户自动保存订单)
*
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
c.getOrder().add(o1);
c.getOrder().add(o2);
session.save(c);
session.getTransaction().commit();
session.close();
}
双向关联维护
我们在开发中要配置双向关联配置。---------可以通过任意一方来操作对方
在操作代码,尽量来要进行单向关联。------可以尽量资源浪费。
在双向关联中,会存在多余的update语句。
我们可以使用inverse属性来设置,双向关联时由哪一方来维护表与表之间的关系。
<set name="order" cascade="save-update" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
Inverse它的值如果为true代表,由对方来维护外键。
Inverse它的值如果为false代表,由本方来维护外键。
关于inverse的取值:
外键在哪一个表中,我们就让哪一方来维护外键。
对象导航
当一个订单关联一个客户,而这个客户关联了另外两个订单,
这时当我们保存其中的某一个客户或者订单时,数据库会以谁为导航来插入数据
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
Order o3 = new Order();
o3.setMoney(3000d);
o3.setReceiverInfo("上海");
// o1.setC(c);
c.getOrder().add(o2);
c.getOrder().add(o3);
// session.save(o1);//插入4条记录
// session.save(c);//插入3条记录
// session.save(o2);//插入1条记录
session.getTransaction().commit();
session.close();
}
级联删除
我们在删除客户时,也要删除订单,如果没有做级联,那么这个操作是不允许。为了维护数据完整性
想要完成操作:我们可以在客户中添加cascade=”delete”;
<set name="order" cascade="delete" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
delete-orphan用法:
得到客户的订单集合,然后利用集合去删除客户的某一个订单.
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注解开发
在hibernate中使用注解开发,可以帮助我们简化hbm文件配置
PO类注解配置:
@Entity 声明一个实体
@Table来描述类与表对应
@Entity
@Table(name="t_customer",catalog="hibernateTest")
public class Customer {
@Id来声明一个主键
@GenerateValue 用它来声明一个主键生成策略
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
默认情况下相当于native
可以选择的主键生成策略 AUTO IDENTITY SEQUENCE
@Column来定义列
@Column(name="c_name",length=30)
private String name;
注意:对于PO类中所有属性,如果你不写注解,默认情况下也会在表中生成对应的列。
列的名称就是属性的名称
@Temporal来声明日期类型
@Temporal(TemporalType.TIMESTAMP)
private Date publicationDate;
可以选择
TemporalType.DATA 只有年月日
TemporalType.TIME 只有小时分钟秒
TemporalType.TIMESTAMP 有年月日小时分钟秒
我们最终需要在hibernate.cfg.xml文件中将我们类中的注解配置引用生效
<mapping class="com.itheima.domain.Book"/>
问题:1.如果我们主键生成策略想使用UUID类型?
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
问题2:如果设定类的属性不在表中映射?
@Transient
private String msString;
对于我们以上讲解的关于属性配置的注解,我们也可以在其对应的getXxx方法去使用
一对多(多对一):
@OneToMany
@ManyToOne
以Customer与Order为例
Customer:
@Entity
@Table(name="t_customer",catalog="hibernateTest")
@NamedQuery(name="myHql",query="from Customer")
@Proxy(lazy=true)
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Cascade(CascadeType.SAVE_UPDATE)
private Set<Order> order = new HashSet<Order>();
生成对应的get/set方法
Order:
@Entity
@Table(name = "t_order", catalog = "hibernateTest")
@NamedQuery(name="findOrderByC",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)
@Cascade(CascadeType.SAVE_UPDATE)
@JoinColumn(name = "c_customer_id")
private Customer c;
生成对应的get/set方法
示例:保存客户时,保存订单
对于这个示例我们需要在Customer中配置cascade操作,save-update
第一种方式,可以使用JPA提供的注解
<many-to-one name="c" class="com.itheima.one2Many.Customer"
column="c_customer_id" cascade="save-update"></many-to-one>
第二种方式:可以使用hibernate提供的注解
@OneToMany(targetEntity = Customer.class)
@Cascade(CascadeType.SAVE_UPDATE)
测试代码:
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("梅剑华");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("北京");
o1.setC(c);
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("上海");
o2.setC(c);
c.getOrder().add(o1);
c.getOrder().add(o2);
session.save(c);
session.getTransaction().commit();
session.close();
}
订单中没有关联客户的id,为什么?
原因:我们在Customer中配置了mappedBy=”c”它代表的是外键的维护由Order方来维护,而Customer不维护,这时你在保存客户时,级联保存订单,是可以的,但是不能维护外键,所以,我们必须在代码中添加订单与客户关系。
//为了维护外键
o1.setC(c);
o2.setC(c);
//为了进行级联操作
c.getOrder().add(o1);
c.getOrder().add(o2);
//进行级联保存
session.save(c);
session.getTransaction().commit();
session.close();
扩展:关于hibernate注解@Cascade中的DELETE_ORPHAN过时
@Cascade(CascadeType.DELETE_ORPHAN)
使用下面方案来替换过时方案
@OneToMany(targetEntity=Order.class,mappedBy="c",orphanRemoval=true)
Hibernate关联映射-多对多:
我们使用注解完成多对多配置.
描述学生与老师.
使用@ManyToMany来配置多对多,只需要在一端配置中间表,另一端使用mappedBy表示放置外键维护权。
Techer:
@Entity
@Table(name="c_teacher",catalog="hibernateTest")
public class Teacher {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name ;
@ManyToMany(targetEntity=Student.class,mappedBy="teacher")
private Set<Student> student = new HashSet<Student>();
生成对应的get/set方法
Student:
@Entity
@Table(name = "t_student", catalog = "hibernateTest")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToMany(targetEntity = Teacher.class)
@JoinTable(name = "s_t", joinColumns = { @JoinColumn(name = "c_student_id") }, inverseJoinColumns = {
@JoinColumn(name = "c_teacher_id") })
@Cascade(CascadeType.SAVE_UPDATE)
private Set<Teacher> teacher = new HashSet<Teacher>();
生成对应的get/set方法
因为我们将外键的维护权利由Student来维护,我们演示保存学生时,将都也级联保存。
测试代码:
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Student s1 = new Student();
s1.setName("梅剑华");
Student s2 = new Student();
s2.setName("马远");
Teacher t1 = new Teacher();
t1.setName("张玮");
Teacher t2 = new Teacher();
t2.setName("马坤坤");
//学生关联老师
s1.getTeacher().add(t1);
s1.getTeacher().add(t2);
s2.getTeacher().add(t1);
s2.getTeacher().add(t2);
session.save(s1);
session.save(s2);
session.getTransaction().commit();
session.close();
}
我们在Student类中配置了级联
@Cascade(CascadeType.SAVE_UPDATE)
级联删除操作测试,删除一个全部都会删掉
Hibernate关联映射-一对一:
以人与身份证号为例
一对一操作有两种映射方式:
1. 在任意一方添加外键
2. 主键映射
外键映射:
User:
@Entity
@Table(name="c_user",catalog="hibernateTest")
public class User {
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
private String name;
@OneToOne(targetEntity=IDcard.class,mappedBy="user")//t_user表放弃对外键的维护权利
private IDcard iDcard;
生成对应的get/set方法
IDcard:
@Entity
@Table(name="c_idcard",catalog="hibernateTest")
public class IDcard {
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
private String cardNum;
@OneToOne(targetEntity=User.class)
@JoinColumn(name="c_user_id")
@Cascade(CascadeType.SAVE_UPDATE)
private User user;
生成对应的get/set方法
joinColumn指定外键列名称,当前配置外键是在t_idcard表中
测试代码:
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
User user = new User();
user.setName("梅剑华");
IDcard iDcard = new IDcard();
iDcard.setCardNum("2141414325235");
iDcard.setUser(user);
session.save(iDcard);
session.getTransaction().commit();
session.close();
}
主键映射(了解):
以Husband与Wife为例
Husband:
@Entity
@Table(name = "t_husband", catalog = "hibernateTest")
public class Husband {
@Id
@GenericGenerator(name = "myForeignKey", strategy = "foreign", parameters = {
@Parameter(name = "property", value = "wife") })
@GeneratedValue(generator="myForeignKey")
private String id;
private String name;
@OneToOne(mappedBy="husband")
@PrimaryKeyJoinColumn
private Wife wife;
生成对应的get/set方法
Husband的主键我们设置成参考wife的主键方式
Wife:
@Entity
@Table(name="t_wife",catalog="hibernateTest")
public class Wife {
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
private String name ;
@OneToOne
@PrimaryKeyJoinColumn
@Cascade(CascadeType.SAVE_UPDATE)
private Husband husband;
生成对应的get/set方法
测试代码:
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Husband husband = new Husband();
husband.setName("王撕葱");
Wife wife = new Wife();
wife.setName("凤姐");
husband.setWife(wife);
wife.setHusband(husband);
session.save(wife);
session.getTransaction().commit();
session.close();
}
Hibernate检索方式概述:
对数据库操作中,最常用的是select.使用hibernate如何select操作。
分为五种:
1导航对象图检索方式,根据已加载的对象导航到其它对象
2.OID检索方式,按照对象的OID来检索对象
3.HQL检索方式,使用面向对象的HQL查询语言
4.QBC检索方式,使用QBC(Query by Criteria)API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口
5.本地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查询。
基本步骤:
1. 得到Session
2. 编写HQL语句
3. 通过session.createQuery(hql)创建一个Query对象
4. 为Query对象设置条件参数
5. 执行list查询所有,它反胃的是List集合 uniqueResut()返回一个查询结果。
@SuppressWarnings("all")
public class HQLTest {
/**
* 命名检索
* 我们可以将hql语句先定义出来,在使用时通过session.getNamedQuery(hqlName);得到一个Query,在执行.
问题:hql定义在什么位置?
1.如果你有hbm配置文件,那么当前的hql操作是对哪一个实体进行操作,就在哪一个 实体的配置文件中声明。
<query name="myHql">
from Customer
</query>
2.如果是使用注解来描述PO的配置
我们直接在PO类中使用
@NamedQuery(name="myHql",query="from Customer")
* @zhoujiaxuan
*/
@Test
public void test8() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 1);
// List<Customer> list = session.getNamedQuery("myHql").list();
// System.out.println(list);
List<Order> list = session.getNamedQuery("findOrderByC").setEntity("c", c).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 投影检索
* 我们主要讲解是关于部分属性查询,可以使用投影将部分属性封装到对象中。
* 注意:我们必须在PO类中提供对应有参的构造方法,也要有无参数构造。
* @zhoujiaxuan
*/
@Test
public void test7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("select new Customer(id,name) from Customer").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 分组统计检索
*
* @zhoujiaxuan
*/
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// Query query = session.createQuery("select count(*) from Order");
// Object result = query.uniqueResult();
// System.out.println(result);
List<Order> list = session.createQuery("select sum(money) from Order group by c").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 分页检索
*
* @zhoujiaxuan
*/
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.createQuery("from Order");
query.setFirstResult(6);
query.setMaxResults(6);
List<Order> list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 条件检索
*
* @zhoujiaxuan
*/
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Order> list = session.createQuery("from Order where money>?").setParameter(0, 2000d).list();
// List<Order> list = session.createQuery("from Order where
// money>:mymoner").setParameter("mymoney", 2000d).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 排序检索
*
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Order> list = session.createQuery("from Order order by money desc").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 基本检索
*
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 准备数据
*
* @zhoujiaxuan
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer = new Customer();
customer.setName("梅剑华");
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setMoney(1000d + i * 100);
order.setReceiverInfo("南京");
order.setC(customer);
session.save(order);
}
session.getTransaction().commit();
session.close();
}
}
QBC
QBC(query by criteria),它是一种更加面向对象的检索方式。
QBC步骤:
1.通过Session得到一个Criteria对象 session.createCriteria()
2.设定条件 Criterion实例 它的获取可以通过Restrictions类提供静态。
Criteria的add方法用于添加查询条件
3. 调用list进行查询 criterfia.list.
@SuppressWarnings("all")
public class QBCTest {
/**
* 离线检索
*
* @zhoujiaxuan
*/
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = DetachedCriteria.forClass(Customer.class).add(Restrictions.like("name", "梅%"))
.getExecutableCriteria(session).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 统计检索
*
* @zhoujiaxuan
*/
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Order.class);
// Object result =
// criteria.setProjection(Projections.rowCount()).uniqueResult();
// System.out.println(result);
List<Object[]> list = criteria
.setProjection(
Projections.projectionList().add(Projections.sum("money")).add(Projections.groupProperty("c")))
.list();
for (Object[] objs : list) {
for (Object object : objs) {
System.out.println(object);
}
}
session.getTransaction().commit();
session.close();
}
/**
* 分页检索
*
* @zhoujiaxuan
*/
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Order.class);
criteria.setFirstResult(6);
criteria.setMaxResults(6);
List<Order> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 条件检索
*
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.createCriteria(Customer.class).add(Restrictions.like("name", "梅%"))
.uniqueResult();
// System.out.println(c);
List<Order> list = session.createCriteria(Order.class)
.add(Restrictions.and(Restrictions.gt("money", 1500d), Restrictions.eq("c", c))).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 排序检索
* 注意在criteri.addOrder()方法的参数使用的Order是hibernate中的对象
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// List<Customer> list =
// session.createCriteria(Order.class).addOrder(org.hibernate.criterion.Order.desc("money")).list();
List<Customer> list = session.createCriteria(Order.class).addOrder(org.hibernate.criterion.Order.asc("money"))
.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 基本检索
*
* @zhoujiaxuan
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createCriteria(Customer.class).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
}
本地SQL:
@SuppressWarnings("all")
public class SQLTest {
/**
* 本地检索
*
* @zhoujiaxuan
*/
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer");
sqlQuery.addEntity(Customer.class);
List<Customer> list = sqlQuery.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
}
本地sql也支持命名查询。
可以将sql语句定义在hbm文件中,也可以使用注解。
<sql-query name="sql">
select * from t_ustomer
</sql-query>
本地命名sql注解定义
@NamedNativeQuery(name="sql",query="select * from t_ustomer")
如果执行这个命名的sql会产生异常
出现问题的原因:是hibernate不知道执行select * from t_customer后如果将结果封装。
SQL多表操作:
1.交叉连接 CROSS JOIN 会产生迪卡尔积
SELECT * FROM t_customer CROSS JOIN t_order;
2.内连接 INNER JOIN ON
SELECT * FROM t_customer AS c INNER JOIN t_order AS o ON c.id=o.c_customer_id;
使用内连接它只能将有关联的数据得到。
隐式内连接 使用 "逗号"将表分开,使用WHERE来消除迪卡尔积
SELECT * FROM t_customer AS c ,t_order o WHERE c.id=o.c_customer_id;
3.外连接 左外LEFT OUTER JOIN 右外RIGHT OUTER JOIN
OUTER可以省略
SELECT * FROM t_customer c LEFT OUTER JOIN t_order o ON c.id=o.c_customer_id;
HQL多表操作
Hql多表操作分类:
1. 交叉连接
2. 内连接
a) 显示内连接
b) 隐式内连接
c) 迫切内连接
3. 外连接
左外连接
迫切左外连接
右外连接
注意:在hibernate中有迫切连接的概念,而sql中没有。
内连接
显示内连接
显示内连接使用的是inner join with
/**
* 显示内连接
* @zhoujiaxuan
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//String hql = "from Order o inner join o.c";
String hql = "from Customer c inner join c.order with c.id = 1";
List<Object[]> list = session.createQuery(hql).list();
for (Object[] objects : list) {
for (Object object : objects) {
System.out.print(object + "\t");
}
System.out.println();
}
session.getTransaction().commit();
session.close();
}
隐式内连接
隐式内连接也我们在sql中操作不一样,它是通过”.”运算符来关联
/**
* 隐式内连接
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//String hql = "from Order o where o.c.id=1";
String hql = "from Customer c where c.order.id < 10";
List list = session.createQuery(hql).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
迫切内连接
迫切内连接得到的结果是直接封装到PO类中,而内连接得到的是Object[]数组,数组中封装的是PO类对象。
/**
* 迫切内连接
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
String hql = "from Order o inner join fetch o.c";
//因为迫切内连接可能造成重复 所以需要进行去重
//String hql = "select distinct c from Customer c inner join fetch c.order";
List<Order> list = session.createQuery(hql).list();
for (Order order : list) {
System.out.println(order);
}
session.getTransaction().commit();
session.close();
}
外链接
/**
* 外连接
* @zhoujiaxuan
*/
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//左外链接
String hql = "from Customer c left join c.order";
//右外连接
//String hql = "from Customer c right join c.order";
List<Object[]> list = session.createQuery(hql).list();
for (Object[] objects : list) {
for (Object object : objects) {
System.out.print(object + "\t");
}
System.out.println();
}
session.getTransaction().commit();
session.close();
}
迫切外链接
/**
* 迫切外连接
* @zhoujiaxuan
*/
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//String hql = "select distinct c from Customer c left join fetch c.order";
String hql = "select distinct c from Customer c left join fetch c.order where c.id=1";
List<Customer> list = session.createQuery(hql).list();
for (Customer customer : list) {
System.out.println(customer);
}
session.getTransaction().commit();
session.close();
}
Hibernate事务管理
事务介绍
问题:什么是事务?
事务就是逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全都失败。
问题:事务四个特性?
原子性:不可分割
一致性:事务在执行前后,要保证数据的一致。
隔离性:一个事务在执行的过程中,不应该受到其它事务的干扰。
持久性:事务一旦结束,数据持久化到数据库。
问题:不考虑事务的隔离性,会产生什么问题?
脏读:一个事务读取到另一个事务的未提交数据
不可重复读:一个事务读取到另一个事务提交的数据(主要是指update),会导致两次读取的结果不一致。
虚读(幻读): 一个事务读取到另一个事务提交的数据(主要是指insert),会导致两次读取结果不一致.
问题:对于上述问题如何解决?
我们可以通过设置隔离级别来解决.
READ_UNCOMMITED 读取未提交,它引发所有的隔离问题
READ_COMMITTED 读已提交,阻止脏读,可能发生不可重复读与虚读.
REPEATABLE_READ 重复读 阻止脏读,不可重复读 可能发生虚读
SERIALIZABLE 串行化 解决所有问题 不允许两个事务,同时操作一个目标数据。(效率低下)
ORACLE 默认的是事务隔离级别 READ_COMMITTED
MYSQL 默认的事务隔离级别 REPEATABLE_READ
Hibernate中设置事务隔离级别
hibernate.connection.isolation
它可取的值有 1 2 4 8
1代表的事务隔离级别为READ UNCOMMITTED
2代表的事务隔离级别为READ COMMITTED
4代表的事务隔离级别为 REPEATABLE READ
8代表的事务隔离级别为 SERIALIZABLE
在hibernate.cfg.xml文件中配置
<property name="hibernate.connection.isolation">4</property>
<!-- 设置事务的管理级别 -->
Hibernate中session管理
Hibernate提供了三种管理session的方式:
1. Session对象的生命周期与本地线程绑定(ThreadLocal)
2. Session对象的生命周期与JTA事务绑定(分布式事务管理)
3. Hibernate委托程序来管理Session的生命周期
我们之前所使用的是第三种 ,通过程序获取一个Session对象,使用它,最后session.close();
在实际开发中我们一般使用的是前两种:
主要介绍关于本地线程绑定Session。
步骤:
1. 需要在hibernate.cfg.xml文件配置
<property name="hibernate.current_session_context_class">thread</property>
2. 在获取session时不要在使用openSession而是使用getCurrentSession()方法。
public static Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
测试:
@Test
public void test1 (){
Session s1 = HibernateUtils.openSession();
Session s2 = HibernateUtils.openSession();
System.out.println(s1==s2);//false
Session s3 = HibernateUtils.getCurrentSession();
Session s4 = HibernateUtils.getCurrentSession();
System.out.println(s3==s4);//true
}
关于getCurrentSession使用时的注意事项:
@Test
public void test2(){
Session session = HibernateUtils.getCurrentSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 3);
System.out.println(c);
session.getTransaction().commit();
//session.close();
}
使用getCurrentSession获取的与线程绑定的session对象,
在事务关闭时,session对象也会close,简单说,就不需要我们在手动close;
Hibernate优化方案
HQL优化
1.使用参数绑定
1.使用绑定参数的原因是让数据库一次解析SQL,对后续的重复请求可以使用用生成好的执行计划,这样做节省CPU时间和内存。
2.避免SQL注入
2.尽量少使用NOT
如果where子句中包含not关键字,那么执行时该字段的索引失效。
3.尽量使用where来替换having
Having在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销
4.减少对表的查询
在含有子查询的HQL中,尽量减少对表的查询,降低开销
5.使用表的别名
当在HQL语句中连接多个表时,使用别名,提高程序阅读性,并把别名前缀与每个列上,这样一来,可以减少解析时间并减少列歧义引起的语法错误。
6.实体的更新与删除
在hibernate3以后支持hql的update与delete操作
一级缓存优化
一级缓存也叫做session缓存,
在一个hibernate session有效,这级缓存的可干预性不强,大多于hibernate自动管理,但它提供清除缓存的方法,这在大批量增加(更新)操作是有效果的,例如,同时增加十万条记录,按常规进行,很可能会出现异常,这时可能需要手动清除一级缓存,session.evict以及session.clear.
检索策略(抓取策略)
延迟加载
延迟加载 是hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
load方法采用的策略延迟加载.
get方法采用的策略立即加载。
检索策略分为两种:
1. 类级别检索
2. 关联级别检索
类级别检索
类级别检索是通过session直接检索某一类对应的数据,例如
Customer c=session.load(Customer.class,1)
Session.createQuery(“from Order”)
类级别检索策略分为立即检索与延迟检索,默认是延迟检索,类级别的检索策略可以通过<class>元素的lazy属性来设置 ,默认值是true
在hbm配置文件中设置
<class name="com.itheima.one2Many.Customer" table="t_customer"
catalog="hibernateTest" lazy="true">
在类中使用注解
@Proxy(lazy=true)
如果将lazy设置为false,代表类级别检索也使用立即检索。这时load与get就一样,都是立即检索。
如果对一个延迟代理对象进行初始化?
hibernate.initialize()
关联级别检索
查询到某个对象,获得其关联的对象或属性,这种称为关联级别检索,例如
c.getOrders().size()
c.getName()
对于关联级别检索我们就要研究其检索策略(抓取策略)
检索策略(抓取策略)
抓取策略介绍
指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。
一对一 <one-to-one>
一对多(多对一) <set>下有<one-to-many> <many-to-one>
多对多 <set>下有<many-to- many>
我们主要是在<set>与<many-to-one>或<one-to-one>上设置fetch lazy
例如:查询一个客户,要关联查询它的订单
客户是一的一方,在客户中有set集合来描述其订单,在配置中我们是使用
<set>
<one-to-many>
</set>..
可以在set标签上设置两个属性 fetch lazy
Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查
Lazy 控制SQL语句何时发送
例如:在查询一个订单时,要查询客户信息
<many-to-one> 或<one-to-one>
也可以设置fetch lazy
Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查
Lazy 控制SQL语句何时发送
总结:
讲解抓取策略
在两方面设置
<set fetch=”” lazy=””>
<many-to-one fetch=”” lazy=””>
<one-to-one>
注解配置抓取策略
问题:如何使用注解来设置
在<setse>设置的fetch与lazy可以使用下面注解来描述
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
在<many-to-one>或<one-to-one>上如何设置 fetch与lazy
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
set上的fetch与lazy
set上的fetch与lazy它主要是用于设置关联的集合信息的抓取策略。
Fetch可取值有:
1. SELECT 多条简单的sql (默认值)
2. JOIN 采用迫切左外连接
3. SUBSELECT 将生成子查询的SQL
lazy可取值有:
1. TURE 延迟检索 (默认值)
2. FALSE 立即检索
3. EXTRA 加强延迟检索(及其懒惰)
第一种组合
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
会首先查询客户信息,当需要订单信息时,才会关联查询订单信息。
第二种组合
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.FALSE)
当查询客户信息时,就会将订单信息也查询,也就是说订单信息没有进行延迟。
第三种组合
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
当查询客户信息时,不会查询订单信息,当需要订单的个数时,也不会查询订单信息,
只会通过count来统计订单个数。
当我们使用size(),contains()或isEmpty()方法时不会查询订单信息。
第四种组合
如果fetch选择的是join方案,那么lazy它会失效。
生成SQl将采用的是迫切左外连接(left outer join fetch)
会立即查询。
@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.EXTRA)
第五种组合
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.TRUE)
会生成子查询,但是我们在查询订单时采用的是延迟加载。
第六种组合
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.FALSE)
会生成子查询,在查询客户信息时,就会将订单信息也查询出来
第七种组合
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
在查询订单时,只会根据情况来确定是否要订单信息,如果不需要,例如我们
程序中size操作,那么就会发出select count(*) from Order where c_customer_id=?
One的一言fetch与lazy
<set fetch lazy>它主要是设置在获取到一的一方时,如果去查询多的一方。
在<many-to-one>或<one-to-one>如果去查询对方。
对于程序 就是在多的一方如何查询一的主方信息
例如:获取到一个订单对象,要查询客户信息。
Fetch可取值:
select 默认值,代表发送一条或多条简单的select语句
join 发送一条迫切左外连接
lazy可取值
false 不采用延迟加载
proxy 默认值 是否采用延迟,需要另一方的类级别延迟策略来决定
no-proxy 不用研究
第一种组合
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
注意:Customer的类级别延迟策略
@Proxy(lazy=true)
public class Customer {
当我们执行时,会首先发送一条sql只查询订单信息,客户信息会延迟,只有真正需要客户信息时,才会发送sql来查询客户信息.
第二种组合
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
注意:Customer的类级别延迟策略
@Proxy(lazy=false)
public class Customer {
当查询订单时,就会将客户信息也查询到,原因是Customer它的类级别延迟为false,也就是立即查询。
第三种组合
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
当查询订单时,不会对客户信息进行延迟,立即查询客户信息
第四种组合
@Fetch(FetchMode.JOIN)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
如果fetch值为join,那么lazy失效。
会发送一条迫切左外连接来查询,也就立即查询。
批量抓取
我们在查询多个对象的关联对象时,可以采用批量抓取方式来对程序进行优化.
要想实现批量抓取:
可以在配置文件中 batch-size属性来设置
可以使用注解 @BatchSize(size=4)
可以采用批量抓取来解决N+1问题.
查询客户,查询订单
@Test
public void test1(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();
for (Customer customer : list) {
System.out.println(customer.getOrder().size());
}
session.getTransaction().commit();
session.close();
}
可以在客户配置文件中配置batch-size,是在<set>标签上
@BatchSize(size=3)
private Set<Order> order = new HashSet<Order>();
查询订单,查询客户
@Test
public void test2(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Order> list = session.createQuery("from Order").list();
for (Order order : list) {
System.out.println(order.getC().getName());
}
session.getTransaction().commit();
session.close();
}
订单与客户,客户它是一个主表,订单是一个从表。
在设置批量抓取时都是在主表中设置
在配置文件中在主表的<calss>标签上设置batch-size
在注解使用中
@BatchSize(size=3)
public class Customer {
注意:无论是根据哪一方来查询别一方,在进行批量抓取时,都是在父方来设置 ,
如果是要查询子信息,那么我们是在<set>上来设置batch-size,如果是从子方来查询父方,
也是在父方设置在<class>设置batch-size.
父与子区分:
有外键的表是子(从关联方就是父(主)表
什么是hibernate:
Hibernate它是一个轻量级的jdbc封装,也就是说,我们可以使用hibernate来完成原来我们使用jdbc完成操作,就是与数据库的交互操作。它是在dao层去使用的。
对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping)
简单说,我们使用orm可以将我们的对象与我们的类去进行映射,使的我们可以去操作对象就完成对表的操作。
hibernate的学习重点:
掌握hibernate的基本配置-------搭建hibenate开发环境
掌握hibernate常用API---------如何使用hibedrnate框架进行开
掌握hibernate的关联映射--------解决表与表之间存在关系问题 1:n 1:1 m:n
掌握hibernate的检索方式-------就是掌握hibernate的查询
掌握hibernate的优化方式------提高hibernate效率
hibernate的快速入门:
下载hibernate;
***注意:高版本的需要jdk8才可以
hibernate目录结构:
documentation目录:存放hibernate的相关文件与API
lib目录:存放hibernate编译和运行所依赖的jar包,其中required子目录下包含了运行hibernate项目必须的jar包
project目录:存放hibernate各种相关的源代码与资源.
在lib/required目录中,包含的jar包
创建数据库跟表:
CREATE DATABASE hibernateTest;
USER hibernateTest;
CREATE TABLE t_customer(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
address VARCHAR(50)
)
创建实体类:
public class Customer
private int id;
private String name ;
private String address;
private String sex;
导入hibernate框架相关依赖jar包
导入lib/required下所有的jar
导入数据库的驱动jar包
日志相关jar包
将hibernate/project/etc/log4j.properties文件导入到工程src下.
hibernate的相关文件配置工作:
有两种:
xxx.hbm.xml 它主要是用于描述类与数据库中的表的映射关系.
hibernate.cfg.xml 它是hibernate框架核心配置文件。
映射配置文件:
位置:它要与实体类在同一个包下.
名称 :类名.hbm.xml
约束:
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- name是类的全名 table 是表名 catal是数据库名 -->
<class name="com.itheima.domain.Customer" table="t_customer"
catalog="hibernateTest">
<!-- 主键生成策略 -->
<id name="id" column="id">
<generator class="native"></generator>
</id>
<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>
核心配置文件:
它主要是hibernate框架所使用的,它主要包含了连接数据库相关信息,hibernate相关配置等。
位置:在src下创建一个hibernate.cfg.xml
约束:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置数据库链接的四个项 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernateTest</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</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>
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- 事务自动提交的设置 -->
<property name="hibernate.connection.autocommit">false</property>
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</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>
<!-- 配合映射文件所在的位置 -->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Hibernate快速 入门开发测试:
@Test
public void saveCustomerTest1(){
Customer customer = new Customer();
customer.setName("马老师");
customer.setAddress("中山桥");
//固定步骤 使用hibernate中的配置来保存数据到数据库中
Configuration configure = new Configuration().configure();
SessionFactory sessionFactory = configure.buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();//开启事务
session.save(customer);//保存操作
session.getTransaction().commit();;//提交事务
session.close();
sessionFactory.close();
}
Hibernate执行原理总结
hibernate工作原理:
1,通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件。
2,由hibernate.cfg.xml中的<mappingresource="com/xx/User.hbm.xml"/>读取解析映射信息。
3,通过config.buildSessionFactory();//得到sessionFactory。
4,sessionFactory.openSession();//得到session。
5,session.beginTransaction();//开启事务。
6,persistent operate;
7,session.getTransaction().commit();//提交事务
8,关闭session;
9,关闭sessionFactory;
Hibernate的配置详解
Hibernate中我们使用时主要有两种配置文件;
核心配置文件 hibernate.cfg.xml:
对于hibernate的核心配置文件它有两种方式:
1. hibernate.cfg.xml
2. hibernate.properties
我们在开发中使用比较多的是hibernate.cfg.xml这种方式,原因它的配置能力更强,易于修改
我们主要讲解的是hibernate.cfg.xml配置
* 可以加载数据库相关信息:
<!-- 配置数据库链接的四个项 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernateTest</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
* hibernate相关配置:
<!-- 可以将数据库发送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>
* 加载映射配置文件:
<!-- 配合映射文件所在的位置 -->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
对于hibernate.cfg.xml配置文件中的内容可以参考hibernate/project/etc/hibernate.properties的配置
*配置这个属性后,我们可以进行表的自动创建
<!-- 自动创建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
Create-drop 每次都会创建一个新的表,执行完成后删除。一般在测试中使用
Create 每次都会创建一个新的表,一般是在测试中使用
update 如果数据库中有表,不创建,没有表创建,如果映射不匹配,会自动更新表结构(只能添加)
validate 只会使用存在的表,并且会对映射关系进行校验.
映射配置文件 xxx.hbm.xml
映射配置文件它的名称是类名.hbm.xml,它一般放置在实体类所在的包下。
这个配置文件的主要作用是建立表与类的映射关系。
统一声明包名,这样在<class>中就不需要写类的全名.
<hibernate-mapping package="com.itheima.domain">
关于<class>标签配置
name属性:类的全名称
table 表的名称,可以省略,这时表的名称就与类名一致
catalog属性:数据库名称 可以省略.如果省略,参考核心配置文件中url路径中的库名称
关于<id>标签
首先它必须存在。<id>是用于建立类中的属性与表中的主键映射。
name 类中的属性名称
column 表中的主键名称 column它也可以省略,这时列名就与类中属性名称一致
length 字段长度
type属性 指定类型
<generator>它主要是描述主键生成策略.
关于<property>标签
它是描述类中属性与表中非主键的映射关系
关于hibernate的映射文件中类型问题
对于type属性它的取值,可以有三种:
1. java中的数据类型
2. hibernate中的数据类型
3. SQL的数据类型
默认是hibernate中数据类型
Hibernate常用API;
Hibernate的核心类和接口一共有6个,分别为:Session,SessionFactory,
Transaction,Query,Criteria和Configuration。这6个核心类和接口在任何开发中都会用到
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这种核心配置,它如何加载映射配置?
//手动加载
//config.addResource("com/itheima/domain/Customer.hbm.xml");直接加载配置文件
//config.addClass(Customer.class);//这种方式会直接在实体类所在的包下查找规范映射配置文件
SessionFactory
首先SessionFactory它的获取是通过Configuration得到。
SessionFactory sessionFactory = configure.buildSessionFactory();
SessionFactory接口负责初始化Hibernate。它充当数据存储源的代理,并负责创建Session对象。
这里用到了工厂模式。
需要注意的是SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够,
当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
通过SessionFactory可以得到Session.
Session session = sessionFactory.openSession();
是从连接池中获取一个连接。
Session session = sessionFactory.getCurrentSession();
获取一个与线程绑定的Session.
SessionFactory它不是轻量级的,不要频繁创建关闭它。
在一个项目中有一个SessionFactory就可以,通过SessionFactory来获取Session进行操作。
问题:怎样可以保证在一个项目中所使用的SessionFactory是同一个哪?
可以写一个工具类
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();
}
}
SessionFactory内部还维护了一个连接池,如果我们要想使用c3p0连接池,应该怎样处理?
1. 我们要导入c3p0的相关jar包
在hibernate/lib/options下有关于c3p0连接池jar包
2. 在hibernate.cfg.xml文件中配置c3p0连接
可以查看etc/hibernate.properties中关于c3p0的配置
<!-- 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>
<property name="hibernate.connection.provider_class">
org.hibernate.connection.C3P0ConnectionProvider</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 事务回滚
问题:如何获取一个Transaction对象
Session.beginTransaction();
问题:如果在程序中没有开启事务,是否存在事务?
有事务,session的每一个操作就会开启一个事务。
默认情况下事务是不会自动提交的。
<!-- 事务自动提交的设置 -->
<property name="hibernate.connection.autocommit">false</property>
不自动提交
<!-- 事务自动提交的设置 -->
<property name="hibernate.connection.autocommit">true</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();
session.beginTransaction();// 开启事务
Query query = session.createQuery("from Customer");
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();// 提交事务
session.close();
}
分页查询
@Test
public void Test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
Query query = session.createQuery("from Customer");
query.setFirstResult(10);
query.setMaxResults(10);
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();// 提交事务
session.close();
}
查询指定列信息
@Test
public void Test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
Query query = session.createQuery("select new Customer(name , address) from Customer where id<5");
List<Customer> list = query.list();
System.out.println(list);
session.getTransaction().commit();// 提交事务
session.close();
}
Select name ,address from Customer; 得到的是List<Object[]>结果
要想得到List<Customer>结果
1. 在Customer类中生成以name,address为参数的构造,注意,无参数构造也要有。
2. Select new Customer(name,address) from Customer;
条件查询
可以使用where关键字
@Test
public void Test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
// 无名称参数
// Query query = session.createQuery("from Customer where name=?");
// query.setParameter(0, "梅剑华2");
// 有名称参数
Query query = session.createQuery("from Customer where name=:myname");
query.setParameter("myname", "梅剑华2");
// List<Customer> list = query.list();
// System.out.println(list);
// 如果保证查询结果唯一 也可以使用
Customer c = (Customer) query.uniqueResult();
System.out.println(c);
session.getTransaction().commit();// 提交事务
session.close();
}
无名称参数 from Customer where name=?
对其进行赋值 query.setParameter(0,”张三”)
有名称参数 from Customer where name=:myname;
对其进行赋值 query.setParameter(“myname”,”李四”);
如果查询结果可以保证就是唯一 的,我们可以使用
query. uniqueResult()来得到一个单独对象.
执行本地SQL
要想执行本地sql
SQLQuery sqlQuery=session.createSqlQuery(String sql);
使用addEntity方法来将结果封装到指定的对象中,如果不封装,得到的是List<Object>
如果sql中有参数,我们使用setParameter方法完成参数传递。
如果结果就是一个可以使用uniqueResult()来得到一个单独对象。
/**
* 使用sql语句查询所有
*
* @zhoujiaxuan
*/
@Test
public void Test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer");
// 指定封装的对象
sqlQuery.addEntity(Customer.class);
List<Customer> list = sqlQuery.list();
System.out.println(list);
// 提交事务
session.getTransaction().commit();
session.close();
}
/**
* 条件查询用sql
*
* @zhoujiaxuan
*/
@Test
public void Test7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer where name=?");
sqlQuery.addEntity(Customer.class);
sqlQuery.setParameter(0, "梅剑华2");
// 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()
@Test
public void Test8() {
Session session = HibernateUtils.openSession();
session.beginTransaction();// 开启事务
Criteria criteria = session.createCriteria(Customer.class);
// 查询所有
// List<Customer> list = criteria.list();
// System.out.println(list);
// 分页查询
// criteria.setFirstResult(10);
// criteria.setMaxResults(10);
// 条件查询
// 查询姓名是梅剑华2的信息
// criteria.add(Restrictions.eq("name", "梅剑华2"));
// 查询地址时非洲的信息
// criteria.add(Restrictions.eq("address", "非洲"));
// Customer c = (Customer) criteria.uniqueResult();
// System.out.println(c);
// 查询姓名是梅剑华2或者地址时非洲的信息
criteria.add(Restrictions.or(Restrictions.eq("address", "非洲"),
Restrictions.eq("name", "梅剑华2")));
List<Customer> list = criteria.list();
System.out.println(list);
// 提交事务
session.getTransaction().commit();
session.close();
}
查询所有操作
Session.createCriteria(实体类.class)得到一个Criteria对象,调用list查询所有
分页操作与query的方法一样
setFirstResult() setMaxResults()
条件查询
criteria.add(Restrictions.eq(“name”,”xxxx”));
criteria.add(Restrictions.or(Restricitons.eq(),Restrictions.list()…..))
我们使用Criteria可以更加面向对象去操作,它非常适合进行多条件组合查询。
Hibernate持久化类与主键生成策略:
什么是持久化类?
Persistent Object (PO)
PO=POJO+hbm映射配置
对于hibernate中的PO编写规则:
1. 必须提供一个无参数的public构造方法
2. 所有属性要private ,对外提供public 的get/set方法
3. 在PO类必须提供一个标识属性,让它与数据库中的主键对应,我们管这个属性叫OID
4. PO类中的属性尽量使用基本数据类型的包装类.
Int-Integer double--Double float-Float
5. PO类它不能使用final修饰符
OID作用:
OID指的是与数据库中表的主键对应的属性。
Hibernate框架它是通过OID来区分不同的PO对象,如果在内存中有两个相同的OID对象,那么hibernate认为它们是同一个对象。
为什么PO类属性它要使用包装类型?
使用基本数据类型是没有办法去描述不存在概念,如果使用包装类型,它就是一个对象,对于对象它的默认值是null.
PO类不可以使用final修饰?(hibernate中的get/load方法的区别)
Get/load方法它们都是根据id去查询对象。
get直接得到了一个持久化类型对象,它就是立即查询操作
load它得到的是持久化类开的代理类型对象(子类对象)。它采用了一种延迟策略来查询数据。
get方法在查询时,如果不存在返回null
load方法在查询时,如果不存在,会产生异常 ObjectNotFoundException.
//持久化类
@Test
public void test1() {
Session session = HibernateUtils.openSession();
// Customer customer = session.get(Customer.class, 2);
// System.out.println(customer.getClass());//com.itheima.domain.Customer
// Customer customer = session.load(Customer.class, 2);
// System.out.println(customer.getClass());//com.itheima.domain.Customer_$$_jvstbeb_0
// Customer customer = session.get(Customer.class, 200);
// System.out.println(customer);//null
Customer customer = session.load(Customer.class, 200);
System.out.println(customer);// 异常
session.close();
}
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持久化对象状态
持久化对象三种状态
有三种:
1. 瞬时态:也叫做临时态或自由态,它一般指我们new出来的对象,它不存在OID,与hibernate session无关联,在数据库中也无记录。它使用完成后,会被jvm直接回收掉,它只是用于信息携带。
简单说:无OID 与数据库中的信息无关联,不在session管理范围内。
2. 持久态:在hibernate session管理范围内,它具有持久化标识OID它的特点,在事务未提交前一直是持久态,当它发生改变时,hibernate是可以检测到的。
简单说:有OID 由session管理,在数据库中有可能有,也有可有没有。
3. 托管态:也叫做游离态或离线态,它是指持久态对象失去了与session的关联,托管态对象它存在OID,在数据库中有可能存在,也有可能不存在。
对于托管态对象,它发生改变时hibernet不能检测到。
//持久化对象的三种状态
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer = new Customer();// 瞬时态
customer.setName("大司马");
customer.setAddress("wuhu");
customer.setSex("nan");
session.save(customer);// 持久态
session.getTransaction().commit();
session.close();
System.out.println(customer);// 托管态
}
持久化类三种状态切换
判断持久化类对象三种状态:
1. 是否有OID
2. 判断是否与session关联
1. 瞬时态(new 出来的)
瞬时------持久 save saveOrUpdate
瞬时-----脱管(游离) 手动设置oid
2. .持久态 它是由session管理
持久-------瞬时 delete() 被删除后持久化对象不在建议使用
持久-----脱管 注意:session它的缓存就是所说的一级缓存
evict(清除一级缓存 中指定的一个对象)
clear(清空一级缓存)
close(关闭,清空一级缓存)
3. .脱管态 (它是无法直接获取)
脱管-----瞬时 直接将oid删除
脱管----持久 update saveOrUpdate lock(过时)
Hibernate一级缓存
Hibernate的一级缓存就是指session缓存。
private transient ActionQueue;
private transient StatefulPersistenceContext persistentContext;
actionQueue它是一个行列队列,它主要记录crud操作的相关信息
persistenceContext它是持久化上下文,它其实是真正缓存。
在session中定义了一系列的集合来存储数据,它们构成session缓存。
只要session没有关闭,它就会一直存在。
当我们通过hibernate中的session提供的一些API例如 save get update等进行操作时,就会将持久化对象保存到session中,当下一次在去查询缓存中具有的对象(OID值来判断),
就不会去从数据库查询,而是直接从缓存中获取。
Hibernate的一级缓存存在的目的就是为了减少对数据库访问。
在hibernate中还有一个二级缓存,它是SessionFactory级别缓存。
//一级缓存的存在
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 1);
Customer cc = session.get(Customer.class, 1);
session.getTransaction().commit();
session.close();
}
持久化对象具有自动更新数据库能力
//持久化具有自动更新数据库的能力
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 1);
c.setName("TOM");
session.getTransaction().commit();
session.close();
}
为什么持久化对象具有自动更新数据库能力?
当事务提交,session关闭,向数据库发送请求是,会判断一级缓存中的数据是否与快照区一直,如果不一样,就会发送update语句
一级缓存常用API
一级缓存特点:
1. 当我们通过session的save,update saveOrupdate进行操作时,如果一级缓存中没有对象,会将这些对象从数据库中查询到,存储到一级缓存。
2. 当我们通过session的load,get,Query的list等方法进行操作时,会先判断一级缓存中是否存在,如果没有才会从数据库获取,并且将查询的数据存储到一级缓存中。
3. 当调用session的close方法时,session缓存清空。
clear 清空一级缓存.
evict 清空一级缓存中指定的一个对象。
refresh重新查询数据库,用数据库中信息来更新一级缓存与快照
public void test5(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();//存储数据到一级缓存
session.clear();//清空一级缓存
Customer c = session.get(Customer.class, 1);//先从一级缓存中获取,如果获取不到 则从数据库中查询
session.evict(c);//从一级缓存中删除某个对象
Customer cc = session.get(Customer.class, 1);
cc.setName("kkkkkk");
session.refresh(cc);//重新查询数据库 用数据库中的信息来更新我们的一级缓存 和快照
session.getTransaction().commit();
session.close();
}
Hibernate常用API-Session补充
update
udpate操作它主要是针对于脱管对象,持久对象具有自动更新能力。
问题1:如果我们直接操作的对象是一个脱管对象,执行update会出现什么情况?
Update操作时,如果对象是一个脱管对象,可以操作,它会将脱管对象转换成持久对象在操作
如果在session中出现相同的oid两个对象,会产生异常
问题2脱管对象的oid如果在数据表中不存在,会报异常?
所以:在操作中,建议我们通过持久化对象来直接修改其操作。
saveOrUpdate
如果对象是一个瞬时对象 --------执行save操作
如果对象是一个脱管对象---------执行update
如果是一个持久对象-------直接返回
delete
删除一个脱管对象,与session关联,在删除
注意:如果执行delete操作,先删除一级缓存,在删除数据库中的数据。
Hibernate关联映射--数据对象三种关系
Hibernate框架基于ORM设计思想,它将关系型数据库中的表与我们java中的类进行映射,一个对象就对应着表中的一条记录,而表中的字段对应着类中的属性。
数据库中表与表之间存在着三种关系,也就是系统设计中的三种实体关系
一对一
原则有两种:
1. 唯一外键对应:在任意一方添加外键来描述对应关系
2. 主键对应:一方的主键作为另一方的主键
比如说员工跟档案,一个员工对应一份档案
此时可以分别在两个表上加上外键指向对方的主键,也可以直接使用主键对应
Class Employee{
Private Archives archives;
}
Class Archives{
Private Employee employee;
}
一对多(多对一)
客户与订单之间一对多关系(多对一)
建表原则:在多的一方添加外键来描述关联关系
在客户表加上一个外键指向订单的主键来描述关系
Class Customer{
Private Set<Order> orders;
}
Class Order{
Private Customer c;
}
多对多
例如学生与老师
建表原则:通过一张中间表来描述其对应关系
中间表至少需要两个字段作为外键来分别指向多对多双方的主键
Class Student{
Set<Teacher> ts;
}
Class Teacher{
Set<Student> ss;
}
Hibernate关联映射--一对多(多对一);
我们以客户(Customer)与订单(Order)为例
实体类创建
订单
public class Order {
private Integer id;
private Double money;
private String receiverInfo;
private Customer c;
客户
public class Customer {
private Integer id;
private String name;
private Set<Order> order = new HashSet<Order>();
Hbm映射文件编写
Order.hbm.xml
<hibernate-mapping>
<class name="com.itheima.one2Many.Order" table="t_order" catalog="hibernateTest">
<id name="id" column="c_id">
<generator class="identity"></generator>
</id>
<property name="money" column="c_money" />
<property name="receiverInfo" column="c_receiverInfo" length="50" />
<many-to-one name="c" class="com.itheima.one2Many.Customer"
column="c_customer_id" cascade="save-update"></many-to-one>
</class>
</hibernate-mapping>
Customer.hbm.xml
<hibernate-mapping>
<class name="com.itheima.one2Many.Customer" table="t_customer"
catalog="hibernateTest">
<id name="id" column="c_id">
<generator class="identity"></generator>
</id>
<property name="name" column="c_name" length="20" />
<set name="order" cascade="save-update" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
</class>
</hibernate-mapping>
测试保存
/**
* 双向关联
*
* @zhoujiaxuan
*/
@Test
public void test() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
o1.setC(c);
o2.setC(c);
c.getOrder().add(o1);
c.getOrder().add(o2);
session.save(c);
session.save(o1);
session.save(o2);
session.getTransaction().commit();
session.close();
}
测试单向关联保存
***org.hibernate.TransientObjectException:
object references an unsaved transient instance - save the transient instance before flushing:
cn.itheima.oneToMany.Customer …………..
这个异常代表提一个持久化对象关联了一个瞬时对象。
我们可以使用级联操作来解决上述的问题.
我们现在要做的是保存订单时保存客户,需要在订单的hbm配置文件中修改
设置cascade=save-update 那么在保存订单时就可以自动将客户保存。
<many-to-one name="c" class="com.itheima.one2Many.Customer"
column="c_customer_id" cascade="save-update"></many-to-one>
如果我们要完成保存客户时,保存订单
<set name="order" cascade="save-update" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
/**
* 级联单项保存(保存订单自动保存客户)
*
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
o1.setC(c);
o2.setC(c);
session.save(o1);
session.save(o2);
session.getTransaction().commit();
session.close();
}
/**
* 级联保存(保存客户自动保存订单)
*
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
c.getOrder().add(o1);
c.getOrder().add(o2);
session.save(c);
session.getTransaction().commit();
session.close();
}
双向关联维护
我们在开发中要配置双向关联配置。---------可以通过任意一方来操作对方
在操作代码,尽量来要进行单向关联。------可以尽量资源浪费。
在双向关联中,会存在多余的update语句。
我们可以使用inverse属性来设置,双向关联时由哪一方来维护表与表之间的关系。
<set name="order" cascade="save-update" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
Inverse它的值如果为true代表,由对方来维护外键。
Inverse它的值如果为false代表,由本方来维护外键。
关于inverse的取值:
外键在哪一个表中,我们就让哪一方来维护外键。
对象导航
当一个订单关联一个客户,而这个客户关联了另外两个订单,
这时当我们保存其中的某一个客户或者订单时,数据库会以谁为导航来插入数据
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("周嘉煊");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("南京");
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("北京");
Order o3 = new Order();
o3.setMoney(3000d);
o3.setReceiverInfo("上海");
// o1.setC(c);
c.getOrder().add(o2);
c.getOrder().add(o3);
// session.save(o1);//插入4条记录
// session.save(c);//插入3条记录
// session.save(o2);//插入1条记录
session.getTransaction().commit();
session.close();
}
级联删除
我们在删除客户时,也要删除订单,如果没有做级联,那么这个操作是不允许。为了维护数据完整性
想要完成操作:我们可以在客户中添加cascade=”delete”;
<set name="order" cascade="delete" inverse="true">
<key column="c_customer_id"></key>
<one-to-many class="com.itheima.one2Many.Order" />
</set>
delete-orphan用法:
得到客户的订单集合,然后利用集合去删除客户的某一个订单.
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注解开发
在hibernate中使用注解开发,可以帮助我们简化hbm文件配置
PO类注解配置:
@Entity 声明一个实体
@Table来描述类与表对应
@Entity
@Table(name="t_customer",catalog="hibernateTest")
public class Customer {
@Id来声明一个主键
@GenerateValue 用它来声明一个主键生成策略
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
默认情况下相当于native
可以选择的主键生成策略 AUTO IDENTITY SEQUENCE
@Column来定义列
@Column(name="c_name",length=30)
private String name;
注意:对于PO类中所有属性,如果你不写注解,默认情况下也会在表中生成对应的列。
列的名称就是属性的名称
@Temporal来声明日期类型
@Temporal(TemporalType.TIMESTAMP)
private Date publicationDate;
可以选择
TemporalType.DATA 只有年月日
TemporalType.TIME 只有小时分钟秒
TemporalType.TIMESTAMP 有年月日小时分钟秒
我们最终需要在hibernate.cfg.xml文件中将我们类中的注解配置引用生效
<mapping class="com.itheima.domain.Book"/>
问题:1.如果我们主键生成策略想使用UUID类型?
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
问题2:如果设定类的属性不在表中映射?
@Transient
private String msString;
对于我们以上讲解的关于属性配置的注解,我们也可以在其对应的getXxx方法去使用
一对多(多对一):
@OneToMany
@ManyToOne
以Customer与Order为例
Customer:
@Entity
@Table(name="t_customer",catalog="hibernateTest")
@NamedQuery(name="myHql",query="from Customer")
@Proxy(lazy=true)
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(targetEntity=Order.class,mappedBy="c")
@Cascade(CascadeType.SAVE_UPDATE)
private Set<Order> order = new HashSet<Order>();
生成对应的get/set方法
Order:
@Entity
@Table(name = "t_order", catalog = "hibernateTest")
@NamedQuery(name="findOrderByC",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)
@Cascade(CascadeType.SAVE_UPDATE)
@JoinColumn(name = "c_customer_id")
private Customer c;
生成对应的get/set方法
示例:保存客户时,保存订单
对于这个示例我们需要在Customer中配置cascade操作,save-update
第一种方式,可以使用JPA提供的注解
<many-to-one name="c" class="com.itheima.one2Many.Customer"
column="c_customer_id" cascade="save-update"></many-to-one>
第二种方式:可以使用hibernate提供的注解
@OneToMany(targetEntity = Customer.class)
@Cascade(CascadeType.SAVE_UPDATE)
测试代码:
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = new Customer();
c.setName("梅剑华");
Order o1 = new Order();
o1.setMoney(1000d);
o1.setReceiverInfo("北京");
o1.setC(c);
Order o2 = new Order();
o2.setMoney(2000d);
o2.setReceiverInfo("上海");
o2.setC(c);
c.getOrder().add(o1);
c.getOrder().add(o2);
session.save(c);
session.getTransaction().commit();
session.close();
}
订单中没有关联客户的id,为什么?
原因:我们在Customer中配置了mappedBy=”c”它代表的是外键的维护由Order方来维护,而Customer不维护,这时你在保存客户时,级联保存订单,是可以的,但是不能维护外键,所以,我们必须在代码中添加订单与客户关系。
//为了维护外键
o1.setC(c);
o2.setC(c);
//为了进行级联操作
c.getOrder().add(o1);
c.getOrder().add(o2);
//进行级联保存
session.save(c);
session.getTransaction().commit();
session.close();
扩展:关于hibernate注解@Cascade中的DELETE_ORPHAN过时
@Cascade(CascadeType.DELETE_ORPHAN)
使用下面方案来替换过时方案
@OneToMany(targetEntity=Order.class,mappedBy="c",orphanRemoval=true)
Hibernate关联映射-多对多:
我们使用注解完成多对多配置.
描述学生与老师.
使用@ManyToMany来配置多对多,只需要在一端配置中间表,另一端使用mappedBy表示放置外键维护权。
Techer:
@Entity
@Table(name="c_teacher",catalog="hibernateTest")
public class Teacher {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name ;
@ManyToMany(targetEntity=Student.class,mappedBy="teacher")
private Set<Student> student = new HashSet<Student>();
生成对应的get/set方法
Student:
@Entity
@Table(name = "t_student", catalog = "hibernateTest")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@ManyToMany(targetEntity = Teacher.class)
@JoinTable(name = "s_t", joinColumns = { @JoinColumn(name = "c_student_id") }, inverseJoinColumns = {
@JoinColumn(name = "c_teacher_id") })
@Cascade(CascadeType.SAVE_UPDATE)
private Set<Teacher> teacher = new HashSet<Teacher>();
生成对应的get/set方法
因为我们将外键的维护权利由Student来维护,我们演示保存学生时,将都也级联保存。
测试代码:
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Student s1 = new Student();
s1.setName("梅剑华");
Student s2 = new Student();
s2.setName("马远");
Teacher t1 = new Teacher();
t1.setName("张玮");
Teacher t2 = new Teacher();
t2.setName("马坤坤");
//学生关联老师
s1.getTeacher().add(t1);
s1.getTeacher().add(t2);
s2.getTeacher().add(t1);
s2.getTeacher().add(t2);
session.save(s1);
session.save(s2);
session.getTransaction().commit();
session.close();
}
我们在Student类中配置了级联
@Cascade(CascadeType.SAVE_UPDATE)
级联删除操作测试,删除一个全部都会删掉
Hibernate关联映射-一对一:
以人与身份证号为例
一对一操作有两种映射方式:
1. 在任意一方添加外键
2. 主键映射
外键映射:
User:
@Entity
@Table(name="c_user",catalog="hibernateTest")
public class User {
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
private String name;
@OneToOne(targetEntity=IDcard.class,mappedBy="user")//t_user表放弃对外键的维护权利
private IDcard iDcard;
生成对应的get/set方法
IDcard:
@Entity
@Table(name="c_idcard",catalog="hibernateTest")
public class IDcard {
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
private String cardNum;
@OneToOne(targetEntity=User.class)
@JoinColumn(name="c_user_id")
@Cascade(CascadeType.SAVE_UPDATE)
private User user;
生成对应的get/set方法
joinColumn指定外键列名称,当前配置外键是在t_idcard表中
测试代码:
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
User user = new User();
user.setName("梅剑华");
IDcard iDcard = new IDcard();
iDcard.setCardNum("2141414325235");
iDcard.setUser(user);
session.save(iDcard);
session.getTransaction().commit();
session.close();
}
主键映射(了解):
以Husband与Wife为例
Husband:
@Entity
@Table(name = "t_husband", catalog = "hibernateTest")
public class Husband {
@Id
@GenericGenerator(name = "myForeignKey", strategy = "foreign", parameters = {
@Parameter(name = "property", value = "wife") })
@GeneratedValue(generator="myForeignKey")
private String id;
private String name;
@OneToOne(mappedBy="husband")
@PrimaryKeyJoinColumn
private Wife wife;
生成对应的get/set方法
Husband的主键我们设置成参考wife的主键方式
Wife:
@Entity
@Table(name="t_wife",catalog="hibernateTest")
public class Wife {
@Id
@GenericGenerator(name="myuuid",strategy="uuid")
@GeneratedValue(generator="myuuid")
private String id;
private String name ;
@OneToOne
@PrimaryKeyJoinColumn
@Cascade(CascadeType.SAVE_UPDATE)
private Husband husband;
生成对应的get/set方法
测试代码:
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Husband husband = new Husband();
husband.setName("王撕葱");
Wife wife = new Wife();
wife.setName("凤姐");
husband.setWife(wife);
wife.setHusband(husband);
session.save(wife);
session.getTransaction().commit();
session.close();
}
Hibernate检索方式概述:
对数据库操作中,最常用的是select.使用hibernate如何select操作。
分为五种:
1导航对象图检索方式,根据已加载的对象导航到其它对象
2.OID检索方式,按照对象的OID来检索对象
3.HQL检索方式,使用面向对象的HQL查询语言
4.QBC检索方式,使用QBC(Query by Criteria)API来检索对象,这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口
5.本地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查询。
基本步骤:
1. 得到Session
2. 编写HQL语句
3. 通过session.createQuery(hql)创建一个Query对象
4. 为Query对象设置条件参数
5. 执行list查询所有,它反胃的是List集合 uniqueResut()返回一个查询结果。
@SuppressWarnings("all")
public class HQLTest {
/**
* 命名检索
* 我们可以将hql语句先定义出来,在使用时通过session.getNamedQuery(hqlName);得到一个Query,在执行.
问题:hql定义在什么位置?
1.如果你有hbm配置文件,那么当前的hql操作是对哪一个实体进行操作,就在哪一个 实体的配置文件中声明。
<query name="myHql">
from Customer
</query>
2.如果是使用注解来描述PO的配置
我们直接在PO类中使用
@NamedQuery(name="myHql",query="from Customer")
* @zhoujiaxuan
*/
@Test
public void test8() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 1);
// List<Customer> list = session.getNamedQuery("myHql").list();
// System.out.println(list);
List<Order> list = session.getNamedQuery("findOrderByC").setEntity("c", c).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 投影检索
* 我们主要讲解是关于部分属性查询,可以使用投影将部分属性封装到对象中。
* 注意:我们必须在PO类中提供对应有参的构造方法,也要有无参数构造。
* @zhoujiaxuan
*/
@Test
public void test7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("select new Customer(id,name) from Customer").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 分组统计检索
*
* @zhoujiaxuan
*/
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// Query query = session.createQuery("select count(*) from Order");
// Object result = query.uniqueResult();
// System.out.println(result);
List<Order> list = session.createQuery("select sum(money) from Order group by c").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 分页检索
*
* @zhoujiaxuan
*/
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Query query = session.createQuery("from Order");
query.setFirstResult(6);
query.setMaxResults(6);
List<Order> list = query.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 条件检索
*
* @zhoujiaxuan
*/
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Order> list = session.createQuery("from Order where money>?").setParameter(0, 2000d).list();
// List<Order> list = session.createQuery("from Order where
// money>:mymoner").setParameter("mymoney", 2000d).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 排序检索
*
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Order> list = session.createQuery("from Order order by money desc").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 基本检索
*
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 准备数据
*
* @zhoujiaxuan
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer customer = new Customer();
customer.setName("梅剑华");
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setMoney(1000d + i * 100);
order.setReceiverInfo("南京");
order.setC(customer);
session.save(order);
}
session.getTransaction().commit();
session.close();
}
}
QBC
QBC(query by criteria),它是一种更加面向对象的检索方式。
QBC步骤:
1.通过Session得到一个Criteria对象 session.createCriteria()
2.设定条件 Criterion实例 它的获取可以通过Restrictions类提供静态。
Criteria的add方法用于添加查询条件
3. 调用list进行查询 criterfia.list.
@SuppressWarnings("all")
public class QBCTest {
/**
* 离线检索
*
* @zhoujiaxuan
*/
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = DetachedCriteria.forClass(Customer.class).add(Restrictions.like("name", "梅%"))
.getExecutableCriteria(session).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 统计检索
*
* @zhoujiaxuan
*/
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Order.class);
// Object result =
// criteria.setProjection(Projections.rowCount()).uniqueResult();
// System.out.println(result);
List<Object[]> list = criteria
.setProjection(
Projections.projectionList().add(Projections.sum("money")).add(Projections.groupProperty("c")))
.list();
for (Object[] objs : list) {
for (Object object : objs) {
System.out.println(object);
}
}
session.getTransaction().commit();
session.close();
}
/**
* 分页检索
*
* @zhoujiaxuan
*/
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Order.class);
criteria.setFirstResult(6);
criteria.setMaxResults(6);
List<Order> list = criteria.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 条件检索
*
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.createCriteria(Customer.class).add(Restrictions.like("name", "梅%"))
.uniqueResult();
// System.out.println(c);
List<Order> list = session.createCriteria(Order.class)
.add(Restrictions.and(Restrictions.gt("money", 1500d), Restrictions.eq("c", c))).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 排序检索
* 注意在criteri.addOrder()方法的参数使用的Order是hibernate中的对象
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// List<Customer> list =
// session.createCriteria(Order.class).addOrder(org.hibernate.criterion.Order.desc("money")).list();
List<Customer> list = session.createCriteria(Order.class).addOrder(org.hibernate.criterion.Order.asc("money"))
.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
/**
* 基本检索
*
* @zhoujiaxuan
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createCriteria(Customer.class).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
}
本地SQL:
@SuppressWarnings("all")
public class SQLTest {
/**
* 本地检索
*
* @zhoujiaxuan
*/
@Test
public void test6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
SQLQuery sqlQuery = session.createSQLQuery("select * from t_customer");
sqlQuery.addEntity(Customer.class);
List<Customer> list = sqlQuery.list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
}
本地sql也支持命名查询。
可以将sql语句定义在hbm文件中,也可以使用注解。
<sql-query name="sql">
select * from t_ustomer
</sql-query>
本地命名sql注解定义
@NamedNativeQuery(name="sql",query="select * from t_ustomer")
如果执行这个命名的sql会产生异常
出现问题的原因:是hibernate不知道执行select * from t_customer后如果将结果封装。
SQL多表操作:
1.交叉连接 CROSS JOIN 会产生迪卡尔积
SELECT * FROM t_customer CROSS JOIN t_order;
2.内连接 INNER JOIN ON
SELECT * FROM t_customer AS c INNER JOIN t_order AS o ON c.id=o.c_customer_id;
使用内连接它只能将有关联的数据得到。
隐式内连接 使用 "逗号"将表分开,使用WHERE来消除迪卡尔积
SELECT * FROM t_customer AS c ,t_order o WHERE c.id=o.c_customer_id;
3.外连接 左外LEFT OUTER JOIN 右外RIGHT OUTER JOIN
OUTER可以省略
SELECT * FROM t_customer c LEFT OUTER JOIN t_order o ON c.id=o.c_customer_id;
HQL多表操作
Hql多表操作分类:
1. 交叉连接
2. 内连接
a) 显示内连接
b) 隐式内连接
c) 迫切内连接
3. 外连接
左外连接
迫切左外连接
右外连接
注意:在hibernate中有迫切连接的概念,而sql中没有。
内连接
显示内连接
显示内连接使用的是inner join with
/**
* 显示内连接
* @zhoujiaxuan
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//String hql = "from Order o inner join o.c";
String hql = "from Customer c inner join c.order with c.id = 1";
List<Object[]> list = session.createQuery(hql).list();
for (Object[] objects : list) {
for (Object object : objects) {
System.out.print(object + "\t");
}
System.out.println();
}
session.getTransaction().commit();
session.close();
}
隐式内连接
隐式内连接也我们在sql中操作不一样,它是通过”.”运算符来关联
/**
* 隐式内连接
* @zhoujiaxuan
*/
@Test
public void test2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//String hql = "from Order o where o.c.id=1";
String hql = "from Customer c where c.order.id < 10";
List list = session.createQuery(hql).list();
System.out.println(list);
session.getTransaction().commit();
session.close();
}
迫切内连接
迫切内连接得到的结果是直接封装到PO类中,而内连接得到的是Object[]数组,数组中封装的是PO类对象。
/**
* 迫切内连接
* @zhoujiaxuan
*/
@Test
public void test3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
String hql = "from Order o inner join fetch o.c";
//因为迫切内连接可能造成重复 所以需要进行去重
//String hql = "select distinct c from Customer c inner join fetch c.order";
List<Order> list = session.createQuery(hql).list();
for (Order order : list) {
System.out.println(order);
}
session.getTransaction().commit();
session.close();
}
外链接
/**
* 外连接
* @zhoujiaxuan
*/
@Test
public void test4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//左外链接
String hql = "from Customer c left join c.order";
//右外连接
//String hql = "from Customer c right join c.order";
List<Object[]> list = session.createQuery(hql).list();
for (Object[] objects : list) {
for (Object object : objects) {
System.out.print(object + "\t");
}
System.out.println();
}
session.getTransaction().commit();
session.close();
}
迫切外链接
/**
* 迫切外连接
* @zhoujiaxuan
*/
@Test
public void test5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
//String hql = "select distinct c from Customer c left join fetch c.order";
String hql = "select distinct c from Customer c left join fetch c.order where c.id=1";
List<Customer> list = session.createQuery(hql).list();
for (Customer customer : list) {
System.out.println(customer);
}
session.getTransaction().commit();
session.close();
}
Hibernate事务管理
事务介绍
问题:什么是事务?
事务就是逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全都失败。
问题:事务四个特性?
原子性:不可分割
一致性:事务在执行前后,要保证数据的一致。
隔离性:一个事务在执行的过程中,不应该受到其它事务的干扰。
持久性:事务一旦结束,数据持久化到数据库。
问题:不考虑事务的隔离性,会产生什么问题?
脏读:一个事务读取到另一个事务的未提交数据
不可重复读:一个事务读取到另一个事务提交的数据(主要是指update),会导致两次读取的结果不一致。
虚读(幻读): 一个事务读取到另一个事务提交的数据(主要是指insert),会导致两次读取结果不一致.
问题:对于上述问题如何解决?
我们可以通过设置隔离级别来解决.
READ_UNCOMMITED 读取未提交,它引发所有的隔离问题
READ_COMMITTED 读已提交,阻止脏读,可能发生不可重复读与虚读.
REPEATABLE_READ 重复读 阻止脏读,不可重复读 可能发生虚读
SERIALIZABLE 串行化 解决所有问题 不允许两个事务,同时操作一个目标数据。(效率低下)
ORACLE 默认的是事务隔离级别 READ_COMMITTED
MYSQL 默认的事务隔离级别 REPEATABLE_READ
Hibernate中设置事务隔离级别
hibernate.connection.isolation
它可取的值有 1 2 4 8
1代表的事务隔离级别为READ UNCOMMITTED
2代表的事务隔离级别为READ COMMITTED
4代表的事务隔离级别为 REPEATABLE READ
8代表的事务隔离级别为 SERIALIZABLE
在hibernate.cfg.xml文件中配置
<property name="hibernate.connection.isolation">4</property>
<!-- 设置事务的管理级别 -->
Hibernate中session管理
Hibernate提供了三种管理session的方式:
1. Session对象的生命周期与本地线程绑定(ThreadLocal)
2. Session对象的生命周期与JTA事务绑定(分布式事务管理)
3. Hibernate委托程序来管理Session的生命周期
我们之前所使用的是第三种 ,通过程序获取一个Session对象,使用它,最后session.close();
在实际开发中我们一般使用的是前两种:
主要介绍关于本地线程绑定Session。
步骤:
1. 需要在hibernate.cfg.xml文件配置
<property name="hibernate.current_session_context_class">thread</property>
2. 在获取session时不要在使用openSession而是使用getCurrentSession()方法。
public static Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
测试:
@Test
public void test1 (){
Session s1 = HibernateUtils.openSession();
Session s2 = HibernateUtils.openSession();
System.out.println(s1==s2);//false
Session s3 = HibernateUtils.getCurrentSession();
Session s4 = HibernateUtils.getCurrentSession();
System.out.println(s3==s4);//true
}
关于getCurrentSession使用时的注意事项:
@Test
public void test2(){
Session session = HibernateUtils.getCurrentSession();
session.beginTransaction();
Customer c = session.get(Customer.class, 3);
System.out.println(c);
session.getTransaction().commit();
//session.close();
}
使用getCurrentSession获取的与线程绑定的session对象,
在事务关闭时,session对象也会close,简单说,就不需要我们在手动close;
Hibernate优化方案
HQL优化
1.使用参数绑定
1.使用绑定参数的原因是让数据库一次解析SQL,对后续的重复请求可以使用用生成好的执行计划,这样做节省CPU时间和内存。
2.避免SQL注入
2.尽量少使用NOT
如果where子句中包含not关键字,那么执行时该字段的索引失效。
3.尽量使用where来替换having
Having在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销
4.减少对表的查询
在含有子查询的HQL中,尽量减少对表的查询,降低开销
5.使用表的别名
当在HQL语句中连接多个表时,使用别名,提高程序阅读性,并把别名前缀与每个列上,这样一来,可以减少解析时间并减少列歧义引起的语法错误。
6.实体的更新与删除
在hibernate3以后支持hql的update与delete操作
一级缓存优化
一级缓存也叫做session缓存,
在一个hibernate session有效,这级缓存的可干预性不强,大多于hibernate自动管理,但它提供清除缓存的方法,这在大批量增加(更新)操作是有效果的,例如,同时增加十万条记录,按常规进行,很可能会出现异常,这时可能需要手动清除一级缓存,session.evict以及session.clear.
检索策略(抓取策略)
延迟加载
延迟加载 是hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
load方法采用的策略延迟加载.
get方法采用的策略立即加载。
检索策略分为两种:
1. 类级别检索
2. 关联级别检索
类级别检索
类级别检索是通过session直接检索某一类对应的数据,例如
Customer c=session.load(Customer.class,1)
Session.createQuery(“from Order”)
类级别检索策略分为立即检索与延迟检索,默认是延迟检索,类级别的检索策略可以通过<class>元素的lazy属性来设置 ,默认值是true
在hbm配置文件中设置
<class name="com.itheima.one2Many.Customer" table="t_customer"
catalog="hibernateTest" lazy="true">
在类中使用注解
@Proxy(lazy=true)
如果将lazy设置为false,代表类级别检索也使用立即检索。这时load与get就一样,都是立即检索。
如果对一个延迟代理对象进行初始化?
hibernate.initialize()
关联级别检索
查询到某个对象,获得其关联的对象或属性,这种称为关联级别检索,例如
c.getOrders().size()
c.getName()
对于关联级别检索我们就要研究其检索策略(抓取策略)
检索策略(抓取策略)
抓取策略介绍
指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。
一对一 <one-to-one>
一对多(多对一) <set>下有<one-to-many> <many-to-one>
多对多 <set>下有<many-to- many>
我们主要是在<set>与<many-to-one>或<one-to-one>上设置fetch lazy
例如:查询一个客户,要关联查询它的订单
客户是一的一方,在客户中有set集合来描述其订单,在配置中我们是使用
<set>
<one-to-many>
</set>..
可以在set标签上设置两个属性 fetch lazy
Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查
Lazy 控制SQL语句何时发送
例如:在查询一个订单时,要查询客户信息
<many-to-one> 或<one-to-one>
也可以设置fetch lazy
Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查
Lazy 控制SQL语句何时发送
总结:
讲解抓取策略
在两方面设置
<set fetch=”” lazy=””>
<many-to-one fetch=”” lazy=””>
<one-to-one>
注解配置抓取策略
问题:如何使用注解来设置
在<setse>设置的fetch与lazy可以使用下面注解来描述
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
在<many-to-one>或<one-to-one>上如何设置 fetch与lazy
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
set上的fetch与lazy
set上的fetch与lazy它主要是用于设置关联的集合信息的抓取策略。
Fetch可取值有:
1. SELECT 多条简单的sql (默认值)
2. JOIN 采用迫切左外连接
3. SUBSELECT 将生成子查询的SQL
lazy可取值有:
1. TURE 延迟检索 (默认值)
2. FALSE 立即检索
3. EXTRA 加强延迟检索(及其懒惰)
第一种组合
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.TRUE)
会首先查询客户信息,当需要订单信息时,才会关联查询订单信息。
第二种组合
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.FALSE)
当查询客户信息时,就会将订单信息也查询,也就是说订单信息没有进行延迟。
第三种组合
@Fetch(FetchMode.SELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
当查询客户信息时,不会查询订单信息,当需要订单的个数时,也不会查询订单信息,
只会通过count来统计订单个数。
当我们使用size(),contains()或isEmpty()方法时不会查询订单信息。
第四种组合
如果fetch选择的是join方案,那么lazy它会失效。
生成SQl将采用的是迫切左外连接(left outer join fetch)
会立即查询。
@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.EXTRA)
第五种组合
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.TRUE)
会生成子查询,但是我们在查询订单时采用的是延迟加载。
第六种组合
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.FALSE)
会生成子查询,在查询客户信息时,就会将订单信息也查询出来
第七种组合
@Fetch(FetchMode.SUBSELECT)
@LazyCollection(LazyCollectionOption.EXTRA)
在查询订单时,只会根据情况来确定是否要订单信息,如果不需要,例如我们
程序中size操作,那么就会发出select count(*) from Order where c_customer_id=?
One的一言fetch与lazy
<set fetch lazy>它主要是设置在获取到一的一方时,如果去查询多的一方。
在<many-to-one>或<one-to-one>如果去查询对方。
对于程序 就是在多的一方如何查询一的主方信息
例如:获取到一个订单对象,要查询客户信息。
Fetch可取值:
select 默认值,代表发送一条或多条简单的select语句
join 发送一条迫切左外连接
lazy可取值
false 不采用延迟加载
proxy 默认值 是否采用延迟,需要另一方的类级别延迟策略来决定
no-proxy 不用研究
第一种组合
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
注意:Customer的类级别延迟策略
@Proxy(lazy=true)
public class Customer {
当我们执行时,会首先发送一条sql只查询订单信息,客户信息会延迟,只有真正需要客户信息时,才会发送sql来查询客户信息.
第二种组合
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
注意:Customer的类级别延迟策略
@Proxy(lazy=false)
public class Customer {
当查询订单时,就会将客户信息也查询到,原因是Customer它的类级别延迟为false,也就是立即查询。
第三种组合
@Fetch(FetchMode.SELECT)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
当查询订单时,不会对客户信息进行延迟,立即查询客户信息
第四种组合
@Fetch(FetchMode.JOIN)
@LazyToOne(LazyToOneOption.PROXY)
private Customer c;
如果fetch值为join,那么lazy失效。
会发送一条迫切左外连接来查询,也就立即查询。
批量抓取
我们在查询多个对象的关联对象时,可以采用批量抓取方式来对程序进行优化.
要想实现批量抓取:
可以在配置文件中 batch-size属性来设置
可以使用注解 @BatchSize(size=4)
可以采用批量抓取来解决N+1问题.
查询客户,查询订单
@Test
public void test1(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list();
for (Customer customer : list) {
System.out.println(customer.getOrder().size());
}
session.getTransaction().commit();
session.close();
}
可以在客户配置文件中配置batch-size,是在<set>标签上
@BatchSize(size=3)
private Set<Order> order = new HashSet<Order>();
查询订单,查询客户
@Test
public void test2(){
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Order> list = session.createQuery("from Order").list();
for (Order order : list) {
System.out.println(order.getC().getName());
}
session.getTransaction().commit();
session.close();
}
订单与客户,客户它是一个主表,订单是一个从表。
在设置批量抓取时都是在主表中设置
在配置文件中在主表的<calss>标签上设置batch-size
在注解使用中
@BatchSize(size=3)
public class Customer {
注意:无论是根据哪一方来查询别一方,在进行批量抓取时,都是在父方来设置 ,
如果是要查询子信息,那么我们是在<set>上来设置batch-size,如果是从子方来查询父方,
也是在父方设置在<class>设置batch-size.
父与子区分:
有外键的表是子(从关联方就是父(主)表
阅读全文
0 0
- 关于hibernate的见解
- 关于hibernate的一点见解
- 关于ERP的见解
- 关于CPU的见解
- 关于指针的见解
- 关于网络游戏的一点见解
- 一些关于考研的见解
- 关于cookie的一些见解
- 关于ROS的个人见解
- 关于sequence的一些见解
- 关于Maven的一些见解
- 关于指针的个人见解
- 关于并发自己的见解
- 关于”囚徒困境“的见解
- 关于AIDL的个人见解
- 个人关于BaseServlet的见解
- 关于rem的个人见解
- 关于sql索引的见解
- Qt笔记·第四篇子类化QDialog
- 工作日报
- Hadoop学习日志1-结构化数据与非结构化数据
- Curl get/post传值
- hibernate之概述,入门案例(配置文件,api)(01)
- 关于hibernate的见解
- 查找数组的相对“平衡点”
- 面向对象
- UE4 PAK热更新
- hdu1224 spfa 最大路
- java se 面向网络的编程(IP,端口,协议)
- 关于《我的前半生》观后感
- ubuntu如何利用终端单步执行程序
- Python与机器学习4