登录
首页 >  数据库 >  MySQL

ssh框架整合案例(字典表,no-session,hebiernate|模板的api,懒加载,级联删除,ajax的递归错误)

来源:SegmentFault

时间:2023-02-24 13:29:18 139浏览 收藏

积累知识,胜过积蓄金银!毕竟在##column_title##开发的过程中,会遇到各种各样的问题,往往都是一些细节知识点还没有掌握好而导致的,因此基础知识点的积累是很重要的。下面本文《ssh框架整合案例(字典表,no-session,hebiernate|模板的api,懒加载,级联删除,ajax的递归错误)》,就带大家讲解一下MySQL、Java、spring、javascript、eclipse知识点,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~

一:字典表

字典信息:在项目中可能会使用到,已经存在的一些信息。
    例如,客户的级别:普通用户,vip用户...
    客户的来源:网络营销,电话营销...
    客户所属行业:电子商务,房地产...
    客户的性别:男,女
    在保存用户的时候,这些信息都是已经存在的,不应该让用户让用户来任意填写,
    而是通过下拉列表来让用户选择。
    这些已经存在的信息称之为字典信息,将字典信息保存在字典表中。

二:表的设计

客户表和级别表,来源表和所属行业表的关系

图片描述

客户和级别表,行业表,来源表都属于多对一的关系
为了简化开发,可以将三张字典数据合成一张字典表

字典表中的内容

图片描述

三:实体之间的设计

customer表中的cust_level,cust_source,cust_industry字段属于外键
对应着字典表basedict中的dict_id主键
在多方(客户实体)中存在一方对象(字典实体)的引用
Customer实体设计

public class Customer implements Serializable{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long cust_id;
    private String cust_name;
    private String cust_phone;
    private String cust_mobile;
    //配置主外键关系    referencedColumnName外键所指向的主键    name:外键名称
    @ManyToOne(targetEntity=BaseDict.class)
    @JoinColumn(name="cust_level",referencedColumnName="dict_id")
    private BaseDict level;    客户级别
    @ManyToOne(targetEntity=BaseDict.class)
    @JoinColumn(name="cust_source",referencedColumnName="dict_id")
    private BaseDict source;   客户来源
    @ManyToOne(targetEntity=BaseDict.class)
    @JoinColumn(name="cust_industry",referencedColumnName="dict_id")
    private BaseDict industry; 客户所属行业

字典实体(BaseDict)

@Entity
@Table(name="base_dict")
public class BaseDict implements Serializable{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long dict_id;            字典表id
    private String dict_type_code;   类别(该条记录是来源,行业还是级别的标识)
    private String dict_type_name;   类别的名称(客户来源,客户行业,客户级别)
    private String dict_item_name;   来源,级别,行业中的具体项(vip,房地产,网络营销)
    private String dict_item_code;    
    private Integer dict_sort;       排序使用 
    private Character dict_enable;
    private String dict_memo;

知识回顾:Hibernate中查询的api

①:oid 通过id查询 get load方法
②hql:在HQL语句中不可能出现于数据库相关的信息,因为它是面向对象来操作的,
    只会出现实体类中的属性或对象如果出现了表名或者表中的字段,
    那么肯定是你的表名和类名相同,表中的字段和类中的属性相同
    查询的api
        Query query = session.createQuery(hql语句);
        query.list()查询所有
        query.uniqueResult()返回单一值
    
    基本查询:from 类名 查询该实体类映射的表中所有的记录封装为List
    条件查询:
        from 类名 where 属性名 like ?         占位符
        from 类名 where 属性名 = ? 
        from 类名 where 属性名 like :name;    名称占位符
        设置查询条件:
            query.setParameter(0, "%o%");//设置?占位符的参数值   
            query.setParameter(name, "%o%");//设置命名占位符的参数值
     分页查询
         String sql = "from Customer";
         Query query = session.createQuery(sql);
         query.setFirstResult(0);//相当于设置limit的第一个参数,起始索引
         query.setMaxResults(3);//相当于设置limit的第二个参数,一页显示多少条记录


hql查询续:
    排序
         String sql = "from Customer order by 属性名 desc";  
        //desc降序  默认为升序 
    聚合函数  count|sum|max|min|avg
        select count(cust_id) from Customer
        count(*)也ok
    投影查询 查询部分列
        要求实体类必须要有select后面的构造函数
        String hql = "select new Customer(c.cust_id,c.cust_name) from Customer c";

③:qbc查询
    session.createCriteria(持久化类的字节码对象)
    分页:
        setFirstResult(int 开启的索引)
        setMaxResults(int 每页显示的条数)
    排序:
        addOrder(Order.desc|asc(属性名));
    统计:
        setProjection(Projections.count|sum|min|max|avg(属性名))
        setProjection(Projections.rowCount())
    条件:
        add(Resitrctions.eq|like|gt|lt(属性名称,参数..));

离线查询对象:
    DetachedCriteria:api和Criteria中完全相同
    DetachedCriteria dc = DetachedCriteria(持久化类的字节码对象);
    在web层进行使用,在条件查询的时候对查询的条件进行封装,在调用service层
    方法得时候,只需要传递dc对象就ok。
HibernateTemplate(Hibernate模板操作数据库)
    save(obj)
    update(obj)
    delete(obj)
    get(class,id)
    load(class,id)
    
