Hibernate关联操作、查询操作、高级特性、并发处理机制

来源:互联网 发布:淘宝模特怎么入行 编辑:程序博客网 时间:2024/05/18 03:52

本文所需的数据库初始文件,Hibernate常用操作的完整示例代码(包含所有Hibernate操作所需jar文件)提供下载学习:http://download.csdn.net/detail/daijin888888/9551724

1、Hibernate关联映射

    1)什么是关联映射?
        如果表之间具有关联关系,Hibernate允许我们在hbm.xml中描述他们的关联关系,然后在我们操作其中一张表时,自动的根据这种关系操作到另外的关系表,那么这种关联关系的设置,我们称之为关联映射。
    2)关联映射的好处?
        一次访问可以关联操作多张表
            --关联查询出关系表的数据
            --关联新增、修改关系表的数据
            --关联删除关系表的数据
    3)关联映射实现步骤
        --了解表之间的关系,且明确关系字段
        --在实体类中追加关联属性,用于封装关联的数据
        --需要配置hbm.xml文件,设置关联关系

*2、一对多关联

    例:假如一个Account有多条Service记录,希望在查询account数据时,自动连带着查询出它对应的service数据。
    1)表关系
        一对多的关系,关系字段是service.account_id。(即Service表中要有与Account表关联的字段account_id,该字段对应Account表中的id)
    2)追加关系属性
        在Account类中,追加关联属性services,其
        类型为Set<Service>。
        private Set<Service> services = new HashSet<Service>();
    3)设置关联关系
        --语法
            在Account.hbm.xml中,设置一对多关系
            <set name="关联属性名">
                <key column="关联字段名"/>
                <one-to-many class="关联表的实体类名"/>
            </set>
        --实现
            <set name="services">
                <key column="account_id"/>
                <one-to-many class="com.*.entity.Service"/>
            </set>
    4)示例代码
        已知:
            --关联属性services
            --关联字段account_id
            --关联对象Service --> 表名service
<span style="font-size:14px;">public void testFind() {    Session session = HibernateUtil.getSession();    Account a = (Account) session.load(Account.class, 1010);    System.out.println("---显示Account账号信息---");    System.out.println(a.getId() + " " + a.getRealName() + " " + a.getIdcardNo());    System.out.println("---显示当前账号下的业务账号---");    Set<Service> services = a.getServices();    for (Service s : services) {        System.out.println(s.getId() + " " + s.getOsUserName() + " " + s.getUnixHost());    }    System.out.println(a.toString());}</span>

*3、多对一关联

    例:希望在查询service数据后,可以自动的查询出它对应的account数据。
    1)关联关系
        service与account具有多对一的关系,关系字段为service.account_id
    2)追加关联属性
        --在Service实体类中追加关联属性account,其类型为Account。
        private Account account;
           此后accountId属性可以去掉,可以通过getAccount().getId()方法来得到account_id的值
    3)设置关联关系
        a、语法:
            --在Service.hbm.xml中,追加关联关系的配置
            --<many-to-one name="关联属性名"
                    column="关系字段名"
                    class="关联表的实体类名"/>
        b、实现:
            <many-to-one name="account"
                column="account_id"
                class="com.*.entity.Account"/>
    4)示例代码
        已知:
            --关联属性account
            --关联字段account_id
            --关联对象Account --> 表名account,主键id
<span style="font-size:14px;">public void testFind() {Session session = HibernateUtil.getSession();Service s = (Service) session.get(Service.class, 2002);System.out.println("----显示业务账号信息---");System.out.println(s.getId() + " " + s.getOsUserName() + " " + s.getUnixHost());System.out.println("----显示相关账务账号信息---");System.out.println(s.getAccount().getId() + " " + s.getAccount().getRealName());}</span>

