JPA的概念与Hibernate
目录 JPA的概念与Hibernate一. JPA的概念二. Hibernate框架简介三. 基本操作1. 导入包2. 编写配置文件3. 编写代码4. Hibernate中对象的三种状态5. 一级缓存6. 二级缓存7. Hibernate中延迟加载8. Hibernate的关联关系9. Hibernate查询
一. JPA的概念
Java persistance API Java持久化API
官方对持久化操作提供的一套接口。包名以javax开头。
Hibernate是一套实现了JPA规范的持久化框架。
spring-data-jpa是spring框架中对JPA的实现。
二. Hibernate框架简介
Hibernate,翻译为冬眠,是一套全自动的ORM(对象关系映射object relation mapping)持久化框架。
优点:跨数据库。(代码的编写与数据库无关,很容易切换数据的版本和类型)。
缓存机制。(一级缓存和二级缓存)。
避免繁琐的sql语句的编写。
缺点:编写的语句不是sql语句,复杂的sql编写比较麻烦。
由于需要转换成JDBC的语句,性能肯定比JDBC操作要低一些。
mysql数据库常见引擎种类:
1. MyISAM 在mysql5之前是默认引擎。
2. InnerDB 在mysql5之后是默认引擎。可以建立主外键,意味着性能会比MyISAM要低。
3. Memory 内存表,性能最高,但是直接放在内存中,消耗内存大。
三. 基本操作
-
导入包
org.hibernate hibernate-core 5.2.10.Final -
编写配置文件
全局的配置文件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-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/j1901?useUnicode=true&amp;characterEncoding=utf-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 方言(指定数据库) -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- 开发阶段开启 -->
<!-- 打印生成后的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 格式化打印的sql语句 -->
<property name="hibernate.format_sql">true</property>
<!-- 会自动生成数据库表 -->
<!--
create: 表示每次都会创建数据库里面的表
create-drop: 表示每次都会创建数据库里面的表,每次停止的时候都会删除表
update: 每次运行都会检查表结构,如果有不同则会修改表结构(开发阶段常用)
validate: 每次都会检查表结构,如果有不同则抛出异常(一般在生产阶段设置)
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!--在同一个线程中共享session-->
<property name="current_session_context_class">thread</property>
<!-- 关联的映射文件 -->
<mapping resource="Product.hbm.xml"></mapping>
</session-factory>
</hibernate-configuration>
每个实体类对应的映射文件Product.hbm.xml
<?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>
<class name="com.qianfeng.day24.entity.Product" table="product">
<id name="id" column="pro_id" type="java.lang.Integer">
<!--
native:让数据库自行处理
assigned: 程序指定
-->
<generator class="native"></generator>
</id>
<property name="name" column="pro_name" type="java.lang.String" length="50"></property>
<property name="price" column="pro_price" type="java.lang.Double"></property>
</class>
</hibernate-mapping>
-
编写代码
// 数据库的基本操作
@Test
public void test(){
// 1. 读取配置文件
Configuration configuration = new Configuration().configure();
// 2. 得到sessionFactory
SessionFactory sessionFactory = configuration.buildSessionFactory();
// 3. 得到session
Session session = sessionFactory.openSession();
// 定义事务
Transaction transaction = null;
try {
// 4. 开启事务
transaction = session.beginTransaction();
// 5. 数据库操作
// 添加操作
// Product product = new Product();
// product.setName(“华为P20”);
// product.setPrice(6000.0);
// session.save(product);
// 在添加的同时,如果表是自动增长,会获取id
// System.out.println("===============" + product.getId());// 删除
// Product product = new Product();
// product.setId(2);
// 建议先查询再删除
// Product product = session.get(Product.class, 2);
// if (product != null) {
// session.delete(product);
// }// 修改操作 Product product = new Product(); product.setId(1); product.setName("华为P30"); product.setPrice(9999.0); session.update(product); // 6. 提交事务 transaction.commit(); }catch (Exception e){ e.printStackTrace(); // 回滚事务 if (transaction != null){ transaction.rollback(); } }finally { // 7. 关闭连接 if (session != null) { session.close(); } } }
-
Hibernate中对象的三种状态
在常见的操作过程中会出现的一些问题:
// 使用new的对象去修改或删除时,在操作之前查询了一次
session.get(Product.class, 1);
// 修改操作
Product product = new Product();
product.setId(1);
product.setName("华为P20");
product.setPrice(5999.0);
session.update(product);
出现如下异常:
org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.qianfeng.day24.entity.Product#1]...
hibernate中对象的三种状态:
Hibernate所管理的对象中会自动添加一个唯一的标识符OID。
瞬态(瞬时状态,临时状态):new出来的对象
持久态:与数据库中的数据有着强关联关系的状态。条件是session没有关闭。由于此状态数据与数据库有强关联,所以对此状态下的数据进行任何操作,在提交事务,或者关闭连接时都会提交到数据库中。
游离态(脱管态):当持久态数据被关闭连接时称为此状态。
上面错误代码可以修改如下:
// 使用new的对象去修改或删除时,在操作之前查询了一次
session.get(Product.class, 1);
// 修改操作
Product product = session.get(Product.class, 1);
product.setName("华为P20");
product.setPrice(5999.0);
transaction.commit(); // 或者使用session.close();(不建议使用close)
- 一级缓存
在Hibernate中,在同一个session中,如果多次查询某一个对象,只会查询一次,哪怕设置为null,也还是有效。例如:
Product product1 = session.get(Product.class, 1);
product1 = null;
Product product2 = session.get(Product.class, 1);
Product product3 = session.get(Product.class, 1);
Product product4 = session.get(Product.class, 1);
Product product5 = session.get(Product.class, 1);
System.out.println("========"+ (product2 == product5));
// 结果为true,而且显示的sql只查询了一次
Hibernate中的一级缓存:是session级别的缓存,即只在session未关闭时有效,会将所有的持久态数据保存在内存中,在session未关闭前,再次查询该持久态数据时,不会真的去查询数据库,而是直接拿缓存中的对象使用,以减少数据库的查询频次,提升使用性能。默认使用,而且不能被关闭。
一级缓存几乎没有什么缺点,所以大胆使用,但是在批量添加时,需要手动清理缓存。
手动清理缓存的几种方式:
-
session.close(); // 直接关闭连接,一级缓存自然被清理。
-
session.clear(); // 表示将session中当前的缓存对象一次性清理。
-
session.evict(obj); // 表示将obj对象从缓存中清理。
实际上所谓一级缓存,就是在session对象中持有一个map属性,将所有的持久化对象放入到此map中,然后当需要查询缓存中数据时,就是去该map中查找,所谓清理缓存,就是清理该map中的数据。
注意:Hibernate中的一级缓存与MyBatis中的一级缓存原理差不多,一样记忆。 -
二级缓存
二级缓存是SessionFactory级别缓存,或者叫全局范围的缓存。存入到二级缓存中的数据,除非服务器重启,否则一直存在,由于缓存是使用空间换时间的策略,所以二级缓存会大量的消耗内存。所以二级缓存并非默认开启,需要手动开启,而且二级缓存一般需要借助其他的第三方库,例如:EHCache, MemCache, Redis, MongoDB等。
因为二级缓存会大量的消耗内存,所以必须把项目需要的一些热门数据放到二级缓存中,不必要的数据不能放入,也就意味着,二级缓存需要有一个很好的管理策略(数据淘汰(驱逐)策略)。
缓存的雪崩和穿透。
- Hibernate中延迟加载
延迟加载,也叫做延时加载或懒加载。表示直到需要的时候才会真正的加载(查询)数据。
注意:在Hibernate中延迟加载的数据,一旦session关闭了,再去访问代理对象,会出现异常org.hibernate.LazyInitializationException: could not initialize proxy - no Session
在MyBatis中,session即使关闭了,还会再次打开并查询,然后关闭。
get方法和load方法的区别:
-
get是直接加载,而load是懒加载。
-
如果没有查询到结果,那么get将返回一个null,而load直接抛出异常。
-
Hibernate的关联关系
多对一
一对多
多对多
一对一
单向和双向:单向的意思就是在一方的实体类上描述关联关系,双向的意思是在双方实体类上描述关联关系。
双向一对多(双向多对一)示例如下:
@Getter
@Setter
public class Product {
private Integer id;
private String name;
private Double price;
private ProductType type;
}
@Getter
@Setter
public class ProductType {
private Integer id;
private String name;
private Set<Product> products = new HashSet<>(0);
}
Product.hbm.xml
<?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>
<class name="com.qianfeng.day24.entity.Product" table="product">
<id name="id" column="pro_id" type="java.lang.Integer">
<!--
native:让数据库自行处理
assigned: 程序指定
-->
<generator class="native"></generator>
</id>
<property name="name" column="pro_name" type="java.lang.String" length="50"></property>
<property name="price" column="pro_price" type="java.lang.Double"></property>
<many-to-one name="type" column="type_id" class="com.qianfeng.day24.entity.ProductType"></many-to-one>
</class>
</hibernate-mapping>
ProductType.hbm.xml
<?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>
<class name="com.qianfeng.day24.entity.ProductType" table="product_type">
<id name="id" column="type_id" type="java.lang.Integer">
<!--
native:让数据库自行处理
assigned: 程序指定
-->
<generator class="native"></generator>
</id>
<property name="name" column="type_name" type="java.lang.String" length="50"></property>
<set name="products">
<key column="type_id"></key>
<one-to-many class="com.qianfeng.day24.entity.Product"></one-to-many>
</set>
</class>
</hibernate-mapping>
一对多关联下的数据操作:
添加:
ProductType type = new ProductType();
type.setName("手机");
session.save(type);
Product product = new Product();
product.setName("P20");
product.setPrice(5999.0);
product.setType(type);
session.save(product);
修改:
// 查询修改后的类型
ProductType type = session.get(ProductType.class, 2);
// 查询当前要修改的产品信息
Product product = session.get(Product.class, 3);
// 设置类型
product.setType(type);
// 不需要调用update方法,只需要提交事务就会自动修改到数据库中
删除:
// 对于其他表没有关联影响的数据,直接使用单表操作删除即可
Product product = session.get(Product.class, 3);
session.delete(product);
// 如果与其他表数据有关联,删除时可能产生异常
ProductType type = session.get(ProductType.class, 2);
session.delete(type);
// 异常如下:Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'type_id' cannot be null
inverse关键字和cascade的作用:
inverse:反转
// 上面的删除语句在 一方的关系配置中配置了inverse=true的时候,出现异常:
// com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
// 当值为false的时候
// false为默认值,还是引发上面的cannot be null异常
// 此处的反转含义是:是否由对方来处理关联关系。当设置为true时,产品表会因为有外键关联而不允许删除,当设置为false时,产品类型表会断开关系,设置产品表外键为null,然后删除当前记录。
// 一般情况下,会将此值设置为true
cascade:级联
delete: 删除时级联
save-update:添加或修改时级联
none:都不级联
all:全部级联
<!-- 设置级联删除 -->
<set name="products" inverse="true" cascade="delete">
<key column="type_id"></key>
<one-to-many class="com.qianfeng.day24.entity.Product"></one-to-many>
</set>
- Hibernate查询
Hibernate中查询分为三种方式:
-
HQL(Hibernate Query Language)Hibernate查询语言
// 必须使用类名和属性名,不能使用表名和字段名
// 1. 基本查询
// List list = session.createQuery(“from Product”, Product.class).list();
// for (Product product : list) {
// System.out.println(product);
// }
// 2. 条件查询
// List list = session.createQuery(“from Product where price = ?”, Product.class)
// .setParameter(0, 3999.0)
// .list();
// for (Product product : list) {
// System.out.println(product);
// }
// 3. 具名查询
// List list = session.createQuery(“select t from Product t where price = :p”, Product.class)
// .setParameter(“p”, 3999.0)
// .list();
// for (Product product : list) {
// System.out.println(product);
// }// 4. 部分字段查询
// List<Object[]> list = session.createQuery(“select t.id, t.name from Product t where price = :p”, Object[].class)
// .setParameter(“p”, 3999.0)
// .list();
// for (Object[] product : list) {
// for (Object o : product) {
// System.out.println(o);
// }
// }
// 5. 查询部分字段并封装字段
// List list = session.createQuery(“select new Product(t.id, t.name) from Product t where price = :p”, Product.class)
// .setParameter(“p”, 3999.0)
// .list();
// for (Product product : list) {
// System.out.println(product);
// }// 6. 函数查询,并返回单条结果
// Long count = session.createQuery(“select count(1) from Product t”, Long.class)
// .uniqueResult();
// System.out.println(count);// 7. 分页并排序查询
// List list = session.createQuery(“from Product t order by t.price desc”, Product.class)
// .setFirstResult(2) // 跳过几条
// .setMaxResults(2) // 显示几条
// .list();
// for (Product product : list) {
// System.out.println(product);
// }// 8. 关联查询
// SELECT p.* FROM product p INNER JOIN product_type t ON p.type_id = t.type_id WHERE t.type_name = ‘华为手机’
// List list = session.createQuery(“SELECT p FROM Product p INNER JOIN ProductType t ON p.type.id = t.id WHERE t.name = ‘华为手机’”, Product.class).list();
// for (Product product : list) {
// System.out.println(product);
// }// 简化1
// List list = session.createQuery(“SELECT p FROM Product p INNER JOIN ProductType t ON p.type = t WHERE t.name = ‘华为手机’”, Product.class).list();
// for (Product product : list) {
// System.out.println(product);
// }// 简化2
// List list = session.createQuery(“SELECT p FROM Product p WHERE p.type.name = ‘华为手机’”, Product.class).list();
// for (Product product : list) {
// System.out.println(product);
// } -
Criteria 面向对象的查询
List list = session.createCriteria(Product.class)
.add(Restrictions.eq(“price”, 3999.0))
.list();
for (Product product : list) {
System.out.println(product);
} -
Native Query( SQL query)原生的SQL查询
List list = session.createNativeQuery(“SELECT p.* FROM product p INNER JOIN product_type t ON p.type_id = t.type_id WHERE t.type_name = ‘华为手机’”, Product.class).list();
for (Product product : list) {
System.out.println(product);
}