    findByCriteria(DetachedCriteria dc):qbc查询
    findByCriteria(DetachedCriteria dc,int firstResult,int maxResults):qbc分页
    find(String hql,Object... args):hql
    

需求一:添加客户到数据库中

添加客户的页面:

图片描述

    在添加用户的时候,要先从数据库中查询出客户级别,信息来源,所属行业
这些字典信息来让用户进行选择
由两种方式:
①:同步方式    先跳转到action,将查询到的字典内容放置到值栈中,然后再请发到add.jsp
②:异步方式    直接到add.jsp页面,当页面加载完成的时候发送ajax请求

先使用第一种方式:

//查询字典数据
    public void getDictValue(){
        通过类别可以查询到来源,级别,所属行业下的所有数据
        levelList = customerService.findBaseDictByTypeCode("006");
        sourceList = customerService.findBaseDictByTypeCode("002");
        industryList = customerService.findBaseDictByTypeCode("001");
    }

字典数据的集合设置为action的成员属性,提供get方法就可以在jsp页面中获取

add.jsp页面

    使用ognl+struts2标签获取致函中的数据(获取客户来源,所属行业类似)
    客户级别 :
    
        

customer表中的cust_level,cust_source,cust_industry字段属于外键
对应着字典表basedict中的dict_id主键
表单中
级别的name属性为:level.dict_id
来源的name属性为: source.dict_id
会使用属性封装的方式封装到BaseDict level中的dict_id
在保存Customer的时候会将对应的外键(cust_level,cust_source,cust_industry)保存

需求2:查询客户列表信息(分页+条件查询)

![图片描述

分页使用的ObjectVlaue(PageBean)部分代码
mysql分页    limit ?,?
    后台需要的条件:起始索引,一页显示的记录数
    前台需要的数据:当前页显示的数据,总页数,总的记录数
    前台需要传递的数据:当前页(没有传递默认为1)一页显示的记录数(没有传递,给出默认值)
    
    起始索引:(当前页-1)*一页显示的记录数
    总记录数:从数据库中查询而来
    总页数:Math.ceil(1.0*总记录数/总页数)
    当前页显示的数据:数据库中查询

public class PageBean implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer startIndex = 0;        //起始索引
    private Integer pageSize = 3;        //一页显示的记录数
    private Integer pageNumber = 1;        //当前页,由前端用户传递,如果用户没有传递默认显示第一页的数据
    private List result;    //封装查询出来的某一页的数据
    private Long totalRecord;        //总记录数        从数据库中查询而来
    //计算而来    总记录数%一页显示的记录==0?总记录数/一页显示的记录:总记录数/一页显示的记录+1
    private Integer totalPage;            //总页数        
    public Integer getTotalPage() {
        totalPage = (int) Math.ceil(1.0*totalRecord/pageSize);
        return totalPage;
    }
    
    //外界来获取起始索引   在内部进行计算
    public Integer getStartIndex() {
        startIndex = (pageNumber - 1) * pageSize;
        return startIndex;
    }
    public void setPageSize(Integer pageSize) {
        if(pageSize != null){
            如果前台没有传递,使用默认值
            this.pageSize = pageSize; 
        }else{
            this.pageSize = 3;
        }
    }
    public void setPageNumber(Integer pageNumber) {
        if(pageNumber != null){
            this.pageNumber = pageNumber; 
        }else{
            如果前台没有传递,使用默认值
            this.pageNumber = 1;
        }
    }
}

条件查询:在web层使用DetachedCriteria来封装查询的条件
web层action中的代码
使用DetachedCriteria来封装查询的条件,使用pageBean来封装当前页和一页显示的数据
调用service层方法的时候传递DetachedCriteria对象和pageBean对象