*4、关联操作

    1)关联查询
        如果需要当前对象和关联属性一起采用一个SQL语句实例化,可以采用下面方法:
        a、(不推荐)修改hbm.xml中的关联属性映射
        lazy属性:
            true表示启用延迟加载;
            false表示关闭延迟加载
        fetch属性:
            join表示采用连接方法与主对象一起查询,此时,lazy="false"失效;
            select(默认)表示单独发送一个SQL查询关联数据
        b、(推荐)通过HQL及join fetch语法
            --from Account a join fetch a.services where a.id=?
            含义:在查询Account对象时,将services关联属性数据采用表连接方式一并查出来。
            --from Service s join fetch s.account where s.id=?
            含义:在查询Service对象时,将account关联属性数据采用表连接方式一并查出来。
            --from Service s join fetch s.account where s.account.id=?
        注意:
            --HQL中写的是对象和属性
            --join fetch后面没有on子句,而fetch的是关联属性
            --query.setInteger来设置整型参数值,下标从0开始。

            --如果明确该HQL只会返回一条记录,可以使用query.uniqueResult()方法返回唯一的记录

        c、示例代码(改写上述一对多关联的查询代码)

<span style="font-size:14px;">public void testFind() {Session session = HibernateUtil.getSession();// Account a = (Account) session.load(Account.class, 1010);String hql = "from Account a join fetch a.services where a.id=?";Query query = session.createQuery(hql);query.setInteger(0, 1010);// ?从0开始Account a = (Account) query.uniqueResult();// 单行查询可采用System.out.println("---显示Account账号信息---");System.out.println(a.getId() + " " + a.getRealName() + " " + a.getIdcardNo());System.out.println("---显示当前账号下的业务账号---");Set<Service> services = a.getServices();for (Service s : services) {System.out.println(s.getId() + " " + s.getOsUserName() + " " + s.getUnixHost());}System.out.println(a.toString());}</span>
    2)级联添加、级联修改
        a、当表具有关联关系时,Hibernate不仅仅提供关联查询的功能,还具有关联添加、修改、删除关联表中数据的能力,这种能力称之为级联操作。
        b、如何实现级联操作
            需要在关联属性设置的位置,追加属性cascade
            --none:默认不支持级联
            --save-update:支持级联添加与更新
            --delete:支持级联删除
            --all:支持级联添加、更新、删除
        c、说明
            通常1对多的表关系,1的一方是主表,多的一方是从表,往往是需要在添加、更新、删除主表时连带添加、更新、删除从表的数据。如:删除账务账号时,要连带删除业务账号的数据
    3)级联删除
        a、设置cascade="delete"或cascade="all"就可以支持级联删除
        b、通常情况下,需要在1的一方set标签处,追加属性inverse="true"
        c、session.delete(obj);obj需要是持久对象,不能new,需要load/get取出
        d、批量删除的方法:
        级联删除采用n+1个delete语句清除主表和外键表的关联数据。
        如果是批量删除,不推荐级联删除,建议采用HQL编写delete删除语句
        delete from Service where account.id=?//删除Service表中所有account.id=?的数据,(该句替代级联操作中的n个delete语句)
        delete from Account where id=?
    4)inverse属性(理解)详细说明点这里
        是否交出关系维护的控制权。即默认情况下,Account和Service对象之间的关系由双方负责维护。意思是对Account或Service对象做级联操作时,需要执行update语句将关联字段设置成相同ID。如果需要取消某一方的关系维护工作,可以在关联属性部分添加inverse="true"设置,这样可以避免update语句执行。
        true:交出控制权,当前的对象不负责维护两张表的关联关系
        false:不交出控制权,当前的对象要负责维护两张表的关联关系

        提示:往往是一的一方(即<one-to-many>映射部分)设置为inverse="true",这样在对一方级联操作时,可以避免大量的update更新语句。


*5、多对多关联

    例:管理员admin_info和角色role_info具有多对多的关系,希望在查询管理员时能够连带着查询出他对应的角色。 数据库设计时,需采用3张表表示。
    ADMIN_INFO(管理员)
    ADMIN_ROLE(管理员和角色关系)
    ROLE(角色)
    1)关系字段
        关系字段位于他们的中间表admin_role中,
        admin_id=admin_info.id
        role_id=role_info.id
    2)追加关联属性在管理员的实体类中追加角色相关属性
        Set<Role> roles
    3)在Admin.hbm.xml中追加关联映射配置
        --语法
        <set name="关联属性名" table="中间表名">
            <key column="Admin的关联字段名"/>
            <many-to-many class="关联表的实体类名"
                column="关联表的关系字段名"/>
        </set>
        --代码
        <set name="roles" table="admin_role">
            <key column="admin_id"/>
            <many-to-many class="com.*.entity.Role"
                column="role_id"/>
        </set>
    4)级联操作
        cascade表示支持级联操作,操作的是另一方的表,而并不是表示级联操作中间表。对于中间表的维护,不需要写cascade属性。
    5)inverse
        通常情况下,多对多关联,不需要写inverse="true",原因是另一方在插入数据时,可能没有管中间表的数据,需要当前的一方来维护,因此不能写inverse="true",否则的话,双方都不维护这个关系,数据上有问题。

    6)示例Java代码

<span style="font-size:14px;">        // 移除角色@Testpublic void testDeleteRole() {Session session = HibernateUtil.getSession();Transaction tx = session.beginTransaction();try {Admin a = (Admin) session.load(Admin.class, 1);Role r1 = (Role) session.load(Role.class, 1);a.getRoles().remove(r1);session.update(a);tx.commit();} catch (HibernateException e) {e.printStackTrace();tx.rollback();} finally {session.close();}}// 追加角色@Testpublic void testAddRole() {Session session = HibernateUtil.getSession();Transaction tx = session.beginTransaction();try {Admin a = (Admin) session.load(Admin.class, 1);Role r1 = (Role) session.load(Role.class, 1);Role r2 = (Role) session.load(Role.class, 43);Role r3 = (Role) session.load(Role.class, 44);a.getRoles().add(r1);a.getRoles().add(r2);a.getRoles().add(r3);session.update(a);tx.commit();} catch (HibernateException e) {e.printStackTrace();tx.rollback();} finally {session.close();}}@Testpublic void testFind() {Session session = HibernateUtil.getSession();Transaction tx = session.beginTransaction();try {Admin a = (Admin) session.load(Admin.class, 1);System.out.println("----显示管理员信息---");System.out.println(a.getId() + " " + a.getName() + " "+ a.getTelephone());System.out.println("----显示管理员角色信息---");for (Role role : a.getRoles()) {System.out.println(role.getName() + " ");}tx.commit();} catch (HibernateException e) {e.printStackTrace();tx.rollback();} finally {session.close();}}</span>

6、继承关联

    例:在电商网站上搜索商品,比如输入iphone来搜索,搜索结果中可以包含手机、手机膜、手机壳、充电器、耳机等等相关的产品信息。这种功能,我们可以在设计表时用一种特殊的一对一的关系来表现。即将所有商品通用的属性提取到一张公共的表中product,具体商品表中只保存该商品特有的属性,那么具体商品表和product表具有一对一的关系。在搜索时只搜索product表,就可以将相关的信息模糊查找出来。
        --通用信息表product(id,name,price,desc)
        --书的商品表book(id,authod,publishing,words)
        --希望在操作book表时,能够自动的将通用的字段维护到product表中。
    1)明确表的关系
        book与product具有一对一的关系,这种关系的目的是为了复用product表中的字段,像是一种继承关系
    2)实体类
        Book extends Product
    3)配置文件中体现关联关系
        --父类型同原先的配置文件写法一致
        --子类型具有特殊性
            <joined-subclass
                name="类型名" table="表名"
                extends="父类名称">
                <key column="关联字段名"/>
                <property name="" type="" column=""/>
                ...
            </joined-subclass>
    4)继承关系中,由于2张表具有类似于父子的关系,那么子表中必须引用父表中的数据,不存在不引用的情况,即必须在维护子表同时维护父表。所以这是固定的情况,就不用写cascade,inverse。
    5)描述类别(了解)
        <joined-subclass>数据库有父类表和子类表
        <union-subclass>数据库有子类表、没有父类表(子类表已包含父类表字段,无父类表,但有父类实体对象)
        <subclass>数据库有父类子类都用一个表(设计较乱,即没有进行表拆分,很少用)

