在這里,將創(chuàng)建一個(gè)簡(jiǎn)化的用戶管理模塊,演示怎樣利用SpringSide提供的數(shù)據(jù)持久層的功能,包括怎樣通過Hibernate的Annotation來配置多對(duì)一映射和多對(duì)多映射。
大家都知道,現(xiàn)在最流行用戶管理模型的是RBAC,也就是基于角色的訪問控制模型,在這種模型中,可以劃分多個(gè)層次,如用戶-角色-資源、用戶-角色-權(quán)限-資源、用戶-角色-角色組-權(quán)限-資源、用戶-角色-角色組-權(quán)限-操作-資源等等,因此,想要?jiǎng)?chuàng)建一個(gè)完善而復(fù)雜的用戶管理模塊,是相當(dāng)具有難度的。在Web2.0時(shí)代,有一個(gè)很重要的開發(fā)思想,那就是先讓程序跑起來,以后再逐步添加復(fù)雜的功能。因此,在這里只創(chuàng)建一個(gè)簡(jiǎn)化的用戶管理模塊。
所謂簡(jiǎn)化,它具有如下幾個(gè)特點(diǎn):
1.在認(rèn)證方式中,選擇基于用戶名和密碼的認(rèn)證,用戶需要提供用戶名、密碼和昵稱,用戶名和昵稱都要求不能重復(fù),用戶名不能包含中文,且不能夠被修改,昵稱可以為中文,也可以被修改。密碼使用MD5加密。
2.不包含用戶的真實(shí)信息,如姓名、年齡、性別、職業(yè)、地址、郵編等等,因?yàn)槿绻@些字段,那么還需要包含更多的額外字段來讓用戶決定是否公開這些信息,因此,去掉這些東西,可以簡(jiǎn)化開發(fā)過程,讓網(wǎng)站能夠盡快的跑起來。
3.聯(lián)系方式只需要用戶提供它的電子郵箱和QQ號(hào)碼。
4.如果用戶密碼丟失,可以通過密碼提示問題找回,隨機(jī)產(chǎn)生的新密碼會(huì)發(fā)到用戶的電子郵箱。
5.省略用戶的個(gè)性化設(shè)置,如個(gè)性化簽名、自定義頭像等。
6.要能夠記錄用戶的注冊(cè)時(shí)間和最后登錄時(shí)間。
7.要具有完善的積分和排名機(jī)制。
8.用戶刪除的時(shí)候不做物理刪除,只標(biāo)記為該用戶不可用。
8.具有簡(jiǎn)化的角色和權(quán)限管理機(jī)制,這里的簡(jiǎn)化主要有以下幾點(diǎn):每個(gè)用戶只能屬于一個(gè)角色,即多對(duì)一關(guān)系,而不是傳統(tǒng)的多對(duì)多關(guān)系;角色不需要分組;沒有專門的資源抽象層;在角色表中只使用一個(gè)字段來表示該角色具有的權(quán)限,權(quán)限以數(shù)字表示,以逗號(hào)分開,如“1,2”,“1,3,15”等等。
9.用戶可以創(chuàng)建群和加入群,為了簡(jiǎn)化,群的創(chuàng)始人即為管理員,并不可改變,用戶加入群需要管理員批準(zhǔn),一個(gè)用戶可以加如多個(gè)群,即多對(duì)多關(guān)系。
從上面的描述可以看出,一個(gè)簡(jiǎn)化的用戶管理系統(tǒng)最少需要三個(gè)表,即users,roles和groups表,其中users和roles之間為多對(duì)一映射,users和groups之間為多對(duì)多映射,為了實(shí)現(xiàn)多對(duì)多映射,并且用戶加入群的時(shí)候需要管理員批準(zhǔn),需要一個(gè)中間表users_groups。下面是在MySQL中創(chuàng)建數(shù)據(jù)表的語句。
創(chuàng)建用戶表:
create table users(
id int not null auto_increment primary key ,
name varchar ( 20 ) not null ,
password char ( 32 ) not null ,
monicker varchar ( 30 ) not null ,
question varchar ( 30 ) not null ,
answer varchar ( 30 ) not null ,
email varchar ( 40 ) not null ,
qq varchar ( 12 ) not null ,
roleid int not null ,
score int not null default ‘ 0 ‘ ,
regtime timestamp not null default CURRENT_TIMESTAMP ,
logintime timestamp not null default ‘ 2007-01-01 00:00:00 ‘ ,
isdeleted varchar ( 2 ) not null default ‘ 0 ‘ ,
index (username),
index (monicker));
為了加快查找用戶的速度,在用戶名和昵稱列上創(chuàng)建了索引。
創(chuàng)建角色表:
create table roles(
id int not null auto_increment primary key ,
name varchar ( 20 ) not null ,
privilegesFlag varchar ( 255 ),
index (rolename)
);
創(chuàng)建群組表:
create table groups(
id int not null auto_increment primary key ,
name varchar ( 40 ) not null ,
creatorid int not null ,
createtime timestamp not null default CURRENT_TIMESTAMP ,
isdeleted varchar ( 2 ) not null default ‘ 0 ‘ ,
index (groupname));
creatorid代表組的創(chuàng)始人,同時(shí)也是管理員,這里同樣設(shè)置群組不做物理刪除。
創(chuàng)建用戶群組多對(duì)多映射輔助表:
create table users_groups(
id int not null auto_increment primary key ,
userid int not null ,
groupid int not null ,
jointime timestamp ,
status tinyint ,
index (userid),
index (groupid)
);
其中status列代表用戶是否通過了管理員的批準(zhǔn),為了加快查找速度,在userid和groupid列上建立索引。
設(shè)計(jì)完數(shù)據(jù)庫,就該設(shè)計(jì)領(lǐng)域?qū)ο罅?,領(lǐng)域?qū)ο蟮脑O(shè)計(jì)方法為先設(shè)計(jì)簡(jiǎn)單的POJO,然后再在POJO上添加Hibernate Annotation來配置映射關(guān)系。在進(jìn)行Annotation配置的時(shí)候,可以從以下幾個(gè)方面進(jìn)行思考。
1、使用什么樣的數(shù)據(jù)類型映射數(shù)據(jù)庫中的列類型?
2、對(duì)象之間是一對(duì)一、一對(duì)多還是多對(duì)多關(guān)系?
3、關(guān)聯(lián)的對(duì)象之間哪一個(gè)作為主控方?
4、對(duì)象之間的關(guān)聯(lián)是單向的還是雙向的?
首先來看看users和roles之間的關(guān)系,考慮到加載一個(gè)用戶數(shù)據(jù)的時(shí)候,往往同時(shí)需要知道他屬于哪個(gè)角色,而加載一個(gè)角色的時(shí)候,就沒有必要知道它管理哪些用戶了,因此,它們是簡(jiǎn)單的單向關(guān)系,是多對(duì)一映射。當(dāng)出現(xiàn)多對(duì)一映射的時(shí)候,永遠(yuǎn)都應(yīng)該選擇多的這一方作為主控方,道理很簡(jiǎn)單,打個(gè)比方,讓一個(gè)國(guó)家元首記住全國(guó)人民的名字基本是不可能的,而讓全國(guó)人民記住國(guó)家元首的名字就很簡(jiǎn)單了。因此,這里User作為主控方,Role作為被控方。
再來看看數(shù)據(jù)類型的映射,對(duì)于簡(jiǎn)單的int、varchar這樣的就不用多說了。而日期時(shí)間類型的映射是一個(gè)重點(diǎn),可以看到,前面的數(shù)據(jù)庫創(chuàng)建語句中,所有需要時(shí)間的地方都使用了timestamp列類型,使用timestamp列類型的唯一目的就是為了能夠使用default CURRENT_TIMESTAMP語句,使用date和datetime類型就不行,在MySQL中,timestamp只能表示從‘1970-01-01 00:00:00‘到2037年的范圍。
MySQL中的timestamp和java.sql.Timestamp表現(xiàn)不一致,在MySQL中,timestamp和datetime類型精度是一樣的,都只能儲(chǔ)存到整數(shù)秒,而timestamp比datetime能表示的時(shí)間范圍要小得多,在Java中,java.util.Date和MySQL的timestamp的精度是一致的,只能儲(chǔ)存到整數(shù)秒,而java.sql.Timestamp還保存毫微秒,因此建議使用java.util.Date來映射timestamp列,使用java.sql.Timestamp只是浪費(fèi)。
MySQL和Java在時(shí)間上面還有一個(gè)沖突,那就是MySQL支持全零的時(shí)間,如‘0000-00-00 00:00:00‘,而Java不支持,因此如果在定義users表的logintime列時(shí)使用logintime timestamp not null default ‘0000-00-00 00:00:00‘,那么在使用Hibernate來獲取User對(duì)象的時(shí)候就會(huì)出錯(cuò),所以在創(chuàng)建數(shù)據(jù)庫的時(shí)候要選擇一個(gè)合法的默認(rèn)時(shí)間,如‘2007-01-01 00:00:00‘。
下面請(qǐng)看User.java的代碼:
package com.xkland.domain;