  DetachedCriteria dc = DetachedCriteria.forClass(Customer.class);
 /*
    * dc.add(Restrictions(propertyName, value))查询提交 
    propertyName:是属性字段 value是条件对应的值
    * 然后再使用对象导航查询去字典表中查询数据
*/
 if(customer.getCust_name() != null &&!customer.getCust_name().trim().equals("")){
    dc.add(Restrictions.like("cust_name","%"+customer.getCust_name().trim()+"%"));
 }
 /*
    * 对查询的条件进行判断和封装 不需要对对象进行非空判断,
    因为使用模型封装实体的时候,实体必须手动创建
    * 如果下拉列表没有没有被选择,select传递的value为-1
 */
 if(customer.getLevel() != null && customer.getLevel().getDict_id() != -1){
    dc.add(Restrictions.eq("level", customer.getLevel()));
  }
 if(customer.getSource() != null && customer.getSource().getDict_id() != -1){
    dc.add(Restrictions.eq("source", customer.getSource()));
 }
 if(customer.getIndustry() != null && customer.getIndustry().getDict_id() != -1){
    dc.add(Restrictions.eq("industry", customer.getIndustry()));
 }
 //传递的参数为离线查询对象(封装条件)和分页需要的pageBean
 pageBean = customerService.findList(dc,pageBean); 

service层的代码

    public PageBean findList(DetachedCriteria dc,PageBean pageBean) {
        /*
         * 需要填充的数据为当前页显示的数据
         * 总的记录数
         */
        //查询总的记录数(需要传递dc离线查询对象,因为不止是分页,是条件+分页)
        Long totalRecord = customerDao.searchTotalRecord(dc);
        pageBean.setTotalRecord(totalRecord);
        //查询当前页显示的记录
        List result = customerDao.searchCustomerList(dc,pageBean);
        pageBean.setResult(result);
        return pageBean;
    }

dao层的代码:

    @Override
    public Long searchTotalRecord(DetachedCriteria dc) {
        //设置投影(聚合)
        dc.setProjection(Projections.rowCount());
        /*
         * 查询语句类似于 select count(*) from Customer 
         * 如果有条件的分页就是select count(*) from Customer where ......
         */
        List record = (List) hibernateTemplate.findByCriteria(dc);
        /*
         * 查询完成之后需要去除投影,因为查询完成总记录数之后
         * 还需要查询当前页显示的数据  使用的是一个离线查询对象
         */
        dc.setProjection(null);
        return record.get(0);
    }
    //分页+条件查询    查询当前页显示的数据
    @Override
    public List searchCustomerList(DetachedCriteria dc, PageBean pageBean) {
        List customerList = (List) hibernateTemplate.findByCriteria(dc, pageBean.getStartIndex(), pageBean.getPageSize()); 
        return customerList;
    }

要注意的就是在进行总记录数查询的时候dc.setProjection(Projections.rowCount());
当查询完成的时候去去除投影,因为接下来查询当前页显示的记录数使用的也是同一个
离线查询对象。

查询条件的回显

两种方式:

    方式一:使用el表达式进行判断
    

方式二:使用jqery属性选择器
使用jquery的属性选择器来进行判断

当下拉列表中option中的值与之前选择的值相同时,让匹配的option选中


前台页面:
    当点击下一页或者索引页的时候,使用的是超链接。
    但是分页+条件查询,查询的条件在表单中
    解决:当点击索引页。上一页下一页之后。
        为超链接提供点击事件:
        将当前点击的页数动态的放置在表单输入项中
        然后提交表单,这样会将查询的条件和当前页一起提交到后台页面
  

当前页的表单
    后一页
    function toPage(pageNumber){
            //获取到提交当前页的输入框
            $("#pageNumberId").val(pageNumber);
            //提交表单
            $("#customerForm").submit();
    }

需求三:修改客户信息:
    先查询后修改:
    据客户的id先查询客户信息,然后请求转发到edit.jsp页面显示要修改的客户信息
    private Customer customer = new Customer();
    //使用模型驱动进行表单数据的封装
    @Override
    public Customer getModel() {
        return customer;
    }
    使用模型驱动封装要修改的的客户id
    此时customer中只有客户的id
    这时候如果还使用customer来接收的话,请求转发到edit.jsp页面中
    通过${成员属性}是获取不到内容的
    因为customer引用重新指向了一个对象
    
