一 服务端项目结构
Java开发工具我用的是Eclipse,配置完Maven后,创建的war工程,接下来就是项目目录结构的划分,这一点的重要性说来可大可小,结构清晰,则开发更加顺利,结构不合理则变动会比较频繁,对于体量较小的项目而言虽然影响不大,但是出于习惯的考虑,还是对结构进行下划分:
- bean包中为数据表对应的Java类;
- common中放置项目中其他类引用的公用设计;
- dao包中放置两部分,一个是数据访问接口设计,一个是接口实现;
- filter包中放置Servlet请求过滤器;
- service包为服务层设计,所有的业务逻辑实现都在这里;
- servlet包下为所有处理请求的Servlet实现类;
- util包下则为各类公用函数的实现。
二 表结构设计
数据表设计不细说了,简简单单几张表,大致看一下对应的Bean就差不多,这里不涉及任何技术问题。但是需要注意的是,表及表字段的命名必须遵守规范,合理的命名规范对于未来可能出现的重构、其他需求而言及其重要,附上Navicat的截图:
三 Bean设计
这里再提一嘴,所有的bean需要有较为严格的结构设计:
- 除和表字段对应的成员属性(私有访问权限),必须有一一对应的属性访问器(getter、setter方法);
- 如果有特殊的对象创建需求,则除特殊的构造函数外,必须保留默认的无参数构造;
- 必须要重写equals、hashCode方法,方法重写一般使用表主键字段作为主要判断逻辑,重写是为了满足业务逻辑上的等值判断;
- 尽量重写toString方法,以便设计时进行日志打印、打桩调试的信息输出。
- 需要注意的是bean属性未必需要和表字段结构一一对应,比如说自增字段,虽然常被设置为主键,但是为了方便未来的拆表及数据迁移,我们还是会使用UUID作为其唯一索引,这种情况下自增id字段就没有必要做为bean的属性存在,其查询逻辑中也不应该使用。
附上大致的设计:
- Module为帖子的分类,学习、工作、两性什么的;
- Topic为帖子的结构,包括帖子的标题、内容;
- TopicReply为帖子的回复内容,包括回复人id、回复时间等;
- TopicInof则为帖子的附加信息,回复数量、发表时间、作者等信息;
- User为用户信息,包括用户的账号、密码等;
- UserSession为用户会话信息,包括登陆时间、token有效期等。
以Module为例:
package com.bubbling.bean;
import java.util.Date;
public class Module
{
private String name;
private String description;
private Date creatime;
private Date updatime;
public Module()
{
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}
public Date getCreatime()
{
return creatime;
}
public void setCreatime(Date creatime)
{
this.creatime = creatime;
}
public Date getUpdatime()
{
return updatime;
}
public void setUpdatime(Date updatime)
{
this.updatime = updatime;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Module other = (Module) obj;
if (name == null)
{
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString()
{
return "Module [name=" + name + ", description=" + description + ", creatime=" + creatime + ", updatime="
+ updatime + "]";
}
}
三 Dao设计
Dao层为数据访问接口,我们所有的设计要尽量面向接口,虽然当下我们的实现采用的是Mysql数据库,且实现为JDBC的方式,但是难保以后不会有实现上的改变,如果有,那么接口的意义就大了,对应用设计来说,整体结构无需任何变化,仅接口实现改变即可。
对Dao的设计,需要充分考虑应用对表数据的访问需求,不能一味的按增删改查需求设计,而对于MVC分层结构的设计来说,对请求的业务处理应该交由服务层实现,也就是说Service应该是Dao的使用大户,那么Dao的接口设计就应该主要考虑Service的实现需求。
Dao也不多说,对于这个项目而言,主要就三个数据访问需求:
- Module,模块
- Topic,帖子
- User,用户
贴一张参考图:
以ModuleDao为例,介绍下接口定义及其实现:
package com.bubbling.dao;
import java.util.List;
import com.bubbling.bean.Module;
public interface ModuleDao
{
public boolean addModule(String name, String description);
public Module getModuleByUuid(String uuid);
public List<Module> getModuleList();
}
ModuleDaoMysqlImpl实现:
package com.bubbling.dao.impl.mysql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.bubbling.bean.Module;
import com.bubbling.dao.ModuleDao;
import com.bubbling.util.DatabaseUtil;
public class ModuleDaoMysqlImpl implements ModuleDao
{
private static final String SqlAddModule = "INSERT INTO module ( `uuid`, `name`, `description`, `creatime` ) VALUES ( REPLACE (UUID(), '-', ''), ?, ?, NOW())";
@Override
public boolean addModule(String name, String description)
{
Connection conn = null;
PreparedStatement st = null;
boolean ret = false;
int index = 1;
try
{
conn = DatabaseUtil.getConnection();
st = conn.prepareStatement(SqlAddModule);
st.setString(index++, name);
st.setString(index++, description);
int i = st.executeUpdate();
if (i > 0)
{
ret = true;
}
}
catch (SQLException e)
{
e.printStackTrace();
}
finally
{
DatabaseUtil.close(conn, st);
}
return ret;
}
private static final String SqlGetModuleByUuid = "SELECT `name`, `description` FROM module WHERE uuid = ?";
@Override
public Module getModuleByUuid(String uuid)
{
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Module module = null;
int index = 1;
try
{
conn = DatabaseUtil.getConnection();
st = conn.prepareStatement(SqlGetModuleByUuid);
st.setString(index++, uuid);
rs = st.executeQuery();
if (rs.next())
{
module = new Module();
module.setName(rs.getString("name"));
module.setDescription(rs.getString("description"));
}
}
catch (SQLException e)
{
e.printStackTrace();
}
finally
{
DatabaseUtil.close(conn, st, rs);
}
return module;
}
private static final String SqlGetModuleList = "SELECT `name`, `description` FROM module";
@Override
public List<Module> getModuleList()
{
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
List<Module> ret = new ArrayList<>();
try
{
conn = DatabaseUtil.getConnection();
st = conn.prepareStatement(SqlGetModuleList);
rs = st.executeQuery();
while (rs.next())
{
Module module = new Module();
module.setName(rs.getString("name"));
module.setDescription(rs.getString("description"));
ret.add(module);
}
if (ret.size() == 0)
{
ret = null;
}
}
catch (SQLException e)
{
e.printStackTrace();
}
finally
{
DatabaseUtil.close(conn, st, rs);
}
return ret;
}
}
注意,上例中数据库连接的获取及关闭定义在了DatabaseUtil中,数据库方面设计将在后面的部分介绍,数据库连接池采用阿里的Druid,数据源配置采用JNDI。