import java.io.Serializable;
import java.util.Date;
import org.springside.core.dao.extend.Undeletable;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence. * ;

@Entity
@Table(name = " users " )
@Undeletable(status = " isDeleted " )

public class User implements Serializable
{
private Integer id;
private String name;
private String password;
private String monicker;
private String question;
private String answer;
private String email;
private String qq;
private Role role;
private Integer score;
private Date regTime;
private Date loginTime;
private Byte isDeleted;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)

public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this .id = id;
}

public String getName()
{
return name;
}
public void setName(String name)
{
this .name = name;
}

public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this .password = password;
}

public String getMonicker()
{
return monicker;
}
public void setMonicker(String monicker)
{
this .monicker = monicker;
}

public String getQuestion()
{
return question;
}
public void setQuestion(String question)
{
this .question = question;
}

public String getAnswer()
{
return answer;
}
public void setAnswer(String answer)
{
this .answer = answer;
}

public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this .email = email;
}

public String getQq()
{
return qq;
}
public void setQq(String qq)
{
this .qq = qq;
}
@ManyToOne
@JoinColumn(name = " roleid " )

public Role getRole()
{
return role;
}
public void setRole(Role role)
{
this .role = role;
}
@Column(name = " score " ,insertable = false )

public Integer getScore()
{
return score;
}
public void setScore(Integer score)
{
this .score = score;
}
@Column(name = " regtime " ,insertable = false )
@Temporal(TemporalType.TIMESTAMP)