    要想在edit.jsp页面中获取到查询到的内容
     有三种方式:
    一:使用customer来接收
        使用ActionContext.getContext.getValueStack().push(customer);
        向root栈中存放customer指向的新对象    可以使用${成员属性获取到新内容}
    二:创建一个新的Customer findCustomer对象来接收,为该customer也提供相应的get方法
        在页面就可以使用${findCustomer.成员属性}来获取导内容
    三:使用模型封装的customer来接收
        在edit.jsp页面就可以使用${model.成员属性}的方法来获取到内容
        

no-session问题:
could not initialize proxy - no Session

原因:懒加载的问题
使用id获取要修改的对象时,使用load方法
访问 service 访问dao 返回是linkman的代理对象
代理对象返回给web层
页面获取数据 代理对象在使用时才会真正的去查询 但是session已经关闭

no-session的解决方案:

1、立即查询
2、延长session的存活时间 在web层将代理对象的数据查询完毕后在让session关闭
spring提供了一个Filter ----- OpenSessionInView

OpenSessionInViewFilterorg.springframework.orm.hibernate5.support.OpenSessionInViewFilterOpenSessionInViewFilter/*
注意一:必须让该过滤器在Struts2核心过滤器之前执行
因为当Struts核心过滤器执行完成之后,action的方法已经被执行
在操作数据库的时候session对象还没有创建,还是会有no-session问题
注意二:OpenSessionInViewFilter    
hibernate5.support.OpenSessionInViewFilter
必须和你导入Hibernate的版本一致,在这里是Hibernate5版本
否则可能会有异常:
org.hibernate.SessionFactory.openSession()Lorg/hibernate/classic/Session

no-session:懒加载问题

原生hibernate(非jpa形式)
类级别的延迟
    load()  直接查询实体对象时是否延迟  
    配置        默认true
    关联级别的延迟
    关联类的延迟
        linkman关联customer
                ----默认proxy
        
    关联集合的延迟
        customer关联linkman
                    ---默认是true

jpa形式的hibernate

一对多 查询一的一方多 多的一方默认延迟加载
    查询customer 对应的linkman 延迟加载
多对一 查询多的一方 一的一方默认立即加载    
    查询linkman  对应的customer的立即加载

删除客户(一方)删除客户的时候要删除客户下对应的联系人,配置级联删除
cascade=CascadeType.REMOVE
一方放弃维护关系,所以不能直接删除,需要先查后删
//在删除一方的时候先查询后删除,因为查询出来的对象和多表的一方存在关系,可以级联删除
customer = hibernateTemplate.get(Customer.class, customer.getCust_id());
hibernateTemplate.delete(customer);

删除多方的时候可以直接删除,单表操作的时候可以直接删除

ajax的递归错误
There is a cycle in the hierarchy
在json格式转换的时候出现死循环
表之间存在关系,Customer和LinkMan表是一对多的关系
在打印customer对象的时候,由于customer中存在LinkMan
会打印LinkMan对象,在打印LinkMan对象中,又存在Customer对象
递归调用,导致内存溢出
需求:
    添加联系人的时候要选择所属客户
    在add.jsp页面加载完成的时候,发送ajax请求
    获取数据库中的所有客户信息

    

web层的代码:

//去数据库中查找所有的客户信息
    @Action("customer_findCustomerList")
    public void findCustomerList() throws IOException{
        List customerList = customerService.findAll();
        //发送的是ajax请求  转换为json格式的数据
        JsonConfig jsonConfig = new JsonConfig();
        设置不参与转换的字段
        jsonConfig.setExcludes(new String[]{"linkMen"});
        String json = JSONArray.fromObject(customerList,jsonConfig).toString();
        //将json格式的数据写会给浏览器,解决中文乱码问题
        HttpServletResponse response = ServletActionContext.getResponse();
        response.setCharacterEncoding("UTF-8");
        response.getWriter().println(json);
    }

今天关于《ssh框架整合案例(字典表,no-session,hebiernate|模板的api,懒加载,级联删除,ajax的递归错误)》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于mysql的内容请关注golang学习网公众号!

声明:本文转载于:SegmentFault 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>