zoukankan      html  css  js  c++  java
  • 转载:hibernate基于注解的维护权反转:@OneToMany(mappedBy=)

    背景说明:首先是SSH环境下,对象基于注解的方式映射到数据库;

    昨天遇到一个比较纠结的问题,@OneToMany(mappedBy="xxx"), mappedBy属性有什么用,然后是写在哪一边?

    还有一个问题是:@JoinColumn(name="xxxxx"),JoinColumn有什么用?

    先贴出最初的代码:一些基本的注解,在一对多的关系上没有使用JoinColumn和mappedBy属性

    部门类:主要是第33、34行

    package com.lizhou.entity.test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;
    
    import org.hibernate.annotations.GenericGenerator;
    
    /**
     * 部门:与员工一对多关系
     * @author bojiangzhou
     *
     */
    @Entity
    @Table(name="department")
    public class Department {
        
        @Id
        @GeneratedValue(generator="_native")
        @GenericGenerator(name="_native", strategy="native")
        private int id; //ID
        
        @Column(length=20)
        private String dname; //部门名称
        
        @OneToMany
        private List<Employee> employeeList = new ArrayList<>(); //部门下的员工集合
    
        // get/set方法59     
    }

    员工类:主要是第32、33行

    package com.lizhou.entity.test;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.ManyToOne;
    import javax.persistence.Table;
    
    import org.hibernate.annotations.GenericGenerator;
    
    /**
     * 员工:与部门多对一关系
     * @author bojiangzhou
     *
     */
    @Entity
    @Table(name="employee")
    public class Employee {
    
        @Id
        @GeneratedValue(generator="_native")
        @GenericGenerator(name="_native", strategy="native")
        private int id; //ID
        
        @Column(length=20)
        private String ename; //员工姓名
        
        @Column(length=20)
        private String phone; //电话
        
        @ManyToOne
        private Department department; //所属部门
        
        
        //get/set方法67     
    }

    最初的注解配置里,在一对多的关系上,即employeeList和department没有使用JoinColumn。

    看下图,employee表会自动添加一个外键列department_id,虽然关系映射上是正确了,但是有一个问题,数据库里多了一张表出来,这不是想要的结果。

    解决方法:在employeeList和department字段上加上@JoinColumn注解

    1 @OneToMany
    2 @JoinColumn(name="departmentId")
    3 private List<Employee> employeeList = new ArrayList<>(); //部门下的员工集合
    1 @ManyToOne//
    2 @JoinColumn(name="departmentId")//
    3 private Department department; //所属部门

    这样一来的话就只有两张表了,所以在一对多或者一对一的关系下,需要加上@JoinColumn来指定外键列,避免生成一张中间表。

    而且经试验,多的一方(Employee)里的department必须加上@JoinColumn,Department里不加不会影响表的结构,不知道会不会有其它影响;

    但是如果Employee属于多的一方,如果没有指定外键列,还是会自动生成一个department_id外键列。

    接下来讨论mappedBy属性:mappedBy属性主要是针对外键而言。与之相对应的是xml中的inverse属性。

    如下是测试类代码:此时还没有设置mappedBy属性,映射时,默认是都由自身维护关联关系。

    package com.lizhou.action.test;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.lizhou.entity.test.Department;
    import com.lizhou.entity.test.Employee;
    
    /**
     * 测试类
     * @author bojiangzhou
     *
     */
    
    public class TestAction {
        
        private static SessionFactory sessionFactory = null;
        
        static {
            //读取classpath中applicationContext.xml配置文件
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            //获取session中配置的sessionFactory对象
            sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
        }
        
        @Test
        public void testSave(){
            //创建一个部门对象
            Department d1 = new Department();
            d1.setDname("研发部");
            
            //创建两个员工对象
            Employee e1 = new Employee();
            e1.setEname("张三");
            e1.setPhone("13111111111");
            Employee e2 = new Employee();
            e2.setEname("李四");
            e2.setPhone("18523222222");
            
            //设置对象关联
            d1.getEmployeeList().add(e1);
            d1.getEmployeeList().add(e2);
            e1.setDepartment(d1);
            e2.setDepartment(d1);
            
            //获取Session
            Session session = sessionFactory.openSession();
            //开始事务
            Transaction t = session.beginTransaction();
            try {
                //添加数据
                session.save(d1);
                session.save(e1);
                session.save(e2);
                //提交事务
                t.commit();
            } catch (RuntimeException e) {
                //有异常则回滚事务
                t.rollback();
                e.printStackTrace();
            } finally {
                //关闭session
                session.close();
            }
        }
        
        
    }

    执行testSave后,控制台打印如下语句:

    1 Hibernate: insert into department (dname) values (?)
    2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
    3 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
    4 Hibernate: update employee set departmentId=? where id=?
    5 Hibernate: update employee set departmentId=? where id=?

    可以看到多了两条update语句,这是因为两边都维护关系,先插入的部门,再插入员工,插入员工时,已经设置好外键了,但部门方也维护关系,会再执行一次更新操作,为员工设置外键,这样就导致多出了两条update语句,这里是有性能损耗的。

    一种解决办法是:将第46、47行去掉,即对象上部门不关联员工

    package com.lizhou.action.test;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.lizhou.entity.test.Department;
    import com.lizhou.entity.test.Employee;
    
    /**
     * 测试类
     * @author bojiangzhou
     *
     */
    
    public class TestAction {
        
        private static SessionFactory sessionFactory = null;
        
        static {
            //读取classpath中applicationContext.xml配置文件
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            //获取session中配置的sessionFactory对象
            sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
        }
        
        @Test
        public void testSave(){
            //创建一个部门对象
            Department d1 = new Department();
            d1.setDname("研发部");
            
            //创建两个员工对象
            Employee e1 = new Employee();
            e1.setEname("张三");
            e1.setPhone("13111111111");
            Employee e2 = new Employee();
            e2.setEname("李四");
            e2.setPhone("18523222222");
            
            //设置对象关联
    //        d1.getEmployeeList().add(e1);
    //        d1.getEmployeeList().add(e2);
            e1.setDepartment(d1);
            e2.setDepartment(d1);
            
            //获取Session
            Session session = sessionFactory.openSession();
            //开始事务
            Transaction t = session.beginTransaction();
            try {
                //添加数据
                session.save(d1);
                session.save(e1);
                session.save(e2);
                //提交事务
                t.commit();
            } catch (RuntimeException e) {
                //有异常则回滚事务
                t.rollback();
                e.printStackTrace();
            } finally {
                //关闭session
                session.close();
            }
        }
        
        
    }
    1 Hibernate: insert into department (dname) values (?)
    2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
    3 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)

    这样部门方就不会去维护外键关系了。但是有一个问题,对象上就没有关联了,我们要做的是对象上要互相关联,数据库方面只让一方去维护关系即可。

    对象上如果不关联,因为部门和员工添加到数据库后,是持久化状态,存在于session缓存中,那session操作缓存中这几个对象时,部门就没有关联员工了,那么就还得再查询一次数据库,这不是想要的结果。

    这时就要用到mappedBy属性了。

    在一的一方配置@OneToMany(mappedBy="department"),将维护权交由多的一方来维护;

    那为什么不让多的一方交出维护权,让一的一方来维护呢?上面的实验也表明了如果让一的一方来维护,始终都会多出两条update语句,因为外键是在多的这一方的,所以维护权应该交由多的一方。

    部门类的配置:第36行和第37行的配置,部门部门交出维护权利,让对方来维护

    package com.lizhou.entity.test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;
    
    import org.hibernate.annotations.GenericGenerator;
    
    /**
     * 部门:与员工一对多关系
     * @author bojiangzhou
     *
     */
    @Entity
    @Table(name="department")
    public class Department {
        
        @Id
        @GeneratedValue(generator="_native")
        @GenericGenerator(name="_native", strategy="native")
        private int id; //ID
        
        @Column(length=20)
        private String dname; //部门名称
        
        @OneToMany(mappedBy="department")
        private List<Employee> employeeList = new ArrayList<>(); //部门下的员工集合
    
        // get/set方法62     
    }

    员工类的配置不变。

    调用testSave时,部门和员工再对象上依然是关联的:第46-49行

    package com.lizhou.action.test;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.lizhou.entity.test.Department;
    import com.lizhou.entity.test.Employee;
    
    /**
     * 测试类
     * @author bojiangzhou
     *
     */
    
    public class TestAction {
        
        private static SessionFactory sessionFactory = null;
        
        static {
            //读取classpath中applicationContext.xml配置文件
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            //获取session中配置的sessionFactory对象
            sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory");
        }
        
        @Test
        public void testSave(){
            //创建一个部门对象
            Department d1 = new Department();
            d1.setDname("研发部");
            
            //创建两个员工对象
            Employee e1 = new Employee();
            e1.setEname("张三");
            e1.setPhone("13111111111");
            Employee e2 = new Employee();
            e2.setEname("李四");
            e2.setPhone("18523222222");
            
            //设置对象关联
            d1.getEmployeeList().add(e1);
            d1.getEmployeeList().add(e2);
            e1.setDepartment(d1);
            e2.setDepartment(d1);
            
            //获取Session
            Session session = sessionFactory.openSession();
            //开始事务
            Transaction t = session.beginTransaction();
            try {
                //添加数据
                session.save(d1);
                session.save(e1);
                session.save(e2);
                //提交事务
                t.commit();
            } catch (RuntimeException e) {
                //有异常则回滚事务
                t.rollback();
                e.printStackTrace();
            } finally {
                //关闭session
                session.close();
            }
        }
        
        
    }

    控制台打印的语句:只有三条插入语句,没有更新语句了

    1 Hibernate: insert into department (dname) values (?)
    2 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)
    3 Hibernate: insert into employee (departmentId, ename, phone) values (?, ?, ?)

    这里遇到一个问题:如果配置mappedBy属性的同时加上@JoinColumn会抛出异常,所以不能同时使用@JoinColumn和mappedBy;因为@JoinColumn本身就是自己来维护外键,和mappedBy冲突了。--->>>不知道这样理解正确否!!^_^

    总结:mappedBy属性跟xml配置文件里的inverse一样。在一对多或一对一的关系映射中,如果不表明mappedBy属性,默认是由本方维护外键。但如果两方都由本方来维护的话,会多出一些update语句,性能有一定的损耗。

    解决的办法就是在一的一方配置上mappedBy属性,将维护权交给多的一方来维护,就不会有update语句了。

    至于为何要将维护权交给多的一方,可以这样考虑:要想一个国家的领导人记住所有人民的名字是不可能的,但可以让所有人民记住领导人的名字!

    注意,配了mappedBy属性后,不要再有@JoinColumn,会冲突!

    OK!!!

  • 相关阅读:
    为什么要学习Linux
    测试开发技术:DOM中 innerHTML、innerText、outerHTML、outerText的区别
    web service 组件
    老李分享:webservice是什么?
    hibernate 和 mybatis 的区别
    mybatis 缓存
    过滤器和拦截器
    Spring 注解
    Spring 全局异常处理
    mybatis Mapper XML 映射文件
  • 原文地址:https://www.cnblogs.com/jietz0407-com/p/6951795.html
Copyright © 2011-2022 走看看