zoukankan      html  css  js  c++  java
  • 【JavaEE】Hibernate继承映射,不用多态查询只查父表的方法

    几个月前,我在博问里面发了一个问题:http://q.cnblogs.com/q/64900/,但是一直没有找到好的答案,关闭问题以后才自己解决了,在这里分享一下。

    首先我重复一下场景,博问里面举的动物的例子,这篇文章里为了和我的代码对应,换一个例子。假设要做一个企业的人员管理系统,有各种各样的用户,有的身份是老板,有的身份是员工,有的身份是保安,等等,这些用户可能有非常多非常多通用的行为,比如说修改年龄,比如说按姓名或者工号在全公司查找,并且大多数情况下我们不关心一个人的具体身份,这时候我希望采取这样一种结构:

    Person类是一个父类,拥有所有员工的公有属性,包括员工号和姓名,Staff和Employer是两种身份的人,都具有员工号和姓名这样的特征,并且Employer呢还有一个座右铭,Staff还有一个薪水。

    class Person implements Serializable {id, name}
    class Employer extends Person {motto}
    class Staff extends Person {salary}

    这是一种继承映射的关系,映射到数据表,就是

    person表
    id name
    员工号,主键 员工姓名
    employer表
    id motto
    员工号,主键,外键指向person表 座右铭
    staff表
    id salary
    员工号,主键,外键指向person表 薪水

    我现在要查询名字叫“张三”的所有员工,那么我可以用下面的方法查询:

    Criteria criteria = getCurrentSession().createCriteria(Person.class);
    criteria.add(Restrictions.eq("name", "张三"));
    List<Person> person = criteria.list();

    此时查询到的Person列表,所有的对象都是Employer或者Staff的具体化对象,Hibernate区分应该是哪一种对象的方式可以参考它生成的SQL语句:

    Hibernate: 
        select
            person0_.id as id1_3_0_,
            person0_.name as name2_3_0_,
            person0_1_.motto as motto1_0_0_,
            person0_2_.salary as salary1_1_0_,
            case 
                when person0_1_.id is not null then 1 
                when person0_2_.id is not null then 2 
                when person0_.id is not null then 0 
            end as clazz_0_ 
        from
            person person0_ 
        left outer join
            Employer person0_1_ 
                on person0_.id=person0_1_.id 
        left outer join
            Staff person0_2_ 
                on person0_.id=person0_2_.id 
        where
            person0_.name=?

    这种多态查询非常的方便,如果只想查Staff,那么只需要写查询的时候,改一下class就可以,这样就只有staff left outer join person,而没有Employer的事情了:

    Criteria criteria = getCurrentSession().createCriteria(Staff.class);

    但是,毋庸置疑,当子表众多的时候,在Person上做多态查询,join的大量使用非常影响性能,而在这种要把Employer和Staff都查询出来存到一个List的情况下,我们一般只关心父表里存储的公有字段,所以我之前一直在寻找能够只查父表的方法,但是却没有找到。

    尝试直接通过annotation禁用多态查询无果后,使用一种替代的解决方案:

    以前是Employer和Staff都去继承Person,现在加一层,Person类是变成一个抽象类,不映射数据库,PersonUntyped和PersonTyped都映射同一个数据表"person"表,需要查Employer或者Staff的时候,还像以前一样,调用:

    Criteria criteria = getCurrentSession().createCriteria(Employer.class);
    List<Person> ems= criteria.list();
    criteria = getCurrentSession().createCriteria(Staff.class);
    List<Person> stas= criteria.list();

    如果需要查父类对象,那么就调用:

    criteria = getCurrentSession().createCriteria(PersonUntyped.class);
    List<Person> pers= criteria.list();

    Hibernate只解析当前类的字段,所以默认情况下PersonUntyped和PersonTyped映射person表时hibernate是不认识Person中的id和name的,需要在Person类上面加MappedSuperclass注解才可以:

    @MappedSuperclass
    public abstract class Person {

    为了达到在Controller中使用同一个操作Person的接口,让Controller中完全感受不到我们加的这一层的存在,首先和以前一样为Employer、Staff和PersonUntyped分别建一个DAO,然后在Service中封装:

    @Service
    @Transactional
    public class PersonServiceImpl implements PersonService {
        
        private PersonDao<Person> personDao;
    
        public Person findById(int type, int id) {
            wireBean(type);
            return personDao.findById(id);
        }
    
        public void wireBean(int type) {
            WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
            // 省略了try/trach,beanName根据type选择是personDao、employerDao还是staffDao
            personDao = wac.getBean(beanName);
        }
    }

    真正数据库设计的时候,父表里还应该有一个type,每一个Service方法的第一个参数规定都是type或者是一个Person对象,可以用person.getType()来获取到这个type,每个Service方法的第一行都要先根据type装载personDao,如果type是1或者2,分别装载employerDao和staffDao,如果是0,就装载只查父表的personDao。

    这样一来这个问题就解决了,但是,当Service类扩充功能的时候,开发人员还要留心在每个函数开始都加一行装载的调用,非常麻烦,如果能在每个方法一开始的时候自动调用装载的代码多么好,非常显然,AOP可以完成这个工作,在PersonService中删除wireBean,新建aspect包,创建一个PersonAspect类:

    @Component
    @Aspect
    public class PersonServiceAspect {
    
        @Autowired
        private WebApplicationContext wac;
        
        @Before("execution(* org.zhangfc.demo4ssh.service.PersonServiceImpl.*(..))")
        public void wirePersonDao(JoinPoint joinPoint) {
            Object [] args = joinPoint.getArgs();
            Object service = joinPoint.getTarget();
            Field dao = null;
            try {
                dao = service.getClass().getDeclaredField("personDao");
                if (!dao.isAccessible()) {
                    dao.setAccessible(true);
                }
            } catch (NoSuchFieldException | SecurityException e) {
                return;
            }
            if (args.length == 0 || args[0].equals(0)) {
                setBean(dao, service, "personDao");
            }
            if (args.length > 1 && args[0].equals(1)) {
                setBean(dao, service, "employerDao");
            }
            if (args.length > 1 && args[0].equals(2)) {
                setBean(dao, service, "staffDao");
            }
        }
        
        public boolean setBean(Field dao, Object service, String beanName){
            try {
                dao.set(service, wac.getBean(beanName));
                return true;
            } catch (BeansException | IllegalArgumentException
                    | IllegalAccessException e) {
                return false;
            }
        }
    
    }

    这样,在Service中就已经看不到了不同Person对象的不同。AOP的配置不再多做描述,可以参看我以前的文章,或者下载本例源码查看。

  • 相关阅读:
    jmeter-测试webservice接口
    Python
    Mysql:PDBC(Python操作数据库-mysql)
    Mysql: JDBC(Java 操作数据库-mysql)
    Mysql:事务、索引(了解)
    Mysql:DQL(Data Query Language
    Mysql:DML(Data Manipulation Language- 数据操作语言)
    Mysql:列类型,表类型,常用字段属性
    Mysql:DDL(Data Definition Language-数据定义语言)
    Mysql:Centos7安装Mysql5.6
  • 原文地址:https://www.cnblogs.com/smarterplanet/p/4204641.html
Copyright © 2011-2022 走看看