01、JavaEE--Hibernate

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&amp;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;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&amp;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属性在多对多的情况下非常危险。

原文地址:https://www.cnblogs.com/pengjingya/p/14992355.html