2017.2.6-2017.2.9
javaee三层结构
- web层:struts2框架
- service层:Spring框架
dao层:hibernate框架
对数据库进行CURD
什么是hibernate框架
- hibernate框架应用于Javaee三层结构中的dao层框架
- 在dao层里面做对数据库crud操作,使用hibernate实现crud操作,hibernate底层代码就是jdbc,hibernate对jdbc进行封装,使用hibernate好处,不需要写复杂jdbc代码了,不需要写sql语句实现
- hibernate是开源的轻量级框架
hibernate版本
- hibernate3.x
- hibernate4.x
- hibernate5.x
什么是orm思想
- hibernate使用orm思想对数据库进行crud操作
- 在web阶段学习javabean,更正确的叫法–实体类
- orm:object relational mapping,对象关系映射
- 让实体类和数据库表进行一一对应
- 不需要直接操作数据库表,而操作表对应的实体类对象
第一个应用
引入jar包
- 引入require文件下的所有jar包
- 引入jpa-metamodel-generator文件夹下的jar包
- 引入MySQL驱动jar包
引入日志jar包
log4j-version.jar log4j-api-version.jar slf4j-log4j12-version.jar
创建实体类
使用hibernate不许自己手动创建表,hibernate会自动创建
配置实体类和数据库表一一对应关系(映射关系)
创建想xml格式的配置文件
配置文件的路径和名称没有固定要求 建议:在实体类所在包里面创建,名称:实体类名称.hbm.xml
配置是xml格式,在配置文件中首先引入xml约束(dtd、schema,hibernate里都为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> <!-- 1。配置类和表对应 class标签 name属性:实体类全路径 table属性:数据表名称 --> <class name="zcw.entity.User" table="user"> <!-- 2.配置实体类id和表id对应 hibernate要求实体类有一个属性唯一值 hibernate要求表有字段作为唯一值 --> <!-- 3.id标签 name属性:实体类里面id属性名称 column属性:生成的表字段名称 --> <id name="id" column="id"> <!-- 4.设置数据表id增长策略 native:生成表id值为主键自动增长 --> <generator class="native"></generator> </id> <!-- 5.配置其他属性和表字段对应 name属性:实体类属性名称 column属性:生成表字段名称 --> <property name="username" column="username"></property> <property name="password" column="password"></property> <property name="address" column="address"></property> </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-configuration> <session-factory> <!-- 第一部分:配置数据库信息 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///hibernate</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">951029</property> <!-- 第二部分:配合hibernate信息 --> <!-- 输出底层SQL语句 --> <property name="hibernate.show_sql">true</property> <!-- 输出低层SQL语句格式 --> <property name="hibernate.format_sql">true</property> <!-- hibernate自动创建表 update:如果已经有表,更新,如果没有,创建 --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 配置数据库方言 在mysql里面实现分页,关键字limit,只是mysql独有 在oracle数据库,实现分页rownum 让hibernate框架识别不同数据库自己特有的语句 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <!-- 第三部分:在映射文件放到核心配置文件中 --> <mapping resource="zcw/entity/User.hbm.xml"/> </session-factory> </hibernate-configuration>
添加操作
- 加载hibernate核心配置文件
- 创建SessionFactory对象
- 使用SessionFactory创建Session
- 开启事务
- 写具体逻辑crud
- 提交事务
关闭资源
package test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.junit.Test; import zcw.entity.User; public class addtest { @Test public void add(){ //第一步 加载hibernate核心配置文件 Configuration cfg=new Configuration(); cfg.configure(); //第二步 创建SessionFactory对象 //读取hibernate核心配置文件内容,创建sessionfactory SessionFactory sessionFactory=cfg.buildSessionFactory(); //第三步 使用SessionFactory对象创建session对象 Session session=sessionFactory.openSession(); //第四步 开启事务 Transaction ts=session.beginTransaction(); //第五步 写具体逻辑crud操作 User user=new User(); user.setUsername("张三"); user.setPassword("666"); user.setAddress("福建"); //调用session方法实现添加 session.save(user); //第六步 提交事务 ts.commit(); //第七步 关闭资源 session.close(); sessionFactory.close(); } }
配置文件详解
- 映射配置文件
- 文件名和位置没有固定要求
- class标签name属性值为类全路径
- id标签和property标签name属性值为实体类属性名称
- id标签和property标签,column可以省略
- property标签type属性,设置生成表字段的类型。hibernate会自动设置类型
- 核心配置文件
- 配置位置要求,在
session-factory
标签内 - 配置三部分
- 数据库部分–必须
- hibernate部分–可选
- 映射文件–必须
-核心配置文件名称(hibernate.cfg.xml)和位置(src下)固定
- 配置位置要求,在
hibernate核心API
Configuration
Configuration cfg=new Configuration(); cfg.configure();
到src目录下面找到名称为hibernate.cfg.xml配置文件,创建对象,把配置文件放到对象里面(加载核心配置文件)
SessionFactory(重点)
使用configuration对象创建SessionFactory对象,创建SessionFactory过程中做的事:
根据核心配置文件中,有数据库配置,有映射文件部分,到数据库里面根据映射关系把表创建
创建SessionFactory过程非常消耗资源
在hibernate操作中,建一个项目一般只创建一个SessionFactory对象
实现
静态代码块只在类加载是后执行,执行一次。
static Configuration cfg=null; static SessionFactory sessionFactory=null; //静态代码块实现 static{ //加载核心配置文件 cfg=new Configuration(); cfg.configure(); sessionFactory=cfg.buildSessionFactory(); } //提供返回sessionFactory的方法 public static SessionFactory getSessionFactory(){ return sessionFactory; }
Session(重点)
- session类似与jdbc中connection
- 调用session里面不同的方法实现crud操作
- 添加–save()
- 修改–update()
- 删除–delete()
- 根据id查询–get()
session对象是单线程对象
session对象不能共用,只能自己使用
Transaction
事务对象
Transaction ts=session.beginTransaction();
- 事务提交和回滚
- commit()
- rollback()
- 事务的四个特性
- 原子性
- 一致性
- 隔离性
- 持久性
实体类编写规则
- 属性私有
- get、set方法公有
- 要求实体类有属性作为唯一值(id)
实体类属性建议不使用基本数据类型,建议使用基本数据类型对应的包装类
- int-Integer
- char-Character
- 其他的都是首字母大写,比如 double-Double
因为有时候基本数据类型表达的不够准确,如:int score=0,但是它不能表示学生没来考试,而Integer=null则可以表示
hibernate主键生成策略
- native:根据使用的数据库来选择生成策略
uuid:hibernate自动生成UUID值
id的数据类型一定要为字符串类型
根据id获取数据
核心代码
// session的get方法有两个参数
// 第一个:实体类的class
// 第二个:id值
User user=session.get(User.class, 1);
System.out.println(user);
修改操作
- 首先获取数据
修改
Transaction ts=session.beginTransaction(); // session的get方法有两个参数 // 第一个:实体类的class // 第二个:id值 User user=session.get(User.class, 1); System.out.println(user); user.setUsername("666"); user.setPassword("111"); session.update(user); ts.commit();
删除操作
- 先查找出来,再删除
创建实体类对象,再删除
/*//第一种:先查找,后删除 User user=session.get(User.class, 1); session.delete(user);*/ //第二种:先创建,后删除 User user=new User(); user.setId(2); session.delete(user);
实体类对象状态(概念)
瞬时态:对象里面没有id,对象与session没有关联
User user=new User(); user.setUsername("zcw");
持久态:对象有id值,对象与session关联
User user=session.get(User.class,1);
托管态:对象有id值,对象与session没有关联
User user=new User(); user.setId(1);
saveOrUpdate
- 执行save操作:当实体类对象为瞬时态时
- 执行update操作:当实体类对象为托管态或持久态时
Hibernate的一级缓存
- 默认是打开状态
- 作用范围:从session创建到session关闭
- 缓存存储的数据为持久态数据
是一种优化技术
Hibernate二级缓存
- 目前已经不使用了,替代技术redis
- 默认不打开
- 作用范围:SessionFactory范围
验证一级缓存
- 首先根据id=1查询
- 再查询id=1
- 看是否有两个sql日志输出
一级缓存执行过程
持久态会自动更新数据库
User user=session(User.class,1);
user.setUsername("q");
ts.commit();
就算没有提交,也会完成更新
Hibernate事务规范写法
SessionFactory sessionFactory = null;
Session session = null;
Transaction ts = null;
try {
sessionFactory = GetSessionFactory.getSessionFactory();
session = sessionFactory.openSession();
ts = session.beginTransaction();
User user = session.get(User.class, 1);
System.out.println(user);
ts.commit();
} catch (Exception e) {
e.printStackTrace();
ts.rollback();
} finally {
session.close();
sessionFactory.close();
}
Hibernate绑定Session
为了确保session为单线程
配置hibernate.cfg.xml文件
<property name="hibernate.current_session_context_class">thread</property>
调用SessionFactory得到
return sessionFactory.getCurrentSession();
- 获取与本地线程绑定session的时候,不需要手动关闭
Query对象查询所有记录
使用query对象需要写HQL语句,语法格式:from 实体类名称
- 创建Query对象
调用query对象里面的方法得到结果
Query query=session.createQuery("from User"); List<User> list=query.list(); for(User user:list){ System.out.println(user); }
Criteria对象查询数据
这个方法无需写sql语句
- 创建Criteria对象
调用对象里面的方法得到结果
//createCriteria方法的参数为实体类class Criteria criteria=session.createCriteria(User.class); List<User>list=criteria.list(); for(User user:list){ System.out.println(user); }
SQLQuery对象查询数据
使用sql语句
- 创建SQLQuery对象
调用方法得到结果集
SQLQuery sqlQuery=session.createSQLQuery("select * from user"); List<Object[]> list=sqlQuery.list(); for(Object[] object:list){ System.out.println(Arrays.toString(object)); }
默认的得到的是数组集合,下面的方法将结果集变为封装到User实体中
SQLQuery sqlQuery=session.createSQLQuery("select * from user"); sqlQuery.addEntity(User.class); List<User>list=sqlQuery.list(); for(User user:list){ System.out.println(user); }
hibernate一对多配置
客户–联系人,客户就是公司(一),联系人就是职工(多)。
外键在多的一方
创建实体类
用户
private Integer cid; private String cname; private String clevel; private String csource; private String cphone; private String cmobile; ......get、set方法
联系人
private Integer lkm_id; private String lkm_name; private String lkm_gender; private String lkm_phone;
让两个实体类互相表示
在客户实体类里面表示多个联系人
private Set<LinkMan> setLinkMan=new HashSet<LinkMan>(); public Set<LinkMan> getSetLinkMan() { return setLinkMan; } public void setSetLinkMan(Set<LinkMan> setLinkMan) { this.setLinkMan = setLinkMan; }
在联系人实体类里面表示所属客户
private Client client; public Client getClient() { return client; } public void setClient(Client client) { this.client = client; }
配置映射关系
- 一个实体类对应一个映射配置文件
配置基本映射文件
Client.hbm.xml映射文件
<class name="zcw.entity.Client" table="client"> <id name="cid" column="id"> <generator class="native"></generator> </id> <property name="cname" column="cname"></property> <property name="clevel" column="clevel"></property> <property name="csource" column="csource"></property> <property name="cphone" column="cphone"></property> <property name="cmobile" column="cmobile"></property> </class>
LinkMan.hbm.xml映射文件
<class name="zcw.entity.LinkMan" table="linkMan"> <id name="lkm_id" column="id"> <generator class="native"></generator> </id> <property name="lkm_name" column="lkm_name"></property> <property name="lkm_gender" column="lkm_gender"></property> <property name="lkm_phone" column="lkm_phone"></property> </class>
在映射文件中,配置一对多关系
Client.hbm.xml
<!-- 在客户映射文件中,表示所有联系人 使用set标签表示所有联系人 set标签里面有name属性,属性值为联系人set集合名称 --> <set name="setLinkMan"> <!-- 一对多建表,有外键 hibernate机制:双向维护外键,在一和多一方都配置外键 column属性值:外键名称 --> <key column="clid"></key> <!-- 客户所有的联系人,class里面写联系人实体类全路径 --> <one-to-many class="zcw.entity.LinkMan"/> </set>
LinkMan.hbm.xml
<!-- 表示联系人所属客户 name属性:应为联系人实体类中使用client对象表示,所以写client class属性:Client类的全路径 column属性:外键名称 --> <many-to-one name="client" class="zcw.entity.Client" column="clid"></many-to-one>
创建核心配置文件,把映射文件引入到核心配置文件中
<mapping resource="zcw/entity/Client.hbm.xml"/> <mapping resource="zcw/entity/LinkMan.hbm.xml"/>
- 测试。成功创建了client和lingkman表
Hibernate一对多级联操作
- 创建客户和联系人对象
- 建立客户对象和联系人对象的关系
- 保存到数据库
复杂写法:
ts = session.beginTransaction();
//添加一个客户,为这个客户添加一个联系人
//1.创建客户和联系人对象
Client client=new Client();
client.setCname("baidu");
client.setClevel("vip");
client.setCsource("internet");
client.setCphone("110");
client.setCmobile("111");
LinkMan linkMan=new LinkMan();
linkMan.setLkm_name("zcw");
linkMan.setLkm_gender("male");
linkMan.setLkm_phone("177");
//2.建立客户对象和联系人对象关系
//2.1将联系人对象放到客户对象的set集合里
client.getSetLinkMan().add(linkMan);
//2.2把客户对象放到联系人里
linkMan.setClient(client);
//3.保存到数据库
session.save(client);
session.save(linkMan);
//提交事务
ts.commit();
简单写法
在Client.hbm.xml文件的set标签写上cascade属性
<set name="setLinkMan" cascade="save-update">
java操作代码中
- 不需将客户对象放到联系人中
不需保存LinkMan对象
ts = session.beginTransaction(); //添加一个客户,为这个客户添加一个联系人 //1.创建客户和联系人对象 Client client=new Client(); client.setCname("baidu"); client.setClevel("vip"); client.setCsource("internet"); client.setCphone("110"); client.setCmobile("111"); LinkMan linkMan=new LinkMan(); linkMan.setLkm_name("zcw"); linkMan.setLkm_gender("male"); linkMan.setLkm_phone("177"); //2.建立客户对象和联系人对象关系 //2.1将联系人对象放到客户对象的set集合里 client.getSetLinkMan().add(linkMan); //3.保存到数据库 session.save(client); //提交事务 ts.commit();
一对多级联删除
将客户和对应的联系人删除
配置Client.hbm.xml文件的set标签
<set name="setLinkMan" cascade="save-update,delete">
删除客户
Client client=session.get(Client.class,2); session.delete(client);
一对多联级修改
//1.根据id查询联系人,根据id查询用户
Client client=session.get(Client.class, 1);
LinkMan linkman=session.get(LinkMan.class, 3);
//把联系人放到客户
client.getSetLinkMan().add(linkman) ;
//把客户放到联系人里面
linkman.setClient(client);
inverse属性
?因为Hibernate双向维护外键,在客户和联系人里面都需要维护外键,修改联系人的时候,首先更新联系人,再更改外键。造成效率问题
解决方法:让client方放弃外键维护。
<set name="setLinkMan" cascade="save-update,delete" inverse="true">
hibernate多对多配置
用户–角色,小张可以是总经理、农民、司机。
另外建一张表,包含两张表的外键
创建两个实体类
User
private Integer id; private String username; private String password;
Rolle
private Integer id; private String name; private String desc;
配置两个实体类的关系
User
private Set<Role> setRole=new HashSet<Role>(); public Set<Role> getSetRole() { return setRole; } public void setSetRole(Set<Role> setRole) { this.setRole = setRole; }
Role
private Set<User> setUser=new HashSet<User>(); public Set<User> getSetUser() { return setUser; } public void setSetUser(Set<User> setUser) { this.setUser = setUser; }
配置文件
- 基本配置
配置多对多关系
User.hbm.xml
<!-- 用户表里表示所有角色,使用set标签 name属性:角色set集合名称 table:第三表名称 --> <set name="setRole" table="user_role"> <!-- key标签里面配置当前映射文件在第三张表外键名称 --> <key column="userid"></key> <!-- class:角色实体类全路径 column:角色在第三张表外键名称 --> <many-to-many class="zcw.manytomany.Role" column="roleid"></many-to-many> </set>
Role.hbm.xml
<set name="setUser" table="user_role"> <!-- key标签里面配置当前映射文件在第三张表外键名称 --> <key column="roleid"></key> <!-- class:角色实体类全路径 column:角色在第三张表外键名称 --> <many-to-many class="zcw.manytomany.User" column="userid"></many-to-many> </set>
- 在主配置文件中引入映射文件
测试
在User.hbm.xml的set标签写上cascade属性
<set name="setRole" table="user_role" cascade="save-update">
写java代码
//创建user、role实体对象 User user1=new User(); User user2=new User(); user1.setUsername("张三"); user1.setPassword("666"); user2.setUsername("李四"); user2.setPassword("666"); Role role1=new Role(); Role role2=new Role(); Role role3=new Role(); role1.setName("项目经理"); role1.setDesc("项目经理"); role2.setName("餐厅老板"); role2.setDesc("餐厅老板"); role2.setName("代码高手"); role2.setDesc("代码高手"); //建立关系 user1.getSetRole().add(role1); user1.getSetRole().add(role2); user1.getSetRole().add(role3); user2.getSetRole().add(role1); user2.getSetRole().add(role3); session.save(user1); session.save(user2);
测试
多对多删除(一般不用)
在User.hbm.xml的set标签设置cascade属性
<set name="setRole" table="user_role" cascade="save-update,delete">
删除操作
User user=session.get(User.class, 5); session.delete(user);
第三张表的维护
让用户拥有某个角色
//让用户2拥有角色3 //1.获得User和Role User user=session.get(User.class, 2); Role role=session.get(Role.class,3) //2.向user中加入role user.getSetRole().add(role);
让用户没有某个角色
//让用户2失去角色2 //1.获得User和Role User user=session.get(User.class, 2); Role role=session.get(Role.class,2); //2.向user中移除role user.getSetRole().remove(role);
Hibernate查询方式
对象导航查询
根据id查询某个客户,再查询这个客户里面所有的联系人
OID查询
根据id查询某一条记录,返回对象
hql查询
Query对象,写hql语句实现查询
QBC查询
Criteria对象
本地查询
SQLQuery对象,实现普通sql实现查询
对象导航查询
Client client = session.get(Client.class, 1);
Set<LinkMan> list=client.getSetLinkMan();
System.out.println(list.size());
OID查询
Client client = session.get(Client.class, 1);
HQL查询
- HQL:Hibernate Query Language,Hibernate提供一种查询语言,hql语言与普通SQL很相似,区别:普通SQL操作数据库表和字段,hql操作实体类和属性。
常用hql语句
- 查询所有:
from 实体类对象
条件查询:
from 实体类名称 where 实体类属性名称=? and 实体类属性名称 like ?
Query query=session.createQuery("from User where id=?"); query.setParameter(0, 1); List<User> list=query.list(); for(User user:list){ System.out.println(user.getPassword()); }
排序查询:from 实体类名称 order by 实体类属性名称 asc/desc
分页查询
//查询记录 Query query=session.createQuery("from LinkMan"); //设置开始位置 query.setFirstResult(0); //设置每页的记录数 query.setMaxResults(3); List<LinkMan> list=query.list();
- 投影查询(查询部分字段值):
select 实体类属性名称,实体类属性名称 from 实体类
不支持*号 - 聚集函数使用:
select count(*) from 实体类名称
- count,max,min,sum,avg
- 查询所有:
QBC查询
查询所有
- 创建Criteria对象
得到结果集
Criteria criteria=session.createCriteria(User.class); List<User>list=criteria.list();
条件查询
//创建criteria对象 Criteria criteria=session.createCriteria(LinkMan.class); //添加条件 criteria.add(Restrictions.eqOrIsNull("lkm_gender", "female")); criteria.add(Restrictions.like("lkm_name", "%h%")); List<LinkMan> list=criteria.list();
排序查询
Criteria criteria=session.createCriteria(LinkMan.class); //设置对那个属性进行排序,设置排序规则 criteria.addOrder(Order.desc("id")); //criteria.addOrder(Order.asc("id"));
分页查询
//创建对象 Criteria criteria=session.createCriteria(LinkMan.class); //设置开始位置 criteria.setFirstResult(0); //设置每页的记录数 criteria.setMaxResults(3);
统计查询
//创建对象 Criteria criteria=session.createCriteria //设置操作 criteria.setProjection(Projections.rowCount()); //调用方法得到结果 Object obj=criteria.uniqueResult(); Long l=(Long)obj; int count=l.intValue();
离线查询(不使用session)
DetachedCriteria detachedCriteria=DetachedCriteria.forClass(LinkMan.class);
//最终执行时才需要用到session
Criteria criteria=detachedCriteria.getExecutableCriteria(sesion);
List<LinkMan> list=criteria.list();
HQL多表查询
内连接
select * from user u,role r where u.id=r.id; 或 select * from user u INNER JOIN role r ON u.id=r.id;
左外连接
select * from user r LEFT OUTER JOIN role r ON u.id=r.id;
右外连接
select * from user r RIGHT OUTER JOIN role r ON u.id=r.id;
HQL内连接
from Client c inner join c.setLinkMan
Query query=session.createQuery("from Client c inner join c.setLinkMan"); //返回的list每部分都是数组形式 List list=query.list();
- 迫切内连接(返回的是对象):from Client c inner join fetch c.setLinkMan
HQL左外连接
- from Client c left outer join c.setLinkMan
- 迫切左外连接:from Client c left outer join fetch c.setLinkMan
HQL右外连接
from Client c right outer join c.setLinkMan
hibernate检索策略(2种)
立即查询:根据id查询,调用get方法,一调用get方法马上发出语句查询数据库
User user=session.get(User.class,1);
延迟查询:根据id查询,还有load方法,调用load方法不会马上发送语句查询数据,只有得到对象里面的值的时候才会发送语句查询数据库
User user=session.load(User.class,1);
延迟查询(2种)
- 类级别延迟:根据id查询返回实体类对象,调用load方法不会马上发送语句
关联级别延迟:查询某个客户,再查询这个客户的所有联系人,查询客户的所有联系人的过程是否需要延迟,这个过程称为关联级别延迟。
在客户映射文件中进行配置,在set标签上使用属性(1)fetch:select(默认)(2)lazy:TRUE延迟(默认)/false不延迟/extra极其延迟
极其延迟就是你调用什么就发送什么语句
批量抓取
应用场景:查询所有的客户,返回list集合,遍历list集合,得到每个客户,得到每个客户的所有联系人
//创建criteria对象
Criteria criteria=session.createCriteria(User.class);
List<User> list=criteria.list();
for(User u:list){
Set<Role> roles=u.getSetRole();
for(Role role:roles){
System.out.println(role.getName());
}
}
上面的代码发送的sql语句太多次,可以配置客户的映射文件,在set标签中写上batch-size属性
<set name="setRole" table="user_role" batch-size="66">
值越大效率越高