美文网首页
Hibernate 关联时的问题及解决

Hibernate 关联时的问题及解决

作者: 宇宙小神特别萌 | 来源:发表于2019-11-29 11:46 被阅读0次
    Hibernate 关联时的问题及解决目录.png

    项目类型:spring5.x+hibernate5

    一、no session 原因及解决

    1、原因:load()延迟查询引起noSession问题

    问题分析:对于根据id查询时,在dao通过load方式查询对象时,加载页面会报 noSession异常。当使用hibernate框架操作数据库的时候,如果做查询的话会有立即加载(get)和延迟加载(load)的区别,延迟加载表示,当你查询某个数据(假设是对象)的时候,hibernate不会立马发送sql语句,而是当我们调用这个对象的属性的时候,也就是真正使用查询出来的数据的时候才会发送sql语句去一级缓存(即session,这里的session和域对象session没有半毛钱关系)中获取,但是正常这个session的开启核关闭是在service层执行的,但是我们真正使用查询的对象的数据时,是在web层,但是这个时候session已经关闭,就会报no-session异常。

    noSession分析图

    解决方式1:让session的关闭时间要在web层使用完之后。 但是web层已经是最后一层了,怎么办?还有比web更后的东西哦,就是过滤器, 所以在web.xml中配置开启和关闭session的过滤器即可 ,但是要配在strut或springmvc过滤器器之前,否则无效。

    web.xml

        <!--hibernate延迟加载,配置在所有filter之前(springmvc过滤器前) 解决无限循环和no Session问题,在get方法上使用@JsonBackReference-->
        <filter>
            <filter-name>hibernateFilter</filter-name>
            <filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
            <init-param>
                <param-name>sessionFactoryBeanName</param-name>
                <param-value>sessionFactory</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>FORWARD</dispatcher>
        </filter-mapping>
    
    
    添加过滤器解决noSession分析图

    使用load方法的解决方案 : 就是把原CustomerService层的绑定的session对象 提取配置到前面的 过滤器中了。

    解决方式2: load改用get立即加载方式查询对象。

        /**
         * 根据id获取用户信息-成功
         * 查询数据不存在,返回null,推荐使用, 不存在延迟加载问题,不采用lazy机制的
         *
         * @param id
         */
        @Override
        public UserEntity getEntity(Integer id) {
            return hibernateTemplate.get(UserEntity.class, id);
        }
    
        /**
         * load查询单条数据,查询数据不存在,报异常(noSession),不推荐使用,存在延迟加载问题-失败
         *
         * @param id
         */
        @Override
        public UserEntity loadEntity(Integer id) {
            return hibernateTemplate.load(UserEntity.class, id);
        }
        
    

    2、原因:没有开启使用注解事务(或者已经开启注解事务但并未使用)

        <!-- 注解方式(配置事物):通过@Transactional启用事物管理,xml方式的配置会覆盖注解配置 -->
        <tx:annotation-driven transaction-manager="transactionManager" />
        
    

    解决方式:开启注解事务并在需要事务环境的类中使用,@Transactional注解可以使用在类上,也可以使用在方法上

    3、原因:手动配置事务时,propagation的值并未含有事务环境

    <!-- 手动配置事务通知属性 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!-- 定义事务传播属性 -->
            <tx:attributes>
                <!--方法头-->
                <tx:method name="save*" propagation="REQUIRED"/>
                <tx:method name="merge*" propagation="REQUIRED"/>
                <tx:method name="insert*" propagation="REQUIRED"/>
                <tx:method name="add*" propagation="REQUIRED"/>
                <tx:method name="set*" propagation="REQUIRED"/>
                <tx:method name="new*" propagation="REQUIRED"/>
                <tx:method name="update*" propagation="REQUIRED"/>
                <tx:method name="edit*" propagation="REQUIRED"/>
                <tx:method name="remove*" propagation="REQUIRED"/>
                <tx:method name="delete*" propagation="REQUIRED"/>
                <tx:method name="execute*" propagation="REQUIRED"/>
                <tx:method name="change*" propagation="REQUIRED"/>
                <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
                <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
                <tx:method name="load*" propagation="REQUIRED" read-only="true"/>
                <tx:method name="*" propagation="REQUIRED" read-only="true"/>
            </tx:attributes>
        </tx:advice>
    
    

    解决方式: 添加方法头,特别注意,将propagation值设置为REQUIRED

    二、懒加载出现死循环

    懒加载出现死循环场景:多对多关联查询,多对一和一对多查询、自关联查询 等会出现对象死循环。

    原因:(你中有我,我中有你)

    具体分析 在于你要转化的对象里配置了对另外一个对象的关联,而那个对象里又配置了对你这个对象的关联,两个对象配置了双向的一对多和多对一的关联关系。 JSON lib在把Dept部门对象转化为json字符串的时候,发现dept里有个Set<Employee>,它就会去级联的把Set<Employee>转化为json字符串,在它遍历Set的时候,发现Employee员工里又有一个Dept部门对象,这时候它又会去尝试把dept转化为json字符串,然后就发现dept里又有Set<Employee>,如此周而复始,就形成了死循环。

    代码演示:一对多(部门对员工)、多对一(员工对部门)

    //员工
    @Getter
    @Setter
    public class Employee {
        private int empId;// 员工的编号
        private String empName;// 员工的名称
        private double salary;// 员工的薪资
    
        private Dept dept;// 员工和部门的关系 
    }
    
    //部门
    @Getter
    @Setter
    public class Dept {
        private int deptId;// 部门编号
        private String deptName;// 部门名称
    
        private Set<Employee> emps;// 部门对应多个员工,即一对多的关系
    }
    
    

    Dept.hbm.xml

        <!--
          第三写其他映射,比如这里的set集合映射,set集合映射主要有以下几点:
            1 实体类申明的集合属性属性(name)
            2 集合属性对应的表(table)
            3 指定集合表的外键字段名称(key中的column)
            4 集合对象对应的实体类(noe-to-many中的class)
            -->
    <!--一对多(一个部门对应多个员工)-->
    <set name="emps" table="t_employee">
        <!--column指定了员工表的外键字段名称 -->
        <key column="deptId"/>
        <!-- class由于上面已经写了包名,这里直接使用即可 -->
        <one-to-many class="Employee" />
    </set>
    
    

    Employee.hbm.xml

        <!--
        多对一的映射配置:多个员工对应一个部门
        name是实体类中申明的属性;
        column外键字段名称,对应多的一方的数据库表中的外键字段名;
        class对应的部门实体类;
        -->
    <many-to-one name="dept" column="deptId" class="Dept"/>
    
    

    出现死循环:用浏览器访问显示

    { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : [ { "empId" : 1, "empName" : "员工1", "salary" : 1000.0, "dept" : { "deptId" : 1, "deptName" : "人事部", "emps" : 
    

    后台出现异常:java.lang.StackOverflowError 死循环

    要考虑两个大的方面:

    • 1.只有一方需要数据(如果你的业务单向关联就可以满足)

    • 2.双方数据都需要的情况(如果你的业务必须使用双向关联)

    1、只有一方需要数据

    解决思路:由于引起死循环查询的原因是序列化产生的,那么,针对序列化做处理,让不需要返回给前端的一方的对象不序列化。

    解决方式1:使用@JsonIgnore

    @Getter
    @Setter
    public class Dept {
        private int deptId;// 部门编号
        private String deptName;// 部门名称
    
        @JsonIgnore //忽略,返回给前端时忽略此属性
        private Set<Employee> emps;// 部门对应多个员工,即一对多的关系
    }
    
    @Getter
    @Setter
    public class Employee {
        private int empId;// 员工的编号
        private String empName;// 员工的名称
        private double salary;// 员工的薪资
    
        @JsonIgnore //忽略,返回给前端时忽略此属性
        private Dept dept;// 员工和部门的关系
    }
    
    

    注:不够友好,双方都要配置注解,若只配置一方,则另一方会报异常。

    效果:

    //查询部门信息
    {
        "deptId":1,
        "deptName":"人事部"
    }
    
    //查询员工信息
    {
        "empId":1,
        "empName":"员工1",
        "salary":1000.0
    }
    
    

    解决方式2:使用fastjson

        <!--解决hibernate 关联关系死循环问题-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.59</version>
            </dependency>
            
    

    spring-mvc.xml配置

        <!--开启springMVC注解驱动. SpringMVC整合FastJson:用"最快的json转换工具"替换SpringMVC的默认json转换-->
        <mvc:annotation-driven>
            <!--不使用默认消息转换器 -->
            <mvc:message-converters register-defaults="false">
                <!--spring消息转换器 -->
                <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
                <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter"/>
    
                <!--解决@Responcebody中文乱码问题 -->
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <constructor-arg value="UTF-8"/>
                </bean>
                <!--配合fastjson支持 -->
                <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                    <property name="defaultCharset" value="UTF-8"/>
                    <property name="supportedMediaTypes">
                        <list>
                            <!--顺序保持这样,避免IE下载出错 -->
                            <value>text/html;charset=UTF-8</value>
                            <value>text/json;charset=UTF-8</value>
                            <value>application/json</value>
                        </list>
                    </property>
                    <property name="fastJsonConfig" ref="fastJsonConfig"/>
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
    
        <!--fastJsonConfig -->
        <bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig">
            <!--默认编码格式 -->
            <property name="charset" value="UTF-8"/>
            <property name="serializerFeatures">
                <list>
                    <value>WriteNullListAsEmpty</value>
                    <value>WriteDateUseDateFormat</value>
                    <value>PrettyFormat</value>
                    <value>WriteMapNullValue</value>
                    <value>WriteNullStringAsEmpty</value>
                    <value>WriteNullListAsEmpty</value>
                    <value>DisableCircularReferenceDetect</value>
                </list>
            </property>
        </bean>
        <!--fastjson支持配置结束 -->
    
    

    使用fastjson注解 @JSONField(serialize=false)

    @Getter
    @Setter
    public class Dept {
        private int deptId;// 部门编号
        private String deptName;// 部门名称
    
        @JSONField(serialize=false)
        private Set<Employee> emps;// 部门对应多个员工,即一对多的关系
    }
    
    @Getter
    @Setter
    public class Employee {
        private int empId;// 员工的编号
        private String empName;// 员工的名称
        private double salary;// 员工的薪资
    
        //@JSONField(serialize=false)
        private Dept dept;// 员工和部门的关系
    }
    
    

    效果:

    //查询部门信息
    {
        "deptId":1,
        "deptName":"人事部"
    }
    
    //查询员工信息
    {
        "dept":{
            "deptId":1,
            "deptName":"人事部"
        },
        "empId":1,
        "empName":"员工1",
        "salary":1000.0
    }
    
    

    这种,比"解决方式1"更友好,只需要单方面取关联数据,完美解决序列化时导致的死循环问题

    解决方式3:@JsonBackReference

    web.xml配置

        <!--hibernate延迟加载,配置在所有filter之前 解决无限循环和no Session问题,在get方法上使用@JsonBackReference-->
        <filter>
            <filter-name>hibernateFilter</filter-name>
            <filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
            <init-param>
                <param-name>sessionFactoryBeanName</param-name>
                <param-value>sessionFactory</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>hibernateFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>FORWARD</dispatcher>
        </filter-mapping>
        
    

    使用@JsonBackReference

    @Getter
    @Setter
    public class Dept {
        private int deptId;// 部门编号
        private String deptName;// 部门名称
    
        //@JsonBackReference //尽量放到get方法上,在序列化时,@JsonBackReference的作用相当于@JsonIgnore
        private Set<Employee> emps;// 部门对应多个员工,即一对多的关系
    }
    
    @Getter
    @Setter
    public class Employee {
        private int empId;// 员工的编号
        private String empName;// 员工的名称
        private double salary;// 员工的薪资
        
        @JsonBackReference //尽量放到get方法上,在序列化时,@JsonBackReference的作用相当于@JsonIgnore
        private Dept dept;// 员工和部门的关系
    }
    
    

    效果:

    //查询部门信息
    {
      "deptId": 1,
      "deptName": "人事部",
      "emps": [
        {
          "empId": 2,
          "empName": "员工2",
          "salary": 2000
        },
        {
          "empId": 1,
          "empName": "员工1",
          "salary": 1000
        }
      ]
    }
    
    //查询员工信息
    {
      "empId": 1,
      "empName": "员工1",
      "salary": 1000
    }
    
    

    效果相当于"解决方式2"的效果。

    2、双方数据都需要的情况

    解决方式1:返回给前端DTO

    DTO与实体类一样,下面是DTO

    //部门DTO
    @Getter
    @Setter
    public class DeptDTO {
        private int deptId;// 部门编号
        private String deptName;// 部门名称
    
        private Set<EmployeeDTO> employeeDTOSet;// 部门对应多个员工,即一对多的关系
    }
    
    //员工DTO
    @Getter
    @Setter
    public class EmployeeDTO {
        private int empId;// 员工的编号
        private String empName;// 员工的名称
        private double salary;// 员工的薪资
    
        private DeptDTO deptDTO;// 员工和部门的关系
    }
    
    

    实体类:

    //部门实体类
    @Getter
    @Setter
    public class Dept {
        private int deptId;// 部门编号
        private String deptName;// 部门名称
    
        private Set<Employee> emps;// 部门对应多个员工,即一对多的关系
    }
    
    //员工实体类
    @Getter
    @Setter
    public class Employee {
        private int empId;// 员工的编号
        private String empName;// 员工的名称
        private double salary;// 员工的薪资
    
        private Dept dept;// 员工和部门的关系
    }
    
    

    使用DTO获取双向数据

        //获取部门
        @Override
        public Object getDept(int deptId){
            Dept dept = this.hibernateTemplate.get(Dept.class, deptId);
            System.out.println("部门名称:"+dept.getDeptName());
            Set<Employee> emps = dept.getEmps();
            Iterator<Employee> iterator = emps.iterator();
            Set<EmployeeDTO> employeeDTOSet = new HashSet<>();
            while (iterator.hasNext()){
                Employee employee = iterator.next();
                System.out.println("员工名称:"+employee.getEmpName());
    
                EmployeeDTO employeeDTO =new EmployeeDTO();
                employeeDTO.setEmpId(employee.getEmpId());
                employeeDTO.setEmpName(employee.getEmpName());
                employeeDTO.setSalary(employee.getSalary());
                employeeDTOSet.add(employeeDTO);
            }
    
            DeptDTO deptDTO = new DeptDTO();
            deptDTO.setDeptId(dept.getDeptId());
            deptDTO.setDeptName(dept.getDeptName());
            deptDTO.setEmployeeDTOSet(employeeDTOSet);
    
            return deptDTO;
            //return dept;
        }
        //获取所有部门
        @Override
        public Object getAllDept(){
            return this.hibernateTemplate.loadAll(Dept.class);
        }
    
        //获取员工
        @Override
        public Object getEmployee(int empId){
            Employee employee = this.hibernateTemplate.get(Employee.class, empId);
            System.out.println("员工名称:"+employee.getEmpName());
            Dept dept = employee.getDept();
            System.out.println("员工【 "+employee.getEmpName()+" 】属于 部门:"+dept.getDeptName());
    
            EmployeeDTO employeeDTO = new EmployeeDTO();
            employeeDTO.setEmpId(employee.getEmpId());
            employeeDTO.setEmpName(employee.getEmpName());
            employeeDTO.setSalary(employee.getSalary());
    
            DeptDTO deptDTO = new DeptDTO();
            deptDTO.setDeptId(employee.getDept().getDeptId());
            deptDTO.setDeptName(employee.getDept().getDeptName());
            employeeDTO.setDeptDTO(deptDTO);
    
            return employeeDTO;
            //return employee;
        }
        
    

    效果:

    //获取部门信息
    {
      "deptId": 1,
      "deptName": "人事部",
      "employeeDTOSet": [
        {
          "empId": 2,
          "empName": "员工2",
          "salary": 2000,
          "deptDTO": null
        },
        {
          "empId": 1,
          "empName": "员工1",
          "salary": 1000,
          "deptDTO": null
        }
      ]
    }
    
    //获取员工信息
    {
      "empId": 1,
      "empName": "员工1",
      "salary": 1000,
      "deptDTO": {
        "deptId": 1,
        "deptName": "人事部",
        "employeeDTOSet": null
      }
    }
    
    

    当然"只有一方需要数据",也是可以使用这种DTO方式的,这种方式比较全能,推荐使用。

    相关文章

      网友评论

          本文标题:Hibernate 关联时的问题及解决

          本文链接:https://www.haomeiwen.com/subject/bjgwwctx.html