Hibernate
框架是用来提高开发效率的,它封装一些功能可以直接使用即可。所以框架可以理解是一个半成品的项目。
Hibernate则是用来操作数据库的框架,在操作数据库的过程中可以用面向对象的方式来完成,而不需要书写SQL语句。
Hibernate属于ORM性质的框架,其中ORM是指使用配置或其他手段将对象信息与数据库的表进行对应。
hibernate属于四级,是完全面向对象的形式操作数据库的。
mybatis属于二级。
dbUtils属于一级。
Hibernate使用
Hibernate框架搭建
- 建表语句
创建数据库和表的建表语句如下:
create database hibernate_demo;
create table `cst_customer` (
`cust_id` bigint(32) not null auto_increment comment '客户编号(主键)',
`cust_name` varchar(32) not null comment '客户名称(公司名称)',
`cust_source` varchar(32) default null comment '客户信息来源',
`cust_industry` varchar(32) default null comment '客户所属行业',
`cust_level` varchar(32) default null comment '客户级别',
`cust_linkman` varchar(64) default null comment '联系人',
`cust_phone` varchar(64) default null comment '固定电话',
`cust_mobile` varchar(16) default null comment '移动电话',
primary key (`cust_id`)
) engine=innodb auto_increment=1 default charset=utf8;
- 数据库配置
创建完数据库之后,需要编写数据库的一些配置信息,名称默认为: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>
<!-- 数据库url -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_demo?useUnicode=true&characterEncoding=utf-8</property>
<!-- 数据库连接用户名 -->
<property name="hibernate.connection.username">root</property>
<!-- 数据库连接密码 -->
<property name="hibernate.connection.password">123456</property>
<!-- 数据库方言
不同的数据库中,sql语法略有区别. 指定方言可以让hibernate框架在生成sql语句时.针对数据库的方言生成.
sql99标准: DDL 定义语言 库表的增删改查
DCL 控制语言 事务 权限
DML 操纵语言 增删改查
注意: MYSQL在选择方言时,请选择最短的方言.
-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- #hibernate.show_sql true
#hibernate.format_sql true-->
<!-- 将hibernate生成的sql语句打印到控制台 -->
<property name="hibernate.show_sql">true</property>
<!-- 将hibernate生成的sql语句格式化(语法缩进) -->
<property name="hibernate.format_sql">true</property>
<!--
## auto schema export 自动导出表结构. 自动建表
#hibernate.hbm2ddl.auto create 自动建表.每次框架运行都会创建新的表.以前表将会被覆盖,表数据会丢失.(开发环境中测试使用)
#hibernate.hbm2ddl.auto create-drop 自动建表.每次框架运行结束都会将所有表删除.(开发环境中测试使用)
#hibernate.hbm2ddl.auto update(推荐使用) 自动生成表.如果已经存在不会再生成.如果表有变动.自动更新表(不会删除任何数据).
#hibernate.hbm2ddl.auto validate 校验.不自动生成表.每次启动会校验数据库中表是否正确.校验失败. -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 引入orm元数据路径书写: 填写src下的路径-->
<mapping resource="com/legend/hibernate/domain/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
注意一:所有的配置信息都必须在<mapping>标签之前配置,否则会出错。
注意二:在使用过程中如果出现java.sql.SQLException: Incorrect string value:xxx错误的话是因为这里没有& 加上即可。
jdbc:mysql://localhost:3306/hibernate_demo?useUnicode=true&amp;characterEncoding=utf-8
注意三:由于Hibernate默认的建表方式不是utf8,想要设置Hibernate默认建表方式可以重写方言来实现:
public class MYSQLDialectUTF8 extends MySQLDialect {
@Override
public String getTableTypeString() {
return " ENGINE=InnoDB DEFAULT CHARSET=utf8";
}
}
然后在连接数据库的时候带上编码,以及方言使用我们重写的类使用如下方式:
<!-- 数据库url -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_demo?useUnicode=true&characterEncoding=utf-8</property>
<!-- 数据库方言-->
<property name="hibernate.dialect">com.legend.hibernate.charset.MYSQLDialectUTF8</property>
这样数据库就默认支持utf8了,如果有必要还需要修改MYSQL的默认编码方式,在MYSQL安装目录找到my.ini,将编码全部改成utf8即可。
- 映射实体配置
创建和数据库表映射的实体类:Customer.java
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_linkman;
private String cust_phone;
private String cust_mobile;
...Get & Set...
}
在实体类的同包下创建它的数据映射配置类Customer.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">
<!-- 配置表与实体对象的关系 -->
<!-- package属性:填写一个包名.在元素内部凡是需要书写完整类名的属性,可以直接写简答类名了. -->
<hibernate-mapping package="com.legend.hibernate.domain" >
<!--
class元素: 配置实体与表的对应关系的
name: 完整类名
table:数据库表名
-->
<class name="Customer" table="cst_customer">
<!-- id元素:配置主键映射的属性
name: 填写主键对应属性名
column(可选): 填写表中的主键列名.默认值:列名会默认使用属性名
type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
每个类型有三种填法: java类型|hibernate类型|数据库类型
not-null(可选):配置该属性(列)是否不能为空. 默认值:false
length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
-->
<id name="cust_id">
<!-- generator:主键生成策略:主键生成策略,就是每条记录录入时,主键的生成规则
identity:主键自增,有数据库维护主键值,录入不需要指定主键。
sequence:Oracle中的主键生成策略。
increment(了解):主键自增,有hibernate来维护。每次插入前会先查询表中id最大值,然后+1作为新主键。
hilo(了解):高低位算法,主键自增,由hibernate来维护。(开发不适用)
native(方便):hilo + seqence + identity自动三选一。
uuid:产生随机字符串作为主键,主键类型必须为String类型。
assigend:自然主键生成策略。hibernate不会管理主键值,由开发人员操作。
-->
<generator class="native"></generator>
</id>
<!-- property元素:除id之外的普通属性映射
name: 填写属性名
column(可选): 填写列名
type(可选):填写列(属性)的类型.hibernate会自动检测实体的属性类型.
每个类型有三种填法: java类型|hibernate类型|数据库类型
not-null(可选):配置该属性(列)是否不能为空. 默认值:false
length(可选):配置数据库中列的长度. 默认值:使用数据库类型的最大长度
-->
<property name="cust_name" column="cust_name">
<!-- <column name="cust_name" sql-type="varchar" ></column> -->
</property>
<property name="cust_source" column="cust_source"></property>
<property name="cust_industry" column="cust_industry" ></property>
<property name="cust_level" column="cust_level" ></property>
<property name="cust_linkman" column="cust_linkman" ></property>
<property name="cust_phone" column="cust_phone" ></property>
<property name="cust_mobile" column="cust_mobile" ></property>
</class>
</hibernate-mapping>
到这里,Hibernate框架基本上已经搭建完毕。然后我们来测试一下Hibernate是否能正常连接:
@Test
public void test01(){
Configuration conf = new Configuration().configure();
SessionFactory sessionFactory = conf.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
//----------------------------------------------
Customer c = new Customer();
c.setCust_name("完美时空");
session.save(c);
//----------------------------------------------
tx.commit();
session.close();
sessionFactory.close();
}
hibernate API详解
-
Configuration类
Configuration是hibernate的入口,负责管理Hibernate的配置信息,这些配置信息都是从配置文件hibernate.cfg.xml或者Hibernate.properties读取的,
当然也可以自定义文件名称,只要在实例化Configuration的时候指定具体的路径就可以了
//1 创建,调用空参构造
Configuration conf = new Configuration();
//2 读取指定主配置文件 => 空参加载方法,加载src下的hibernate.cfg.xml文件
conf.configure();
//3 读取指定orm元数据(扩展),如果主配置中已经引入映射配置.不需要手动加载
//conf.addResource(resourceName);
//conf.addClass(persistentClass);
-
SessionFactory类
SessionFactory用于创建操作数据库核心对象session对象的工厂。
//4 根据配置信息,创建 SessionFactory对象
SessionFactory sf = conf.buildSessionFactory();
//5 获得session
//打开一个新的session对象
sf.openSession();
//获得一个与线程绑定的session对象(明天讲解)
sf.getCurrentSession();
需要注意的是:
1.sessionfactory 负责保存和使用所有配置信息。消耗内存资源非常大。
2.sessionFactory属于线程安全的对象设计。
结论: 保证在web项目中,只创建一个sessionFactory。
-
Session
Session对象表示hibernate和数据库之间的连接会话,类似于JDBC的Connection对象,可以对数据库进行增删改查。它是Hibernate操作数据库的核心对象。
Session session = sf.openSession();
//4 session获得操作事务的Transaction对象
//获得操作事务的tx对象
//Transaction tx = session.getTransaction();
//开启事务并获得操作事务的tx对象(建议使用)
Transaction tx2 = session.beginTransaction();
//----------------------------------------------
// 操作数据库代码
//----------------------------------------------
tx2.commit();//提交事务
tx2.rollback();//回滚事务
session.close();//释放资源
sf.close();//释放资源
Hibernate增删改查
- 增加数据
@Test
public void insert() {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 增加数据
Customer customer = new Customer();
customer.setCust_name("人生得意须纵欢");
session.save(customer);
transaction.commit();
session.close();
sessionFactory.close();
}
- 查询数据
@Test
public void query() {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 查询数据
Customer customer = session.get(Customer.class, 1l);
System.out.println(customer.getCust_name());
transaction.commit();
session.close();
sessionFactory.close();
}
注意:由于id是long类型的数据,所以查询id为1的时候,参数需要填写1l。
- 修改数据
@Test
public void update() {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 修改数据 -> 拿到对象 -> 修改数据 -> 更新数据
Customer customer = session.get(Customer.class, 1l);
customer.setCust_name("莫使金樽空对月");
session.update(customer);
transaction.commit();
session.close();
sessionFactory.close();
}
注意:根据面向对象的思想来说,首先要拿到对象,然后对对象的内容进行修改,然后再存回数据库。
- 删除数据
@Test
public void delete() {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
// 开启事务
Transaction transaction = session.beginTransaction();
// 删除数据 -> 拿到对象 -> 删除数据
Customer customer = session.get(Customer.class, 1l);
session.delete(customer);
transaction.commit();
session.close();
sessionFactory.close();
}
注意:根据面向对象的思想来说,首先要拿到对象,然后再删除对象。
-
Hibernate封装
我们知道,由于SessionFactory是一个耗费资源的对象,而且一个Web应用一般只对应一个SessionFactory,所以在使用的时候需要对其进行封装。
public class HibernateUtils {
private static SessionFactory mSessionFactory;
static{
//1 创建,调用空参构造
Configuration conf = new Configuration().configure();
//2 根据配置信息,创建 SessionFactory对象
mSessionFactory = conf.buildSessionFactory();
}
//获得session => 获得全新session
public static Session openSession(){
//3 获得session
Session session = mSessionFactory.openSession();
return session;
}
//获得session => 获得与线程绑定的session
public static Session getCurrentSession(){
//3 获得session
Session session = mSessionFactory.getCurrentSession();
return session;
}
}
-
解决中文乱码监听器
这个监听器是针对整个Web应用的数据库乱码处理的,是通用的类。
/**
* 通用编码解决方案
*/
public class GenericEncodingFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
chain.doFilter(myrequest, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
}
// 自定义request对象
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
private boolean hasEncode;
public MyRequest(HttpServletRequest request) {
super(request);// super必须写
this.request = request;
}
// 对需要增强方法 进行覆盖
@Override
public Map getParameterMap() {
// 先获得请求方式
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
// post请求
try {
// 处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get")) {
// get请求
Map<String, String[]> parameterMap = request.getParameterMap();
if (!hasEncode) { // 确保get手动编码逻辑只运行一次
for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
try {
// 处理get乱码
values[i] = new String(values[i].getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
hasEncode = true;
}
return parameterMap;
}
return super.getParameterMap();
}
@Override
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
return values[0]; // 取回参数的第一个值
}
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;
}
}
然后在web.xml配置该监听器:
<!-- 通用乱码过滤器 -->
<filter>
<filter-name>GenericEncodingFilter</filter-name>
<filter-class>com.legend.hibernate.filter.GenericEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GenericEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Hibernate主键规则
Hibernate的主键生成规则需要在ORM的映射配置文件中进行修改,具体可以参考下面代码:
<id name="cust_id">
<!-- generator:主键生成策略:主键生成策略,就是每条记录录入时,主键的生成规则
identity:主键自增,有数据库维护主键值,录入不需要指定主键。
sequence:Oracle中的主键生成策略。
increment(了解):主键自增,有hibernate来维护。每次插入前会先查询表中id最大值,然后+1作为新主键。
hilo(了解):高低位算法,主键自增,由hibernate来维护。(开发不适用)
native(方便):hilo + seqence + identity自动三选一。
uuid:产生随机字符串作为主键,主键类型必须为String类型。
assigend:自然主键生成策略。hibernate不会管理主键值,由开发人员操作。
-->
<generator class="native"></generator>
一般推荐使用native的规则,因为这种在Mysql、Oracle数据库中都是通用的。
ibernate对象状态
Hibernate中的对象包含三种状态,分为是:瞬时状态、持久化状态、游离或托管状态。
代码如下所示:
/**
* 对象的三种状态
* save方法:其实不能理解为保存,应该是将瞬时状态转换为持久化状态。
* 主键自增:执行save方法时,因为对象转换为持久化状态,必须生成ID值,需要执行insert语句生成。
*/
@Test
public void test1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 没有ID,没有与session关联 => 瞬时状态
Customer customer = new Customer();
customer.setCust_name("联想");
// 有id和有关联 => 持久化状态
session.save(customer);
transaction.commit();
// 有id但没关联 => 游离或托管状态
session.close();
}
注意:凡是经过session操作的对象都是持久化状态的对象。
ibernate进阶
一级缓存
缓存是提高效率的一种手段,Hibernate中的缓存技术也是用来提高数据库的一种手段。
Session缓存技术 :提高查询的效率。
缓存快照:减少不必要的修改语句发送。
事务操作
- 事务特性
特性 | 说明 |
---|---|
原子性(atomicity) | 指的是事务最小不可分割的单位,事务中的操作要么都发生,要么都不发生。 |
一致性(consistency) | 指的是事务前后数据保持完整,不会出现不统一的情况。 |
隔离性(isolation) | 操作事务的时候,不同的事务之间相互隔离,不能被干扰到。 |
持久性(durability) | 事务一旦提交,那么数据都真的持久化保存到设备中去了,不能再回滚或任何的更改。 |
-
事务的隔离级别
事务的隔离级别主要是用于处理数据库并发时的一种手段。
名称 | 说明 |
---|---|
TRANSACTION_READ_COMMITTED | 指示不可以发生脏读的常量;不可重复和虚读可以发生。 |
TRANSACTION_READ_UNCOMMITTED | 指示可以发生脏读、不可重复读和虚读的常量。 |
TRANSACTION_REPEATABLE_READ | 指示不可以发生脏读和不可重复读的常量;虚读可以发生。 |
TRANSACTION_SERIALIZABLE | 指示不可以发生脏读、不可重复读和虚读的常量。 |
Hibernate中支持数据库的事务操作,Mysql的默认隔离级别是4,我们可以在hibernate中对其进行配置:
<!-- 指定hibernate操作数据库时的隔离级别
#hibernate.connection.isolation 1|2|4|8
0001 1 读未提交
0010 2 读已提交
0100 4 可重复读
1000 8 串行化
-->
<property name="hibernate.connection.isolation">4</property>
我们平时在操作JDBC时,每次去获取Connection都只能得到一个,并不能满足高并发的情况。因为Connection是线程不安全的。
每次获得connection都需要浪费cpu资源和内存资源,是很浪费资源的。所以诞生了数据库连接池。数据库连接池的Connection都是从ThreadLocal中去
获取的,如果ThreadLocal中存在则用,保证多个dao的操作都是同一个Connection对象,以保证事务。
那么在项目中如何管理事务呢?
1、在业务开始之前打开事务,在业务执行过后提交事务,出现异常则回滚事务。
public void save(Customer customer) {
Session session = HibernateUtils.getCurrentSession();
// 打开事务
Transaction transaction = session.beginTransaction();
CustomerDao dao = new CustomerDao();
try {
dao.save(customer);
}catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
// 关闭事务
transaction.commit();
}
2、在dao层操作数据库需要用到session对象,我们要确保dao层和service层使用同一个session对象。
<!--指定session与当前线程绑定,解决session不一致的问题-->
<property name="hibernate.current_session_context_class">thread</property>
此时,我们就可以通过SessionFactory的getCurrentSession()来获取Session对象,而且每次获取的都是同一个:
@Test
public void testSession() {
Session session1 = HibernateUtils.getCurrentSession();
Session session2 = HibernateUtils.getCurrentSession();
System.out.println(session1 == session2);
Session session3 = HibernateUtils.openSession();
Session session4 = HibernateUtils.openSession();
System.out.println(session3 == session4);
}
输出结果:
true
false
结论:由此可以证明getCurrentSession()每次获取都是同一个Session对象,而openSession()方法获取的不是同一个对象。
3、通过getCurrentSession()方法获得的Session对象在事务提交后,Session会自动关闭,如果手动关闭则会异常。
单表查询
Hibernate中有三种查询方式:原生SQL查询(复杂业务)、HQL查询、Ceiteria查询。
- 原生SQL查询
1、基本查询操作
在企业开发中,复杂的业务操作都还是使用原生SQL的方式进行操作。
@Test
public void queryBySQL() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行查询操作
String sql = "select * from cst_customer";
SQLQuery query = session.createSQLQuery(sql);
List<Object[]> list = query.list();
// query.uniqueResult();
for (Object[] objs : list) {
System.out.println(Arrays.toString(objs));
}
transaction.commit();
session.close();
}
需要注意调用list()方法的返回值的泛型是Object[]数组,封装所查询到的数据表的所有内容。
如果想将数据结果集封装到指定的对象中的话,则需要进行指定。
@Test
public void query() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行查询操作
String sql = "select * from cust_customer";
SQLQuery query = session.createSQLQuery(sql);
// 指定将结果集封装到哪个对象中
query.addEntity(Customer.class);
List<Customer> list = query.list();
System.out.println(list.get(0).getCust_name());
transaction.commit();
session.close();
}
2、条件查询操作
需要注意,这里跟Mysql保持一致,位置都是从0开始的。
@Test
public void queryByWhere() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行查询操作
String sql = "select * from cust_customer where cust_id = ?";
SQLQuery query = session.createSQLQuery(sql);
// 指定占位符的参数
query.setParameter(0, 1l);
// 指定将结果集封装到哪个对象中
query.addEntity(Customer.class);
List<Customer> list = query.list();
System.out.println(list.get(0).getCust_name());
transaction.commit();
session.close();
}
3、分页查询操作
@Test
public void queryByage() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行查询操作
String sql = "select * from cust_customer limit ?,?";
SQLQuery query = session.createSQLQuery(sql);
// 指定占位符的参数
query.setParameter(0, 0);
query.setParameter(1, 1);
// 指定将结果集封装到哪个对象中
query.addEntity(Customer.class);
List<Customer> list = query.list();
System.out.println(list.get(0).getCust_name());
transaction.commit();
session.close();
}
-
HQL查询
HQL(Hibernate Query Language)查询是Hibernate独有的面向对象的查询语言。在多表查询,但是表不复杂的情况下使用。
1、基本查询操作
@Test
public void queryByHQL() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 1> 书写HQL语句
//String hql = "from com.legend.hibernate.domain.Customer"; // 类名重复时使用
String hql = "from Customer";
// 2> 根据HQL语句创建查询对象
Query query = session.createQuery(hql);
// 3> 根据查询对象获得查询结果
List<Customer> list = query.list();// 多条结果使用,返回list类型数据。
//query.uniqueResult(); // 接收唯一的结果。
System.out.println(list.size());
transaction.commit();
}
2、条件查询操作
在HQL语句中是不可能出现任何数据库相关的内容,只会出现对象名和属性名,所以条件查询跟的条件是属性名称。
@Test
public void queryHQLBy() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 1> 书写HQL语句,条件是属性名
String hql = "from Customer where cust_id = 1l";
// 2> 根据HQL语句创建查询对象
Query query = session.createQuery(hql);
// 3> 根据查询对象获得查询结果
Customer customer = (Customer) query.uniqueResult();
System.out.println(customer.getCust_name());
transaction.commit();
}
如果条件想根据占位符的方式进行输入可以采用如下写法:
@Test
public void queryHQLBy1() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 1> 书写HQL语句,条件是属性名
String hql = "from Customer where cust_id = ?";
// 2> 根据HQL语句创建查询对象
Query query = session.createQuery(hql);
// 3> 根据占位符传入参数
//query.setLong(0, 1l);
query.setParameter(0, 1l);
// 4> 根据查询对象获得查询结果
Customer customer = (Customer) query.uniqueResult();
System.out.println(customer.getCust_name());
transaction.commit();
}
在HQL中还有一种命名占位符的方式,它的写法如下:
@Test
public void queryHQLBy2() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 1> 书写HQL语句,条件是属性名
String hql = "from Customer where cust_id = :xxx";
// 2> 根据HQL语句创建查询对象
Query query = session.createQuery(hql);
// 3> 根据占位符传入参数
query.setParameter("xxx", 1l);
// 4> 根据查询对象获得查询结果
Customer customer = (Customer) query.uniqueResult();
System.out.println(customer.getCust_name());
transaction.commit();
}
3、分页查询操作
在HQL中使用分页查询,可以使用如下方式:
@Test
public void queryHQLByPage() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 1> 书写HQL语句,条件是属性名
String hql = "from Customer";
// 2> 根据HQL语句创建查询对象
Query query = session.createQuery(hql);
// 3> 设置分页信息
query.setFirstResult(0);
query.setMaxResults(1);
// 4> 根据查询对象获得查询结果
List<Customer> list = query.list();
System.out.println(list.get(0).getCust_name());
transaction.commit();
}
4、聚合函数查询
@Test
public void query() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行数据库操作
String hql1 = "select count(*) from com.legend.hibernate.domain.Customer";
String hql2 = "select sum(cust_id) from com.legend.hibernate.domain.Customer";
String hql3 = "select avg(cust_id) from com.legend.hibernate.domain.Customer";
String hql4 = "select max(cust_id) from com.legend.hibernate.domain.Customer";
String hql5 = "select min(cust_id) from com.legend.hibernate.domain.Customer";
Query query = session.createQuery(hql5);
Number number = (Number) query.uniqueResult();
System.out.println(number);
// 提交事务
transaction.commit();
session.close();
}
其中Number对象是Long、Integer....的父类,所以可以接收全部类型。
5、投影查询
所谓投影查询,其实就是查询对象的某一个属性。
@Test
public void query() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 查询单个属性
String hql = "select cust_name from com.legend.hibernate.domain.Customer";
Query query = session.createQuery(hql);
List list = query.list();
System.out.println(list);
// 查询多个属性
String hql1 = "select cust_name,cust_id from com.legend.hibernate.domain.Customer";
Query query1 = session.createQuery(hql1);
List<Object[]> list1 = query1.list();
System.out.println(list1);
// 查询多个属性封装到Customer,构造函数必须存在,且顺序一致。
String hql2 = "select new Customer(cust_id,cust_name) from com.legend.hibernate.domain.Customer";
Query query2 = session.createQuery(hql2);
List<Customer> list2 = query2.list();
System.out.println(list2);
transaction.commit();
session.close();
}
-
Criteria查询
Criteria查询也是Hibernate自创的查询方式,它是没有数据库语句的面向对象查询方式。主要用于单表查询。
1、基本查询操作
@Test
public void query() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行查询操作
Criteria criteria = session.createCriteria(Customer.class);
// Customer customer = (Customer) criteria.uniqueResult();
List<Customer> list = criteria.list();
System.out.println(list.size());
transaction.commit();
session.close();
}
2、条件查询操作
使用Criteria进行条件语句查询时,有如下方法:
数据库符号 | 方法名 |
---|---|
> | Restrictions.eq(String propertyName, Object value) |
>= | Restrictions.ge(String propertyName, Object value) |
< | Restrictions.lt(String propertyName, Object value) |
<= | Restrictions.le(String propertyName, Object value) |
== | Restrictions.eq(String propertyName, Object value) |
!= | Restrictions.ne(String propertyName, Object value) |
in | Restrictions.in(String propertyName, Object[] values) |
between and | Restrictions.between(String propertyName, Object lo, Object hi) |
like | Restrictions.like(String propertyName, Object value) |
is not null | Restrictions.isNotNull(String propertyName) |
is null | Restrictions.isNull(String propertyName) |
or | Restrictions.or(Criterion lhs, Criterion rhs) |
and | Restrictions.and(Criterion lhs, Criterion rhs) |
范例:
@Test
public void query1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行查询操作
Criteria criteria = session.createCriteria(Customer.class);
// 添加查询参数
criteria.add(Restrictions.eq("cust_id", 1l));
Customer customer = (Customer) criteria.uniqueResult();
System.out.println(customer.getCust_name());
transaction.commit();
session.close();
}
3、分页查询操作
@Test
public void queryByPage() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行分页查询操作
Criteria criteria = session.createCriteria(Customer.class);
// 添加分页信息
criteria.setFirstResult(0);
criteria.setMaxResults(1);
// 执行查询操作
List<Customer> list = criteria.list();
System.out.println(list.get(0).getCust_name());
transaction.commit();
session.close();
}
4、查询总记录
如果想通过聚合函数查询总行数,可以使用下面的方法:
@Test
public void queryByCount() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 执行分页查询操作
Criteria criteria = session.createCriteria(Customer.class);
// 设置查询聚合函数 => 总行数
criteria.setProjection(Projections.rowCount());
// 执行查询操作
Long count = (Long) criteria.uniqueResult();
System.out.println(count);
transaction.commit();
session.close();
}
多表查询
- 原生SQL查询
交叉连接-笛卡尔积(避免)
select * from A,B
内连接
|-隐式内连接
select * from A,B where b.aid = a.id
|-显式内连接
select * from A inner join B on b.aid = a.id
外连接
|- 左外
select * from A left [outer] join B on b.aid = a.id
|- 右外
select * from A right [outer] join B on b.aid = a.id
多表查询待补充。
多表映射
假设我们有两个实体Customer(顾客)和LinkMan(联系人),那么他们的正常关系是一个顾客对应多个联系人。
一对多和多对一
我们知道一个客户对应多个联系人,在实体中设计的话则需要在客户实体中设计集合来包含多个联系人,用来表示一对多的关系;然后在联系人实体中设计
单个客户的对象来表明多对一的关系。那么我们的实体设计如下:
Customer.java
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_linkman;
private String cust_phone;
private String cust_mobile;
// 表明一对多的关系
private Set<LinkMan> linkMens = new HashSet<>();
...省略get和set方法...
}
LinkMan.java
public class LinkMan {
private Long lkm_id;
private Character lkm_gender;
private String lkm_name;
private String lkm_phone;
private String lkm_email;
private String lkm_qq;
private String lkm_mobile;
private String lkm_memo;
private String lkm_position;
// 表达多对一的关系
private Customer customer;
...省略get和set方法...
}
Customer这个属性就代表了客户表和联系人表之间的cid了。这样就表示出客户和联系人之间一对多和多对一的关系。
其中Customer和LinkMan对应如下两张表,下面的建表命令:
-- Customer
create table `cst_customer` (
`cust_id` bigint(32) not null auto_increment comment '客户编号(主键)',
`cust_name` varchar(32) not null comment '客户名称(公司名称)',
`cust_user_id` bigint(32) default null comment '负责人id',
`cust_create_id` bigint(32) default null comment '创建人id',
`cust_source` varchar(32) default null comment '客户信息来源',
`cust_industry` varchar(32) default null comment '客户所属行业',
`cust_level` varchar(32) default null comment '客户级别',
`cust_linkman` varchar(64) default null comment '联系人',
`cust_phone` varchar(64) default null comment '固定电话',
`cust_mobile` varchar(16) default null comment '移动电话',
primary key (`cust_id`)
) engine=innodb auto_increment=1 default charset=utf8;
-- LinkMan
create table `cst_linkman` (
`lkm_id` bigint(32) not null auto_increment comment '联系人编号(主键)',
`lkm_name` varchar(16) default null comment '联系人姓名',
`lkm_cust_id` bigint(32) not null comment '客户id',
`lkm_gender` char(1) default null comment '联系人性别',
`lkm_phone` varchar(16) default null comment '联系人办公电话',
`lkm_mobile` varchar(16) default null comment '联系人手机',
`lkm_email` varchar(64) default null comment '联系人邮箱',
`lkm_qq` varchar(16) default null comment '联系人qq',
`lkm_position` varchar(16) default null comment '联系人职位',
`lkm_memo` varchar(512) default null comment '联系人备注',
primary key (`lkm_id`),
key `fk_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
constraint `fk_cst_linkman_lkm_cust_id` foreign key (`lkm_cust_id`) references `cst_customer` (`cust_id`) on delete no action on update no action
) engine=innodb auto_increment=3 default charset=utf8;
-
ORM映射
当我们创建完实体类后,就需要做实体类的ORM映射来和数据库表建立关联了,我们分别创建两张ORM映射配置
Customer.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 package="com.legend.hibernate.domain" >
<class name="Customer" table="cst_customer">
<id name="cust_id">
<generator class="native"></generator>
</id>
<property name="cust_name">
</property>
<property name="cust_source"></property>
<property name="cust_industry" ></property>
<property name="cust_level"></property>
<property name="cust_linkman"></property>
<property name="cust_phone"></property>
<property name="cust_mobile"></property>
<!--集合,一对多关系,在配置文件中配置
name属性:集合属性名
column属性:外键列名
class属性:与我关联的对象完整类名
-->
<set name="linkMens">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
</class>
</hibernate-mapping>
LinkMan.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 package="com.legend.hibernate.domain" >
<class name="LinkMan" table="cst_linkman">
<id name="lkm_id">
<generator class="native"></generator>
</id>
<property name="lkm_gender"></property>
<property name="lkm_name"></property>
<property name="lkm_phone"></property>
<property name="lkm_email"></property>
<property name="lkm_qq"></property>
<property name="lkm_mobile"></property>
<property name="lkm_memo"></property>
<property name="lkm_position"></property>
<!--多对一的关系
name属性:集合属性名
column属性:外键列名
class属性:与我关联的对象完整类名
-->
<many-to-one name="customer" column="lkm_cust_id" class="Customer"></many-to-one>
</class>
</hibernate-mapping>
在创建实体关系时<key>标签的column必须保持和<many-to-one>标签中的column保持完全一致。
在配置完实体关系后,千万要记得在Hibernater的配置文件中增加实体的ORM元数据配置。
<!-- 引入orm元数据路径书写: 填写src下的路径-->
<mapping resource="com/legend/hibernate/domain/Customer.hbm.xml"/>
<mapping resource="com/legend/hibernate/domain/LinkMan.hbm.xml"/>
范例:测试多表ORM元数据配置是否成功
@Test
public void test() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("百度公司");
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("张总");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("刘总");
// 表达一对多关系
customer.getLinkMens().add(linkMan1);
customer.getLinkMens().add(linkMan2);
// 表达多对一的关系
linkMan1.setCustomer(customer);
linkMan2.setCustomer(customer);
session.save(customer);
session.save(linkMan1);
session.save(linkMan2);
transaction.commit();
session.close();
}
注意:由于Hibernate在创建数据库表的时候默认会用lartin编码,会导致我们无法插入数据,可以手动以UTF8的方式建表。
-
级联操作
在上方的代码操作中,每次添加客户都需要创建多个联系人,如果我们想在保存客户的时候同时保存联系人信息,则可以使用级联操作。
<!--集合,一对多关系,在配置文件中配置
name属性:集合属性名
column属性:外键列名
class属性:与我关联的对象完整类名
级联操作:cascade
save-update:级联保存更新
delete:级联删除
all:save-update and delete
-->
<set name="linkMens" cascade="save-update">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
设置为cascade为save-update后,我们的操作数据库的代码可以修改为:
@Test
public void test1() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("百度公司");
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("张总");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("刘总");
// 表达一对多关系
customer.getLinkMens().add(linkMan1);
customer.getLinkMens().add(linkMan2);
// 表达多对一的关系
linkMan1.setCustomer(customer);
linkMan2.setCustomer(customer);
session.save(customer);
//session.save(linkMan1);
//session.save(linkMan2);
transaction.commit();
session.close();
}
这样我们在保存客户的时候会自动将联系人一起保存,下面我们测试下删除,将cascade设置为delete:
<set name="linkMens" cascade="delete">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
这样我们在操作时,删除客户的同时会自动删除客户下的联系人信息。
@Test
public void delete() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 联动删除操作
Customer customer = session.get(Customer.class, 4l);
session.delete(customer);
transaction.commit();
session.close();
}
如果我们将cascade配置为all的话,那么它同时具备级联保存和级联删除两种操作,save-update and delte。
注意:级联操作不但可以正向操作,还可以方向操作,不但可以在客户中设置来联动联系人,也可以在联系人设置来联动客户。
-
关系维护
我们在正常操作客户和联系的时候,在保存时,两方都会维护自己的关系和外键关系,所以关系被维护两次,那么多余的关系维护就冗余了。
Hibernate:
insert
into
cst_customer
(cust_name, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile)
values
(?, ?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
cst_linkman
(lkm_gender, lkm_name, lkm_phone, lkm_email, lkm_qq, lkm_mobile, lkm_memo, lkm_position, lkm_cust_id)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate:
update
cst_linkman
set
lkm_cust_id=?
where
lkm_id=?
从输出语句可以看出一共有三条语句,其中第三条是冗余的语句。我们可以在hibernater中通过inverse属性来维护客户的关系,避免冗余的维护。
<!--inverse属性:配置关系是否维护
true:Customer不维护关系。
false(默认):Customer维护关系。
-->
<set name="linkMens" cascade="delete" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
配置inverse属性为true后,此时控制台只输出两条语句,则避免了冗余的语句,优化性能。
Hibernate:
insert
into
cst_customer
(cust_name, cust_source, cust_industry, cust_level, cust_linkman, cust_phone, cust_mobile)
values
(?, ?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
cst_linkman
(lkm_gender, lkm_name, lkm_phone, lkm_email, lkm_qq, lkm_mobile, lkm_memo, lkm_position, lkm_cust_id)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?)
注意:在多的一方不能放弃维护关系(外键存在),必须有一方来维护关系,reverse属性只是将双方都维护的方式转换为只需要多的一方来维护。
如果客户在放弃维护联系人关系,我们在操作数据库的时候可以省略客户对联系人维护的代码:
@Test
public void test() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("百度公司");
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("张总");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("刘总");
// 表达一对多关系,如果客户放弃维护联系人的关系,这两行代码可以省略。
//customer.getLinkMens().add(linkMan1);
//customer.getLinkMens().add(linkMan2);
// 表达多对一的关系
linkMan1.setCustomer(customer);
linkMan2.setCustomer(customer);
session.save(customer);
session.save(linkMan1);
session.save(linkMan2);
transaction.commit();
session.close();
}
如果客户放弃维护联系人关系的时候,是无法删除客户的,需要先删除联系人,然后再删除客户。也可以开启casecade="delete"属性来联动删除。
多对多关系
我们知道在多对多关系和一对多关系有很大不同,一对多和多对一可以通过外键的形式来处理关系,而多对多只能通过中间表的方式来处理关系。
而且中间表必须两列,而且都是外键,分别引用两张表的主键。
在多对多的实体关系关系中,我们可以在双方都使用集合来表达可以拥有多个对方。那么我实体设计如下:
User.java
public class User {
private Long user_id;
private String user_code;
private String user_name;
private String user_password;
private Character user_state;
// 表达和Role的关系
private Set<Role> roles = new HashSet<>();
...省略get和set方法...
}
Role.java
public class Role {
private Long role_id;
private String role_name;
private String role_memo;
// 表达和User的关系
private Set<User> users = new HashSet<>();
...省略get和set方法...
}
这样就表达了两个实体之间,多对多的关系了,互相持有多个对方。
其中User和Role对应如下两张表,下面的建表语句:
// User
create table `sys_user` (
`user_id` bigint(32) not null auto_increment comment '用户id',
`user_code` varchar(32) not null comment '用户账号',
`user_name` varchar(64) not null comment '用户名称',
`user_password` varchar(32) not null comment '用户密码',
`user_state` char(1) not null comment '1:正常,0:暂停',
primary key (`user_id`)
) engine=innodb auto_increment=9 default charset=utf8;
// Role
create table `sys_role` (
`role_id` bigint(32) not null auto_increment,
`role_name` varchar(32) not null comment '角色名称',
`role_memo` varchar(128) default null comment '备注',
primary key (`role_id`)
) engine=innodb auto_increment=6 default charset=utf8;
除此之外,因为多对多的关系需要一张虚拟的表来进行关系映射,所以虚拟表的建表语句如下:
create table `sys_user_role` (
`role_id` bigint(32) not null comment '角色id',
`user_id` bigint(32) not null comment '用户id',
primary key (`role_id`,`user_id`),
key `fk_user_role_user_id` (`user_id`),
constraint `fk_user_role_role_id` foreign key (`role_id`) references `sys_role` (`role_id`) on delete no action on update no action,
constraint `fk_user_role_user_id` foreign key (`user_id`) references `sys_user` (`user_id`) on delete no action on update no action
) engine=innodb default charset=utf8;
-
ORM映射
当我们创建完实体类后,就需要做实体类的ORM映射来和数据库表建立关联了,我们分别创建两张ORM映射配置
User.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">
<!-- 配置表与实体对象的关系 -->
<!-- package属性:填写一个包名.在元素内部凡是需要书写完整类名的属性,可以直接写简答类名了. -->
<hibernate-mapping package="com.legend.hibernate.domain" >
<class name="User" table="sys_user">
<id name="user_id">
<!-- generator:主键生成策略,每条记录录入时,主键的生成规则-->
<generator class="native"></generator>
</id>
<property name="user_code"></property>
<property name="user_name"></property>
<property name="user_password" ></property>
<property name="user_state"></property>
<!--多对多关系表达 name:集合属性名;table:配置中间表名-->
<set name="roles" table="sys_user_role">
<!--column属性表示外键,别人引用我的外键。-->
<key column="user_id"></key>
<!--class:我与哪个类是多对多关系;column:我引用别人的外键-->
<many-to-many class="Role" column="role_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
Role.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">
<!-- 配置表与实体对象的关系 -->
<!-- package属性:填写一个包名.在元素内部凡是需要书写完整类名的属性,可以直接写简答类名了. -->
<hibernate-mapping package="com.legend.hibernate.domain" >
<class name="Role" table="sys_role">
<id name="role_id">
<!-- generator:主键生成策略,每条记录录入时,主键的生成规则-->
<generator class="native"></generator>
</id>
<property name="role_name"></property>
<property name="role_memo"></property>
<!--多对多关系表达 name:集合属性名;table:配置中间表名-->
<set name="users" table="sys_user_role">
<!--column属性表示外键,别人引用我的外键。-->
<key column="role_id"></key>
<!--class:我与哪个类是多对多关系;column:我引用别人的外键-->
<many-to-many class="User" column="user_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
在配置完实体关系后,千万要记得在Hibernater的配置文件中增加实体的ORM元数据配置。
<mapping resource="com/legend/hibernate/domain/User.hbm.xml"/>
<mapping resource="com/legend/hibernate/domain/Role.hbm.xml"/>
范例:测试多表设计的实体是否正常
@Test
public void test() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 1> 创建两个User
User user1 = new User();
user1.setUser_name("孙悟空");
User user2 = new User();
user2.setUser_name("猪八戒");
// 2> 创建两个Role
Role role1 = new Role();
role1.setRole_name("看家护院");
Role role2 = new Role();
role2.setRole_name("化斋打水");
// 3> 用户表达关系
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role1);
user2.getRoles().add(role2);
// 4> 角色表述关系
role1.getUsers().add(user1);
role1.getUsers().add(user2);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
// 5> 调用save方法保存
session.save(user1);
session.save(user2);
session.save(role1);
session.save(role2);
transaction.commit();
session.close();
}
上方是非常标准的插入方式,运行发现出现错误:
这是因为Hibernate对双方的第三张关系表进行两次维护,两次维护导致虚拟关系表中的外键冲突。二种解决方案:
1、第一种方式就是只保存某个表的操作
// 4> 角色表述关系,去掉如下代码
role1.getUsers().add(user1);
role1.getUsers().add(user2);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
2、第二种就是在某一个ORM文件中设置inverse为true来放弃外键维护。
<!--多对多关系表达 name:集合属性名;table:配置中间表名-->
<set name="users" table="sys_user_role" inverse="true">
<!--column属性表示外键,别人引用我的外键。-->
<key column="role_id"></key>
<!--class:我与哪个类是多对多关系;column:我引用别人的外键-->
<many-to-many class="User" column="user_id"></many-to-many>
</set>
注意:在开发过程中,如果遇到多对多的关系,一定要让某一方放弃外键的维护,放弃的规则是根据业务来判断哪一方放弃。
假设有员工和职位两张表,因为职位是需要员工去支配的,所以员工则需要维护外键,而职位则需要放弃维护外键。
- 多对多关系操作
1、为用户新增一个角色
// 为用户新增一个角色
@Test
public void insert() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 1> 获得需要新增角色的用户
User user = session.get(User.class, 1l);
// 2> 创建新的角色
Role role = new Role();
role.setRole_name("端茶倒水");
// 3> 将角色添加到用户中
user.getRoles().add(role);
// 4> 将角色转换为持久化
session.save(user);
transaction.commit();
session.close();
}
除此之外,也可以使用级联的方式来新增角色,在User.hbm.xml中配置级联方式:
<!--多对多关系表达 name:集合属性名;table:配置中间表名-->
<set name="roles" table="sys_user_role" cascade="save-update">
<!--column属性表示外键,别人引用我的外键。-->
<key column="user_id"></key>
<!--class:我与哪个类是多对多关系;column:我引用别人的外键-->
<many-to-many class="Role" column="role_id"></many-to-many>
</set>
然后代码中注释掉session.save()方法,然后运行也可以成功保存。
// 为用户新增一个角色
@Test
public void insert() {
Session session = HibernateUtils.openSession();
Transaction transaction = session.beginTransaction();
// 1> 获得需要新增角色的用户
User user = session.get(User.class, 1l);
// 2> 创建新的角色
Role role = new Role();
role.setRole_name("端茶倒水");
// 3> 将角色添加到用户中
user.getRoles().add(role);
transaction.commit();
session.close();
}
注意:在开发中不用级联的方式,即使用也只用save-update属性,因为delete属性在多对多的情况下非常危险。