1、简介
ORM(Object Relationship Mapping,对象关系映射),利用面向对象编写的应用最终将对象信息保存在关系型数据库中。但是如果直接在应用中编写底层数据库相关的SQL语句会使程序过分依赖特定数据库,不利于程序的移植与扩展,而且不同数据库的SQL语法不同,相同的功能在不同的数据库中有不同的实现方式,因此需要ORM框架实现面向对象与数据存储的分离。
Hibernate是Java领域在对JDBC进行轻量级封装的开源ORM框架。如下图所示,hibernate作为持久化层,将业务逻辑层生成的数据对象持久化地储存在数据库中
Hibernate执行过程如下,首先读取数据库配置文件hibernate.cfg.xml生成Configuration对象,之后读取对象的映射文件Entity.hbm.xml生成工厂对象,最后创建Session对象来进行数据库操作,Session类似于JDBC的Connection,通过session来进行数据库的增删改查操作。不同的是,一个connection可以提供多个session对象。在进行具体操作时,必须以事务Transaction的方式来执行,hibernate默认不会自动提交,所以在session操作完成之后需要手动提交事务才能保存数据库的操作。
可以通过sessionFactory.getCurrentSession()或者openSession()两种方法获取session对象,第一种会自动结束session,但是需要在cfg.xml文件中配置<property name="current_session_context_class">thread</property>。第二种不需要其他配置,但是需要手动结束session,否则每次SessionFactory会分配一个新的session对象,造成溢出。
2、创建一个Hibernate应用
1、首先需要导入jar包到lib目录下,除了引入hibernate所需的jar包外,还需要引入jdbc的jar包
2、在src目录下创建配置文件hibernate.cfg.xml,在其中配置数据库的url、驱动、对应的映射实体、用户名、密码和其他数据库的配置。
3、接着创建对应的实体化类,例如为student表在mypackage目录下创建StudentEntity类,该类要满足JavaBean要求(类公有,属性私有并有对应的get/set方法)
4、生成表和类的映射,创建Students.hbm.xml文件来描述对应的映射关系,也可以在StudentEntity类中通过注解来声明映射关系
5、通过Junit来测试映射对象插入数据库
以上为手动创建过程,通过IDEA可以十分方便的创建一个Hibernate应用:
项目根目录右击,选择new->Module弹出如下,选择Hibernate框架,然后勾选创建默认配置文件和引入数据库,并且第一次需要下载默认Libraries,之后弹出界面选择文件名和保存位置,完成工程创建。
之后弹出界面配置数据库与对应的映射。也可以在IDEA的View->Tool Windows->Persistence调出Hibernate窗口在界面的左下角,然后右击当前的HibernateXML项目,选择Generate Persistence Mapping生成映射界面
以XML配置映射
Choose Data Source选择要映射的数据库,Package选择生成的Java对象类保存的包名,Entity prefix/suffix设置对象类名前后缀,例如这里生成的是entity包下的StudentEntity类。Database Schema Mapping选择要映射的数据库中的表名,最后Generate Separate XML per Entity以XML文件的方式来为每张表生成映射关系文件。
设置之后自动生成了hibernate.cfg.xml文件如下,在<property>中设置数据库,其中url为数据库的地址,driver_class为数据库的驱动,dialect为数据库的方言,设置方言可以针对不同数据库进行优化,username、password分别为数据库的用户名和密码。show_sql、format_sql设置格式化输出数据库操作语句。hbm2ddl.auto设置操作数据库的方式,update代表更新数据表,create会新建一个表,如果原来存在则先删除之后再新建。
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost:3306/test</property>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="connection.username">root</property>
<property name="connection.password">password</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<mapping resource="StudentEntity.hbm.xml"/>
<mapping resource="CoursesEntity.hbm.xml"/>
</session-factory>
</hibernate-configuration>
随之生成的StudentEntity.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="entity.StudentEntity" table="student" schema="test">
<id name="id" column="id"/>
<property name="name" column="Name"/>
<property name="age" column="Age"/>
</class>
</hibernate-mapping>
并且IDEA的反向工程会在entity文件夹下自动生成Java的StudentEntity类 ,包含字段对应的变量及其get/set方法以及equals与hashCode方法,注意如果其中没有默认无参构造方法,则需要手动添加,否则之后执行会报错
package entity;
import java.util.Objects;
public class StudentEntity {
private int id;
private String name;
private Integer age;
public StudentEntity() { //手动添加无参构造方法
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StudentEntity that = (StudentEntity) o;
return id == that.id &&
Objects.equals(name, that.name) &&
Objects.equals(age, that.age);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
}
以注解方式表示映射
除了使用.hbm.xml文件之外,也可以通过注解的方式来表示Java类和数据表之间的映射关系,在import database schema的步骤中选择Generate JPA Annotations
随之生成了StudentEntity类文件如下,可见不仅生成了字段对应的变量与get/set方法,而且为类和方法添加了注解表明映射关系
@Entity
@Table(name = "student", schema = "test", catalog = "")
public class StudentEntity {
private int id;
private String name;
private Integer age;
@Id
@Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Basic
@Column(name = "Name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Basic
@Column(name = "Age")
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StudentEntity that = (StudentEntity) o;
return id == that.id &&
Objects.equals(name, that.name) &&
Objects.equals(age, that.age);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
}
3、使用Hibernate
这样就生成了一个简单的hibernate架构,我们通过JUnit对生成的Student映射进行测试,在StudentEntity.class文件内点击alt+insert,选择generate Test,生成类的测试方法,选择自动生成setUp与tearDown方法,用于在测试之前和之后执行的操作
如下所示为生成的StudentEntityTest文件,在setUp中创建hibernate服务并开启事务,在tearDown中关闭资源。在@test注解中创建一个方法用于测试,创建一个对象并保存到数据库的对应的student表中。之后查看数据库,发现插入了学生“赵六”的数据。
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.service.ServiceRegistry;
import org.junit.jupiter.api.Test;
class StudentEntityTest {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@org.junit.jupiter.api.BeforeEach
void setUp() {
//创建服务注册对象
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().configure().build();
//创建会话工厂对象
sessionFactory = new MetadataSources(serviceRegistry).buildMetadata().buildSessionFactory();
//会话对象
session = sessionFactory.openSession();
//开启事物
transaction = session.beginTransaction();
}
@org.junit.jupiter.api.AfterEach
void tearDown() {
//提交事物
transaction.commit();
//关闭会话
session.close();
//关闭会话工厂
sessionFactory.close();
}
@Test
void testStudent(){
StudentEntity student = new StudentEntity(); //创建一个对象
student.setId(1007);
student.setName("赵六");
student.setAge(12);
session.save(student); //将对象保存到数据库中对应的表
}
}
CRUD操作
hibernate的增删改查通过session的save()、delete()、update()、get()/load()方法来实现,其中get()会在调用后立即查询并返回一个对象,load()只保存对象id,当之后用到对象其他属性时才会执行查询。而且在查询不到对象信息时,get()返回null,而load()会抛出一个hibernate.ObjectNotFoundException,例如测试文件如下
@Test
void testStudent(){
StudentEntity student = new StudentEntity();
student.setId(1007);
student.setName("赵六");
student.setAge(12);
session.save(student); //向数据库中持久化保存对象
StudentEntity s2=session.get(StudentEntity.class,1007); //通过主键id取出对象
System.out.println(s2.getName());
s2.setAge(22);
session.update(s2); //修改对象
session.delete(s2); //删除对象
}
组件属性
有时需要将一张表的多个属性组合成一个对象作为映射,而不是直接映射。例如students表有city和street两个字段,需要首相将其映射为一个Address对象,然后再作为Student的一个属性,这就是组件。
首先声明一个Adress类
public class Address {
private String city;
private String street;
public Address() {
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
接着修改Student类,添加address属性并设置对应的get/set方法
public class StudentEntity {
private int id;
private String name;
private Integer age;
private Address address; //添加address对象属性
......
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
接着修改student.hbm.xml映射文件,增加address组件
<class name="entity.StudentEntity" table="students" schema="test">
<id name="id" column="id"/>
<property name="name" column="Name"/>
<property name="age" column="Age"/>
<component name="address" class="entity.Address">
<property name="city" column="city"/>
<property name="street" column="street"/>
</component>
</class>
接下来在测试方法中首先获取id为1006的学生,然后为其添加address对象属性后保存
void testStudent(){
StudentEntity s2=session.get(StudentEntity.class,1006); //通过主键id取出对象
Address address=new Address();
address.setCity("北京");
address.setStreet("新直门");
s2.setAddress(address); //为学生添加address对象
session.save(s2);
}
查看数据库结果如下,可见已经保存了address对应的city和street字段