【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; }