1、知识点
1、 理解业务层和数据层的区别;
2、 掌握DAO Design Pattern的主要组成部分及代码开发;
3、 初步理解数据表关系的配置;
2、具体内容
2.1、业务层与数据层
项目开发 = 业务 + 技术,对于技术的实现是很简单,但是前提必须要有业务的支持。
所谓的业务就是指一个功能,而一个功能之中,有可能牵扯到多个数据操作;
例如:菲菲去睡觉。
所以所谓的一个业务(BO,Business Object、Service)实际上指的就是会牵扯到多个数据层(DAO, Data Access Object)的操作,如果按照项目开发的流程来讲,肯定是需要先分析完业务之后才可以做数据库设计。
2.2、开发题目
使用Emp表完成数据的CRUD操作,但是现在的初期先使用基本字段:empno、ename、job、hiredate、sal、comm,但是要完成如下几个功能:
n 【业务层】增加雇员信息;
l 【数据层】雇员的编号要求用户自己输入,那么肯定在增加雇员之前要判断雇员编号是否存在;
l 【数据层】执行雇员增加的指令;
n 【业务层】根据ID查找雇员信息;
l 【数据层】根据雇员编号取得一个完整信息;
n 【业务层】根据ID删除雇员信息;
l 【数据层】根据雇员ID删除雇员数据;
n 【业务层】更新雇员的完整信息;
l 【数据层】将输入的数据全部进行更新;
n 【业务层】查询全部数据,或者是模糊查询全部数据,返回数据的同时,要求返回符合条件的数据量。
l 【数据层】模糊查询所有满足条件的数据;
l 【数据层】查询满足条件的全部数据量,使用COUNT()函数统计。
2.3、先进行数据层的开发——DAO层开发
对于一个DAO层而言,由于要负责具体的数据操作,所有在这一层之中的组成部分有如下几个:
n JDBC连接处理类:进行数据库连接的取得和关闭;
n 数据与类之间的转换类:简单Java类;
n 定义数据描述的操作接口:规定出所有的数据操作的全部标准;
n 数据操作接口的实现类:进行具体的操作实现,都使用PreparedStatement;
2.3.1、开发数据库操作工具类:JDBCConnection
对于在之前讲解JDBC的操作之中可以发现,所以的JDBC操作都必须经过如下几个步骤:
n 数据库驱动的加载;
n 数据库的连接操作;
n 数据库的CRUD;
n 数据库的关闭;
每一种数据层的操作只有第三步不一样,那么干脆就将第1、2、4步的功能合成一个类完成,这个类就是JDBCConnection类的功能,而且此类要求有一个保存的文件夹:dbc。
除了dbc目录之外,也需要一个完整项目的目录,例如,现在为:cn.wolex.oracle,即:现在的完整的数据库连接类的名称:cn.wolex.oracle.JDBCConnection。
package cn.wolex.oracle;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;
public class JDBCConnection { private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver"; private static final String DBURL = "jdbc:oracle:thin:@192.168.0.105:1521:daocn"; private static final String DBUSER = "scott"; private static final String DBPASSWORD = "tiger"; private Connection conn = null;
public JDBCConnection() { try { Class.forName(DBDRIVER); this.conn = DriverManager.getConnection(DBURL, DBUSER, DBPASSWORD); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } }
public void close() { if (this.conn != null) { // 避免空指向异常 try {// 数据库的关闭很少会出现错误,即使出现了也跟你没关系 this.conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } |
如果现在按照DAO严格的标准来讲,都应该考虑到数据库移植的问题,如果说是数据库要想移植的话,则此时代码的设计应该如下:
如果一个代码真的要在不同的数据库之间移植的话,首先这种要求并不多,其次,如果全部都是手工编写的话,根本难度很大,工作量也太大了,所以现在的开发之中都会利用Hibernate完成数据库的移植操作,本程序的设计是在Hibernate出现之前的程序开发。
2.3.2、开发数据包裹类:Emp
在之前曾经强调过,对于数据表和简单Java类之间是存在联系的。
l 数据表的名称 = 简答Java类的名称;
l 数据表的字段 = 属性或其他引用关系;
l 数据表中的每行记录 = 简单Java类的多个对象。
既然现在要想取得数据,就必须定义一个可以包裹数据表每行记录的对象,而这一功能就应该由简单Java类完成,而这个类必须保存在vo包(Value Object),封装数值的。
对于简单Java类而言,现在的开发又有了一些新的要求:
1. 这个类必须实现java.io. Serializable接口,以满足日后所需要的远程传输使用;
2. 类中的所有属性都必须使用包装类完成,不再使用基本数据类型。
范例:开发cn.wolex.oracle.vo.Emp类;
package cn.wolex.oracle.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 Double sal; private Double comm; private Date hiredate;
public Integer getEmpno() { return empno; }
public void setEmpno(Integer empno) { this.empno = empno; }
public String getEname() { return ename; }
public void setEname(String ename) { this.ename = ename; }
public String getJob() { return job; }
public void setJob(String job) { this.job = job; }
public Double getSal() { return sal; }
public void setSal(Double sal) { this.sal = sal; }
public Double getComm() { return comm; }
public void setComm(Double comm) { this.comm = comm; }
public Date getHiredate() { return hiredate; }
public void setHiredate(Date hiredate) { this.hiredate = hiredate; }
} |
本类的功能就是进行数据的传输,取得使用。
2.3.3、开发数据层的操作标准——DAO
在一个项目之中,每一个数据层的操作基本上都会对应一张表的操作,那么一张表的所有操作就是CRUD四个核心功能,但是会根据一些具体的业务增加不同的新功能,这些都应该规定于操作的标准。
如果作为标准的接口现在有如下两个要求:
n 由于接口和类的命名规范一样,所以从名称上很难发现什么是类,什么是接口,那么对于以后的开发而言,只要是接口,建议前面加上一个字母“I”,表示interface的含义;
n 由于一张表基本上都存在一个对应的操作标准,所以DAO接口的命名应该按照“表名称DAO”的形式,如果说现在操作的是emp表,则完整的DAO名称应该是:IEmpDAO;
n DAO的接口必须保存在合适的包之中:cn.wolex.oracle.dao.IEmpDAO;
n DAO接口之中的所有方法主要是分为两类,更新和删除,所以对于方法有如下命名规范:
l 更新操作:方法名称以“doXxx”的形式命名,例如:doCreate()、doUpdate()、doRemove();
l 查询操作:方法名称以:“findByXxx”、“findAll”、“getXxx”等等形式命名,一般如果是查找数据的话,使用“find”开头的方法,而如果对数据进行统计的话则使用“get”开头的方法;
l 由于数据层操作之中,会存在异常,那么所有的异常都应该返回被调用处进行输出。
范例:开发操作的标准——IEmpDAO
package cn.wolex.oracle.dao;
import java.util.List;
import cn.wolex.oracle.vo.Emp;
public interface IEmpDAO {
/** * 数据库增加操作 * * @param vo * 包装了所有要保存的数据 * @return如果返回true则表示数据增加成功,如果返回false则表示数据增加失败 * @throws Exception * 如果现在操作中出现了问题,则抛出异常,表示交给被调用处处理 */ public boolean doCreate(Emp vo) throws Exception;
/** * 数据库的删除操作 * * @param id * 要删除的数据ID * @return如果返回true则表示删除成功,如果返回false则表示数据删除失败 * @throws Exception * 如果现在操作中出现了问题,则抛出异常,表示交给被调用处处理 */ public boolean doRemove(Integer id) throws Exception;
/** * 数据的更新操作 * * @param vo * 包裹了新的数据的VO对象 * @return如果返回true则表示数据更新成功,如果返回false则表示数据更新失败 * @throws Exception * 如果现在操作中出现了问题,则抛出异常,表示交给被调用处处理 */ public boolean doUpdate(Emp vo) throws Exception;
/** * 数据的查询操作 * * @param id * 要查询的雇员ID * @return返回一个雇员的完整信息,如果没有查询到,则返回null * @throws Exception * 如果现在操作中出现了问题,则抛出异常,表示交给被调用处处理 */ public Emp findById(Integer id) throws Exception;
/** * 根据关键字进行模糊的查询 * * @param column * 模糊查询的字段 * @param keyword * 模糊查询的关键字 * @param currentPage * 当前所在的页数,用于分页的控制 * @param linesize * 每页显示的记录数 * @return返回查询的一组数据,如果没有任何数据返回,则List接口的size()返回是0 * @throws Exception * 如果现在操作中出现了问题,则抛出异常,表示交给被调用处处理 */ public List<Emp> findAll(String column, String keyword, Integer currentPage, Integer linesize) throws Exception;
/** * 进行数据量的统计 * * @param column * 要模糊查询的字段 * @param keyword * 模糊查询的关键字 * @return COUNT()函数的统计结果,如果没有记录则返回0 * @throws Exception * 如果现在操作中出现了问题,则抛出异常,表示交给被调用处处理 */ public Long getAllcount(String column, String keyword) throws Exception; } |
以上的所有标准是根据数据层中的要求实现的。
2.3.4、开发数据层的实现类
既然现在操作的标准都有了,那么按照固定的思路肯定要有子类,那么这个子类肯定是作为DAO标准的实现类,所以必须保存在dao.impl子包之中,而且后缀要加上一个“Impl”,表示实现类的含义,所以对于IEmpDAO接口的子类就应该是EmpDAOImpl,但是如果现在按照如下的方式编写程序,问是否有问题?
@Override public boolean doCreate(Emp vo) throws Exception { JDBCConnection dbc = new JDBCConnection(); String sql = "INSERT INTO emp(empno,ename,job,hiredate,sal,comm) VALUE(?,?,?,?,?,?)"; PreparedStatement pstmt = dbc.getConnection().prepareStatement(sql); pstmt.setInt(1, vo.getEmpno()); pstmt.setString(2, vo.getEname()); pstmt.setString(3, vo.getJob()); pstmt.setDate(4, new java.sql.Date(vo.getHiredate().getTime())); pstmt.setDouble(5, vo.getSal()); pstmt.setDouble(6, vo.getComm()); if (pstmt.executeUpdate() > 0) { return true; } dbc.close(); // 关闭数据库连接 return false; } |
现在的程序没有任何语法错误,但是可以安全使用吗?
本实现如果按照如上的方式编写,会存在以下几个问题:
n 问题一:对于一个业务操作,肯定会有多个数据操作的支持,那么如果一个业务操作要进行多个数据层操作的时候,就需要同时打开关闭多次数据库连接,这种做法肯定不行,因为一次业务只能打开和关闭数据库一次;
n 问题二:代码结构问题,按照异常的处理原则来讲,如果现在程序运行中出现异常之后,则异常语句之后的代码将不再执行,而返回给被调用处,所以如果程序出现了问题,则数据库在也无法关闭。
既然一个业务要进行多个数据层的操作,那么干脆就把数据层的数据库的打开和关闭操作交给业务层去完成即可,而本实现;类只需要一个数据库连接对象即可。
package cn.wolex.oracle.impl;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import cn.wolex.oracle.dao.IEmpDAO; import cn.wolex.oracle.vo.Emp;
public class EmpDAOImpl implements IEmpDAO {
private Connection conn = null; private PreparedStatement pstmt = null;
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) VALUE(?,?,?,?,?,?)"; 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()); if (this.pstmt.executeUpdate() > 0) { return true; } // 不需要单独关闭PreparedStatement,因为关闭Connection后它也随之被关闭了。 return false; }
@Override public boolean doRemove(Integer id) throws Exception { String sql = "DELETE emp WHERE empno=?"; this.pstmt = this.conn.prepareStatement(sql); this.pstmt.setInt(1, id); if (this.pstmt.executeUpdate() > 0) { return true; } return false; }
@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()); if (this.pstmt.executeUpdate() > 0) { return true; } return false; }
@Override public Emp findById(Integer id) throws Exception { Emp emp = null; String sql = "SELECT empno,ename,job,hiredate,sal,comm WHERE empno=?"; this.pstmt = this.conn.prepareStatement(sql); this.pstmt.setInt(1, id); ResultSet rs = this.pstmt.executeQuery(); if (rs.next()) { // 有返回值表示查询到了 emp = new Emp(); // 实例化对象,将数据保存在VO之中 emp.setEmpno(rs.getInt(1)); emp.setEname(rs.getString(2)); emp.setJob(rs.getString(3)); emp.setHiredate(rs.getDate(4)); emp.setSal(rs.getDouble(5)); emp.setComm(rs.getDouble(6)); } return null; }
@Override public List<Emp> findAll(String column, String keyword, Integer currentPage, Integer lineSize) throws Exception { List<Emp> all = new ArrayList<Emp>(); 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(); if (rs.next()) { Emp emp = new Emp(); // 实例化对象,将数据保存在VO之中 emp.setEmpno(rs.getInt(1)); emp.setEname(rs.getString(2)); emp.setJob(rs.getString(3)); emp.setHiredate(rs.getDate(4)); emp.setSal(rs.getDouble(5)); emp.setComm(rs.getDouble(6)); all.add(emp); } return null; }
@Override public Long getAllcount(String column, String keyword) throws Exception { long count = 0; 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()) { count = rs.getInt(1); } return count; } } |
本类之中只是完整的编写完了每一个操作的SQL语句,按照代理设计模式中的想法,属于真实的数据业务操作主题。
2.3.5、DAO工厂类
既然现在存在了接口,那么按照面向对象的概念来解释,如果要想取得接口的实例化对象,最好有一个工厂类,这样可以避免耦合的问题。
工厂类要求保存在:cn.wolex.oracle.factory包之中。
package cn.wolex.oracle.factory;
import java.sql.Connection; import cn.wolex.oracle.dao.IEmpDAO; import cn.wolex.oracle.impl.EmpDAOImpl;
public class DAOFactory { public static IEmpDAO getIEmpDAOInstance(Connection conn) { return new EmpDAOImpl(conn); } } |
此时数据层的完整代码就开发完成了。
2.4、业务层
对于业务的开发,实际上与数据层是非常类似的,首先来确定一下业务层的功能:
l 进行多项数据层的调用;
l 进行数据库连接的打开与关闭;
所有对于整个业务层的组成部分由有以下几个:
l 业务操作的标准:指定出所有的具体操作业务;
l 业务操作标准的实现子类:调用数据层完成操作;
l 业务操作的工厂类;
所有的业务层建议都保存在service包之中,而命名也是按照与之前数据层的操作一致的方式。
2.4.1、业务操作标准
业务操作标准的接口命名格式“I + 表名称 + Service”。如果是操作emp表的标准,则接口名称应该是“IEmpService”。
package cn.wolex.oracle.service;
import java.util.Map; import cn.wolex.oracle.vo.Emp;
public interface IEmpService {
/** * 完成增加雇员的业务 * * @param vo * 包装了数据的VO对象 * @return如果返回true则表示增加业务完成,否则表示失败 * @throws Exception * 中途出现的错误都要交还给被调用处处理 */ public boolean insert(Emp vo) throws Exception;
public boolean update(Emp vo) throws Exception;
public boolean delete(Integer id) throws Exception;
public Emp find(Integer id) throws Exception;
/** * 模糊查询 * * @param column * @param keyword * @param currentPage * @param lineSize * @return返回的是Map数据,因为现在要返回两种数据:List<Emp>集合和Long。那么使用Map的好处: * 将List<Emp>的这个数据使用(key=allemp)保存在Map; * 将Long这个统计数据使用(key=allcount)保存在Map。 * @throws Exception */ public Map<String, Object> findAll(String column, String keyword, Integer currentPage, Integer lineSize) throws Exception; } |
唯一麻烦的也就在于Map返回的findAll()方法上,因为这个方法要返回是两种数据类型。
2.4.2、业务操作标准的实现类
业务的操作标准实现类应该负责数据库的打开和关闭,以及调用多个数据层操作,这个子类肯定要与DAOFactory、JDBCConnection等类有关系。
package cn.wolex.oracle.service.impl;
import java.util.HashMap; import java.util.Map; import cn.wolex.oracle.dbc.JDBCConnection; import cn.wolex.oracle.factory.DAOFactory; import cn.wolex.oracle.service.IEmpService; import cn.wolex.oracle.vo.Emp;
public class EmpServiceImpl implements IEmpService {
private JDBCConnection dbc = new JDBCConnection();
@Override public boolean insert(Emp vo) throws Exception { try { if (DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .findById(vo.getEmpno()) == null) { return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .doCreate(vo); } } catch (Exception e) { throw e; } finally { dbc.close(); } return false; }
@Override public boolean update(Emp vo) throws Exception { try { return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .doUpdate(vo); } catch (Exception e) { throw e; } finally { dbc.close(); } }
@Override public boolean delete(Integer id) throws Exception { try { return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .doRemove(id); } catch (Exception e) { throw e; } finally { dbc.close(); } }
@Override public Emp find(Integer id) throws Exception { try { return DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .findById(id); } catch (Exception e) { throw e; } finally { dbc.close(); } }
@Override public Map<String, Object> findAll(String column, String keyword, Integer currentPage, Integer lineSize) throws Exception { Map<String, Object> map = new HashMap<String, Object>(); try { map.put("allemp", DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .findAll(column, keyword, currentPage, lineSize)); map.put("allcount", DAOFactory.getIEmpDAOInstance(this.dbc.getConnection()) .getAllcount(column, keyword)); } catch (Exception e) { throw e; } finally { dbc.close(); } return map; } } |
此时在两端的操作程序的关系已经很明确了,一个负责业务处理,一个负责数据处理,但是不管如何实现的,最终的实现都是依靠接口制定的标准完成的。
2.4.3、定义服务工厂类
只有是取得接口的实例化对象,肯定需要工厂类的支持。
package cn.wolex.oracle.factory; import cn.wolex.oracle.service.IEmpService; import cn.wolex.oracle.service.impl.EmpServiceImpl;
public class ServiceFactory { public static IEmpService getIEmpServiceInstance() { return new EmpServiceImpl(); } } |
此时业务层的功能已经正常的完成,可以直接使用了。
2.5、测试Emp的操作
下面主要进行两个测试:一个是增加的测试,另外一个是查询全部的测试。
2.5.1、编写测试程序
范例:编写TestEmpInsert程序完成测试。
package cn.wolex.oracle.test;
import cn.wolex.oracle.factory.ServiceFactory; import cn.wolex.oracle.vo.Emp;
public class TestEmpInsert { public static void main(String[] args) throws Exception { Emp emp = new Emp(); emp.setEmpno(9090); emp.setEname("吴力"); emp.setJob("DBA"); emp.setHiredate(new java.util.Date()); emp.setSal(8700.0); emp.setComm(12.5); boolean flag = ServiceFactory.getIEmpServiceInstance().insert(emp); System.out.println(flag ? "插入成功!" : "插入失败!"); } } |
插入成功! |
本程序完成了数据的插入功能,而且在之前也强调了,主方法就是一个客户端,客户端的操作越少越好,而且标准开发之中,一定要记住,客户端中不允许导入java.sql包。
2.5.2、编写查询全部操作
查询全部数据需要分页显示,而且返回的是一个Map集合。
|
如果以后再使用DAO的时候发现出现的是“SQLException”的话,则表示的是数据库的SQL语句有问题,此时就检查DAO实现类的相应方法。
实际上查询的过程与增加的时序图是完全一样的,因为代码的结构都一样,所以说以后只要是进行数据库的操作,那么肯定都要使用DAO方式完成。
2.6