文章目录
Hibernate笔记
Hibernate的入门(Hibernate的环境搭建,Hibernate的API,Hibernate的CRUD)
什么是框架
框架:指的是软件的半成品,已经完成了部分功能
EE的三层架构
客户端层 | JavaAppplet->Html,css,JS |
---|---|
web层 | Servlet,JSP |
业务逻辑层 | JavaBean |
持久层 | JDBC |
Servlet+JSP+JavaBean+JDBC使用这套架构可以开发市面上的所有有用。但在企业中不会使用(过于底层,效率低)。企业中开发一般使用SSH(Struts+Spring+Hibernate),SSM(SpringMVC+Spring+Mybatis)
Hibernate的概述
开放源代码的对象关系映射框架,简单来说就是使用对象编程思维来操纵数据库,一个__持久层__的__ORM__框架
什么是ORM
ORM:Object Relational Mapping(对象关系映射)
传统的JDBC的开发:
UserDao{
public void save(User user){
Connection conn=...;
PreParedStatement stmt=...;
conn=...;
String sql="insert into...";
stmt=conn...;
stmt=setXXX();
stmt.executeUpate();
}
}
ORM框架开发
Java:Object
User{
String name;
String password;
}
Mysql:table
create table user(
name varchar(20) primary key,
password varchar(20) not null
)
dao层
UserDao{
public void save(User user){
session.save(user);
}
}
java对象和数据库表建立一种映射关系,从而操作对象就可以操作数据库中的表
为什么要学习Hibernate框架呢?
1,简化了数据访问层频繁的重复性代码,并且减少了内存消耗,加快了运行效率
2,简化了DAO层编码工作
3,性能非常好
4,可拓展性强
Hibernate的开发环境
Hiberante3.x
Hibernate4.x
Hibernate5.x
jar包
数据库驱动包
Hinbernate开发的必须的jar包
Hibernate引入日志记录表
建表
CREATE TABLE user(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
password VARCHAR(20) NOT NULL,
cellphone VARCHAR(20) NOT NULL
);
domain
package domain;
public class User {
private int id;
private String username;
private String password;
private String cellphone;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCellphone() {
return cellphone;
}
public void setCellphone(String cellphone) {
this.cellphone = cellphone;
}
}
创建映射
映射需要通过xml的配置文件来完成,这个配置文件可以任意命名。尽量统一命名规范(类名.hbm.xml)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--package[要映射的对象所在包](在domain包下)-->
<hibernate-mapping package="domain">
<!--一个节点一般来说对应一个映射文件-->
<!-- name[映射对象的类型],table[对象对应的表]-->
<class name="User" table="user">
<!--主键映射,属性名为id,列名也为id-->
<id name="id" column="id">
<generator class="native"/>
</id>
<!--非主键映射,属性和列名一一对应-->
<property name="username" column="username"></property>
<property name="cellphone" column="cellphone"></property>
<property name="password" column="password"></property>
</class>
</hibernate-mapping>
配置详解
- class标签的配置
- 标签用来建立类与表的映射关系
- 属性:
- name:类的全路径
- table:表名
- catalog:数据库名
- id标签的配置
- 标签用来建立类中的属性与表中的主键的对应关系
- 属性:
- name:类中属性名
- column:表中的字段名
- length:长度
- type:类型
- property标签的配置
- 用来用来建立类中普通属性与表的字段的对应关系
- 属性:
- name:表中的属性名
- column:表中的字段名
- length:长度
- type:类型
- not-null:非空约束
- unique:唯一约束
Hibernate的核心配置文件
Hibernate的核心配置文件的名称: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">
<!-- hibernate的主配置文件 -->
<hibernate-configuration>
<!-- 通常,一个session-factory节点代表一个数据库 -->
<session-factory>
<!-- 1. 数据库连接配置 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///test?characterEncoding=UTF-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">761020zy</property>
<!--
数据库方法配置, hibernate在运行的时候,会根据不同的方言生成符合当前数据库语法的sql
-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 2.1 显示hibernate在运行时候执行的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 2.2 格式化sql -->
<property name="hibernate.format_sql">true</property>
<!-- 2.3 自动建表 -->
<!-- <property name="hibernate.hbm2ddl.auto">create</property>-->
<!--3. 加载所有映射-->
<mapping resource="User.hbm.xml"/>
</session-factory>
配置详解
- 必须的配置
- 连接数据库的基本的参数
- 驱动类
- url路径
- 用户名
- 密码
- 方言
- 连接数据库的基本的参数
- 可选的配置
- 显示SQL:hibernate.show_sql
- 格式化SQL:hibernate_formate_sql
- 自动建表:hibernate.hbm2dd1.auto
- none:不使用hiberate的自动建表
- create:如果数据库中已经有表,删除原有表,如果没有表,新建表
- create_drop:如果数据库中已经有表,删除原有表,执行操作,删除这个表。如果没有表,新建一个,使用完了删除该表
- update:如果数据库中有表,使用原有表,如果没有表,创建新表(更新表结构)
- validate:如果没有表,不会创建表。只会使用数据库中原有的表(校验映射和表的结构)
- 映射文件的引入
- 引入映射文件的配置
- mapping resource
- 引入映射文件的配置
测试
package test;
import domain.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class test {
public static void main(String[] args) {
//创建对象
User user = new User();
user.setPassword("857857");
user.setCellphone("123321");
user.setUsername("张三");
//获取加载配置管理类
Configuration configuration = new Configuration();
//不给参数就默认加载hibernate.cfg.xml文件,
configuration.configure();
//创建Session工厂对象
SessionFactory factory = configuration.buildSessionFactory();
//得到Session对象
Session session = factory.openSession();
//使用Hibernate操作数据库,都要开启事务,得到事务对象
Transaction transaction = session.getTransaction();
//开启事务
transaction.begin();
//把对象添加到数据库中
// session.save(user);
session.save(user);
//提交事务
transaction.commit();
//关闭Session
session.close();
}
}
Hibernatede API
- Configuration
- 加载核心配置文件
- hibernate.properties
Configuration cfg=new Configuration(); - hibernate.cfg.xml
Configuration cfg=new Configuration().configure();
- hibernate.properties
- 加载映射文件
- 手动加载:configuration.addResource(“xxx.hbm.xml的全路径”)
- 加载核心配置文件
- SessionFactory:session工厂
- 维护了Hibernate的连接池和Hibernate的二级缓存,是线程安全的对象,一个项目创建一个对象即可,可以创建一个工具类
- Session
- Session代表的是Hibernate与数据库的连接对象。不是线程安全的。与数据库交互桥梁
- 保存方法:
- Serializable save(Object obj)
- 查询方法:
- T get(Class c,Serializable id);
- T load(Class c,Serializable id);
- get和load的区别
- get方法:
- 采用的是立即加载,执行到这行代码的时候,就会马上发送SQL语句去查询
- 查询后返回是真实对象本身
- 查询一个找不到的对象的时候,返回null
- load方法:采用的是懒加载,执行到这行代码的时候,不会发送SQL语句,当真正使用这个对象的时候才会发送SQL语句
- 查询后返回的是代理对象,利用javassist技术产生的代理
- 查询一个找不到的对象的时候,返回ObjectNotFoundException
- get方法:
- 修改方法:
- void update(Object obj)
- 直接创建对象,进行修改
- 先查询,再修改(推荐)
- void update(Object obj)
- 删除方法
- void delete(Object obj)
- 直接创建对象,删除
- 先查询再删除(推荐)
- void delete(Object obj)
- 保存或更新
- void saveOrUpdate(Object obj)
- 有主键就更新,没有就保存
接收HQL:hibernate Query Language面向对象的查询语言
createQuery(“from User”)
接收SQL:
createSQKQuery(“select * from User”)
- Transaction:事务对象
- commit();
- rollback();
Hibernate的持久化,主键生成策略以及缓存,其他API
什么是持久化类
持久化:将内存中的一个对象持久化到数据库中过程。Hibernate框架就是用来进行持久化的框架。
持久化类:一个java对象与数据库的表建立了映射关系,那么这个类在Hibernate中称为是持久化类
持久化类=java类+映射关系
持久化类的编写规则
对持久化类提供一个__无参数的构造方法__ | Hibernate底层需要使用反射生成实例 |
---|---|
对持久化类提供一个__唯一标识OID与数据库主键对应__ | Hibernate中获取,设置对象的值 |
对持久化类提供一个__唯一标识OID与数据库主键对应__ | java中通过对象的地址区分是否是同一个对象,数据库中通过主键确定是否是同一个记录,在Hibernate中通过持久化类的OID的属性区分是否是同一个对象 |
持久化类的属性尽量使用包装类(Integer,Long,Double,String而不是int,long,doule)的类型 | 因为基本数据类型默认为0,那么0就会有很多的歧义,包装类的默认值是null |
持久化类不要使用final进行修饰 | 延迟加载本身是hibernate一个优化的手段,返回的是一个代理对象(javassist)可以对没有实现接口的类产生代理–使用了非常底层字节码增强技术,需要__继承__这个类进行代理 |
主键的分类
自然主键
自然主键:主键的本身就是表中的一个字段(实体中的一个具体的属性)
创建一个人员表,人员都会有一个身份证号(唯一的不可重复的),使用了身份证号作为主键,这种主键称为自然主键
代理主键
代理主键:主键的本身不是表中必须的一个字段(不是实体中的某个具体的属性)
创建一个人员表,没有使用人员中的身份证号,用了一个与这个表不相关的字段ID,这种主键称为代理主键
在实际开发中,尽量使用代理主键
一旦自然逐渐参与到业务逻辑中,后期有可能需要修改源代码
好的程序设计满足OCP原则:对程序的扩展是开发的,对修改源码是封闭的
主键的生成策略
在实际开发中一般不允许用户手动设置主键,一般将主键交给数据库,手动编写程序进行设置,在Hibernate中为了减少程序编写,提供了很多种的主键的生成策略
increment | hibernate中提供的自动增长机制,适用short,int,long类型的主键。在单线程程序中使用。首先发送一条语句,select max(id) from table然后id+1作为下一条记录的主键 |
---|---|
identity | 适用于short,int,long类型的主键,使用的是数据库底层的自动增长机制。使用于有自动增长机制数据库,但是Oracle是没有自动增长 |
sequence | 适用short,int,long类型的主键,采用的是序列的方式(Oracle支持序列),像MySQL就不能使用sequence |
uuid | 适用于字符串类型主键。使用hibernate中的随机方式生成字符串主键 |
native | 本地策略,可以在identity和sequence之间进行自动切换 |
assigned | hibernate放弃外键的管理,需要通过手动编写程序或者用户自己设置 |
foreign | 外部的。一对一的一种关联映射的情况下使用 |
持久化类的三种状态
Hibernate是持久层框架,通过持久化类完成ORM操作。Hibernate为了更好的管理持久化类,将持久化类分成三种状态。
瞬时态
这种对象没有唯一的表示OID,没有被session管理,称为是瞬时态对象
持久态
这种对象有唯一表示OID,被session管理,称为是持久态对象,可以自动更新数据库
脱管态
这种对象有唯一表示OID,没有被session管理,称为脱管态对象
@Test
public void demo(){
Customer customer=new Customer();
customer.setName("zhangsan");
Serializable id=session.save(customer);//瞬时态对象:没有唯一表示OID,没有被session管理
session.get(Customer.class,id);//持久态对象:有唯一标识OID,被session管理
transaction.commit();
session.close();
System.out.println("name:"+customer.getName());//脱管态对象:有唯一标识OID,没有被session管理
}
持久化类的状态转换
- 瞬时态对象
- 获得
- Customer customer=new Customer();
- 状态转换
- 瞬时->持久save(Object obj),saveOrUpdate(Object obj)
- 瞬时->脱管customer.setId(1)
- 获得
- 持久态对象
- 获取
- get(),load(),find(),iterate()
- Cunstomer customer=session.get(Customer.class,1l)
- 状态转换
- 持久->瞬时delete()
- 持久->脱管close(),clear(),evict(Object,obj)
- 获取
- 脱管态对象
- 获得
- Customer customer=new Customer();
- customer.setId(1l)
- 状态转换
- 脱管->持久update(),saveOrUpdate();
- 脱管->瞬时customer.setId(null)
- 获得
什么是缓存
缓存:是一种优化的方式,将数据存入到内存中,使用的时候直接从缓存中获取,不用通过存储源
Hibernate的一级缓存
Hibernate框架中提供了优化手段:缓存,抓取策略。Hibnerante中提供了两种缓存机制:一级缓存,二级缓存。
Hibernate的一级缓存:称为是Session级别的缓存,一级缓存生命周期与session一致(一级缓存是由session中的一系列的java集合构成)。一级缓存是自带的不可卸载的。
Hibernate的二级缓存是SessionFactory级别的缓存,需要配置的缓存,可以被其他缓存技术代替,比如:redis
Hibernate的一级缓存的结构
一级缓存中特殊区域:快照区
比较缓存区和快照区的数据:一致就不更新数据,不一致就更新数据库
session.evict(Object obj)//清空obj对象
session.clear()//清空一级缓存中所有数据
事务回顾
什么是事务
事务:事务指的是逻辑上的一组操作,组成这组操作的各个逻辑三元要么全部成功,要么全部失败
事务特性
原子性:事务不可分割
一致性:事务执行的前后,数据的完整性保持一致
隔离性:一个事物执行的过程中,不应该受到其他事务的干扰
持久性:事务执行完成之后,数据就持久到数据库中
如果不考虑隔离性,引发安全性问题
- 读问题:
- 脏读:一个事务读到另一个事务未提交的数据
- 不可重复读:一个事务读到另一个事务已经提交的update数据,导致在前一个事务多次查询结果不一致
- 虚读:一个事务读到另一个事务已经提交的insert数据,导致在前一个事务多次查询结果不一致
- 写问题:
- 引发两类丢失更新
读问题的解决
- 设置事务的隔离级别
- Read uncommitted:以上读问题都会发生
- Read committed:解决脏读
- Repeatable read:解决脏读和不可重复读
- Serializable:解决所有读问题
<!-- 设置事务隔离级别-->
<property name="hibernate.connection.isolation">4</property>//1,2,4,8
Hibernate解决Service层事务管理
- 必须保证连接对象是同一个
- 向下传递:DBUtils
- 使用ThreadLocal对象
- 将这个连接绑定到当前线程中
- 在DAO的方法中,通过当前线程获取连接对象
- Hibernate框架内部已经绑定好了ThreadLocal
- 在SessionFactory中提供了一个方法getCurrentSession()
- 通过一个配置完成
Hibernate工具类
public class hibernateUtils {
public static final Configuration cfg;
public static final SessionFactory sf;
static {
cfg = new org.hibernate.cfg.Configuration().configure();
sf = cfg.buildSessionFactory();
}
public static Session openSession() {
return sf.openSession();
}
}
改写
public static Session gerCurrentSession(){
return sf.getCurrentSession();
}
配置(Session绑定当前线程)
<property name="hibernate.current_session_context_class">thread</property>
Hibernate的其他API
Query
Query接口用于接收HQL,查询多个对象
基础查询
Query query = session.createQuery("from User");
query.setFirstResult(0);
query.setMaxResults(3);
List<User> list=query.list();
for(User u:list){
System.out.println(u.getUsername());
}
条件查询
Query query = session.createQuery("from User where username like ?");
query.setParameter(0,"张%");
List<User> list=query.list();
for(User u:list){
System.out.println(u.getUsername());
}
分页查询
Query query= session.createQuery("from User");
query.setMaxResults(1);
query.setFirstResult(0);
List<User> list = query.list();
for (User u : list) {
System.out.println(u.getUsername());
}
Criteria
条件查询,更加面向对象的查询方式
基础查询
Criteria criteria=session.createCriteria(User.class);
List<User> list = criteria.list();
for (User u : list) {
System.out.println(u.getUsername());
}
条件查询
Criteria criteria=session.createCriteria(User.class);
criteria.add(Restrictions.like("username","张%"));
List<User> list = criteria.list();
for (User u : list) {
System.out.println(u.getUsername());
}
分页查询
Criteria criteria=session.createCriteria(User.class);
criteria.setFirstResult(0);
criteria.setMaxResults(1);
List<User> list = criteria.list();
for (User u : list) {
System.out.println(u.getUsername());
}
SQLQuery
接收SQL,特别复杂情况下使用
Hibernate的一对多,多对多,一对一配置
数据库表与表之间的关系
一对多关系
- 什么样关系属于一对多?
- 一个部门对于多个员工,一个员工只能属于某一个部门
- 一个客户对应多个联系人,一个联系人只能属于某一个客户
例:
客户表(1)
客户ID | 客户名称 |
---|---|
1 | 王东 |
2 | 赵洪 |
联系人(n)
联系人ID | 联系人名称 | 客户ID |
---|---|---|
1 | 凤姐 | 1 |
2 | 如花 | 1 |
3 | 李兵 | 2 |
一对多建表原则:在多的一方创建外键指向一的一方的主键
多对多关系
- 什么样关系属于多对多?
- 一个学生可以选择多门课程,一门课程也可以被多个学生选择
- 一个用户可以选择多个角色,一个角色也可以被多个用户选择
例:
学生表
sid | sname |
---|---|
1 | 张三 |
2 | 李四 |
课程表
cid | cname |
---|---|
1 | Java |
2 | C++ |
3 | UI |
学生选课表
sid | cid |
---|---|
1 | 1 |
1 | 2 |
2 | 2 |
多对多建表原则:创建一个中间表,中间表至少有两个字段分别作为外键指向多对多双方的主键
一对一关系
- 什么样关系属于一对一?
- 一个公司只有一个注册地址,一个注册地址只能被一个公司注册
公司表
- 一个公司只有一个注册地址,一个注册地址只能被一个公司注册
cid | cname |
---|---|
1 | 黑马 |
2 | 百度 |
地址表(唯一外键(保证外键唯一unique,否则是一对多了)指向主键)
aid | aname | cid |
---|---|---|
1 | 北京西三旗 | 1 |
2 | 北京西二旗 | 2 |
地址表(主键关联)
aid | aname |
---|---|
1 | 北京西三旗 |
2 | 北京西二旗 |
创建实体和映射
一对多
代码部分
例:user和address,一个user只有一个address,一个address有很多user
实体类
User(多的一方)
package domain;
public class User {
// 表中字段:user_id
private int user_id;
// 表中字段:user_name
private String user_name;
// 一个user只有一个address
// 存放一的一方数据
private Address address;
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address(一的一方)
package domain;
import java.util.LinkedHashSet;
import java.util.Set;
public class Address {
// 表中字段:city_user_id
private int city_id;
// 表中字段:city_name
private String city_name;
// 表中字段:city_description
private String city_description;
// 存放多的一方数据
private Set<User> userSet=new LinkedHashSet<>();
public int getCity_id() {
return city_id;
}
public void setCity_id(int city_id) {
this.city_id = city_id;
}
public String getCity_name() {
return city_name;
}
public void setCity_name(String city_name) {
this.city_name = city_name;
}
public String getCity_description() {
return city_description;
}
public void setCity_description(String city_description) {
this.city_description = city_description;
}
public Set<User> getUserSet() {
return userSet;
}
public void setUserSet(Set<User> userSet) {
this.userSet = userSet;
}
}
User的配置文件(多的一方)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="domain.User" table="user">
<!--建立OID与主键映射-->
<id name="user_id" column="user_id">
<generator class="native"/>
</id>
<!--建立普通属性与数据表字段映射-->
<property name="user_name" column="user_name"/>
<!--配置多对一的关系:放置的是一的一方的对象-->
<!--name:一的一方的对象的属性名称-->
<!--class:一的一方的类的全路径-->
<!--column:多的一方的表的外键名称-->
<many-to-one name="address" class="domain.Address" column="city_id"/>
</class>
</hibernate-mapping>
Address的配置文件(一的一方)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="domain.Address" table="address">
<!--建立OID与主键映射-->
<id name="city_id" column="city_user_id">
<generator class="native"/>
</id>
<!--建立普通属性和数据库表字段映射-->
<property name="city_name" column="city_name"/>
<property name="city_description" column="city_description"/>
<!--配置多对一的映射:放置的多的一方的集合-->
<!--name:多的一方的对象集合的属性名称-->
<!--column:多的一方的外键名称-->
<!--class:多的一方的类的全路径-->
<set name="userSet" table="user">
<key column="city_id"/>
<one-to-many class="domain.User"/>
</set>
</class>
</hibernate-mapping>
核心配置文件
<?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的主配置文件 -->
<hibernate-configuration>
<!-- 通常,一个session-factory节点代表一个数据库 -->
<session-factory>
<!--数据库连接配置 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///test?characterEncoding=UTF-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">761020zy</property>
<!--
数据库方法配置, hibernate在运行的时候,会根据不同的方言生成符合当前数据库语法的sql
-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!--其他相关配置 -->
<!--显示hibernate在运行时候执行的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!--格式化sql -->
<property name="hibernate.format_sql">true</property>
<!--自动建表 -->
<property name="hibernate.hbm2ddl.auto">create</property>
<!--Session绑定当前线程-->
<property name="hibernate.current_session_context_class">thread</property>
<!--设置事务隔离级别-->
<property name="hibernate.connection.isolation">4</property>
<!--加载所有映射-->
<mapping resource="User.hbm.xml"/>
<mapping resource="Address.hbm.xml"/>
</session-factory>
</hibernate-configuration>
测试部分
public class test {
public static void main(String[] args) {
//获取加载配置管理类
Configuration configuration = new Configuration();
//不给参数就默认加载hibernate.cfg.xml文件,
configuration.configure();
//创建Session工厂对象
SessionFactory factory = configuration.buildSessionFactory();
//得到Session对象
Session session = factory.getCurrentSession();
//使用Hibernate操作数据库,都要开启事务,得到事务对象
Transaction transaction = session.getTransaction();
//开启事务
transaction.begin();
//把对象添加到数据库中
User user01 = new User();
user01.setUser_name("朱总");
User user02 = new User();
user02.setUser_name("郭总");
User user03 = new User();
user03.setUser_name("聪老板");
Address address01 = new Address();
address01.setCity_name("四川");
address01.setCity_description("四川人民爱吃辣");
Address address02 = new Address();
address02.setCity_name("广东");
address02.setCity_description("马化腾牛逼");
address01.getUserSet().add(user01);
address01.getUserSet().add(user02);
address02.getUserSet().add(user03);
user01.setAddress(address01);
user02.setAddress(address01);
user03.setAddress(address02);
session.save(address02);
session.save(address01);
session.save(user01);
session.save(user02);
session.save(user03);
//提交事务
transaction.commit();
}
}
一对多关系只保存一边是否可以?
测试部分
//保存数据
session.save(address02);
session.save(address01);
不行,会报错
org.hibernate.TransientObjectException
原因:持久态对象关联了一个瞬时态对象
解决:级联操作
- 什么是级联
- 级联指的是,操作一个对象的时候,是否会同时操作其关联的对象
- 级联是有方向性
- 操作一的一方的时候,是否操作到多的一方
- 操作多的一方的时候,是否操作到一的一方
- 级联保存或更新
保存地址级联用户(只save地址,也能保存用户)
<set name="userSet" table="user" cascade="save-update">
保存用户级联地址(只save用户,也能保存地址)
<many-to-one name="address" class="domain.Address" column="city_id" cascade="save-update"/>
- 级联删除
- 删除一方的时候,同时将另一方的数据也一并删除
没有设置级联删除,默认情况:修改了user的外键为null,再删除address
- 删除一方的时候,同时将另一方的数据也一并删除
Address address=(Address) session.get(Address.class,2);
session.delete(address);
设置了级联删除:删除address,同时删除user记录
删除address级联删除user,删除的主体是address,需要在address.hbm.xml中配置
<set name="userSet" table="user" cascade="delete">
测试对象的导航
//把User对象添加到Address对象的set集合中
address01.getUserSet().add(user01);
address01.getUserSet().add(user02);
user03.setAddress(address01);
//保存数据
//双方都设置了cascade
// session.save(address01);//三条insert数据
// session.save(user03);//四条insert数据
// session.save(user02);//一条insert数据
//提交事务
transaction.commit();
一对多设置了双向关联产生多余的SQL语句
问题:将一位user的address进行更改
双向维护关系产多余的SQL(两条update语句)
User user = (User) session.get(User.class, 1);
Address address = (Address) session.get(Address.class, 2);
user.setAddress(address);
address.getUserSet().add(user);
解决多余的SQL语句
- 单项维护
- 使一方放弃外键维护权(一条update语句)
- 一的一方放弃," inverse=“true”
- 一对多的关联查询的修改的时候也会用到
区分cascade和inverse
//set配置了cascade="save-update" inverse="true"
session.save(address)//address会插入到数据库,user也会插入到数据库,但是外键为null
多对多关联映射
特权和角色:一个角色有很多的特权,权利可以被很多的角色拥有,例如:总经理可以开除员工,和巡视公司,保安可以巡视公司,和看门。
创建表
CREATE TABLE role (
id int PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
description VARCHAR(255)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE privilege (
id int PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
description VARCHAR(255)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE role_privilege (
role_id int,
privilege_id int,
PRIMARY KEY (role_id, privilege_id),
CONSTRAINT role_id_FK1 FOREIGN KEY (role_id) REFERENCES role (id),
CONSTRAINT privilege_id_FK FOREIGN KEY (privilege_id) REFERENCES privilege (id)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
特权类
package domain;
import java.util.HashSet;
import java.util.Set;
public class Privilege {
private int privilege_id;
private String privilege_name;
private String privilege_description;
// 一个特权可以被多个角色拥有
private Set<Role> roles = new HashSet<Role>();
public int getPrivilege_id() {
return privilege_id;
}
public void setPrivilege_id(int privilege_id) {
this.privilege_id = privilege_id;
}
public String getPrivilege_name() {
return privilege_name;
}
public void setPrivilege_name(String privilege_name) {
this.privilege_name = privilege_name;
}
public String getPrivilege_description() {
return privilege_description;
}
public void setPrivilege_description(String privilege_description) {
this.privilege_description = privilege_description;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
角色类
package domain;
import java.util.HashSet;
import java.util.Set;
public class Role {
private int role_id;
private String role_name;
private String role_description;
// 表示一个角色拥有的特权
private Set<Privilege> privileges = new HashSet<Privilege>();
public int getRole_id() {
return role_id;
}
public void setRole_id(int role_id) {
this.role_id = role_id;
}
public String getRole_name() {
return role_name;
}
public void setRole_name(String role_name) {
this.role_name = role_name;
}
public String getRole_description() {
return role_description;
}
public void setRole_description(String role_description) {
this.role_description = role_description;
}
public Set<Privilege> getPrivileges() {
return privileges;
}
public void setPrivileges(Set<Privilege> privileges) {
this.privileges = privileges;
}
}
Role的映射配置
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="domain.Role" table="role">
<!-- OID和主键的映射-->
<id name="role_id" column="id">
<generator class="native"/>
</id>
<!-- 建立普通属性与字段的映射-->
<property name="role_name" column="name"/>
<property name="role_description" column="description"/>
<!--建立角色和特权的多对多的映射关系-->
<!-- set-->
<!-- * name:对方的集合的属性名称-->
<!-- * table:多对多的关系需要使用中间表,放的是中间表的名称-->
<!-- key-->
<!-- * column:当前的对象对应中间表的外键的名称-->
<!-- many-to-many-->
<!-- * class:对方类的全路径-->
<!-- * column:对方的对象在中间表中的外键的名称-->
<set name="privileges" table="role_privilege">
<key column="role_id"/>
<many-to-many class="domain.Privilege" column="privilege_id"/>
</set>
</class>
</hibernate-mapping>
Privilege的映射配置
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="domain.Privilege" table="privilege">
<!-- OID和主键的映射-->
<id name="privilege_id" column="id">
<generator class="native"/>
</id>
<!-- 普通属性和字段的映射-->
<property name="privilege_name" column="name"/>
<property name="privilege_description" column="description"/>
<set name="roles" table="role_privilege" inverse="true">
<key column="privilege_id"/>
<many-to-many class="domain.Role" column="role_id"/>
</set>
</class>
</hibernate-mapping>
hibernate的核心配置文件
<?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的主配置文件 -->
<hibernate-configuration>
<!-- 通常,一个session-factory节点代表一个数据库 -->
<session-factory>
<!--数据库连接配置 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///test?characterEncoding=UTF-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">761020zy</property>
<!--
数据库方法配置, hibernate在运行的时候,会根据不同的方言生成符合当前数据库语法的sql
-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!--其他相关配置 -->
<!--显示hibernate在运行时候执行的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!--格式化sql -->
<property name="hibernate.format_sql">true</property>
<!--自动建表 -->
<property name="hibernate.hbm2ddl.auto">create</property>
<!--Session绑定当前线程-->
<property name="hibernate.current_session_context_class">thread</property>
<!--设置事务隔离级别-->
<property name="hibernate.connection.isolation">4</property>
<!--加载所有映射-->
<!-- <mapping resource="User.hbm.xml"/>-->
<!-- <mapping resource="Address.hbm.xml"/>-->
<mapping resource="Privilege.hbm.xml"/>
<mapping resource="Role.hbm.xml"/>
</session-factory>
</hibernate-configuration>
测试代码
package test;
import Utils.hibernateUtils;
import domain.Privilege;
import domain.Role;
import org.hibernate.*;
public class test {
public static void main(String[] args) {
Session session = hibernateUtils.gerCurrentSession();
Transaction tx = session.beginTransaction();
// 两个角色
Role role01 = new Role();
Role role02 = new Role();
role01.setRole_name("角色1");
role01.setRole_description("这是角色1");
role02.setRole_name("角色2");
role02.setRole_description("这是角色2");
// 三个特权
Privilege privilege01 = new Privilege();
Privilege privilege02 = new Privilege();
Privilege privilege03 = new Privilege();
privilege01.setPrivilege_description("这是特权1");
privilege01.setPrivilege_name("特权1");
privilege02.setPrivilege_description("这是特权2");
privilege02.setPrivilege_name("特权2");
privilege03.setPrivilege_description("这是特权3");
privilege03.setPrivilege_name("特权3");
//多对多建立了双向的关系必须有一方放弃对主键的维护,一般是被动方(特权)放弃外键维护(inverse="true")
role01.getPrivileges().add(privilege01);
role01.getPrivileges().add(privilege02);
role01.getPrivileges().add(privilege03);
role02.getPrivileges().add(privilege01);
role02.getPrivileges().add(privilege02);
privilege01.getRoles().add(role01);
privilege01.getRoles().add(role02);
privilege02.getRoles().add(role01);
privilege02.getRoles().add(role02);
privilege03.getRoles().add(role01);
session.save(role01);
session.save(role02);
session.save(privilege01);
session.save(privilege02);
session.save(privilege03);
tx.commit();
}
}
多对多的单向级联保存
role01.getPrivileges().add(privilege01);
role01.getPrivileges().add(privilege02);
role01.getPrivileges().add(privilege03);
role02.getPrivileges().add(privilege01);
role02.getPrivileges().add(privilege02);
session.save(role01);
session.save(role02);
配置映射
<set name="privileges" table="role_privilege" cascade="save-update">
多对多的级联删除
Role role = (Role) session.get(Role.class, 1);
session.delete(role);
配置映射
<set name="privileges" table="role_privilege" cascade="delete">
多对多的其他操作
给角色选择特权
Role role = (Role) session.get(Role.class, 2);
Privilege privilege = (Privilege) session.get(Privilege.class, 1);
role.getPrivileges().add(privilege);
给角色改选特权
Role role = (Role) session.get(Role.class, 1);
Privilege privilege1 = (Privilege) session.get(Privilege.class, 1);
Privilege privilege2 = (Privilege) session.get(Privilege.class, 2);
role.getPrivileges().remove(privilege2);
role.getPrivileges().add(privilege1);
给角色删除特权
Role role = (Role) session.get(Role.class, 1);
Privilege privilege1 = (Privilege) session.get(Privilege.class, 1);
Privilege privilege2 = (Privilege) session.get(Privilege.class, 2);
role.getPrivileges().remove(privilege2);
Hibernate的查询方式,抓取策略
在Hibernate中提供了很多种的查询方式。Hibernate共提供了五种查询方式
Hibernate的查询方式:OID查询
OID检索:Hibernate根据对象的OID(主键)进行检索
使用get方法
User user = (User) session.get(User.class, 1);
使用load方法
User user = (User) session.load(User.class, 1);
Hibernate的查询方式:对象导航查询
对象导航检索:Hibernate根据一个已经查询到的对象,获得其关联的对象的一种查询方式
`` User user = (User) session.get(User.class, 1);
Address address = user.getAddress();
Hibernate的查询方式:HQL检索
HQL查询:Hibernate Query Language:Hibernata的查询语言,是一种面向对象的方式的查询语言,语法类似SQL。通过sesssion.createQuery(),用于接收一个 HQL进行查询方式
HQL的简单查询
Query query = session.createQuery("from User");//from 类名
List<User> list = query.list();
for (User user : list) {
System.out.println(user.toString());
}
HQL的排序查询
默认升序排序
List<User> list = session.createQuery("from User order by user_id").list();
for (User user : list) {
System.out.println(user.toString());
}
降序排序 升序asc,降序desc
List<User> list = session.createQuery("from User order by user_id desc").list();
for (User user : list) {
System.out.println(user.toString());
}
HQL的条件查询
条件的查询
1.按位置绑定
//一个条件
Query query = session.createQuery("from User where user_name like ? ");
query.setParameter(0, "赵%");
List<User> list = query.list();
//多个条件
Query query = session.createQuery("from User where user_name like ? and user_id=?");
query.setParameter(0, "赵%");
query.setParameter(1, 3);
List<User> list = query.list();
2.按名称绑定
Query query = session.createQuery("from User where user_name=:aaa and user_id=:bbb");
query.setParameter("aaa", "赵小老弟");
query.setParameter("bbb", 3);
List<User> list=query.list();
for (User user : list) {
System.out.println(user.toString());
}
HQL的投影查询
投影查询:查询对象的某个或某些属性
单个属性
List<Object> list=session.createQuery("select u.user_name from User u").list();
for(Object obj:list){
System.out.println(obj);
}
多个属性
List<Object[]> list=session.createQuery("select u.user_id,u.user_name from User u").list();
for(Object[] obj:list){
System.out.println(Arrays.toString(obj));
}
多个属性,并且封装到对象中
List<User> list = session.createQuery("select new User(user_id,user_name) from User").list();
for (User user : list) {
System.out.println(user.getUser_id() + ":" + user.getUser_name());
}
HQL的分页查询
Query query = session.createQuery("from User ");
query.setFirstResult(0);
query.setMaxResults(3);
List<User> list = query.list();
for (User user : list) {
System.out.println(user.toString());
}
HQL的分组统计查询
聚合函数的使用:count(),max(),min(),avg(),sum()
Query query = session.createQuery("select count(*) from User ");
List<Object> list = query.list();
System.out.println(list.get(0));
分组统计
Query query = session.createQuery("select user_name,count(*) from User group by user_name having count(*)=1");
List<Object[]> list = query.list();
for (Object[] obj : list) {
System.out.println(Arrays.toString(obj));
}
HQL的多表查询
- 连接查询
- 交叉连接:笛卡尔积
- 内连接:inner join
- 隐式内连接
select * from a,b where a.id=b.id - 显示内连接
select * from a inner join b where a.id=b.id
- 隐式内连接
- 外连接
- 左外连接:left outer join
select * from a left outer join b on a.id=b.id - 右外连接:right outer join
select * from a right outer join b on a.id=b.id
- 左外连接:left outer join
- 子查询
HQL的多表查询
- 连接查询
- 交叉连接
- 内连接
- 显示内连接
- 隐式内连接
- 迫切内连接
- 外连接
- 左外连接
- 右外连接
- 迫切左外连接
内连接
List<Object[]> list = session.createQuery("from Address a inner join a.userSet").list();
for (Object[] objects: list) {
System.out.println(Arrays.toString(objects));
}
迫切内连接
//通知hibernate,将另一个对象的数据封装到该对象中
List<Address> list=session.createQuery("select distinct a from Address a inner join fetch a.userSet").list();
for(Address address:list){
System.out.println(address);
}
可能会报错
java.lang.StackOverflowError(堆栈溢出)
解决方案:
取消任意一方或双方,关系表重写的toString。
Hibernate的查询方式:QBC检索
QBC查询:Query By Criteria,条件查询。是一种更加面向对象化的查询
简单查询
Criteria criteria=session.createCriteria(User.class);
List<User> list=criteria.list();
for(User user:list){
System.out.println(user);
}
排序查询
Criteria criteria = session.createCriteria(User.class);
criteria.addOrder(Order.desc("user_id"));
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
分页查询
Criteria criteria = session.createCriteria(User.class);
criteria.setFirstResult(0);
criteria.setMaxResults(3);
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
条件查询
Criteria criteria = session.createCriteria(User.class);
// = eq
// > gt
// >= ge
// < lt
// <= le
// <> ne
// like
// in
// and
// or
criteria.add(Restrictions.eq("user_name", "赵小老弟"));
criteria.add(Restrictions.like("user_id", 3));
List<User> list = criteria.list();
for (User user : list) {
System.out.println(user);
}
统计查询
// add :普通的条件,where后面的
// addOrder:排序
// setProjection:聚合函数和group by
Criteria criteria = session.createCriteria(User.class);
criteria.setProjection(Projections.rowCount());
Long num = (Long) criteria.uniqueResult();
System.out.println(num);
离线条件查询(SSH)–DetachedCriteria
使用DetachedCriteria–离线(脱离session使用)
Hibernate的查询方式:SQL检索
SQLQuery sqlQuery = session.createSQLQuery("select * from Address");
sqlQuery.addEntity(Address.class);
List<Object> list=sqlQuery.list();
for(Object obj:list){
System.out.println(obj);
}
Hibernate的抓取策略(优化)
什么是延迟加载
延迟加载:lazy(懒加载),执行到该行代码的时候,不会发送语句去查询,在真正使用这个对象的属性的时候才会发送SQL语句进行查询
延迟加载的分类
- 类级别的延迟加载
- 指的是通过load方法查询某个对象的时候,是否采用延迟。
session.load(Address.class,1);- 将lazy设置为false
- 将持久化类使用final修饰
- Hibernate.initialize(对象)
- 指的是通过load方法查询某个对象的时候,是否采用延迟。
<class name="domain.Address" table="address" lazy="true">
- 关联级别的延迟加载
- 指的是在查询到某个对象的时候,查询其关联的对象的时候,是否采用延迟加载
Address address=session.get(Address.class,1)
address.getUserSet();
- 指的是在查询到某个对象的时候,查询其关联的对象的时候,是否采用延迟加载
<set name="userSet" table="user" lazy="true">
<many-to-one name="address" class="domain.Address" column="city_id" lazy="false"/>
抓取策略往往会和关联级别的延迟加载一起使用,优化语句
抓取策略
抓取策略的概述
通过一个对象抓取到关联对象需要发送SQL语句,SQL语句如何发送,发送成什么样格式通过策略进行配置。
通过或者上通过fetch属性进行设置
fetch和这些标签上的lazy如何设置优化方发送的SQL语句
上的fetch和lazy
- fetch:抓取策略,控制SQL语句格式
- select:默认值,发送普通的select语句,查询关联对象
- join:发送一条迫切左外连接查询关联对象
- subselect:发送一条子查询查询其关联对象
- lazy:延迟加载,控制查询关联对象的时候是否采用延迟
- true:默认值,查询关联对象的时候,采用延迟加载
- false:查询关联对象的时候,不采用延迟对象
- extra:极其懒惰
在实际开发中,一般采取默认值
<set name="userSet" table="user" lazy="true" fetch="select">
Address address = (Address) session.get(Address.class, 1);//发送查询address的SQL语句
// select
// address0_.city_user_id as city1_1_0_,
// address0_.city_name as city2_1_0_,
// address0_.city_description as city3_1_0_
// from
// address address0_
// where
// address0_.city_user_id=?
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());//发送查询size的SQL语句
// select
// userset0_.city_id as city3_1_1_,
// userset0_.user_id as user1_1_,
// userset0_.user_id as user1_0_0_,
// userset0_.user_name as user2_0_0_,
// userset0_.city_id as city3_0_0_
// from
// user userset0_
// where
// userset0_.city_id=?
<set name="userSet" table="user" lazy="false" fetch="select">
Address address = (Address) session.get(Address.class, 1);//发送两条SQL语句:查询Address,查询User
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());
// select
// address0_.city_user_id as city1_1_0_,
// address0_.city_name as city2_1_0_,
// address0_.city_description as city3_1_0_
// from
// address address0_
// where
// address0_.city_user_id=?
// select
// userset0_.city_id as city3_1_1_,
// userset0_.user_id as user1_1_,
// userset0_.user_id as user1_0_0_,
// userset0_.user_name as user2_0_0_,
// userset0_.city_id as city3_0_0_
// from
// user userset0_
// where
// userset0_.city_id=?
<set name="userSet" table="user" lazy="extra" fetch="select">
Address address = (Address) session.get(Address.class, 1);//发送查询客户的SQL语句
// select
// address0_.city_user_id as city1_1_0_,
// address0_.city_name as city2_1_0_,
// address0_.city_description as city3_1_0_
// from
// address address0_
// where
// address0_.city_user_id=?
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());//发送一条select count() from...;
// select
// count(user_id)
// from
// user
// where
// city_id =?
<set name="userSet" table="user" lazy="true" fetch="join">//lazy就失效了,设置成什么都一样
Address address = (Address) session.get(Address.class, 1);//发送一条迫切左外连接查询记录
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());
<set name="userSet" table="user" lazy="subselect" fetch="join">
```java
List<Address> list = session.createQuery("from Address ").list();//发送查询所有客户的SQL
for (Address address : list) {
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());//发送一条子查询
}
上的fetch和lazy
- fetch:抓取策略,控制SQL语句格式
- select:默认值,发送普通的select语句,查询关联对象
- join:发送一条迫切左外连接
- lazy:延迟加载,控制查询关联对象的时候是否采用延迟
- proxy:默认值,proxy的具体取值,取决于另一端上的lazy的值
- false:查询关联对象,不采用延迟
- no-proxy:(不会使用)
- 批量抓取
- 什么是批量抓取
- 一批关联对象一起抓取,batch-size
<set name="userSet" table="user" batch-size="4">
<class name="domain.Address" table="address" batch-size="3">
y_user_id as city1_1_0_,
// address0_.city_name as city2_1_0_,
// address0_.city_description as city3_1_0_
// from
// address address0_
// where
// address0_.city_user_id=?
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());//发送一条select count() from…;
// select
// count(user_id)
// from
// user
// where
// city_id =?
----------------------------
```xml
<set name="userSet" table="user" lazy="true" fetch="join">//lazy就失效了,设置成什么都一样
Address address = (Address) session.get(Address.class, 1);//发送一条迫切左外连接查询记录
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());
<set name="userSet" table="user" lazy="subselect" fetch="join">
```java
List<Address> list = session.createQuery("from Address ").list();//发送查询所有客户的SQL
for (Address address : list) {
System.out.println(address.getCity_name());
System.out.println(address.getUserSet().size());//发送一条子查询
}
上的fetch和lazy
- fetch:抓取策略,控制SQL语句格式
- select:默认值,发送普通的select语句,查询关联对象
- join:发送一条迫切左外连接
- lazy:延迟加载,控制查询关联对象的时候是否采用延迟
- proxy:默认值,proxy的具体取值,取决于另一端上的lazy的值
- false:查询关联对象,不采用延迟
- no-proxy:(不会使用)
- 批量抓取
- 什么是批量抓取
- 一批关联对象一起抓取,batch-size
<set name="userSet" table="user" batch-size="4">
<class name="domain.Address" table="address" batch-size="3">