1.程序的分层
如果对程序进行划分,那么最常见的划分方式:显示层+控制层+业务鞥+数据层,其中,显示层和控制层一般称为前台,业务层和数据层成为后台业务层。
在整个项目之中,后台业务是最核心的部分,因为移动应用的火爆问题,前台所以对于前台层不再简单局限于WEB层,可能是Android,IOS,而且随着技术的发展。前台可能使用Python或者Node.js进行包装。
业务层是整个程序提供的操作功能,数据层是数据库的原子操作。业务层就是一个操作。而一个业务层的操作要想完成多个数据层的操作共同完成。整个过程中,发现数据层完成的只是一个个原子性的数据库开发。而在实际开发过程中,每一个操作的业务需要牵扯多个原子性的操作,也就是说所有原子性的操作最终实在业务层中完成。
在实际开发中,业务层的设计师非常复杂。往往需要几个总业务层,之下还有若干子业务层。
2.实例分析。
现在要求使用结果emp表(empno,ename,job,hiredarte,sal,comm)实现如下操作(客户提出的所有的需求就是业务层,也就是一个功能)
- 【业务层】实现雇员数据的添加,但是保证被添加的雇员编号不会重复
- |--【数据层】判断要增加的雇员编号是否存在
- |--【数据层】如果雇员编号不存在则进行数据的保存操作
- 【业务层】实现雇员数据的修改操作
- |--【数据层】执行数据的修改操作
- 【业务层】实现多个雇员数据的删除操作
- |--【数据层】执行雇员的限定删除操作
- 【业务层】可以根据雇员编号查到一个雇员的信息
- |--【数据层】根据雇员编号查询指定的雇员数据
- 【业务层】可以查询所有雇员的信息
- |--【数据层】查询全部雇员数据
- 【业务层】可以实现数据的分页显示,同时又可以返回所有雇员的数量
- |--【数据层】雇员数据的分页查询
- |--【数据层】使用count()函数统计出所有的雇员数量。
结论:用户所提出的所有的需求都应该划分为业务层,因为他指的是功能,而开发人员必须根据业务层进行数据层的设计。
3.项目准备
设置项目名称DAOProject,项目使用Mysql,需要为其配置好数据库的驱动程序。
为了方便的进行统一的管理,所有的项目的父包名称统一设置为:cn.mldn。而子包要根据不同的功能模块进行划分。
3.1数据库连接类
所有的的数据库连接操作都是固定的步骤,可以单独定义一个DatabaseConnection类,这个类主要负责数据库连接对象的取得和关闭。既然是一个专门用于数据库的连接操作,那么可以将其保存在dbc子包中。
package cn.mldn.dbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 本类方法专门负责数据库的连接与关闭操作,在实例化本类对象时就意味着要进行数据库的开发
* 所以在本类的构造方法里要进行数据库驱动的加载与数据库的连接取得
* @author Leeffy
*
*/
public class DatabaseConnection {
private static final String DBDRIVER="com.mysql.jdbc.driver";
private static final String DBURL="jdbc:mysql://localhost/student?useUnicode=true&characterEncoding=UTF-8";
private static final String DBUSER="root";
private static final String PASSWORD="";
Connection conn=null;
/**
* 在构造方法里面为conn对象进行实例化,可以直接取得数据库的连接对象<br>
* 由于所有的操作都是基于数据库完成的,如果数据库取得不了连接,那么也就意味着所有的操作都可以停止了
*/
public DatabaseConnection() {
try {
Class.forName(DBDRIVER);
this.conn = DriverManager.getConnection(DBURL,DBUSER,PASSWORD);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 取得一个数据库的连接对象
* @return Connection实例化对象
*/
public Connection getConnection() {
return this.conn;
}
/**
* 负责数据库的关闭
*/
public void close() {
if(this.conn!=null) {//现在有连接对象
try {
this.conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
整个操作过程之中,DatabaseConnection只是无条件的提供有数据库连接,而至于说有多少个连接对象,他都不关心。
额外话题:从最早的DAO设计模式来讲实际上还会考虑一个问题,多数据库间的移植问题。此时就需要设置一个专门的表示连接标准的接口。
3.2、开发ValueObject(VO)
现在的程序严格来讲已经给出了4个层次。不同的层次之间一定要进行数据的传递,但是既然操作的是指定的数据表,所以数据的结构必须要与表的结构一一对应,那么自然可以想到简单Java类。
在实际的工作之中,针对简单java类的开发给出如下要求
- 考虑到日后程序有可能出现的分布式应用问题,所以简单Java类必须实现java.io.Serialiable接口。
- 简单Java类的名称必须与表名一致。例:student_info表的类名,StudentInfo;
- 类中的属性不允许使用基本数据类型,都必须使用基本数据类型的包装类:基本数据类型的数值型默认值是0,而如果是包装类默认值就是null;
- 类中可以定义有多个构造方法,但是必须要保留一个无参构造方法。
- 类中的属性必须使用private封装,封装后的属性必须提供有getter、setter方法
- 【可选要求】复写equals()、toString()、hashCode()。
将所有的简单Java类保存在vo包中。
范例:定义Emp.java
package cn.mldn.vo;
import java.io.Serializable;
import java.util.Date;
@SuppressWarnings("serial")
public class Emp implements Serializable {
private Integer empno;
private String ename;
private String job;
private Date hiredate;
private Double sal;
private Double comm;
}
不管有多少张表,只要是实体表,那么一定要写简单Java类,而且不要试图想着一次性将所有的表都转换到位。所有的类都要保存在vo包中。
3.3开发数据层
数据层最终是交给业务层进行调用的,所以业务层必须知道数据层的执行标准,即,业务层需要明确的知道数据层的操作方法,但是不需要知道他的具体实现。
3.3.1开发数据层操作标准
不同层之间如果要进行访问,那么必须要提供有接口,以定义操作标准,那么对于数据层也是一样的,因为数据层的最终要交给业务层执行,所以需要先定义数据层的借口。
对于数据层的接口给出如下的开发要求:
- 数据层既然进行数据操作的,那么就将其保存在dao包下;
- 既然不同的数据表的操作有可能使用不同的数据层开发,那么针对于数据表进行命名:
- |- emp表,那么数据层的标准就应该定义为IEmpDAO;
- 对于整个数据层的开发严格来讲只有两类功能
- |-数据更新:建议他的操作方法以doXxx()的形式命名,例如:doCreate()、doUpdate()、doRemove();
- |-数据查询:对于查询分为两种:
- |-查询表中数据:以findXxx()形式命名,例如:findById()、findByName()、findAll()
- |-统计表中的数据:以getXxx()形式命名,如:getAllCount();
范例:定义IEmpDAO接口
package cn.mldn.dao;
import java.util.List;
import java.util.Set;
import cn.mldn.vo.Emp;
/**
* 定义emp表的数据层的操作标准
* @author Leeffy
*
*/
public interface IEmpDAO {
/**
* 实现数据的增加操作
* @param vo 包含了要增加数据的VO对象
* @return 数据保存成功返回true,否则返回false
* @throws Exception SQL执行异常
*/
public boolean doCreate(Emp vo) throws Exception;
/**
* 实现数据的修改操作,本次修改是根据id进行全部字段数据的修改
* @param vo包含了要修改的信息,一定要提供有ID内容
* @return 数据修改成功返回true,否则返回false
* @throws Exception SQL执行异常
*/
public boolean doUpdate(Emp vo) throws Exception;
/**
* 执行数据的批量删除操作,所有要删除的数据以Set集合的形式保存
* @param ids 包含了所有要删除的数据ID,不包含有重复内容
* @return 删除成功返回true(删除数据个数与要删除的数据个数相同),否则返回false
* @throws Exception SQL执行异常
*/
public boolean doRemoveBatch(Set<Integer> ids) throws Exception;
/**
* 根据雇员编号查询指定的雇员信息
* @param id 要查询的雇员编号
* @return 如果雇员信息存在,则将数据以VO类对象的形式返回,否则返回null
* @throws Exception SQL执行异常
*/
public Emp findById(Integer id) throws Exception;
/**
* 查询指定数据表的全部记录,并且以集合的形式返回
* @return 如果表中有数据,则数据封装为VO对象而后利用List集合返回,否则,集合的长度为0(size==0,而不是null)
* @throws Exception SQL执行异常
*/
public List<Emp> findAll()throws Exception;
/**
* 分页进行数据的模糊查询,查询的结果以集合的形式返回
* @param currentPage 当前所在的页
* @param lineSize 每页要显示的数据行数
* @param column 要进行模糊查询的数据列
* @param keyword 模糊查询的关键字
* @return
* @throws Exception SQL执行异常
*/
public List<Emp> findAllSplit(Integer currentPage,Integer lineSize,String column,String keyword)throws Exception;
/**
* 进行模糊查询数据量的统计,如果表中没有记录统计的结果就是0
* @param column 要进行模糊查询的数据列
* @param keyword 模糊查询的关键字
* @return 返回表中的数据量,如果没有数据返回0
* @throws Exception SQL执行异常
*/
public Integer getAllCount(String column,String keyword) throws Exception;
}
3.3.2数据层实现类
数据层需要被业务层调用,数据层需要进行数据库的执行(PreparedStatement),由于在开发之中一个业务层操作需要执行多个数据层的调用,数据库的打开和关闭应该由业务层控制。
所有的数据层实现类要求保存在dao.impl子包下。
范例:EmpDAOImpl子类
package cn.mldn.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import cn.mldn.dao.IEmpDAO;
import cn.mldn.vo.Emp;
public class EmpDAOImpl implements IEmpDAO {
private Connection conn;//需要利用Connection对象操作
private PreparedStatement pstmt;
/**
* 如果要想使用数据层进行原子性的功能操作实现,必须提供有Connection接口对象<br>
* 另外,由于开发之中业务层要调用数据层,所以数据库的打开与关闭交由业务层处理
* @param conn 标识数据库的连接对象
*/
public EmpDAOImpl(Connection conn) {
this.conn = conn;
}
@Override
public boolean doCreate(Emp vo) throws Exception {
String sql = "INSERT INTO EMP(empno,ename,job,hiredate,sal,comm) VALUES (?,?,?,?,?,?)";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setInt(1, vo.getEmpno());
this.pstmt.setString(2, vo.getEname());
this.pstmt.setString(3, vo.getJob());
this.pstmt.setDate(4, new java.sql.Date(vo.getHiredate().getTime()));
this.pstmt.setDouble(5, vo.getSal());
this.pstmt.setDouble(6, vo.getComm());
return this.pstmt.executeUpdate()>0;
}
@Override
public boolean doUpdate(Emp vo) throws Exception {
String sql = "Update EMP SET ename=?,job=?,hiredate=?,sal=?,comm=? where empno=?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1, vo.getEname());
this.pstmt.setString(2, vo.getJob());
this.pstmt.setDate(3, new java.sql.Date(vo.getHiredate().getTime()));
this.pstmt.setDouble(4, vo.getSal());
this.pstmt.setDouble(5, vo.getComm());
this.pstmt.setInt(6, vo.getEmpno());
return this.pstmt.executeUpdate()>0;
}
@Override
public boolean doRemoveBatch(Set<Integer> ids) throws Exception {
if(ids==null||ids.size()==0) {//没有要删除的数据
return false;
}
StringBuffer sql = new StringBuffer();
sql.append("DELETE FROM emp WHERE empno IN(");
Iterator<Integer> iter = ids.iterator();
while(iter.hasNext()) {
sql.append(iter.next()).append(",");
}
sql.delete(sql.length()-1, sql.length()).append(")");
this.pstmt = this.conn.prepareStatement(sql.toString());
return this.pstmt.executeUpdate()==ids.size();
}
@Override
public Emp findById(Integer id) throws Exception {
Emp vo = null;
String sql = "SELECT empno,ename,job,hiredate,sal,comm FROM emp WHERE empno=?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setInt(1, id);
ResultSet rs = this.pstmt.executeQuery();
if(rs.next()) {
vo = new Emp();
vo.setEmpno(rs.getInt(1));
vo.setEname(rs.getString(2));
vo.setJob(rs.getString(3));
vo.setHiredate(rs.getDate(4));
vo.setSal(rs.getDouble(5));
vo.setComm(rs.getDouble(6));
}
return vo;
}
@Override
public List<Emp> findAll() throws Exception {
List<Emp> all = new ArrayList<>();
String sql = "SELECT empno,ename,job,hiredate,sal,comm FROM emp";
this.pstmt = this.conn.prepareStatement(sql);
ResultSet rs = this.pstmt.executeQuery();
while(rs.next()) {
Emp vo = new Emp();
vo = new Emp();
vo.setEmpno(rs.getInt(1));
vo.setEname(rs.getString(2));
vo.setJob(rs.getString(3));
vo.setHiredate(rs.getDate(4));
vo.setSal(rs.getDouble(5));
vo.setComm(rs.getDouble(6));
all.add(vo);
}
return all;
}
@Override
public List<Emp> findAllSplit(Integer currentPage, Integer lineSize, String column, String keyword)
throws Exception {
List<Emp> all = new ArrayList<>();
String sql = "SELECT * FROM "
+ "(SELECT empno,ename,job,hiredate,sal,comm,ROWNUM rn FROM emp"
+ "WHERE "+column+" LIKE ? AND ROWNUM<=?) temp "
+ "where temp.rn>?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1, "%"+keyword+"%");
this.pstmt.setInt(2, currentPage*lineSize);
this.pstmt.setInt(3, (currentPage-1)*lineSize);
ResultSet rs = this.pstmt.executeQuery();
while(rs.next()) {
Emp vo = new Emp();
vo = new Emp();
vo.setEmpno(rs.getInt(1));
vo.setEname(rs.getString(2));
vo.setJob(rs.getString(3));
vo.setHiredate(rs.getDate(4));
vo.setSal(rs.getDouble(5));
vo.setComm(rs.getDouble(6));
all.add(vo);
}
return all;
}
@Override
public Integer getAllCount(String column, String keyword) throws Exception {
String sql = "SELECT COUNT(empno) FROM emp WHERE "+column+" LIKE ?";
this.pstmt = this.conn.prepareStatement(sql);
this.pstmt.setString(1, "%"+keyword+"%");
ResultSet rs = this.pstmt.executeQuery();
if(rs.next()) {
return rs.getInt(1);
}
return null;
}
}
3.3.2、定义数据层工厂类——DAOFactory
业务层要想进行数据层的调用,那么必须要取得IEmpDAO接口对象,但是不同层之间如果想取得接口对象实例 ,需要使用工厂设计模式,这个工厂类将其保存在factory子包下。
范例:定义工厂类
package cn.mldn.factory;
import java.sql.Connection;
import cn.mldn.dao.IEmpDAO;
import cn.mldn.dao.impl.EmpDAOImpl;
public class DAOFactory {
public static IEmpDAO getIEmpDAOInstance(Connection conn) {
return new EmpDAOImpl(conn);
}
}
使用工厂的特征就是外层不需要知道具体的子类
3.4开发业务层
业务层是真正留给外部调用的,可能是控制层,或者是直接调用。既然业务层也是由不同的层进行调用,业务层开发的首要任务就是定义业务层的操作标准。
3.4.1开发业务层标准——IEmpService
业务层也可以成为serivice层,既然描述的是emp表的操作,所以名称定义为IEmpService,并且保存在service的子包下,但是对于业务层的方法定义没有明确要求,只不过个人建议写上有意义的统一名称。
范例:定义IEmpService操作标准
package cn.mldn.service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import cn.mldn.vo.Emp;
/**
* 定义emp表的业务层执行标准,此类一定要负责数据库的打开与关闭操作
* 此类可以通过DAOFactory类取得IEmpDAO接口对象
* @author Leeffy
*
*/
public interface IEmpService {
/**
* 实现雇员数据的增加操作,本次操作要调用IEmpDAO接口的如下方法:<br>
* <li>需要调用IEmpDAO.fingById()方法,判断要增加的数据的id是否已经存在;
* <li>如果现在要增加的数据编号不存在则调用IEmpDAO.doCreate()方法,返回操作的结果
* @param vo 包含了要增加数据的VO对象
* @return 如果增加数据的ID重复或者保存失败返回false,否则返回true
* @throws Exception SQL执行异常
*/
public boolean insert(Emp vo) throws Exception;
/**
* 实现雇员数据的修改操作,本次调用IEmpDAO.doUpdate()方法,本次修改属于全部内容的修改;
* @param vo 包含了要修改数据的V哦对象
* @return 修改成功返回true,否则返回false
* @throws Exception SQL执行异常
*/
public boolean update(Emp vo) throws Exception;
/**
* 执行雇员数据的删除操作,可以删除多个雇员信息,调用IEmpDAO.doRemoveBatch()方法
* @param ids 包含了全部要删除数据的集合,没有重复数据
* @return 删除成功返回true,否则返回false
* @throws Exception SQL执行异常
*/
public boolean delete(Set<Integer> ids) throws Exception;
/**
* 根据雇员编号查找完整信息,调用IEmpDAO.findById()方法。
* @param ids 要查找雇员的编号
* @return 如果找到以VO对象返回,否则返回null
* @throws Exception SQL执行异常
*/
public Emp get(Integer ids) throws Exception;
/**
* 查询全部雇员信息,调用IEmpDAO.findAll()方法
* @return 查询结果以list集合的形式返回,如果没有数据则集合的长度为0
* @throws Exception throws Exception;
*/
public List<Emp> list() throws Exception;
/**
* 实现数据的模糊查询与数据统计,要调用IEmpDAO接口的两个方法:<br>
* <li>调用IEmpDAO.findAllSplit()方法,查询出所有的表数据,返回List<Emp>;
* <li>调用IEmpDAO.getAllCount()方法,查询出所有的数据量,返回Integer
* @param currentPage 当前页
* @param lineSize 每页的记录数
* @param column 模糊查询的数据列
* @param keyword 模糊查询的关键字
* @return 本方法返回多种数据类型,所以使用Map集合返回,由于类型不同意,所以所有Value设置为Object
* <li> key = allEmps,
* <li>
* @throws Exception
*/
public Map<String,Object> list(int currentPage,int lineSize ,String column,String keyword) throws Exception;
}
本接口的方法设计完全符合之前的分析过程。
3.4.2 业务层实现类
业务层实现的核心功能
- 负责控制数据库的打开与关闭,当存在了业务层对象后其母的就是为了操作数据库;即:业务层对象实例化之后就必须准备好数据库连接;
- 根据DAOFactory调用getIEmpDAOInstance()方法后取得IEmpDAO接口对象,
- 业务层的实现类保存在dao.impl子包中
范例:定义EmpServiceImpl子类。
package cn.mldn.service.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import cn.mldn.dbc.DatabaseConnection;
import cn.mldn.factory.DAOFactory;
import cn.mldn.service.IEmpService;
import cn.mldn.vo.Emp;
public class EmpServiceImpl implements IEmpService {
//在这个类的对象就提供有一个数据库连接类的实例化对象
private DatabaseConnection dbc = new DatabaseConnection();
@Override
public boolean insert(Emp vo) throws Exception {
try {
//要增加的雇员编号如果不存在,则findById()返回的结果就是null,null表示可以进行新雇员数据的保存
if(DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findById(vo.getEmpno())==null) {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doCreate(vo);
}
return false;
}catch(Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public boolean update(Emp vo) throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doUpdate(vo);
}catch(Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public boolean delete(Set<Integer> ids) throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).doRemoveBatch(ids);
}catch(Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public Emp get(Integer ids) throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findById(ids);
}catch(Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public List<Emp> list() throws Exception {
try {
return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findAll();
}catch(Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
@Override
public Map<String, Object> list(int currentPage, int lineSize, String column, String keyword) throws Exception {
try {
Map<String,Object> map = new HashMap<String, Object>();
map.put("allEmps",DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).findAllSplit(currentPage, lineSize, column, keyword));
map.put("empCount", DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()).getAllCount(column, keyword));
return map;
}catch(Exception e) {
throw e;
}finally {
this.dbc.close();
}
}
}
不同层之间访问依靠的就是工厂类和接口进行操作。
3.5.3、定义业务层工厂类——ServiceFactory
业务层最终依然需要被其他的层所使用的,所以需要为其定义工厂类,该类也同样保存在factory子包下,如果从开发奖,业务层应该分为两种:
- 前台业务逻辑:可以将其保存在service.front包中,工厂类:ServiceFrontFactory;
- 后台业务逻辑:可以将其保存在service.back包中,工厂类:ServiceBackFactory;
范例:定义ServiceFactory:
package cn.mldn.factory;
import cn.mldn.service.IEmpService;
import cn.mldn.service.impl.EmpServiceImpl;
public class ServiceFactory {
public static IEmpService getIEmpServiceInstance() {
return new EmpServiceImpl();
}
}
在实际的编写之中,子类永远都是不可见的,同时在整个操作里面,控制层完全看不见数据库的任何操作(任何JDBC代码)