Hibernate的使用
- 本文是笔记!
- 使用JDBC做数据库相关功能开发会做很多重复性的工作,比如创建连接,关闭连接,把字段逐一映射到属性中。 Hibernate把这一切都封装起来了,使得数据库访问变得轻松而简单,代码也更加容易维护。
- 第一次使用Hibernate会有一个比较繁琐的配置过程,以后再使用就会相对简单了。
- 基于框架的程序要成功运行,对于JAR包的版本,配置文件的正确性有一个严苛的要求,任何一个地方出错了,都会导致程序无法正确运行。如果你是第一次学习本框架,务必严格按照教程的指导,完全模仿操作,直到成功看到运行效果。 第一次成功之后,信心,思路都会有较好的铺垫,然后再根据自己的疑惑,在“成功”的代码上做原本想做的改动和调整,这样可以大大节约学习的时间,提高效率。
hibernate插入
基本原理图
- 应用程序通过Hibernate把一个
Product
对象插入到数据库对应的表中。 hibernate.cfg.xml
配置文件提供链接数据库的基本信息:- 账号,密码,驱动,数据库ip,端口
Product.hbm.xml
提供对象与表之间的映射关系。- 对应哪一个表,对应什么属性,对应什么字段?
创建数据库
create database test;
创建表
use test; CREATE TABLE product_( id int(11) NOT NULL AUTO_INCREMENT, name varchar(30), price float , PRIMAY KEY(id) )
导入依赖jar包
创建实体类
- 实体类用于映射数据库中的表
product_
package com.how2java.pojo; public class Product { int id; String name; float price; public int getId() { return id; } public String getName(){ return name; } public float getPrice() { return price; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPrice(float price) { this.price = price; } }
配置Product.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"> <hibernate-mapping package="com.how2java.pojo"> <class name="Product" table="product_"> <id name="id" column="id"> <generator class="native"> </generator> </id> <property name="name" /> <property name="price" /> </class> </hibernate-mapping>
- 在包com.how2java.pojo下 新建一个配置文件Product.hbm.xml, 用于映射Product类对应数据库中的product_表
注: 文件名 Product.hbm.xml P一定要大写,要和类保持一致 <class name="Product" table="product_">
- 表示类Product对应表product_
<id name="id" column="id"> <generator class="native"> </generator> </id>
-
表示属性id,映射表里的字段id
<generator class="native">
意味着id的自增长方式采用数据库的本地方式 -
<property name="name" />
-
这里配置的时候,只写了属性name,没有通过column=“name” 显式的指定字段,那么字段的名字也是name.
配置hibernate.cfg.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"> <hibernate-mapping package="com.how2java.pojo"> <class name="Product" table="product_"> <id name="id" column="id"> <generator class="native"> </generator> </id> <property name="name" /> <property name="price" /> </class> </hibernate-mapping>
创建测试类TestHibernate
-
创建一个Product对象,并通过Hibernate把这个对象,插入到数据库中。
-
hibernate的基本步骤是:
- 获取
SessionFactory
- 通过
SessionFactory
获取一个Session
- 在
Session
的基础上开启一个事务。 - 通过调用
Session
的save()
方法来将对象保存到数据表中。 - 提交事务
- 关闭
Session
和SessionFactory
。
- 获取
-
代码实现:
package com.how2java.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cgf.Configuration; import com.how2java.pojo.Product; public class TestHibernate { public static void main(String[] args){ //获取SessionFactory SessionFactory sessionFactory = new Configuration().conifgure().buildSessionFactory(); //获取Session Session session = SessionFactory.openSession(); //开启事务 session.beginTransaction(); //创建对象 Product product = new Product(); product.setName("Lenovo Y7000P"); product.setPrice(7800); //调用save方法 session.save(product); session.getTransaction().commit(); session.close(); sessionFactory.close(); } }
通过
for
循环插入10个对象到数据库中- 代码实现:
package com.how2java.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cgf.Configuration; import com.how2java.pojo.Product; public class TestHibernate { public static void main(String[] args){ //获取SessionFactory SessionFactory sessionFactory = new Configuration().conifgure().buildSessionFactory(); //获取Session Session session = SessionFactory.openSession(); //开启事务 session.beginTransaction(); //创建对象 for(int i =0;i<10;i++){ Product product = new Product(); product.setName("Lenovo Y7000P"+i); product.setPrice(7800); } //调用save方法 session.save(product); session.getTransaction().commit(); session.close(); sessionFactory.close(); } }
hibernate对象状态
- 实体类在hibernate中共有三种状态
- 瞬时,持久,托管
- 瞬时状态:
- 值得是实体类没有和hibernate发生任何的关系,在数据库中也没有对应的记录,一旦JVM结束,这个对象也就消失了。
- 持久状态:
- 指的是一个对象和hibernate发生联系,有对应的
session
,并且在数据库中有对应的记录。
- 指的是一个对象和hibernate发生联系,有对应的
- 托管状态;
- 指的是一个对象虽然在数据库中有对应的一条记录,但是它所对应的
session
已经关闭了。
- 指的是一个对象虽然在数据库中有对应的一条记录,但是它所对应的
- 代码示例:
package com.how2java.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import com.how2java.pojo.Product; public class TestHibernate { /** * @param args */ public static void main(String[] args) { SessionFactory sf = new Configuration().configure().buildSessionFactory(); Session s = sf.openSession(); s.beginTransaction(); Product p = new Product(); p.setName("p1"); System.out.println("此时p是瞬时状态"); s.save(p); System.out.println("此时p是持久状态"); s.getTransaction().commit(); s.close(); System.out.println("此时p是脱管状态"); sf.close(); } }
hibernate获取对象
- 通过对象的id来获取对象
- 通过
session
的get()
方法来获取对象,传入的参数为类对象和id。 - 代码示例:
public static void getHero(int id){ SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.openSession(); session.beginTransaction(); //通过id来获取对象 Product p = (Product) session.get(Product.class,id); System.out.printf("id为%d的对象名称为%s,价格为%f",id,p.getName(),p.getPrice()); session.close(); sessionFactory.close(); }
hibernate删除对象
- 通过id来获取对象
- 删除这个对象
- 代码示例:
public static void deleteProduct(int id){ SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.openSession(); session.beginTransaction(); //通过id来获取对象 Product p = (Product) session.get(Product.class,id); System.out.printf("id为%d的对象名称为%s,价格为%f",id,p.getName(),p.getPrice()); //删除这个对象 session.delete(p); //提交 session.getTransaction().commit(); //关闭 session.close(); sessionFactory.close(); }
hibernate修改对象
- 通过id获取对象
- 调用对象的方法实现修改
- 使用
session
的update()
方法实现更新 - 代码示例:
public static void updateProduct(int id){ SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.openSession(); session.beginTransaction(); //通过id来获取对象 Product p = (Product) session.get(Product.class,id); System.out.printf("id为%d的对象名称为%s,价格为%f",id,p.getName(),p.getPrice()); //删除这个对象 p.setName("new smartphone"); //更新对象 session.update(p); //提交 session.getTransaction().commit(); //关闭 session.close(); sessionFactory.close(); }
hibernate查询对象
使用HQL语句进行查询
- hibernate查询对象使用
HQL
进行,Hibernate Query Language
是hibernate专门用于查询数据的语句,有别与SQL,HQL更接近于面向对象的思维方式。 - 首先创建一个
Query
对象。 - 设置
Query
语句中的参数,和基1的PreraredStatement
不一样,Query
是基0的。 - 通过
Query
对象的list()
方法得到查询结果。 - 代码示例:
public static void queryProduct(Session session,String name){ session.beginTransaction(); Query query = session.createQuery("from Product p where p.name like ?"); query.setString(0,"%"+name+"%"); List<Product> products = query.list(); for(Product p :products){ System.out.println(p.getName()); } session.getTransaction().commit(); }
使用Criteria语句进行查询
- 与使用SQL和HQL语句进行查询不一样,使用Criteria语句进行查询是完全面向对象的查询方式,不会有任何sql语句的痕迹
- 利用
session
创建一个Criteria
对象,然后利用add()
方法添加约束条件Restrictions.like
进行模糊查询。 - 利用
Criteria
对象的list()
方法获取查询结果。 - 代码示例:
public static void criteriaProduct(Session session,String name){ session.beginTransaction(); Criteria criteria = session.createCriteria(Product.class); criteria.add(Restrictions.like("name","%"+name+"%")); List<Product> products = criteria.list(); for(Product p :products){ System.out.println(p.getName()); } session.getTransaction().commit(); }
使用SQL语句进行查询
- hibernate保留了对sql语句的支持,也可以使用sql语句进行查询
- 代码示例:
public static void sqlProduct(Session session,String name){ session.beginTransaction(); String sql = "select * from product_ p where p.name like '%"+name+"%'"; Query query = session.createSQLQuery(sql); List<Object[]> products = query.list(); for(Object[] os :products){ for(Object o:os){ System.out.printf(o+"\t"); } System.out.println(); } session.getTransaction().commit(); }
hibernate关系
hibernate多对一关系
- 每一个Product都对应一个Category,而一个Category对应多个Product。如何使用hibernate实现这种关系的对应?
- 准备
Category.java
package com.how2java.pojo; /** * @author xsl20 */ public class Category { public int id; public String name; public int getId() { return id; } public String getName() { return name; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } }
- 在Product中添加Category属性
Category category; public void setCategory(Category category) { this.category = category; } public Category getCategory() { return category; }
- 在product.hbm.xml中设置多对一关系
<hibernate-mapping package="com.how2java.pojo"> <class name="Product" table="product_"> <id name="id" column="id"> <generator class="native"> </generator> </id> <property name="name" /> <property name="price" /> <many-to-one name="category" class="Category" column="cid" /> </class> </hibernate-mapping>
- 在hibernate.cfg.xml中添加Category的映射。
<mapping resource="com/how2java/pojo/Category.hbm.xml" />
- 测试
many to one
的关系
public static void testManyToOne(Session session,int id){ session.beginTransaction(); Category category = new Category(); category.setName("c1"); session.save(category); Product product = (Product) session.get(Product.class,id); product.setCategory(category); session.update(product); session.getTransaction().commit(); }
hibernate一对多关系
- 一个category对应着多个product,这就是多对一关系
- 为
Category
类添加Set<Product>
public Set<Product> products; public Set<Product> getProducts() { return products; } public void setProducts(Set<Product> products) { this.products = products; }
- 在category.hbm.xml中添加多对一关系
<set name="products" lazy="false"> <key column="cid" not-null="false"/> <one-to-many class="Product"/> </set>
- 测试
one-to-many
关系
public static void testOneToMany(Session session,int id){ session.beginTransaction(); Category category = (Category) session.get(Category.class,id); Set<Product> products = category.getProducts(); for(Product p :products){ System.out.println(p.getName()); } }
hibernate的多对多关系
-
一个user可以购买多个product,一种product也可以被多个user购买
-
一个多对多的关系的实现,必须使用中间表来维护这种关系。
-
添加
User.java
package com.how2java.pojo; import java.util.Set; /** * @author xsl20 */ public class User { public int id; public String name; Set<Product> products; public int getId() { return id; } public String getName() { return name; } public Set<Product> getProducts() { return products; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setProducts(Set<Product> products) { this.products = products; } }
Product.java
增加User集合
public Set<User> users; public Set<User> getUsers() { return users; } public void setUsers(Set<User> users) { this.users = users; }
- 在product.hbm.xml中添加多对多关系
<set name="users" table="user_product" lazy="false"> <key column="pid" /> <many-to-many column="uid" class="User" /> </set>
- 在hibernate.config.xml中添加User的映射
<mapping resource="com/how2java/pojo/User.hbm.xml" />
- 测试多对多关系
public static void testManyToMany(Session session,int id){ session.beginTransaction(); //新建三个用户 Set<User> users = new HashSet<>(); for(int i=0;i<3;i++){ User u = new User(); u.setName("user"+i); users.add(u); session.save(u); } //产品1被用户1,2,3购买 Product product = (Product) session.get(Product.class,id); product.setUsers(users); session.save(product); session.getTransaction().commit(); }
hibernate概念
事务
- hibernate的任何对数据有改动的操作,都发生在事务中。
- 在事务中发生的多个行为成功或者失败是统一的,要么都成功,要么都失败。
- MySQL必须是innoDB格式的才能支持事务。
- 查看表的类型:
show table status from test
延迟加载
- hibernate中的延迟加载分为属性的延迟加载和关系的延迟加载
- 属性的延迟加载:
- 当使用
load
的方式来获取对象时,只有访问了这个对象的属性,hibernate才会到数据库中进行查询,否则是不会访问数据库的。
- 当使用
- 关系的延迟加载
- 延迟加载由叫做
lazyload
,在one-many和many-many的时候都可以使用关系的延迟加载。 - 例如在获取category对象时,使用关系延迟加载就不会查询和其对应关系的product。只有通过category取product时才会加载。
- 延迟加载由叫做
级联
- 什么是级联?简单来讲就是没有配置级联时,当你删除分类时,对应的产品并不会被删除。但是如果配置了合适的级联,当删除分类时,对应的产品就会被删除。
- 级联一共有四种类型:
- all:所有的操作都执行级联操作
- none:所有的操作都不执行级联操作
- delete:删除操作执行级联操作
- save-update:保存和更新执行级联操作
- 指定级联的方式:
<set name="product" cascade="delete" lazy="false">
- 在运行删除代码时,如果删掉了category就会连带着将其对应的product都删掉。
一级缓存
- hibernate默认开启一级缓存,一级缓存存放在session上。
- 当对同一个id的对象进行连续两次查询时,第一次查询时,由于缓存中并没有该对象的存在,所以需要运行sql语句去查找。但是第二次查询时,缓存中已经存在了该对象,无需再次查找。
二级缓存
- hibernate的二级缓存是在SessionFactory上的。
- 两个session,都查询id为1的对象,第一个session运行时调用了sql语句,第二个session查询如果不需要执行sql语句,就说明开启了二级缓存。
- 在hibernate.config.xml中增加开启二级缓存的语句。
<property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
- 在src文件夹下创建ehcache.xml
<ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> </ehcache>
- 对二级缓存的实体类进行配置,增加
<cache usage="read-only">
<?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 package="com.how2java.pojo"> <class name="Category" table="category_"> <cache usage="read-only" /> <id name="id" column="id"> <generator class="native"> </generator> </id> <property name="name" /> <set name="products" lazy="true"> <key column="cid" not-null="false" /> <one-to-many class="Product" /> </set> </class> </hibernate-mapping>
分页查询
- 使用hibernate的Criteria语句进行分页查询,不区分数据库,命令都是一样的。
public static void searchPage(Session session,String name,int begin,int len){ session.beginTransaction(); Criteria criteria = session.createCriteria(Product.class); criteria.add(Restrictions.like("name","%"+name+"%")); criteria.setFirstResult(begin); criteria.setMaxResults(len); //从begin开始查找len个 List<Product> products = criteria.list(); for(Product p:products){ System.out.println(p.getName()); } session.getTransaction().commit(); }
获取对象的两种方式
-
获取对象有两种方式,一种是
get
,一种是load
-
它们的差别在于两点:
- 延迟加载
- load的方式是延迟加载的,只有对象的属性被访问时,才会调用sql语句。
- get的方式是非延迟加载的,无论后面是否会访问对象的属性,都会执行sql语句。
- 对于id不存在时的操作
- get方法会返回null
- load方法会抛出异常
- 延迟加载
获取session的两种方式
- hibernate有两种获取session的方式,一种是
openSession()
,另外一种是getCurrentSession()
- 这两种方式的区别在于:
openSession
每次都会得到一个新的Session对象,getCurrentSession
在同一个线程中得到的是同一个Session,但是在不同的线程中得到的是不同的Session对象。openSession
只有在增加,删除和修改时需要事务,而在查询时不需要事务,getCurrentSession
是所有操作都必须放在事务中进行,并且提交事务之后,session就会自动关闭。
乐观锁
- hibernate使用乐观锁来处理脏数据问题
- 首先创造一个场景来提供脏数据:
- 通过session1获取id为1的product,然后更改其价格增加1000
- 在更新该product之前,通过session2获取id为1的product再在原价格加1000
- 更新两个product
package com.how2java.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import com.how2java.pojo.Product; public class TestHibernate { public static void main(String[] args) { SessionFactory sf = new Configuration().configure().buildSessionFactory(); Session s1 = sf.openSession(); Session s2 = sf.openSession(); s1.beginTransaction(); s2.beginTransaction(); Product p1 = (Product) s1.get(Product.class, 1); System.out.println("产品原本价格是: " + p1.getPrice()); p1.setPrice(p1.getPrice() + 1000); Product p2 = (Product) s2.get(Product.class, 1); p2.setPrice(p2.getPrice() + 1000); s1.update(p1); s2.update(p2); s1.getTransaction().commit(); s2.getTransaction().commit(); Product p = (Product) s1.get(Product.class, 1); System.out.println("经过两次价格增加后,价格变为: " + p.getPrice()); s1.close(); s2.close(); sf.close(); } }
- 处理以上的脏数据需要配置Product.hbm.xml,增加版本信息。
<version name="version" column="ver" type="int"></version>
-
增加一个version字段,用于版本控制,这是乐观锁的核心思想。
-
版本控制的是更新时的过程,当session获取id为1的product时,version为1,当更新时,只有version仍然为1时,才能正确更新。并且依次正确的更新之后,version改为2。
-
修改
Product.java
,增加version属性
int version; public int getVersion() { return version; } public void setVersion(int version) { this.version = version; }
-
重新运行上面的代码,将如法正确运行。
-
乐观锁的原理:
- 假设数据库中产品的价格是10000,version是10
- session1,session2分别获取了该对象
- 都修改了对象的价格
- session1试图保存到数据库,检测version依旧=10,成功保存,并把version修改为11
- session2试图保存到数据库,检测version=11,说明该数据已经被其他人动过了,保存失败,抛出异常。
C3P0连接池
-
建立数据库连接时是比较消耗时间的,所以通常都回采用数据库连接池的技术来建立多条数据库连接的时间。
-
hibernate本身是提供了数据库连接池的,但是hibernate官网也不推荐它自带的数据库连接池。
-
一般都会使用第三方的数据库连接池。
-
C3P0是免费的第三方数据库连接池,并且有不错的表现。
-
当运行此数不大的时候,从运行效果来看,是看不出区别的,只有在高并发的情况下,才会体现出来。
-
配置hibernate.config.xml
<property name="hibernate.connection.provider_class"> org.hibernate.connection.C3P0ConnectionProvider </property> <property name="hibernate.c3p0.max_size">20</property> <property name="hibernate.c3p0.min_size">5</property> <property name="hibernate.c3p0.timeout">50000</property> <property name="hibernate.c3p0.max_statements">100</property> <property name="hibernate.c3p0.idle_test_period">3000</property> <!-- 当连接池耗尽并接到获得连接的请求,则新增加连接的数量 --> <property name="hibernate.c3p0.acquire_increment">2</property> <!-- 是否验证,检查连接 --> <property name="hibernate.c3p0.validate">false</property>
- 导入jar包,进行测试。
注解
- hibernate的注解是什么?简单来讲,hibernate原本的的映射信息,原本是保存在xml文件中的,现在直接使用JAVA的注解完成。
- 在
Product.java
上直接进行注解
package com.how2java.pojo; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "product_") public class Product { int id; String name; float price; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") public int getId() { return id; } public void setId(int id) { this.id = id; } @Column(name = "name") public String getName() { return name; } public void setName(String name) { this.name = name; } @Column(name = "price") public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } }
hibernate注解类与属性
- 类注解
@Entity
表示这是一个实体类@Table(name = "product_")
表示这个实体类对应的表名
@Entity @Table(name = "product_") public class Product { int id; String name; float price; }
- 属性注解
- 属性注解是配置在该属性对应的getter方法上的
@Id
表示这是主键@GeneratedValue(strategy = Generation Type.IDENTY)
表示自增长方式使用mysql自带的@Column(name = "id")
表示映射到字段id
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") public int getId() { return id; }
- 应用程序通过Hibernate把一个