*7、Hibernate查询

    1)*HQL查询(Hibernate Query Language)
        属于面向对象查询语句,针对Hibernate映射过来的POJO进行查询,从而实现对数据库的查询。
        a、以非主键做条件查询
            --条件参数以?表示,query.setString(0,"");
            --条件参数以:x表示,query.setString("x","");

            示例代码:

<span style="font-size:14px;">        // 测试按非主键做条件查询@Testpublic void testFind1() {String hql = "from Service where account.id=? and unixHost=?";Session session = HibernateUtil.getSession();Query query = session.createQuery(hql);query.setInteger(0, 1011);query.setString(1, "192.168.0.23");List<Service> list = query.list();for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName() + " "+ s.getUnixHost());}session.close();}// 等价于testFind1,采用“:标识符”替代@Testpublic void testFind2() {String hql = "from Service where account.id=:aid and unixHost=:host";Session session = HibernateUtil.getSession();Query query = session.createQuery(hql);query.setInteger("aid", 1011);query.setString("host", "192.168.0.23");List<Service> list = query.list();for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName() + " "+ s.getUnixHost());}session.close();}</span>

        b、只查询一部分属性
            --默认返回的集合中封装的是Object[]
            --new Service(id,unixHost,osUserName)使返回的结合中封装的是Service对象,
                注意:
                    Service中需要追加响应的构造器;
                    不要丢掉无参构造器;

            示例代码:

<span style="font-size:14px;">        // 获取部分字段结果,默认采用Object[]封装数据@Testpublic void testFind3() {String hql = "select s.id,s.unixHost,s.osUserName from Service s where s.account.id=?";Session session = HibernateUtil.getSession();Query query = session.createQuery(hql);query.setInteger(0, 1011);List<Object[]> list = query.list();for (Object[] objs : list) {System.out.println(objs[0] + " " + objs[1] + " " + objs[2] + " ");}session.close();}// 等价于testFind3,需要添加对应的构造方法@Testpublic void testFind4() {String hql = "select new Service(s.id,s.unixHost,s.osUserName) from Service s where s.account.id=?";Session session = HibernateUtil.getSession();Query query = session.createQuery(hql);query.setInteger(0, 1011);List<Service> list = query.list();for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName() + " "+ s.getUnixHost());}session.close();}</span>

        c、HQL定义在配置文件中(了解即可)
            --通过query元素在配置文件中定义HQL
            --query元素写在class的后面
            --session.getNamedQuery(HQL名);

            示例代码:<query/>与<class/>在hbm.xml中同级摆放

<span style="font-size:14px;"><query name="findAll"><!-- CDATA内包括纯文本字段,防止出现特殊字符 --><![CDATA[from Service]]></query></span>
----------------------
<span style="font-size:14px;">        // 将HQL定义到hbm.xml中(只适用静态HQL语句结构)@Testpublic void testFind5() {Session session = HibernateUtil.getSession();// 获取hbm.xml中<query>定义的hql语句Query query = session.getNamedQuery("findAll");List<Service> list = query.list();for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName() + " "+ s.getUnixHost());}session.close();}</span>

        d、分页查询
            --查询记录
                query.setFirstResult((page-1)*pageSize);
                query.setMaxResults(pageSize);
            --查询总页数
                select count(*) from Service

            示例代码:

<span style="font-size:14px;">        // 测试分页查询@Testpublic void testFind6() {int page = 2;String hql = "from Service order by id";Session session = HibernateUtil.getSession();Query query = session.createQuery(hql);// 追加分页参数设置query.setFirstResult((page - 1) * 3);// 设置抓起记录的起点,从0开始query.setMaxResults(3);// 设置最大抓取数量List<Service> list = query.list();// 执行查询,如果没有分页设置就查所有值for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName() + " "+ s.getUnixHost());}session.close();}// select count(*) from SERVICE@Testpublic void testFind7() {String hql = "select count(*) from Service";Session session = HibernateUtil.getSession();Query query = session.createQuery(hql);Long size = (Long) query.uniqueResult();System.out.println("记录总数:" + size);session.close();}</span>

        e、关联查询(记住一种即可)
            --from Service s,Account a
                where s.account.id=a.id
            --from Service s inner join s.account a
            --select s.account.realName from Service s
        总结:
        HQL与SQL的相同点
            --支持select,from,where,group,order by子句
            --支持inner join,left join等连接
            --支持>,<,>=,<=,in,not in,between,like等条件
            --支持分组统计函数count,sum,max,min,avg
        HQL与SQL的不同点
            --HQL语句区分大小写,即大小写敏感。关键字不区分。
            --HQL中写的是对象名和属性名,而不是表名和字段名
            --不支持join on中的on子句
            --不支持select *
            --不支持数据库函数,比如日期函数to_date()、字符函数to_char()等
    2)Criteria查询(不够直观,了解即可)
        使用Hibernate的API来拼一个HQL
        Criteria c = session.createCriteria(Service.class);

           示例代码:

<span style="font-size:14px;">        // 使用Hibernate的API来拼一个HQL@Testpublic void testFind1() {Session session = HibernateUtil.getSession();Criteria c = session.createCriteria(Service.class);c.add(Restrictions.and(Restrictions.like("osUserName", "huang%"),Restrictions.eq("unixHost", "192.168.0.26")));// 追加查询条件List<Service> list = c.list();// 执行查询,如果没有分页设置就查所有值c.addOrder(Order.desc("id"));// 追加排序// c.setFirstResult(arg0);//分页// c.setMaxResults(arg0);for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName() + " "+ s.getUnixHost());}session.close();}</span>

    3)SQL查询
        直接帮助我们调用JDBC来执行SQL查询
        SQLQuery sqlQuery = session.createSQLQuery(sql);

            示例代码:

<span style="font-size:14px;">        @Testpublic void testFind1() {String sql = "select * from Service";Session session = HibernateUtil.getSession();SQLQuery sqlQuery = session.createSQLQuery(sql);sqlQuery.setFirstResult(0);// 分页sqlQuery.setMaxResults(3);// 默认采用数组封装一条记录List<Object[]> list = sqlQuery.list();for (Object[] objs : list) {System.out.println(objs[0] + " " + objs[2]);}session.close();}//同testFind1(),指定封装记录的实体@Testpublic void testFind2() {String sql = "select * from Service";Session session = HibernateUtil.getSession();SQLQuery sqlQuery = session.createSQLQuery(sql);sqlQuery.setFirstResult(0);// 分页sqlQuery.setMaxResults(3);// 指定封装记录的实体类sqlQuery.addEntity(Service.class);// 采用指定的Service类型封装一条记录List<Service> list = sqlQuery.list();for (Service s : list) {System.out.println(s.getId() + " " + s.getOsUserName());}session.close();}</span>

8、Hibernate高级特性(了解)

    1)二级缓存
        a、二级缓存(默认关闭)
            --二级缓存是SessionFactory级别的缓存,由SessionFactory负责管理
            --缓存的也是实体对象
            --缓存数据可以被不同的Session间共享
            --适用环境:对象数据频繁共享;对象数据变化频率小
        b、二级缓存的使用步骤
            --导缓存包ehcache.jar
            --导入缓存配置文件ehcache.xml
            --在hibernate.cfg.xml中,设置开启二级缓存,并且设置缓存驱动类

<span style="font-size:14px;"><!-- 使用二级缓存 --><property name="hibernate.cache.use_second_level_cache">true</property><!-- 指定二级缓存组件的驱动类ehcache.jar --><property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property></span>

            --在要缓存的POJO的关系映射文件hbm.xml中设置元素<cache usage="readonly"/>

       示例代码:

