ORM,object relation mapping 对象关系映射,底层是对JDBC的封装,来对数据库进行crud操作。
1.hibernate映射配置文件
xxx.hbm.xml
<引入dtd约束>
<!--配置映射的实体类-->
<class name="实体类全路径" table="数据库表名">
<!--配置实体类的id类型字段,一般在数据库中作为主键的属性,native代表自增>
<id name="实体类中id属性全名" column="数据库对应的字段名,一般和name一样即可">
<generator class="native"></generator>
</id>
<!--配置实体类其他属性-->
<property name="实体类中属性名字" column="数据库中的字段名,一般和name一致即可"/>
<class>
2.hibernate核心配置文件
放置于src下面,名字是hibernate.cfg.xml
<session-factory>
<!--配置数据库信息,四大参数-->
<property name="数据库驱动">com.mysql.jdbc.Driver</property>
<property name="...url">jdbc:mysql://localhost:3306/数据库名</property>
<property name="username">用户名</property>
<property name="passwrord">密码</property>
<!--配置hibernate信息-->
<!--输出底层的sql语句-->
<property name="hibernate.show_sql">true</property>
<!--格式化输出的sql语句-->
<property name="hibernate.format_sql">true</property>
<!--自动构建数据库表-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!--配置数据库方言-->
<property name="hibernate.dialect>...</property>
<!--配置实体类映射文件-->
<mapping resources="实体类映射文件的全路径,把.改为/"/>
3.实现添加操作
第一步 加载核心配置文件
Configuration cfg = new Configuration();
cfg.configure();
第二部 创建SessionFactory对象
SessionFactory sessionFactory = cfg.buildSessionFactory();
第三部 通过SessionFactory对象创建Session
Session session = sessionFactory.openSession();
第四部 开启事务
Transaction tx = session.beginTransaction();
第五步 编写具体逻辑crud操作
User user = new User();
user.setSid(101);
user.setSname("霸天虎");
session.save(user);
第六步 提交事务
第七部 关闭资源
session.close();//后打开的先关闭
sessionFactory.close();//先打开的后关闭
4.hibernate的核心api
Configuration
1.读取src下的hibernate.cfg.xml配置文件
2.读取实体类映射文件xxx.hbm.xml映射文件
3.配置访问数据库的参数,驱动、url、username、password
4.管理hibernate的配置信息
SessionFactory
1.使用Configuration的buildSessionFactory方法构建SessionFactory,在构建SessionFactory的时候会根据映射生成表;
2.创建SessionFactory的过程特别消耗资源
因此我们通常一个项目只创建一个SessionFactory,通过编写工具类,在工具类中的静态代码块构建SessionFactory,保证只有在类加载时会执行静态代码块创建SessionFactory的实例,然后对外提供静态的get方法,用来获取SessionFactory。
Session
1.调用session中的方法对数据库进行crud操作
添加 save()
删除 delete()
修改 update()
根据id查询 get()
2.session对象是单线程对象,不能供用,只能够自己使用。
Transaction
事务涉及到的两个操作,commit()提交和rollback()回滚
涉及到的四个特性
原子性:多个操作绑定在一起,要么全部成功,要么全部失败;
一致性:比如转账,事务成功或者失败前后,转账人资金和被转账人资金的总和是一致的;
隔离性:事务之间是相互隔离的,一个事务的操作和数据不能和其他事物交互。
持久性:事务提交完成之后,数据库中的数据改变必须是持久的。
5.主键生成策略
<id name="实体类id属性名" column="数据库表中id字段名">
<generator class="这个值有很多种"></generator>
1.native:hibernate支持跨平台自动识别数据库,比如mysql和oracle的自增是不一样的,使用native字段可以自动识别;
2.increment:自动增长,用于long、int、short类型生成的唯一标识,只有在没有其他进程向同一张表中插入数据时才能使用,集群下不要使用;
3.sequence:序列,一般用于oracle、DB2等数据库;
4.identity:支持自动增长的数据库,比如mysql,DB2;
5.uuid:生成String类型的32位16进制的唯一标识。
6.实体类的crud操作
1.添加
User user = new User();
user.setSid(101);
user.setSname("大哥");
session.save(user);
2.根据id查询
User user = session.get(User.class,101);
3.修改
User user = session.get(User.class,101);
user.setSname("二哥");
session.update(user);
4.删除
User user = session.get(User.class,101);
session.delete(user);
7.hibernate实体类对象状态
瞬时态
对象没有id值,不在session的管理范围内
一般执行save操作
持久态
执行save操作后,
对象有id值,在session的管理范围内
要注意,不一定在数据库中也有,执行save操作后,会先放到一级缓存中,事务提交后,会完成添加到数据库。
托管态
有id,但是不再session的管理范围内。他在内存中存在,只是session关闭后,hashmap(key存放OID,value存放指向内存中对象的引用)缓存也没了,指向这个对象的引用也关闭了。
8.hibernate一级缓存
缓存特点:
一级缓存:默认打开;
使用范围是session创建到关闭
存储的数据必须是持久态的
二级缓存:目前不使用,使用redis替代
特性:
持久态会自动更称数据库
当查询到持久态对象后,会保存在一级缓存和快照中,我们修改对象后,一级缓存中的对象改变,但是快照的对象没有改变,提交事务时会对比一级缓存中和快照的对象,如果不一致就会更新一级缓存中的对象到数据库。
9.hibernate绑定session,保证为本地单线程
在hibernate.cfg.xml中配置
<property name="hibernate.current_session_context_class">thread</property>
调用sessionFactory方法返回与本地线程绑定的session;
public static Session getSessionObject(){
return sessionFactory.getCurrentSession();
}
绑定后的session不需要手动关闭;
10.hibernate的api使用
Query对象,支持使用hql语句
Query query = session.createQuery("from User");
List<User> userlist = query.list();
Criteria对象,支持使用方法
Criteria criteria = session.createCriteria(User.class);
List<User> userlist = criteria.list();
SQLQuery对象,支持调用底层sql语句
SQLQuery sqlQuery = session.createSQLQuery("select * from t_user");
SQLQuery返回的结果List中默认是数组形式的,可以通过调用addEntity(User.class)方法让它以对象形式返回;
sqlQuery.addEntity(User.class);
List<User> userlist = sqlQuery.list();
11.表和表之间的关系
一对多:通过外键关联,在表中数据较多的一方建立外键;
多对多:通过第三张表创建两个字段作为外键,分别指向两张表的主键;
12.hibernate的一对多操作
首先建立以下情景:用户类User和银行卡类Card类,一个用户有多的银行卡,但是每个银行卡只能有一个用户。
1.在用户的实体类中,使用set集合用来表示有多张银行卡
Set<Card> cardSet = new HashSet<Card>;
生成set和get方法;
2.在银行卡的实体类中,使用一个用户对象来表示有一个用户
User user = new User();
生成set和get方法;
3.在实体类映射文件user.hbm.xml和card.hbm.xml中进行配置
user.hbm.xml
<class name="user类的全路径" table="t_user">
<id name="uid" column="uid>
<generator class="native"></generator>
</id>
<property name="username" column="username"></property>
...
<set name="cardSet注意是用户实体类中set集合的属性名">
<key column="cid"></key>
<one-to-many class="card实体类的全路径"></one-to-many>
</set>
</class>
card.hbm.xml
<class name="card类的全路径" table="t_card">
<id name="cid" column="cid">
...
...
<property ...></property>
...
<many-to-one name="user" class="user类的全路径" column="uid"/>
</class>
13.一对多级联操作
1.级联保存
新添加一个用户,为这个用户添加多张银行卡。
同时操作两张表,就是级联操作。
User user = new User();
user.setUsername("大哥");
Card card1 = new Card();
card1.setCname("卡1");
user.getCardSet.add(card1);
card1.setUser(user);
session.save(user);
session.save(car1);
2.级联删除
删除一个用户,这个用户下的所有银行卡也都删除。
1).在user.hbm.xml文件中的set标签内进行以下配置
<set name="cardSet" cascade="delete">
2).代码中即可直接删除用户
User user = session .get(User.class,1);
session.delete(user);
底层是这样的:
首先先根据id查询到用户,然后根据用户的外键查询到银行卡,将银行卡中的外键值全部设置为null,然后删除银行卡,删除用户。
3).修改一张银行卡的所有者为另一个用户
User user = session.get(User.class,2);
Card card = session.get(Card.class,1);
user.getCardSet.add(card);
card.setUser(user);
tx.commit();//持久太会自动更新数据库
4).inverse属性
因为hibernate是双向维护外键的,造成在修改银行卡对象时修改了一次外键,修改用户的时候又修改了一次外键,造成效率和性能问题。
解决方法:
令其中一方放弃外键维护。一般让一对多中一的那一方放弃维护外键,就比如,一个老师可能记不住所有学生,但是学生都能记住老师。
在user.hbm.xml中配置,在set标签中使用Inverse属性
<set name="cardSet" inverse="true">
true的意思是确认放弃外键维护。
14.多对多操作
1.多对多映射配置
在两个实体类中均使用set集合作为外键关联;
然后生成set和get方法。
创建实体类映射文件,配置多对多关系
情景:一个用户有多个角色,一个角色也有多个用户。
在user.hbm.xml的class标签中添加set标签
<set name="roleSet" table="user_role这里写第三张表的名字">
<key column="uid当前实体类在第三章表中的外间名"></key>
<many-to-many class="role实体类的全路径" column="rid"></many-to-many>
</set>
在role.hbm.xml的class标签中添加set标签
<set name="userSet" table="user_role这里写第三张表的名字">
<key column="rid当前实体类在第三章表中的外间名"></key>
<many-to-many class="user实体类的全路径" column="uid"></many-to-many>
</set>
在hibernate.cfg.xml中添加两个配置文件的映射
2.多对多级联保存
在用户的映射配置文件中的set标签内配置cascade属性
<set name="roleSet" table="user_role" cascade="save-update">
代码
User user = new User();
user.setSname("大哥");
Role role = new Role();
role.setRname("职员");
user.getRoleSet.add(role);
session.save(user);
3.多对多级联删除
在用户的映射配置文件的set标签内,配置cascade属性
<set name="roleSet" table="user_role" cascade="save-update,delete">
代码
User user = session.get(User.class,1);
session.delete(user);
但这样的级联删除存在问题,因为多对多的关系,
大哥→职员,酒鬼
二哥→职员,学者
级联删除大哥,会先删除用户大哥,级联删除职员和酒鬼两个角色,二哥就只剩下了学者。
4.维护第三张表
用户和角色这样的多对多关系,一般通过对第三张表的维护来实现一些级联操作。
添加——为用户添加某个角色操作
User user = session.get(User.class,2);
Role role = session.get(Role.class,3);
user.getRoleSet().add(role);
删除——为用户删除某个角色操作
User user = session.get(User.class,2);
Role role = session.get(Role.class,3);
user.getRoleSet().remove(role);
15.hibernate查询方式
1.对象导航查询(通过获得实体类外键属性set集合)
情景:根据id查到公司,然后通过公司的外间属性员工获得所有员工
Company com = session.get(Company.class,1);
Set<Employee> eeSet = com.getEmployeeSet();
然后遍历set集合即可。
2.OID查询
根据id查询,就是Session中的get方法
情景:查询cid为1的公司
Company com = session.get(Company.class,1);
3.HQL查询
使用Query对象
Query query = session.createQuery("from Company");//hql主要是对实体类对象的操作,因此from后面跟的不是表名,是对应的实体类对象名字
List<Company> comlist = query.list();
1.hql条件查询
from 实体类名字 where 实体类属性 =?
Query query = session.createQuery("from Company where cid=? and cname=?");
处理占位符,使用Query中的setParameter()方法,需要注意的是:方法中的第一个参数指的是hql语句中第几个占位符,并且从0开始算第一个。
query.setParameter(0,1);
query.setParameter(1,"保护伞");
List<Company> comlist = query.list();
2.排序查询
Query query = session.createQuery("from Company order by cid desc");
同样使用order by关键字,并且asc为默认升序,desc为降序
List<Company> comlist = query.list();
3.分页查询
先查出所有
Query query = session.createQuery("from Company");
然后设置开始的位置,每页显示的最大数量
query.setFirstResult(0); //表示从0开始
query.setMaxResults(10); //每页显示10条记录
List<Company> comlist = query.list();
4.投影查询
就是查询表中的特定字段,部分字段
Query query = session.createQuery("select cname,cmem_num from Company");
List<Object[]> list = query.list();
遍历list集合中的数组
5.聚集函数
举例:count()
Query query = session.createQuery("select count(*) from Company");
Object num = query.uniqueResult(); //因为返回的只是计数的一个值,因此使用uniqueResult方法转化为一个对象。
4.QBC查询
使用Criteria对象操作
Criteria criteria = session.createCriteria(Company.class);
List<Company> comlist = criteria.list();
遍历list即可
1.条件查询
Criteria criteria = session.createCriteria(Company.class);
设置查询条件,通过Restrictions设置条件
criteria.add(Restrictions.eq("cid",1)), //Restrictions类提供很多匹配方法,例如eq就是条件匹配中的"=",ht大于,gt小于,模糊查询则可以使用like("cname","阿里%");
criteria.add(Restrictions.eq("cname","保护伞"));
List<Company> comlist = criteria.list();
2.排序查询
Criteria criteria = session.createCriteria(Company.class);
使用Criteria的方法addOrder添加排序条件,使用Order的类方法asc表示按照参数进行升序排列
criteria.addOrder(Order.asc("cid"));
3.分页查询
Criteria criteria = session.createCriteria(Company.class);
设置分页参数,起始位置,每页显示数量
criteria.setFirstResult(0);
criteria.setMaxResults(10);
4.统计查询
Criteria criteria = session.createCriteria(Company.class);
设置计数操作,Projections类静态方法rowCount表示计数
criteria.setProjection(Projections.rowCount);
同样返回的只是一个数字,我们把结果转化为对象
Object num = criteria.uniqueResult();
5.离线查询
查询条件一般是由web层传来,然后由web层到service再到dao层,通过session创建Criteria对象,组装查询条件来执行。
离线查询的Criteria对象创建时是脱离session的,离线查询的好处是可以在web层创建Criteria对象,然后组装查询条件,将criteria对象发送至dao层后再关联session执行查询,这样做简化了dao层的操作。
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Company.class);
Criteria criteria = detachedCriteria.getExecuteCriteria(session);
List<Company> comlist = criteria.list();
5.HQL多表查询
1.内连接
from Company C inner join C.employeeSet
内连接返回的结果在list中是数组的形式
2.迫切内连接
from Company C inner join fetch C.employeeSet
迫切内连接返回的结果在list中是对象的形式
3.左外连接
from Company C left outer join C.employeeSet
左外连接返回的结果在list中是数组形式
4.迫切左外连接
from Company C left outer join fetch C.employeeSet
迫切左外连接返回的结果在list中是对象的形式
5.右外连接
from Company C right outer join C.employeeSet
6.hibernate检索策略
1.立即检索(立即查询)
get方法,即执行get方法时立刻向数据库发送底层sql语句
2.延迟检索(延迟查询)
load方法
User user = session.load(User.class,1); //此时不会发送语句
System.out.println(user.getUid()); //这个时侯也不会发送语句,因为这个时候内存中有id值,不需要向数据库发送语句,会返回给id值
System.out.println(user.getUsername()); //这个时候会发送语句,只有当你请求的属性内存中没有,才会向数据库发送语句
类级别延迟
返回为类对象,且不会立刻发送语句
关联级别延迟
查询某个公司,在查询公司的所有职员,查询公司的所有职员的过程中的延迟
在映射文件中配置关联级别延迟,在公司的配置文件中set标签添加属性fetch和lazy
<set name="employeeSet" fetch="select" lazy="false/true(默认)/extra">
lazy的取值:false表示不开启延迟,立即查询
true表示开启延迟,默认情况,当在关联查询中通过公司的外键属性的get方法获取时不会立刻发送语句
Set<Employee> eeSet = user.getEmployeeSet(); //此时不会向数据库发送查询语句,使用employee对象的时候才会发送
extra表示加强延迟,不仅不会立刻发送,使用的时候需要什么字段返回什么字段
7.批量抓取
情景:查询所有公司的所有职员
通常情况下我们先查询出所有公司,返回存放公司的list集合后,遍历公司list,并且每遍历一个公司,再遍历这个公司下的所有员工
问题:每次遍历都会向数据库中发送语句,性能低
优化:再公司的映射配置文件中的set标签内配置batch-size属性
<set name="employeeSet" batch-size="4">
batch-size的值要求整数,并且没有特殊要求,当数值越大,遍历时像数据库中发送的语句越少,性能越好。
值为几就表示一次关联查询出多少条记录,为4则一次关联查询4条关联记录,一共10条就会分3次查询。
底层其实是通过指定的外键列表,使用单条select语句一次批量查出多条关联记录。