public Date getRegTime()
{
return regTime;
}
public void setRegTime(Date regTime)
{
this .regTime = regTime;
}
@Column(name = " logintime " ,insertable = false )
@Temporal(TemporalType.TIMESTAMP)

public Date getLoginTime()
{
return loginTime;
}
public void setLoginTime(Date loginTime)
{
this .loginTime = loginTime;
}
@Column(name = " isdeleted " ,insertable = false )

public Byte getIsDeleted()
{
return isDeleted;
}
public void setIsDeleted(Byte isDeleted)
{
this .isDeleted = isDeleted;
}
}
這里只對(duì)幾個(gè)特殊的Annotation做一下注釋:
1、因?yàn)閯?chuàng)建數(shù)據(jù)表的時(shí)候使用的是users,而實(shí)體類為User,單復(fù)數(shù)不同引發(fā)名稱不一致,因此需要@Table(name="users");
2、因?yàn)樵摫碇械臄?shù)據(jù)不做物理刪除,所以加上@Undeletable(status="isDeleted"),結(jié)合SpringSide提供的HibernateEntityExtendDao類,可以在調(diào)用remove方法的時(shí)候?qū)sdeleted列設(shè)置為"-1";
3、創(chuàng)建數(shù)據(jù)表的時(shí)候,所有的列名都是用的小寫字母,因此有的列映射需要明確指定,如@Column(name = "logintime",insertable=false);
4、對(duì)于在創(chuàng)建數(shù)據(jù)表的時(shí)候定義了默認(rèn)值的列,如regtime、regtime、logintime、isdeleted,在向數(shù)據(jù)庫中添加數(shù)據(jù)的時(shí)候,可以不在insert語句中指定這些列,而讓它們使用默認(rèn)值,因此,需要告訴Hibernate在生成insert語句的時(shí)候不要包含這些列,可以使用insertable=false語句,如@Column(name = "regtime",insertable=false);
5、指定時(shí)間精度,使用@Temporal(TemporalType.TIMESTAMP);
6、指定users表通過roleid和roles表進(jìn)行多對(duì)一映射,使用@ManyToOne和@JoinColumn(name="roleid")
Role.java則比較簡(jiǎn)單,如下:
package com.xkland.domain;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = " roles " )

