• 【SSH网上商城项目实战05】完成数据库的级联查询和分页


            上一节我们完成了EasyUI菜单的实现。这一节我们主要来写一下CategoryServiceImpl实现类,完成数据库的级联查询。一般项目从后往前做,先做service(我们没有抽取Dao,最后再抽取),做完了再做上面层。

            在写之前,先看一下数据库中的表的情况:

    drop database if exists shop;  
    /*创建数据库,并设置编码*/  
    create database shop default character set utf8;  
      
    use shop;  
    /*删除管理员表*/  
    drop table if exists account;  
    /*删除商品类别表*/  
    drop table if exists category;  
      
    /*============================*/  
    /*      Table:管理员表结构                       */  
    /*============================*/  
    create table account  
    (  
        /* 管理员编号,自动增长 */  
        id int primary key not null auto_increment,  
        /* 管理员登录名 */  
        login varchar(20),  
        /* 管理员姓名 */  
        name varchar(20),  
        /* 管理员密码 */  
        pass varchar(20)  
    );  
      
    /*============================*/  
    /*     Table:商品类别表结构                      */  
    /*============================*/  
    create table category  
    (  
       /* 类别编号,自动增长 */  
       id  int primary key not null auto_increment,  
       /* 类别名称 */  
       type varchar(20),  
       /* 类别是否为热点类别,热点类别才有可能显示在首页*/  
       hot  bool default false,  
       /* 外键,此类别由哪位管理员管理 */  
       account_id int,  
       constraint aid_FK foreign key(account_id) references account(id)  
    );  

          主要有两张表,商品类别表和管理员表,并且商品类别表中提供了一个外键关联管理员表。也就是商品和管理员是多对一的关系。现在我们开始编写查询商品的类别信息,需要级联管理员。

    1. 实现级联查询方法

            首先在CategoryService接口中定义该方法:

    public interface CategoryService extends BaseService<Category> {  
        //查询类别信息,级联管理员  
        public List<Category> queryJoinAccount(String type); //使用类别的名称查询  
    } 

      然后我们在CategoryService的实现类CategoryServiceImpl中实现这个方法:

    @Service("categoryService")  
    public class CategoryServiceImpl extends BaseServiceImpl<Category> implements CategoryService {  
      
        @Override  
        public List<Category> queryJoinAccount(String type) {  
            String hql = "from Category c where c.type like :type";  
            return getSession().createQuery(hql)  
                    .setString("type", "%" + type + "%").list();  
        }  
    }  

      在两个Model中我们配一下关联注解:

    //Category类中  
    @ManyToOne(fetch = FetchType.EAGER)  
    @JoinColumn(name = "account_id")  
    public Account getAccount() {  
        return this.account;  
    }  
    //Account类中  
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "account")  
    public Set<Category> getCategories() {  
        return this.categories;  
    } 

       然后我们在测试类中测试一下:

    @RunWith(SpringJUnit4ClassRunner.class)  
    @ContextConfiguration(locations="classpath:beans.xml")  
    public class CategoryServiceImplTest {  
      
        @Resource  
        private CategoryService categoryService;  
          
        @Test  
         public void testQueryJoinAccount() {  
            for(Category c : categoryService.queryJoinAccount("")) {  
                 System.out.println(c);  
                 System.out.println(c.getAccount());  
            }  
        }  
    }

    2. 级联查询存在的问题

            我们看一下控制台的输出可以看出,它发了不止一条SQL语句,但是我们明明只查询了一次,为什么会发这么多语句呢?这就是常见的1+N问题。所谓的1+N问题,就是首先发出一条语句查询当前对象,然后发出N条语句查询关联对象,因此效率变得很低。这里就两个对象,如果有更多的对象,那效率就会大打折扣了,我们该如何解决这个问题呢?

            可能大家会想到将fetch设置生FetchType.LAZY就不会发多条语句了,但是这肯定不行,因为设置成LAZY后,我们就拿不到Account对象了,比较好的解决方法是我们自己写hql语句,使用join fetch。具体看修改后的CategoryServiceImpl实现类:

    @Service("categoryService")  
    public class CategoryServiceImpl extends BaseServiceImpl<Category> implements CategoryService {  
      
        @Override  
        public List<Category> queryJoinAccount(String type) {  
            String hql = "from Category c left join fetch c.account where c.type like :type";  
            return getSession().createQuery(hql)  
                    .setString("type", "%" + type + "%").list();  
        }  
    }

      left join表示关联Account一起查询,fetch表示将Account对象加到Category中去,这样就只会发一条SQL语句了,并且返回的Category中也包含了Account对象了。 like:type";需要注意type后面不能有空格。

    3. 完成分页功能

            hibernate中的分页很简单,只需要调用两个方法setFirstResult和setMaxResults即可:我们修改一下CategoryService接口和它的实现类CategoryServiceImpl:

    //CategoryService  
    public interface CategoryService extends BaseService<Category> {  
        //查询类别信息,级联管理员  
        public List<Category> queryJoinAccount(String type, int page, int size); //并实现分页  
    }  
      
    //CategoryServiceImpl  
    @Service("categoryService")  
    public class CategoryServiceImpl extends BaseServiceImpl<Category> implements CategoryService {  
      
        @Override  
        public List<Category> queryJoinAccount(String type, int page, int size) {  
            String hql = "from Category c left join fetch c.account where c.type like :type";  
            return getSession().createQuery(hql)  
                    .setString("type", "%" + type + "%")  
                    .setFirstResult((page-1) * size) //从第几个开始显示  
                    .setMaxResults(size) //显示几个  
                    .list();  
        }  
    }

    我们在测试类中测试一下:

    @RunWith(SpringJUnit4ClassRunner.class)  
    @ContextConfiguration(locations="classpath:beans.xml")  
    public class CategoryServiceImplTest {  
      
        @Resource  
        private CategoryService categoryService;  
      
        @Test  
        public void testQueryJoinAccount() {  
            for(Category c : categoryService.queryJoinAccount("",1,2)) { //显示第一页,每页2条数据  
                System.out.println(c + "," + c.getAccount());  
            }  
        }  
    }  

    本篇可能会遇到的错误:

    org.hibernate.hql.internal.ast.QuerySyntaxException: Category is not mapped [from Category c left join fetch c.account where c.type like :type]

    Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Category is not mapped

    这个的原因是 如果已经在hibernate.cfg.xml上加上

    <mapping class="cn.it.shop.model.Category" />
    <mapping class="cn.it.shop.model.Account" />

    还是提示无法映射到 是因为在model里面 你需要加上@Entity  @Table 以及id自增@Id // 表示主键 @GeneratedValue

        @Column(name = "id", unique = true, nullable = false)和 每个字段的 @Column  (楼主犯错的原因是因为以为如果是数据库有表逆向生成model不需要写,如果先写model才需要写这些@Entity和@Table @column逆向生成表)
    /**
     * Category entity. @author MyEclipse Persistence Tools
     */
    
    @Entity
    @Table(name="category")
    public class Category implements java.io.Serializable {
    
    
        private Integer id;
        private String type;
        private Boolean hot;
        
        private Account account;
        private Set<Product> products = new HashSet<Product>(0);
        
        
        public Category() {
        
        }
        @Override
        public String toString() {
            return "Category [id=" + id + ", type=" + type + ", hot=" + hot
                    + ", account=" + account + "]";
        }
        
        
        public Category(Account account, String type, Boolean hot,
                Set<Product> products) {
            this.account = account;
            this.type = type;
            this.hot = hot;
            this.products = products;
        }
        
        public Category(Integer id, String type, Boolean hot) {
            super();
            this.id = id;
            this.type = type;
            this.hot = hot;
        }
        
        public Category(String type, Boolean hot) {
            super();
            this.type = type;
            this.hot = hot;
        }
    
        // Property accessors
        @Id              // 表示主键
        @GeneratedValue
        @Column(name = "id", unique = true, nullable = false)
        public Integer getId() {
            return this.id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
        
        //多对一 //    @ManyToOne(fetch = FetchType.LAZY)
        //@ManyToOne(fetch=FetchType.EAGER,cascade=CascadeType.ALL) //急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。
        @ManyToOne(fetch=FetchType.EAGER)//insert 会update null问题
        @JoinColumn(name="account_id")//注释本表中指向另一个表的外键。
        public Account getAccount(){
            return this.account;
        }
        public void setAccount(Account account) {
            this.account = account;
        }
    
        @Column(name="type",length = 20)
        public String getType() {
            return this.type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
        @Column(name="hot")
        public Boolean getHot() {
            return this.hot;
        }
    
        public void setHot(Boolean hot) {
            this.hot = hot;
        }
  • 相关阅读:
    2012 Multi-University #8
    2016"百度之星"
    Codeforces Round #352 (Div. 2)
    数位DP CF 55D Beautiful numbers
    数位DP GYM 100827 E Hill Number
    2012 Multi-University #9
    2012 Multi-University #10
    java生成指定范围的随机数
    MySql查询时间段的方法
    eclipse报错GC overhead limit exceed,卡顿
  • 原文地址:https://www.cnblogs.com/shanheyongmu/p/5805410.html
Copyright © 2020-2023  润新知