小型BBS一
该系统是
Struts
与
Ibatis
结合运用的一个实例,通过做一个小型的
bbs
论坛,了解如何结合使用
Struts
与
Ibatis
。业务逻辑层与数据层的实现如下:
一.系统的功能
:
用户:注册,登录
文章:发贴,回复,编辑,浏览
二.系统的架构
采用三层架构:
WEB
层,业务逻辑层,
DAO
层,在讲解三层体系架构时需要先明白什么是横向划分?什么是纵向划分?
横向划分:也就是按所谓的
MVC
分层划分。该系统就采用这种结构。
纵向划分:也就是所谓的模块划分。
如下图所示:
三.系统的实现
首先做业务逻辑层(采用的是
Java
工程)
1.
设计
domain
(域对象)
l
User.java id
用户的编号;
logonName
(登录的用户名)在系统中是不允许重复的;
nickName
昵称,它是一个对业务逻辑无影响的字段;
password
用户密码。具体代码略。
l
Article.java
该文件是封装文章的一个实体
bean
,代码如下:
package com.sample.domain;
import java.util.Date;
public class Article {
private int id; //
文章的
id
号
private String title; //
文章的标题
private String content;//
文章内容
private Date issue; //
文章发布的日期
private int parentId; //
发帖时用到的属性指向主贴,即针对那个文章进行评论的
private User author;//
作者
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getIssue() {
return issue;
}
public void setIssue(Date issue) {
this.issue = issue;
}
public int getParentId() {
return parentId;
}
public void setParentId(int parentId) {
this.parentId = parentId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
2.
业务层接口
l
Interface UserService
代码如下:
public interface UserService {
//
注册新用户
public void regist(User user);
//
用户登录
public User logon(String username, String password);
//
浏览用户信息
public User getUserById(int id);
}
注意:方法名做到见名知意,方便理解。
l
Interface ArticleService
代码如下:
public interface ArticleService {
/**
*
发贴
* @param article
要发表的文章
*/
public void addArticle(Article article);
/**
*
根据
id
号取得相应文章
* @param artId
文章的
id
* @return
代表文章的对象
*/
public Article getArticleById(int artId);
/**
*
删除文章
* @param artId
要删除的文章的
id
* @param opreator
操作者(用于判断是否有删除权限)
*/
public void delArticle(int artId, User operator);
/**
*
编辑文章
* @param art
要编辑的文章
* @param operator
操作者(用于判断是否有编辑权限)
*/
public void updateArticle(Article art, User operator);
/**
*
获得跟帖的列表
* @param parentid
原帖的文章
id
* @return
原帖的跟贴列表
*/
public List<Article> getChildrenArticles(int parentId);
//
获得帖子列表
public List<Article> getArticles();
}
3.
数据访问层接口(
DAO
)
l
Inerface UserDao
代码如下:
public interface UserDao {
/**
*
增加用户
* @param user
要增加的新用户
*/
public void addUser(User user);
/**
*
根据用户名查找用户
* @param logonName
要查找的用户名
* @return
如果找到了,返回该用户;如果没找到,返回
null
。
*/
public User getUserByUsername(String logonName);
/**
*
根据用户名和密码查找用户
* @param logonName
要查找的用户名
* @param password
要查找的用户的密码
* @return
如果找到了,返回该用户;如果没找到,返回
null
。
*/
public User getUserByUsernameAndPassword(String logonName, String password);
/**
*
根据
id
查找用户
* @param id
要查找的用户
id
* @return
如果找到了,返回该用户;如果没找到,返回
null
。
*/
public User getUserById(int userId);
}
l
Interface ArticleDao
代码如下:
public interface ArticleDao {
/**
*
增加文章
* @param article
要发表的文章
*/
public void addArticle(Article article);
/**
*
根据
id
号取得相应文章
* @param artId
文章的
artId
* @return
代表文章的对象
*/
public Article getArticleById(int artId);
/**
*
删除文章
* @param artId
要删除的文章的
id
*/
public void delArticle(int artId);
/**
*
编辑文章
* @param art
要编辑的文章
*/
public void updateArticle(Article art);
/**
*
获得跟帖的列表
* @param parentId
原帖的文章
id
* @return
原帖的跟贴列表
*/
public List<Article> getChildrenArticles(int parentId);
}
4.
业务逻辑层的实现类
l
UserServiceImpl
程序代码如下:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
//
浏览用户信息
public User getUserById(int userId) {
//
没有需要特殊处理的逻辑,直接委托
DAO
去做即可
return this.userDao.getUserById(userId);
}
//
用户登录
public User logon(String logonName, String password) {
User user = this.userDao.getUserByLogonNameAndPassword(logonName,
password);
/*
判断返回值的操作,是一种面向过程的方式。在面向对象程序设计中,尽量采用抛异常的方式。这样做的好处是使上层代码可以将正常流程与异常流程分开。
*/
if (user == null)
throw new ServiceException("
用户名或密码错误!
");
return user;
}
//
注册新用户
public void regist(User user) {
//
首先检查该用户名是否已被占用
User u = this.userDao.getUserByLogonName(user.getLogonName());
if (u != null)
throw new ServiceException("
登录名重复!
");
this.userDao.addUser(user);
}
}
l
UserServiceTest
该类是一个单元测试类,用来测试用户的注册,登录等。使用时需首先导入
junit
发布包中的
jar
包,或导入
eclipse
自带的
junit
的
jar
包。
用
junit
写测试用例最重要的好处就是节约时间,及早发现问题,以降低修复问题的成本。程序代码如下:
public class UserServiceTest extends TestCase {
UserService userService;
public UserServiceTest(String arg0) {
super(arg0);
userService = new UserServiceImpl(new UserDaoIbatisImpl());
}
public void testRegist() {
User user = this.regist("1");
assertTrue(user.getId() > 0);
try {
//
测试重复注册
userService.regist(user);
//
如果没有抛异常,进而走到下一行代码,则说明测试失败。
fail("
注册重复的登录名
!");
} catch (ServiceException e) {
//
如果抛出异常,则表明测试通过(能够检测得到用户名重复)
}
}
//
测试是否是注册用户
public void testLogon() {
this.regist("2");
User user = this.userService.logon("logonName2", "password2");
assertTrue(user.getId() > 0);
try {
this.userService.logon("logonName", "password");
fail("
登录错误!
");
} catch (ServiceException e) {
// right;
}
}
public void testGetUserById() {
User user = this.regist("3");
User u = this.userService.getUserById(user.getId());
assertEquals(user.getLogonName(), u.getLogonName());
}
private User regist(String post) {
User user = new User();
user.setLogonName("logonName" + post);
user.setNickName("nickName" + post);
user.setPassword("password" + post);
userService.regist(user);
return user;
}
}
/*
该类用来创建一个模拟的
DAO
。因为实际开发的时候,不同层的开发人员不可能互相等待对方的代码都开发完之后,再去测试自己的代码,所以需要构造一个模拟的
DAO*/
class MockUserDao implements UserDao {
private Map<Integer, User> users = new HashMap<Integer, User>();
private int id = 1;
public void addUser(User user) {
user.setId(id++);
System.out.println(users);
users.put(user.getId(), user);
System.out.println(users);
}
public User getUserById(int userId) {
return users.get(userId);
}
public User getUserByLogonName(String logonName) {
for (Iterator<User> iter = users.values().iterator(); iter.hasNext();) {
User user = iter.next();
if (user.getLogonName().equals(logonName))
return user;
}
return null;
}
public User getUserByLogonNameAndPassword(String logonName, String password) {
for (Iterator<User> iter = users.values().iterator(); iter.hasNext();) {
User user = iter.next();
if (user.getLogonName().equals(logonName)&& user.getPassword().equals(password))
return user;
}
return null;
}
}
l
ArticleServiceImpl
代码略类似
UserServiceImpl
5. DAO
的实现类
l
设计数据表
根据需求数据库采用
MySQL,
数据表的结构如下所示:
表
5.1
用户表(
user
)
字段
|
字段类型
|
描述
|
id
|
int
|
用户编号
|
logon_name
|
varchar
|
用户名
|
nick_name
|
varchar
|
用户昵称
|
password
|
varchar
|
用户密码
|
表
5.2
帖子表(
article
)
字段
|
字段类型
|
描述
|
id
|
int
|
帖子编号
|
title
|
varchar
|
帖子标题
|
content
|
text
|
帖子内容
|
parent_id
|
int
|
主贴的编号
|
issue
|
date
|
帖子发表日期
|
author_id
|
int
|
作者的编号
|
设计表时注意以下细节:
(1)
列的命名一般是两个单词用下划线连接。
(2)
为了提高查询效率用户表中
logon_name
和
password
均加索引,但
这样每次更新和删除时就会新创建一个索引,影响效率。所以一般情况下如果不是出现在
where
语句后的字段就不要加索引。
(
3
)
对业务逻辑无影响的字段添加外键可以实现级联操作,以便让数据库自己去维护数据的完整性。如果不建外键,那么也可以通过程序在业务逻辑层中维护
,
以提高性能。
l
UserDaoIbatisImpl
该类是利用
ibatis
组件构建的
userdao
的一个实现类来完成与后台数据的交互。其中
SqlMapClient
这个类是利用自己定义的工具类
IbatisUtils
去获取而不是通过外层类来传递,以避免耦合。
IbatisUtils
代码如下:
final class IbatisUtils {
private static SqlMapClient sqlMap = null;
// static
代码块在初始化时自动执行
static {
String res = "cn/pf/tbbs/dao/impl/SqlMapConfig.xml";
Reader reader = null;
try {
/*
下面这句话会产生异常。但是不要
catch
,以防初始化的时候,上层不知道下层发生了什么事情。
*/
reader = Resources.getResourceAsReader(res);
sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
static SqlMapClient getSqlMapClient() {
return sqlMap;
}
private IbatisUtils() {
}
}
UserDaoIbatisImpl
的代码如下:
import com.sample.exception.DataAccessException;
public class UserDaoIbatisImpl implements UserDao {
private SqlMapClient sqlMap = IbatisUtils.getSqlMapClient();
public void addUser(User user) {
try {
this.sqlMap.insert("addUser", user);
} catch (SQLException e) {
/*
不要直接抛出
sql
异常,而是抛出一个自定义的异常使得分层隔离。因为
sql
异常是针对数据库的,如果抛出,上层就必须捕获这种
sql
异常,从而造成上层对底层具体实现的耦合。
*/
throw new DataAccessException(e.getMessage(), e);
}
}
public User getUserById(int userId) {
User user = null;
try {
user = (User) this.sqlMap.queryForObject("getUserById", userId);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return user;
}
public User getUserByLogonName(String logonName) {
User user = null;
try {
user = (User) this.sqlMap.queryForObject("getUserByLogonName",
logonName);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return user;
}
public User getUserByLogonNameAndPassword(String logonName, String password) {
User user = null;
try {
User u = new User();
u.setLogonName(logonName);
u.setPassword(password);
user = (User) this.sqlMap.queryForObject(
"getUserByLogonNameAndPassword", u);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return user;
}
}
l
ArticleDaoIbatisImpl
该类是数据层
ArticleDao
的实现类,通过调用
SqlMapClient
的各种方法来实现对文章的添加,删除,查询和修改。代码如下:
public class ArticleDaoIbatisImpl implements ArticleDao {
private SqlMapClient sqlMap = IbatisUtils.getSqlMapClient();
public void addArticle(Article article) {
try {
this.sqlMap.insert("addArticle", article);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
public void delArticle(int artId) {
try {
this.sqlMap.delete("delArticle", artId);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
public Article getArticleById(int artId) {
Article art = null;
try {
art = (Article) this.sqlMap.queryForObject("getArticleById", artId);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return art;
}
@SuppressWarnings("unchecked")
public List<Article> getChildrenArticles(int parentId) {
List<Article> arts = new ArrayList<Article>();
try {
arts = (List<Article>) this.sqlMap.queryForList(
"getChildrenArticles", parentId);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return arts;
}
public void updateArticle(Article art) {
try {
this.sqlMap.update("updateArticle", art);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
}
@SuppressWarnings("unchecked")
public List<Article> getArticles() {
List<Article> arts = new ArrayList<Article>();
try {
arts = (List<Article>) this.sqlMap
.queryForList("getArticles", null);
} catch (SQLException e) {
throw new DataAccessException(e.getMessage(), e);
}
return arts;
}
}