public class Role implements Serializable
{
private Integer id;
private String name;
private String privilegesFlag;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)

public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this .id = id;
}

public String getName()
{
return name;
}
public void setName(String name)
{
this .name = name;
}

public String getPrivilegesFlag()
{
return privilegesFlag;
}
public void setPrivilegesFlag(String privilegesFlag)
{
this .privilegesFlag = privilegesFlag;
}
}
下一步再來看看users和groups之間的映射關(guān)系,不難想象,當(dāng)載入一個(gè)用戶的資料時(shí),往往需要知道他加入了哪些群,而載入一個(gè)群的資料時(shí),往往需要知道它有哪些用戶,因此,他們之間是一個(gè)雙向的關(guān)系,同時(shí),載入一個(gè)群的資料時(shí),還需要知道它的管理員是誰,因此又同時(shí)存在一個(gè)單向的多對(duì)一關(guān)系。在多對(duì)多關(guān)系中,設(shè)定User為主控方,所以需要在User.java中添加如下代碼?
private List < Group > groups;
@ManyToMany(targetEntity = User. class ,

cascade =
{CascadeType.PERSIST, CascadeType.MERGE} )
@JoinTable(name = " users_groups " ,

joinColumns =
{@JoinColumn(name = " userid " )} ,

inverseJoinColumns =
{@JoinColumn(name = " groupid " )} )

public List < Group > getGroups()
{
return groups;
}
public void setGroups(List < Group > groups)
{
this .groups = groups;
}
而整個(gè)Group.java的代碼如下:
package com.xkland.domain;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import org.springside.core.dao.extend.Undeletable;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence. * ;

@Entity
@Table(name = " groups " )
@Undeletable(status = " isDeleted " )

public class Group implements Serializable
{
private Integer id;
private String name;
private User creator;
private Date createTime;
private String isDeleted;
private List < User > users;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)

public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this .id = id;
}

public String getName()
{
return name;
}
public void setName(String name)
{
this .name = name;
}

@ManyToOne(cascade =
{CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name = " creatorid " )

public User getCreator()
{
return creator;
}
public void setCreator(User creator)
{
this .creator = creator;
}
@Column(name = " createtime " ,insertable = false )
@Temporal(TemporalType.TIMESTAMP)

public Date getCreateTime()
{
return createTime;
}
public void setCreateTime(Date createTime)
{
this .createTime = createTime;
}
@Column(name = " isdeleted " ,insertable = false )

public String getIsDeleted()
{
return isDeleted;
}
public void setIsDeleted(String isDeleted)
{
this .isDeleted = isDeleted;
}
@ManyToMany(cascade =
{CascadeType.PERSIST, CascadeType.MERGE} ,
mappedBy = " groups " ,
targetEntity = User. class )

public List < User > getUsers()
{
return users;
}
public void setUsers(List < User > users)
{
this .users = users;
}
}
好了,該開始測(cè)試了,看看經(jīng)過前面設(shè)計(jì)和配置的代碼能否正常工作。首先,先創(chuàng)建三個(gè)Manager,這三個(gè)Manager都繼承自org.springside.core.dao.extend.HibernateEntityExtendDao,至于HibernateEntityExtendDao的功能,請(qǐng)參考SpringSide的文檔。代碼如下:
UserManager.java:
package com.xkland.manager;

import org.springside.core.dao.extend.HibernateEntityExtendDao;
import com.xkland.domain.User;


public class UserManager extends HibernateEntityExtendDao < User >
{
}
RoleManager.java:
package com.xkland.manager;

import org.springside.core.dao.extend.HibernateEntityExtendDao;
import com.xkland.domain.Role;


public class RoleManager extends HibernateEntityExtendDao < Role >
{
}
GroupManager.java:
package com.xkland.manager;

import org.springside.core.dao.extend.HibernateEntityExtendDao;
import com.xkland.domain.Group;


