1.综述
总的来说,jeesite中hibernate的应用主要有2个方面,annotation和查询语句。前者主要是指定实体类与数据库表的各种关系,而后者则包括criteria,它以面向对象对方式来实现各种查询逻辑,以及HQL语句,hibernate自定的查询语句。
2.annotation
先说annotation,就拿User来举例,首先对与User类,有如下的实现:
@Entity
@Table(name = "sys_user")
@DynamicInsert @DynamicUpdate
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User extends IdEntity<User> {
...
}
@Entity用来告诉系统,这个类是一个实体类,提醒系统将它们跟普通POJO类分开。
@Table指定该实体类所对应的数据库表名
@DynamicInsert和@DynamicUpdate两个注释主要目的是提升操作数据的效率。@DynamicUpdate说明在更新时只修改有改动的字段,否则数据库会修改该列的所有字段。@DynamicInsert则针对插入时。
@Cache指定一个数据库表的缓存,也是效率相关。
以上这部分是针对实体类本身的,比较简单,而且直接拓下来完全没问题。
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer sys_id;
这句话在user类里没有用到,可是也特别常用。
@Id功能是指定主键。表示这个数据库表的主键是sys_id;
@GeneratedValue指定主键的生成方式,比如自增和手动等。
顺便一提,针对属性的注释可以选择在属性上或者在属性的get方法上。get方法会在把内容存入数据库这个过程中产生作用,而set方法会在把内容取出数据库这个过程中产生作用。
@ManyToOne
@JoinColumn(name="company_id")
@NotFound(action = NotFoundAction.IGNORE)
@JsonIgnore
@NotNull(message="归属公司不能为空")
@ExcelField(title="归属公司", align=2, sort=20)
public Office getCompany() {
return company;
}
现在开始在难一点的annotation上,主要是在对@ManyToOne,@OneToMany以及@ManyToMany的理解上。
上述代码是User类的getCompany函数及其注释。对与User来说,一个User对应一个Company,而一个Company对应多个User。所以User和Company是多对一的关系。在数据库表中,这种关系的实现形式是在多的那方加入一的那方的外键。
例如User(id,company_id,...),Company(id,...)这种形式。
@ManyToOne就是告诉系统实体属性在数据库表里是以这种形式来组织的。
@JoinColum则是指定这个外键字段在数据库表里的名称。
@NotFound顾名思义,在找不到引用外键多时候采取什么行为,默认是抛出异常,这里采取的是忽略,意思就是允许User没有对应的Company。
@JsonIgnore标明该属性不会在Json进行序列化的时候包含在内。但是我还没有弄懂为什么User类会需要转换为Json。
@NotNull看名字大家都懂的。
@ExcelField自定义的注释,导出excel用的。
这样处理之后,当需要将User中的company存入数据库时,hibernate就会将company所属的另一个实体类office的属性存入office类所对应的数据库表中,并且与User表进行外键关联。
接下来跳过OneToMany,咱们直接来看ManyToMany:
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "sys_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") })
@Where(clause="del_flag='"+DEL_FLAG_NORMAL+"'")
@OrderBy("id") @Fetch(FetchMode.SUBSELECT)
@NotFound(action = NotFoundAction.IGNORE)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonIgnore
@ExcelField(title="拥有角色", align=1, sort=800, fieldType=RoleListType.class)
public List<Role> getRoleList() {
return roleList;
}
@ManyToMany表示的是多对多的关系,一个用户可以有多个角色,一个角色也可以被多个用户所有。这种关系在数据库中一般是用一个关系数据库表来实现。
@JoinTable就是用来指定这个表的,在我们的系统中,user表和role表的关联表就是sys_user_role表,就在JoinTable的name字段里指定。其中joinColumns与inverseJoinColumns字段也是必须的字段,前者指定本类,也就是这里的User类在关联表中的字段名,既user_id,后者指的是对方类的字段名,这里便是role_id。
两个annotation组合起来,就构成了一个三张表的查询,当需要通过user查出rolelist的时候,先得到user_id,再在关系表中查询处多个role_id,最后取出多个role对象,返回List
以上便是数据库存取实体类的配置注释部分,通过这些注释,等同于构建了一个普通的java实体类,然后利用配置文件将之于某个数据库表关联起来,当然数据库多个表之间的关系也会反应在这个配置文件中。
3.查询方法
数据库配好了,我们可以轻松查询里面的数据了。不过hibernate提供了多种方法来进行查询,这些查询方法各有优劣。在jeesite里常用的查询方法有HQL,criteria,和普通SQL,criteria又分为普通criteria和DetachedCriteria。SQL的优点是学习成本低,反正大家多少懂一点,缺点是不能跨平台,换个数据库就不行了。HQL跟SQL差不多,但是hibernate进行了一部分封装,使用HQL就能够直接支持跨平台,缺点是查询语句要事先写好,动态性不强。criteria是面向对象的查询方法,能够动态的添加和删除条件。SQL基本不需要讲了,HQL其实也很简单,我会简单的列举jeesite中HQL的使用,criteria学习成本相对较高,接下来我主要会介绍这个部分。
首先看看HQL吧,jeesite的大部分DAO里,都有一个有一个具体的查询语句,调用find或者update函数。这些函数里通常有一段stirng字符串,这字符串是jeesite的作者自己加工的,最终这段字符串会被加工为HQL语句。
public List<Article> findByIdIn(String[] ids){
return find("from Article where id in (:p1)", new Parameter(new Object[]{ids}));
}
这段代码的作用是根据Id查询出具体的Article实体,其中的string在find函数中被装配为hibernate.Query,然后通过query.list()函数返回查询结果。这里之所以用HQL的原因是这一查询要求使用频繁并且要求不会变化,所以直接写HQL语句,直观且高效。
criteria被实现在BaseDao类中,它是所有Dao类的基类。
public Page<T> find(Page<T> page, DetachedCriteria detachedCriteria)
上述函数就是使用critera的find函数,它以一个DetachedCriteria作为参数,之后引导查询。这样做的好处是当一个具体的service想执行特定的查询时,将会自己构建一个查询criteria,然后传给DAO里的find,就能进行想要的查询。
下面会以jeesite前台页面的主页面所用查询为例的梳理整个过程,该页面内容写在frontindex.jsp文件中。
<div class="span4">
<h4>
<small>
<a href="${ctx}/list-2${urlSuffix}" class="pull-right">
更多>>
</a>
</small>
组织机构
</h4>
<ul>
<c:forEach items="${fnc:getArticleList(site.id, 2, 8, '')}" var="article">
<li>
<span class="pull-right">
<fmt:formatDate value="${article.updateDate}" pattern="yyyy.MM.dd"/>
</span>
<a href="${article.url}" style="color:${article.color}">${fns:abbr(article.title,28)}
</a>
</li>
</c:forEach>
</ul>
</div>
这段代码中最重要的就是fnc:getArticleList(site.id, 2, 8, '')
这个函数,整个函数的java实现在CmsUtils里。而这整个代码段的作用是从数据库中取出相应的8条记录,显示到登陆主页的展示框中。
public static List<Article> getArticleList(String siteId, String categoryId, int number, String param){
Page<Article> page = new Page<Article>(1, number, -1);
Article article = new Article(new Category(categoryId, new Site(siteId)));
if (StringUtils.isNotBlank(param)){
@SuppressWarnings({ "rawtypes" })
Map map = JsonMapper.getInstance().fromJson("{"+param+"}", Map.class);
if (new Integer(1).equals(map.get("posid")) || new Integer(2).equals(map.get("posid"))){
article.setPosid(String.valueOf(map.get("posid")));
}
if (new Integer(1).equals(map.get("image"))){
article.setImage(Article.YES);
}
if (StringUtils.isNotBlank((String)map.get("orderBy"))){
page.setOrderBy((String)map.get("orderBy"));
}
}
article.setDelFlag(Article.DEL_FLAG_NORMAL);
page = articleService.find(page, article, false);
return page.getList();
}
先看参数,第一个参数site.id参数不用关注,它的主页分为主网站和子网站,目前只有两种,这个ID是区分这两种网站的。第二个参数2是文章的分类id,从jsp文件可以看出这个div框所展现的是所有与组织机构相关的文章,数据库里也显示这一分类的分类ID是2。第三个参数是取出的文章个数,根据框的大小来设定,这里用的是8,也就是8条list。最后是预留参数,目前还没发现哪里在用。代码段里if之内的字段就是解析这一param之用,由于暂时没有使用所以不必关注。简单来讲,这个过程传入分类ID构建了一个新article类,传入数目构成了一个新的page类,然后将这两个类传到page = articleService.find(page, article, false)中。我们再来看articleService.find的代码。
public Page<Article> find(Page<Article> page, Article article, boolean isDataScopeFilter) {
// 更新过期的权重,间隔为“6”个小时
// 这部分我暂时未找到权重在哪里使用的,并且运行到现在所有权重都为0,不用更新,因此这部分可以不用关注。
//目前看来应该是文章排序相关
Date updateExpiredWeightDate = (Date) CacheUtils.get ("updateExpiredWeightDateByArticle");
if (updateExpiredWeightDate == null || (updateExpiredWeightDate != null && updateExpiredWeightDate.getTime() < new Date().getTime())){
articleDao.updateExpiredWeight();
CacheUtils.put("updateExpiredWeightDateByArticle", DateUtils.addHours(new Date(), 6));
}
// 通过articleDao创建了一个新的criteria
// criteria的类别是Article,表示该criteria的返回值是Article
DetachedCriteria dc = articleDao.createDetachedCriteria();
// 在dc当中添加两个别名,所谓别名是在查询关联数据表的时候使用
// hibernate会在Article实体类中寻找名为catergory的属性
// 并在查询的时候将两个实体类关联到一起
// Article与category的关系是一对多的关系
// 他们在数据库里的关联字段是category_id
// 这些都写在Article中get函数的annotation中
dc.createAlias("category", "category");
dc.createAlias("category.site", "category.site");
//如果category存在并且合法
if (article.getCategory()!=null && StringUtils.isNotBlank(article.getCategory().getId()) && !Category.isRoot(article.getCategory().getId())){
//首先通过传入的id取出category的实体
Category category = categoryDao.get(article.getCategory().getId());
//接着再进行判断,如果成功取出,则开始构建存取逻辑
if (category!=null){
//最外层是一个或逻辑,逻辑1与逻辑2满足一个条件即可
//接着是逻辑1:article中的category.id要与传入的分类id相等
//接着是逻辑2:article中category的父类包含传入分类id
//这一逻辑可以如下总结,对于我们传入的分类id,找到满足以下条件的文章
//文章的分类与传入分类相同,或者文章的分类是传入分类的子类
dc.add(Restrictions.or(
Restrictions.eq("category.id", category.getId()),
Restrictions.like("category.parentIds", "%,"+category.getId()+",%")));
//然后添加第二个逻辑
//文章所对应的网站id要与分类中的网站id相同
//这一网站id也是由前台传入,保留在catergory中
dc.add(Restrictions.eq("category.site.id", category.getSite().getId()));
//接着把已经取出的category存入article中
article.setCategory(category);
//如果不能取出category,说明数据库中不存在此分类?
//我也不知道为何category合法却不能取出
}else{
//则只比较site,不比较category
dc.add(Restrictions.eq("category.site.id", Site.getCurrentSiteId()));
}
//如果category不合法或者压根就不存在
}else{
//还是只比较site就行了
//我擦,category一旦有问题,解决方案就是把所有分类都取出来
dc.add(Restrictions.eq("category.site.id", Site.getCurrentSiteId()));
}
//之后就是根据具体设置来添加逻辑
//如果article的title存在
//在我这条逻辑线里这东西不可能存在
//在别的查询逻辑里可能存在title
if (StringUtils.isNotEmpty(article.getTitle())){
//那么找出包含此title的文章
dc.add(Restrictions.like("title", "%"+article.getTitle()+"%"));
}
//如果posid存在
//不知道是干什么的
if (StringUtils.isNotEmpty(article.getPosid())){
dc.add(Restrictions.like("posid", "%,"+article.getPosid()+",%"));
}
//如果图片地址存在
if (StringUtils.isNotEmpty(article.getImage()) && Article.YES.equals(article.getImage())){
//...
dc.add (Restrictions.and (Restrictions.isNotNull ("image"), Restrictions.ne ("image","")));
}
//如果createby有记录
if (article.getCreateBy()!=null && StringUtils.isNotBlank(article.getCreateBy().getId())){
//...
dc.add(Restrictions.eq("createBy.id", article.getCreateBy().getId()));
}
//如果需要进行范围过滤
if (isDataScopeFilter){
//再由dc创造两个别名,之前提到过别名是多表查询时使用的
//第一个是别名涉及的是article->category->office
//第二个就是article->createBy
//注意offic实际上对应的是Office实体类
//createBy对应的是User实体类
dc.createAlias("category.office", "categoryOffice").createAlias("createBy", "createBy");
//这里之后涉及office的查询实际上是三张表的查询了
//文章表(1)->(N)分类表(N)->(1)部门表
//各种关联由hibernate处理,我们不用处理细节
//dataScopeFilter是权限过滤函数
//它取出当前用户的所有角色
//合并这些角色对应的权限
//然后按照权限规则添加关于文章归属部门的查询条件
dc.add(dataScopeFilter(UserUtils.getUser(), "categoryOffice", "createBy"));
}
dc.add(Restrictions.eq(Article.FIELD_DEL_FLAG, article.getDelFlag()));
if (StringUtils.isBlank(page.getOrderBy())){
//为dc添加排序规则
dc.addOrder(Order.desc("weight"));
dc.addOrder(Order.desc("updateDate"));
}
//最终交给DAO进行查询
return articleDao.find(page, dc);
}
详细解析些在注释里了,其中包含了jeesite中criteria的最常见用法。