<span style="font-size:14px;"><span style="font-size:14px;">        @Testpublic void testFind1() {// 第一次查询,使用session1Session session1 = HibernateUtil.getSession();Service s1 = (Service) session1.get(Service.class, 2002);System.out.println(s1.getOsUserName() + " " + s1.getUnixHost());session1.close();// 第二次查询,使用session2(配置二级缓存后,两次查询均到二级缓存中取数据)// HibernateUtil.getSessionFactory().evict(Service.class);// 移除后则还是查询两次Session session2 = HibernateUtil.getSession();Service s2 = (Service) session2.get(Service.class, 2002);System.out.println(s2.getOsUserName() + " " + s2.getUnixHost());}</span></span>

    2)查询缓存
        a、查询缓存
            一级和二级缓存只能是缓存单个对象。如果遇到某个字符串结果、数组结果或者List集合,可以使用查询缓存存储。
            --可以看成是特殊的二级缓存
            --使用的是二级缓存的缓存空间
            --缓存的是除实体对象之外的数据类型
            --依赖于二级缓存,默认是关闭的,需开启二级缓存才能使用。
        b、使用步骤
            --开启二级缓存
            --在hibernate.cfg.xml中设置开启查询缓存

<span style="font-size:14px;"><!-- 开启查询缓存 --><property name="hibernate.cache.use_query_cache">true</property></span>

            --query.list()查询之前,设置允许进行查询缓存,query.setCacheable(true);
        c、使用环境
            --需要频繁执行的相同的查询语句
            --查询结果集内容改变频率小
            --结果集数据量不要太多
        提示:相同的SQL,第一次去数据库查询,后续几次从缓存取出。因为缓存的是SQL+结果集内容

         示例代码:

<span style="font-size:14px;"><span style="font-size:14px;">        @Test    public void testFind() {        find();        System.out.println("-------");        find();    }    private void find() {        String hql = "from Service where account.id=?";        Session session = HibernateUtil.getSession();        Query query = session.createQuery(hql);        query.setInteger(0, 1011);        // 启用查询缓存执行        query.setCacheable(true);        List<Service> list = query.list();        for (Service s : list) {            System.out.println(s.getId() + " " + s.getOsUserName());        }    }</span></span>

9、Hibernate并发处理-加锁

    举例:
        模拟12306购票机制,假设当前有一张火车票的表tickets(id,line,amount,version),模拟多人同时购票的场景。
    1)悲观锁
        --程序悲观的认为,每个访问者都存在并发的问题,于是会对每条数据加锁,那么只有当前持有锁的访问者释放该锁时,下一个访问者才能访问数据,这种机制称之为悲观锁。
        --就是说无论如何都要给一条数据加锁,不管该数据是否会发生并发的问题。
        --特点
            效率低,安全性高
        --实现:
            get(Class,id,LockMode.UPGRADE)
            select * from emp for update
        注:这里利用的是数据库自带的for update子句进行加锁,并非是Hibernate自己发明的加锁机制。
    2)乐观锁
        --程序乐观的认为,每个访问者都不会有并发的问题产生,因此不去加锁。而是当数据更新时,判断该数据是否发生了版本的变化,如果发生变化,则说明在此期间有并发产生,因此报错给予提示,本次更新失败。
        --借助版本字段,当第一个用户更新提交后,Hibernate会将该字段更新加1,这样后续用户提交的对象版本字段比数据库中的小,即在更新时发现版本变了,就抛出异常并更新失败。
        --一个成功,其他失败
        --特点
            效率高,用户体验差
        --实现
            a、需要在表中追加版本字段,用于记录数据的版本;
            b、实体类中追加版本属性;
            c、hbm.xml中追加版本的配置<version name="" type="" column="">
    3)如何选择
        --如果是并发量大的情况,应选择乐观锁
        --如果是并发量小的情况,应选择悲观锁
            
            
       

0 0