程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> SpringSide開發實戰(四):打通數據持久層的任督二脈

SpringSide開發實戰(四):打通數據持久層的任督二脈

編輯:關於JAVA

在這裡,將創建一個簡化的用戶管理模塊,演示怎樣利用SpringSide提供的數據持久層 的功能,包括怎樣通過Hibernate的Annotation來配置多對一映射和多對多映射。

大家都知道,現在最流行用戶管理模型的是RBAC,也就是基於角色的訪問控制模型,在 這種模型中,可以劃分多個層次,如用戶-角色-資源、用戶-角色-權限-資源、用戶-角色- 角色組-權限-資源、用戶-角色-角色組-權限-操作-資源等等,因此,想要創建一個完善而 復雜的用戶管理模塊,是相當具有難度的。在Web2.0時代,有一個很重要的開發思想,那就 是先讓程序跑起來,以後再逐步添加復雜的功能。因此,在這裡只創建一個簡化的用戶管理 模塊。

所謂簡化,它具有如下幾個特點:

1.在認證方式中,選擇基於用戶名和密碼的認證,用戶需要提供用戶名、密碼和昵稱, 用戶名和昵稱都要求不能重復,用戶名不能包含中文,且不能夠被修改,昵稱可以為中文, 也可以被修改。密碼使用MD5加密。

2.不包含用戶的真實信息,如姓名、年齡、性別、職業、地址、郵編等等,因為如果包 含這些字段,那麼還需要包含更多的額外字段來讓用戶決定是否公開這些信息,因此,去掉 這些東西,可以簡化開發過程,讓網站能夠盡快的跑起來。

3.聯系方式只需要用戶提供它的電子郵箱和QQ號碼。

4.如果用戶密碼丟失,可以通過密碼提示問題找回,隨機產生的新密碼會發到用戶的電 子郵箱。

5.省略用戶的個性化設置,如個性化簽名、自定義頭像等。

6.要能夠記錄用戶的注冊時間和最後登錄時間。

7.要具有完善的積分和排名機制。

8.用戶刪除的時候不做物理刪除,只標記為該用戶不可用。

8.具有簡化的角色和權限管理機制,這裡的簡化主要有以下幾點:每個用戶只能屬於一 個角色,即多對一關系,而不是傳統的多對多關系;角色不需要分組;沒有專門的資源抽象 層;在角色表中只使用一個字段來表示該角色具有的權限,權限以數字表示,以逗號分開, 如“1,2”,“1,3,15”等等。

9.用戶可以創建群和加入群,為了簡化,群的創始人即為管理員,並不可改變,用戶加 入群需要管理員批准,一個用戶可以加如多個群,即多對多關系。

從上面的描述可以看出,一個簡化的用戶管理系統最少需要三個表,即users,roles和 groups表,其中users和roles之間為多對一映射,users和groups之間為多對多映射,為了 實現多對多映射,並且用戶加入群的時候需要管理員批准,需要一個中間表users_groups。 下面是在MySQL中創建數據表的語句。

創建用戶表:

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));

為了加快查找用戶的速度,在用戶名和昵稱列上創建了索引。

創建角色表:

create  table roles(
id int  not  null auto_increment primary  key ,
name varchar ( 20 ) not  null ,
privilegesFlag varchar ( 255 ),
index (rolename)
);

創建群組表:

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代表組的創始人,同時也是管理員,這裡同樣設置群組不做物理刪除。

創建用戶群組多對多映射輔助表:

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列代表用戶是否通過了管理員的批准,為了加快查找速度,在userid和 groupid列上建立索引。

設計完數據庫,就該設計領域對象了,領域對象的設計方法為先設計簡單的POJO,然後 再在POJO上添加Hibernate Annotation來配置映射關系。在進行Annotation配置的時候,可 以從以下幾個方面進行思考。

1、使用什麼樣的數據類型映射數據庫中的列類型?

2、對象之間是一對一、一對多還是多對多關系?

3、關聯的對象之間哪一個作為主控方?

4、對象之間的關聯是單向的還是雙向的?

首先來看看users和roles之間的關系,考慮到加載一個用戶數據的時候,往往同時需要 知道他屬於哪個角色,而加載一個角色的時候,就沒有必要知道它管理哪些用戶了,因此, 它們是簡單的單向關系,是多對一映射。當出現多對一映射的時候,永遠都應該選擇多的這 一方作為主控方,道理很簡單,打個比方,讓一個國家元首記住全國人民的名字基本是不可 能的,而讓全國人民記住國家元首的名字就很簡單了。因此,這裡User作為主控方,Role作 為被控方。

再來看看數據類型的映射,對於簡單的int、varchar這樣的就不用多說了。而日期時間 類型的映射是一個重點,可以看到,前面的數據庫創建語句中,所有需要時間的地方都使用 了timestamp列類型,使用timestamp列類型的唯一目的就是為了能夠使用default CURRENT_TIMESTAMP語句,使用date和datetime類型就不行,在MySQL中,timestamp只能表 示從'1970-01-01 00:00:00'到2037年的范圍。

