Hibernate配置文件和常用方法函数详解(二)
配置映射文件和数据库的关系
<property name="hibernate.hbm2ddl.auto">update</property>
- update:如果数据库没有创建表,自动创建表
- create:每次启动hibernate都会创建表
- create-drop:每次启动hibernate都会创建表,并执行完后删除表
- validate :检查hbm的文件,如果和数据库的字段不一致会抛异常
数据库方言
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
*hbm.xml映射文件解说
实体类entity(model)的编写规则
- 我们在使用Hibernate时,书写User类,这个类我们称为JavaBean
- JavaBean可以理解为可以提供私有属性,并提供get和set方法
- POJO(Plain Ordinary Java Object)其实就是一个普通的JavaBean
- 实体类也被称为模型,在Hibernate中,也称为实体类,是与数据库表直接关联的
编写规则如下
- 提供一个无参public控制符的构造方法
- 提供一个标识(属性、映射数据表主键字段,提供id)
- 所有属性提供public访问控制符的get和set方法
- 标识属性应尽量使用基本类型的包装类
- 不要使用final修饰实体(将无法生成代理对象进行优化)
持久化对象的唯一标识OID
- Java按地址区分同一个类的不同对象
- 关系数据库使用主键区分同一条记录
- Hibernate使用OID来建立内存中的对象和数据库记录的对应关系(结论:对象的OID和数据库的表的主键对应)
- 保证OID的唯一性,应该让Hibernate来为OID进行赋值
区分自然主键和代理主键
- 主键需要具备不为空/不能重复/不能被修改
- 自然主键:在业务中,某个属性符合主键的三个要求,该属性可以作为主键列(登录名可以是自然主键)
- 代理主键:在业务中,不存在符合以上三个条件的属性,那么就增加一个没有意义的列作为主键
基本数据类型和包装类型
- 基本数据类型和包装类型对应hibernate的映射类型相同
- 基本类型无法表达null,数字类型的默认值是0
- 包装类默认值为null,当对于默认值由业务意义的时候需要使用包装类型
SQL、Hibernate和对象类型对应表
image.png主键的生成策略【非常重要】
1、id主键,如果属性与表字段不一样需要指定column
2、column属性对应表的列名
3、type数据类型
主键生成策略 常用2、5、6、7
1、increment由hibernate自己维护自动增加,原理使用max函数,然后+1,不建议使用,因为有线程安全问题
2、identity:hibernate使用数据库自带的自动增长方式
例如:mysql是auto_increment
3、squence:hibernate使用采用数据库系统
例如:oracle提供的系统
4、hilo:hibernate自己实现的算法,自己生成主键(hilo算法)几乎不用
5、native根据数据库自动选择identity、squnce、hilo
6、uuid采用字符串唯一值
7、assigned自然主键,由程序自动维护
具体代码如下:
<!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.gfy.hibernate.domain.User" table="t_user">
<!--1、id主键,如果属性与表字段不一样需要指定column
2、column属性对应表的列名
3、type数据类型-->
<id name="uid" column="id">
<!--主键生成策略 常用2、5、6、7
1、increment由hibernate自己维护自动增加,原理使用max函数,然后+1,不建议使用,因为有线程安全问题
2、identity:hibernate使用数据库自带的自动增长方式
例如:mysql是auto_increment
3、squence:hibernate使用采用数据库系统
例如:oracle提供的系统
4、hilo:hibernate自己实现的算法,自己生成主键(hilo算法)几乎不用
5、native根据数据库自动选择identitysqunce、hilo中的几乎不用
6、uuid采用字符串唯一值
7、assigned自然主键,由程序自动维护-->
<generator class="native"></generator>
</id>
<property name="username"></property>
<property name="password"></property>
<property name="gender"></property>
</class>
</hibernate-mapping>
普通属性
class标签的dynamic-insert=“true”,是否动态生成插入语句【如果属性字段为空,就不会有这些字段的插入语句】
<class name="com.gfy.hibernate.domain.User" table="t_user" dynamic-insert="true">
class标签的dynamic-update=“true”与insert类似
type的使用和设置,如图:
image.pngDate类型
- property中type不写,数据库对应的类型,datetime【年,月,日,时,分,秒】
- type中date数据库对应的类型,date【年、月、日】
- type中time,数据库对应的类型time【时、分、秒】
- type中timestamp,数据库对应的类型timestamp【时间戳】不太好用,如果需要时间戳,建议使用long类型
Hibernate实体的状态
状态介绍
- 实体Entity有三种状态,瞬时状态、持久状态、脱管状态
- 瞬时状态 transient、session没有缓存,数据库也没有记录,oid没有值
- 持久状态,persistent、session有缓存,数据库有记录,oid有值
- 脱管状态 datached session没有缓存,数据库有记录,oid有值
瞬时转换持久:新创建一个对象,经过save或者saveOrUpdate调用后,会变成持久状态
总结状态转换过程:查询操作get、load、createQuery、createCriteria等获的都是持久状态,瞬时状态调用save、update之后变成持久状态
持久状态转换脱管状态:session.clear()清除所有、session.close()关闭、session.evict(obj)清除指定的PO对象
一级缓存概念
一级缓存又称为session级别的缓存,当获得一次会话(session)、hibernate在session中创建多个集合(map)用于存放操作数据(PO对象)为程序,优化服务,如果之后需要相应的数据,hibernate优先从session中缓存中获取,如果有就使用,如果没有在查询数据库,当session关闭时,一级缓存销毁
User user = (User) session.get(User.class,1);
System.out.println(user);
User user1 = (User) session.get(User.class,1);
System.out.println(user1);
移除缓存
//方法一
session.clear();
//方法二
session.evict(user)
一级缓存快照【掌握】
快照与一级缓存一样的存放位置,对一级缓存数据进行备份,保证数据库的数据与一级缓存的数据必须一致,如果一级缓存修改了,在执行commit提交时,将自己刷新为一级缓存,执行update语句,将一级缓存的数据与更新到数据库
image.png
Session session = HibernateUtils.openSession();
session.beginTransaction().begin();
User user = (User) session.get(User.class,1);
user.setUsername("lisi02");
session.beginTransaction().commit();
session.close();
session.flush()手动刷新、保持一致缓存与数据库一致
此时会执行两条update语句,如果去除flush代码只有一条update语句
一级缓存细节
HQL的结果或会进入一级缓存,控制台查看只有一次查询语句
List list = session.createQuery("from User").list();
User user = (User) session.get(User.class,1);
System.out.println(user);
SQL不会对数据进行session缓存,控制台查看有两条查询语句
SQLQuery query = session.createSQLQuery("select * from t_user");
System.out.println(query.list());
User user = (User) session.get(User.class,1);
System.out.println(user);
Criteria 对数据进行一级缓存,控制台查看只有一次查询语句
Criteria criteria = session.createCriteria(User.class);
System.out.println(criteria.list());
User user = (User) session.get(User.class,1);
System.out.println(user);
其他API
save和persist方法的区别
save方法:瞬状态转换持久态,会初始化OID
1、执行save方法,立即触发insert语句,从数据库获得的主键的值OID
2、执行save之前,设置OID将忽略
3、如果执行查询session缓存移除时,在执行save方法,将执行insert
User user = new User();
user.setUid(2);
user.setUsername("April");
user.setPassword("123456");
user.setGender("female");
session.save(user);
image.png
image.png
在执行save方法,立即触发insert语句,从数据库中获取主键的值
persist方法:瞬时状态转换持久态
- 在保存对象之前,不能设置id,否则会报错
- save和persist都是持久化对象的作用
- save因为需要返回一个主键,因此会立即执行insert语句,而Persist在事务外部,调用时则不会立即执行insert语句,在事务内调用还会立即执行insert语句
Hibernate的多表关联关系映射
image.png一对多:一表(主表)必须有主键,多表(从表)必须外键,主表主键与从表外键,形成主外键关系
一对一:主外键关系
案例:顾客与订单的关系
分析:一个顾客有多个订单,一个订单只能有一个顾客,因此这种关系是一对多的关系,顾客是主表
顾客类
public class Customer {
private Integer id;
private String name;
private Set<Order> orders = new HashSet<Order>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
}
订单类
public class Order {
private Integer id;
private String orderNo;
private Customer customer;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
Customer.hbm.xml
<!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.gfy.hibernate.domain">
<class name="Customer" table="t_customer">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name"></property>
<set name="orders">
<key column="customer_id"></key>
<one-to-many class="Order"></one-to-many>
</set>
</class>
</hibernate-mapping>
Order.hbm.xml
<!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.gfy.hibernate.domain">
<class name="Order" table="t_order">
<id name="id">
<generator class="native"></generator>
</id>
<property name="orderNo"></property>
<many-to-one name="customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
具体执行的代码如下:
Session session = HibernateUtils.openSession();
session.beginTransaction().begin();
Customer customer = new Customer();
customer.setName("April");
Order order = new Order();
order.setCustomer(customer);
order.setOrderNo("JD"+Math.random()*10000);
order.setName("lipstick");
Order order1 = new Order();
order1.setCustomer(customer);
order1.setOrderNo("TB"+Math.random()*10000);
order1.setName("eyebrow pencil");
Set<Order> orders = new HashSet<>();
orders.add(order);
orders.add(order1);
customer.setOrders(orders);
//因为订单依赖主键的id 所以这里需要先保存顾客
session.save(customer);
session.save(order);
session.save(order1);
session.beginTransaction().commit();
session.close();
数据库查询结果
image.pngimage.png
那么问题来了,为啥我们在执行上面的代码的时候查询数据库在没有创建顾客和订单表的情况下能够查询到这两张表的数据呢,表是如何创建和插入数据的,其实我们在文章开头都已经讲过了,配置文件配置如下就可以了
<property name="hibernate.hbm2ddl.auto">update</property>
当然我们还需要把JavaBean实体类的映射属性和数据库表的属性进行关联
<mapping resource="com/gfy/hibernate/domain/Customer.hbm.xml"/>
<mapping resource="com/gfy/hibernate/domain/Order.hbm.xml"/>