public class GroupManager extends HibernateEntityExtendDao < Group >
{

}
下一步,將User.class、Role.class、Group.class等領(lǐng)域?qū)ο筇砑拥絪rc\main\resources\config\hibernate.cfg.xml中,如下:
<! DOCTYPE hibernate - configuration PUBLIC
" -//Hibernate/Hibernate Configuration DTD 3.0//EN "
" http://hibernate./hibernate-configuration-3.0.dtd " >
< hibernate - configuration >
< session - factory >
<!--< mapping class = " org.springside.helloworld.model.User " />-->
< mapping class = " com.xkland.domain.Role " />
< mapping class = " com.xkland.domain.User " />
< mapping class = " com.xkland.domain.Group " />
</ session - factory >
</ hibernate - configuration >
再下一步,將上面的三個(gè)Manager類交給Spring管起來,配置src\main\resources\spring\serviceContext.xml,如下:
<? xml version = " 1.0 " encoding = " UTF-8 " ?>
<! DOCTYPE beans PUBLIC " -//SPRING//DTD BEAN 2.0//EN " " http://www./dtd/spring-beans-2.0.dtd " >
< beans default - lazy - init = " true " default - autowire = " byName " >
< bean id = " roleManager " class = " com.xkland.manager.RoleManager " />
< bean id = " userManager " class = " com.xkland.manager.UserManager " />
< bean id = " groupManager " class = " com.xkland.manager.GroupManager " />
</ beans >
最后一步,編寫一個(gè)Action類,用Spring將上面的三個(gè)Manager注入到Action中,測(cè)試能否順利的操作數(shù)據(jù)庫。Action類的代碼如下:
package com.xkland.action;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xkland.manager. * ;
import com.xkland.domain. * ;


public class WelcomeAction extends Action
{
private RoleManager roleManager;
private UserManager userManager;
private GroupManager groupManager;
// 以下代碼的作用是注入三個(gè)Manager
public void setUserManager(UserManager userManager)
{
this .userManager = userManager;
}
public void setRoleManager(RoleManager roleManager)
{
this .roleManager = roleManager;
}

public void setGroupManager(GroupManager groupManager)
{
this .groupManager = groupManager;
}
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response

)
{
// 以下代碼測(cè)試能否添加role
Role role = new Role();
role.setName( " 第一個(gè)角色 " );
role.setPrivilegesFlag( " 1,2,3,4, " );
roleManager.save(role);
// 以下代碼測(cè)試能否添加user
User user = new User();
user.setAnswer( " aa " );
user.setEmail( " aa " );
user.setQq( " aa " );
user.setName( " abcdefg " );
user.setPassword( " aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa " );
user.setQuestion( " aa " );
user.setMonicker( " abcdefg " );
user.setRole(roleManager.get( 1 ));
userManager.save(user);
// 以下代碼測(cè)試能否添加group
Group group = new Group();
group.setName( " 第一個(gè)用戶組 " );
group.setCreator(user);
groupManager.save(group);
// 以下代碼測(cè)試將user和group建立關(guān)聯(lián)
user = userManager.get( 1 );
group = groupManager.get( 1 );
user.getGroups().add(group);
group.getUsers().add(user);
userManager.save(user);
groupManager.save(group);
// 重定向到
return new ActionForward( " /welcome.jsp " );
}
}
怎樣配置Action這里就不用多嘴了,請(qǐng)參考SpringSide的文檔。這里還要說一句,一定要記得修改src\main\resources\spring\applicationContext.xml中的事務(wù)配置中的package,否則運(yùn)行會(huì)出錯(cuò),配置文件片斷如下:
<!-- 基本事務(wù)定義,使用transactionManager作事務(wù)管理,默認(rèn)get * 方法的事務(wù)為readonly,其余方法按默認(rèn)設(shè)置.
默認(rèn)的設(shè)置請(qǐng)參考Spring文檔事務(wù)一章. -->
< tx:advice id = " txAdvice " >
< tx:attributes >
< tx:method name = " get* " read - only = " true " />
< tx:method name = " find* " read - only = " true " />
< tx:method name = " * " />
</ tx:attributes >
</ tx:advice >