MySQL中的timestamp和java.sql.Timestamp表現不一致,在MySQL中,timestamp和 datetime類型精度是一樣的,都只能儲存到整數秒,而timestamp比datetime能表示的時間 范圍要小得多,在Java中,java.util.Date和MySQL的timestamp的精度是一致的,只能儲存 到整數秒,而java.sql.Timestamp還保存毫微秒,因此建議使用java.util.Date來映射 timestamp列,使用java.sql.Timestamp只是浪費。

MySQL和Java在時間上面還有一個沖突,那就是MySQL支持全零的時間,如'0000-00-00 00:00:00',而Java不支持,因此如果在定義users表的logintime列時使用logintime timestamp not null default '0000-00-00 00:00:00',那麼在使用Hibernate來獲取User 對象的時候就會出錯,所以在創建數據庫的時候要選擇一個合法的默認時間,如'2007-01- 01 00:00:00'。

下面請看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;
} 
}

這裡只對幾個特殊的Annotation做一下注釋:

1、因為創建數據表的時候使用的是users,而實體類為User,單復數不同引發名稱不一 致,因此需要@Table(name="users");

2、因為該表中的數據不做物理刪除,所以加上@Undeletable(status="isDeleted"),結 合SpringSide提供的HibernateEntityExtendDao類,可以在調用remove方法的時候將 isdeleted列設置為"-1";

3、創建數據表的時候,所有的列名都是用的小寫字母,因此有的列映射需要明確指定, 如@Column(name = "logintime",insertable=false);

4、對於在創建數據表的時候定義了默認值的列,如regtime、regtime、logintime、 isdeleted,在向數據庫中添加數據的時候,可以不在insert語句中指定這些列,而讓它們 使用默認值,因此,需要告訴Hibernate在生成insert語句的時候不要包含這些列,可以使 用insertable=false語句,如@Column(name = "regtime",insertable=false);

5、指定時間精度,使用@Temporal(TemporalType.TIMESTAMP);

6、指定users表通過roleid和roles表進行多對一映射,使用@ManyToOne和@JoinColumn (name="roleid")

Role.java則比較簡單,如下:

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之間的映射關系,不難想象,當載入一個用戶的資料時, 往往需要知道他加入了哪些群,而載入一個群的資料時,往往需要知道它有哪些用戶,因此 ,他們之間是一個雙向的關系,同時,載入一個群的資料時,還需要知道它的管理員是誰, 因此又同時存在一個單向的多對一關系。在多對多關系中,設定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;
}

而整個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;
   }
}

好了,該開始測試了,看看經過前面設計和配置的代碼能否正常工作。首先,先創建三 個Manager,這三個Manager都繼承自 org.springside.core.dao.extend.HibernateEntityExtendDao,至於 HibernateEntityExtendDao的功能,請參考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等領域對象添加到src\main\resources\config\hibernate.cfg.xml中,如下:

<! DOCTYPE hibernate-configuration PUBLIC
     "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd" >
< hibernate-configuration >
   < session-factory >
     < mapping class ="com.xkland.domain.Role" />
     < mapping class ="com.xkland.domain.User" />
     < mapping class ="com.xkland.domain.Group" />
   </ session-factory >
</ hibernate-configuration >

再下一步,將上面的三個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.springframework.org/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 >

最後一步,編寫一個Action類,用Spring將上面的三個Manager注入到Action中,測試能 否順利的操作數據庫。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;
  // 以下代碼的作用是注入三個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
  ) {
  // 以下代碼測試能否添加role
  Role role =  new Role();
  role.setName( " 第一個角色 " );
  role.setPrivilegesFlag( " 1,2,3,4, " );
  roleManager.save(role);
  // 以下代碼測試能否添加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);
  // 以下代碼測試能否添加group
  Group group =  new Group();
  group.setName( " 第一個用戶組 " );
  group.setCreator(user);
  groupManager.save(group);
  // 以下代碼測試將user和group建立關聯
  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這裡就不用多嘴了,請參考SpringSide的文檔。這裡還要說一句,一定 要記得修改src\main\resources\spring\applicationContext.xml中的事務配置中的package, 否則運行會出錯,配置文件片斷如下:

<!-- 以AspectJ方式 定義 AOP -->
   < aop:config proxy-target-class ="true" >
     <!-- 注意,請把第2個*號換為項目package -->
     < aop:advisor pointcut ="execution(* *..manager.*Manager.*(..))" advice-ref ="txAdvice" />
     < aop:advisor pointcut ="execution(* org.springside.core.dao.*Dao.*(..))"  advice-ref ="txAdvice" />
   </ aop:config >
   <!-- 基本事務定義,使用transactionManager作事務管理,默認get*方法的事務為 readonly,其余方法按默認設置.
       默認的設置請參考Spring文檔事務一章. -->
   < 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 >

如果有興趣,還可以把hibernate.show_sql設置為true,以便觀察Hibernate生成的SQL 語句,如下圖:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved