JAVA-JDBC: (4) DAO设计思想及骨架搭建

这篇我们总结下JDBC中 数据访问对象(Data Access Object DAO)设计模式及其粗略的骨架搭建过程。至于DAO 编程中三个常常被忽略的方面:事务界定、异常处理和日志记录,可以参考博文:DAO异常和事务


1. DAO简介

数据访问对象(DAO)使我们可以将底层数据访问逻辑与业务逻辑分离开来。为每一个数据源提供 CRUD (创建、读取、更新、删除)操作。

这里写图片描述


2. JDBC 示例;

2.1 文件结构:

这里写图片描述

注释:

  • jdao.domain包下的Stu.java对应数据库中的Stu表。
  • jdao.dao包下的StuDao.java定义了业务逻辑层操作数据访问层所涉及到的方法。这里主要是CRUD方法。包下的DaoException.java定义了
  • SQLException异常从编译时异常到运行时异常的转换、同时也避免了对StuDao.java接口类的污染、降低由异常导致的耦合。
  • jdao.dao.impl包下的StuDaoImpl.java是StuDao.java的具体实现。具体来操作数据访问层的方法。
  • jdao.busi包下的StuDaoTest.java相当于业务逻辑层层的业务流程。
2.2 异常解释;
2.2.1 jdao.domain==>Stu.java
package jdao.domain;
import java.util.Date;
/**
 * 定义domain对象、对应于数据库中的Stu表。
 * 相应的字段名就是表的列名称;
 * @author Administrator
 */
public class Stu {

    private String stuId;
    private String stuName;
    private String sex;
    private float score;
    private Date birthday;

    public String getStuId() {
        return stuId;
    }
    public void setStuId(String stuId) {
        this.stuId = stuId;
    }
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public float getScore() {
        return score;
    }
    public void setScore(float score) {
        this.score = score;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}
2.2.2 jdao.dao==>StuDao.java
package jdao.dao;
import jdao.domain.Stu;
/**
 * 业务逻辑层打交道的对象、业务逻辑层主要是通过
 * 对这些上层接口的操作来操作数据访问层;
 * @author Administrator
 *
 */
public interface StuDao {
    //向数据库中增加一个Stu;
    void addStu(Stu stu);
    //根据stu的id查找对象
    Stu findStuById(String stuId);
    //根据stu的name查找对象;
    Stu findStuByName(String stuName);
    //更新
    void updateStu(Stu stu);
    //删除
    void deleteStu(Stu stu);
}
2.2.3 jdao.dao.impl==>StuDaoImpl.java
package jdao.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import jdao.dao.StuDao;
import jdao.domain.Stu;
import jdao.utils.JDBCUtils;

public class StuDaoImpl implements StuDao {

    public void addStu(Stu stu) {
        Connection conn = null;
        PreparedStatement ps = null;
        String sql;
        try{
            //注册驱动建立连接
            conn = JDBCUtils.getConnection();
            sql = "insert into stu(stuid,sutname,sex,score,birthday) values(?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, stu.getStuId());
            ps.setString(2, stu.getStuName());
            ps.setString(3, stu.getSex());
            ps.setFloat(4, stu.getScore());
            ps.setDate(5,new java.sql.Date(stu.getBirthday().getTime()));
            ps.executeUpdate();
        } catch (SQLException e) {
            //**  对异常的第一种处理方式、默认打印堆栈**
            e.printStackTrace();

            //** 把异常抛出去让上一层知道出错了、**
            //throw e;

        }finally{
            JDBCUtils.free(null, ps, conn);
        }

    }
}
2.2.4 jdao.dao.impl==>StuDaoImpl.java
package jdao.busi;
import java.util.Date;
import jdao.dao.StuDao;
import jdao.dao.impl.StuDaoImpl;
import jdao.domain.Stu;

public class StuDaoTest {
    private static StuDao stuDao = new StuDaoImpl();
    static void register(Stu stu){
        stuDao.addStu(stu);
        System.out.println("注册成功了,可以发邮件确认、进行下一步工作了");
    }

    public static void main(String[] args) {

        Stu stu = new Stu();
        stu.setStuId("S151210");
        stu.setStuName("qian");
        stu.setSex("nan");
        stu.setScore(99.9f);
        stu.setBirthday(new Date());

        register(stu);
    }
}

注释: 这里对异常的两种处理方式是十分危险和错误的。

  • 首先 对于catch(SQLException e){e.printStackTrace()} 说明;

这里写图片描述

我们在daoimpl实现中捕获了这个异常、却默认的打印了下堆中、那么就会出现下面这样的问题:

这里写图片描述

我们在实现中sql语句编写错了、或者出现一些错误导致了SQLException异常的产生。这样我们只是在后台简单的打印了下错误的堆栈信息、程序却继续的执行下去了,也就是说我们的业务逻辑层不知道在SQL操作中已经出错了,而是继续往下执行。进行下一步的处理,例如给用户发送邮件激活等、这样会在后面用户进行激活时,又会出现数据库中没有这个用户的信息的错误、此时找错误比较难。

  • 其次,对catch(SQLException e){ throw e} 说明;
    由于SQLException e 是一个编译时异常。此时我们需要改接口StuDao.java 让addStu(Stu stu)也抛出SQLException异常、并且在业务逻辑层就会面临两个问题:第一捕获这个异常、第二:继续向上抛这个异常:代码修改如下:
    这里写图片描述
    相应的StuDao接口类也得抛异常,不可能孩子抛父亲不抛。
    这里写图片描述
    相应的业务逻辑层的StuDaoTest如下:
    这里写图片描述

注释:这种情况最大的弊病在于:Dao接口被污染了,被耦合了,因为它抛出来具体的异常SQLException 、如果我们底层数据访问层访问的不是数据库,而是文件、那么这里相应的会变成抛IO异常。那么就是在说、我们业务逻辑层是在给具体的数据访问层打交道、而不能是单单的对接口打交道了。

2.4 异常的正确处理方式、我们在下面完整的代码中显示:

3. 完整的代码如下:

这里写图片描述

  • 1. jdao.domain==>Stu.java 如 2.2.1所示

  • 2. jdao.dao==>StuDao.java 如2.2.2所示

  • 3. jdao.dao.impl==>StuDaoImpl.java 如下:

package jdao.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import jdao.dao.DaoException;
import jdao.dao.StuDao;
import jdao.domain.Stu;
import jdao.utils.JDBCUtils;

public class StuDaoImpl implements StuDao {

    public void addStu(Stu stu) {
        Connection conn = null;
        PreparedStatement ps = null;
        String sql;
        try{
            conn = JDBCUtils.getConnection();
            sql = "insert into stu(stuid,stuname,sex,score,birthday) values(?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, stu.getStuId());
            ps.setString(2, stu.getStuName());
            ps.setString(3, stu.getSex());
            ps.setFloat(4, stu.getScore());
            ps.setDate(5,new java.sql.Date(
            stu.getBirthday().getTime()));
            ps.executeUpdate();
        } catch (SQLException e) {
            //对这个异常不能随便的处理;将其转化成一个运行是异常、
            //如果出错让程序自己停下来、
            throw new DaoException(e.getMessage(),e);           
        }finally{
            JDBCUtils.free(null, ps, conn);
        }

    }

    @Override
    public Stu findStuById(String stuId) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        String sql;
        Stu stu = null;
        try{
            conn = JDBCUtils.getConnection();
            sql = "select stuid,stuname,sex,score,birthday from stu where stuid =?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, stuId);
            rs = ps.executeQuery();

            while(rs.next()){
                stu = stuMapping(rs);
            }
        } catch (SQLException e) {
            throw new DaoException(e.getMessage(),e);           
        }finally{
            JDBCUtils.free(rs, ps, conn);
        }
        return stu;
    }

    //重复步骤、我们把它抽成一个方法;
    private Stu stuMapping(ResultSet rs) throws SQLException {
        Stu stu;
        stu = new Stu();
        stu.setStuId(rs.getString("stuid"));
        stu.setStuName(rs.getString("stuname"));
        stu.setSex(rs.getString("sex"));
        stu.setScore(rs.getFloat("score"));
        stu.setBirthday(rs.getDate("birthday"));
        return stu;
    }

    @Override
    public Stu findStuByName(String stuName) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        String sql;
        Stu stu = null;
        try{
            conn = JDBCUtils.getConnection();
            sql = "select stuid,stuname,sex,score,birthday from stu where stuname =?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, stuName);
            rs = ps.executeQuery();

            while(rs.next()){
                stu = stuMapping(rs);
            }
        } catch (SQLException e) {
            throw new DaoException(e.getMessage(),e);           
        }finally{
            JDBCUtils.free(rs, ps, conn);
        }
        return stu;
    }

    @Override
    public void updateStu(Stu stu) {
        Connection conn = null;
        PreparedStatement ps = null;
        String sql;
        try{
            //注册驱动建立连接
            conn = JDBCUtils.getConnection();
            sql = "update stu set stuname = ?, sex = ?, score = ?, birthday = ? where stuid= ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, stu.getStuName());
            ps.setString(2, stu.getSex());
            ps.setFloat(3, stu.getScore());
            ps.setDate(4, new java.sql.Date(stu.getBirthday().getTime()));
            ps.setString(5, stu.getStuId());

            ps.executeUpdate();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            throw new DaoException(e.getMessage(),e);
        }finally{
            JDBCUtils.free(null, ps, conn);
        }
    }

    @Override
    public void deleteStu(Stu stu) {
        Connection conn = null;
        PreparedStatement ps = null;
        String sql;
        try{
            //注册驱动建立连接
            conn = JDBCUtils.getConnection();
            sql = "delete from stu where stuid= ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, stu.getStuId());            
            ps.executeUpdate();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            throw new DaoException(e.getMessage(),e);
        }finally{
            JDBCUtils.free(null, ps, conn);
        }
    }

}
  • * 4. jdao.busi==>StuDaoTest.java 如下:*
package jdao.busi;

import java.util.Date;

import jdao.dao.StuDao;
import jdao.dao.impl.StuDaoImpl;
import jdao.domain.Stu;

public class StuDaoTest {
    private static StuDao stuDao = new StuDaoImpl();
    //注册用户
    static void register(Stu stu){
        stuDao.addStu(stu);
        System.out.println("注册成功了,可以发邮件确认、进行下一步工作了");
    }

    //查找用户根据id
    static Stu getStuById(String stuId){
        return stuDao.findStuById(stuId);
    }

    //查找用户根据name
    static Stu getStuByName(String stuName){
        return stuDao.findStuByName(stuName);
    }

    //根据用户的id修改用户的信息
    static void reviseStuById(Stu stu){
        stuDao.updateStu(stu);
    }

    //根据用户的id删除用户信息;
    static void freeStuById(Stu stu){
        stuDao.deleteStu(stu);
    }
    public static void main(String[] args) {

        Stu stu = new Stu();
        stu.setStuId("S151206");
        stu.setStuName("song");
        stu.setSex("nan");
        stu.setScore(80.6f);
        stu.setBirthday(new Date());

        register(stu);

        Stu stuOne = getStuById("S151208");
        Stu stuTwo = getStuByName("qian");
        System.out.println(stuOne.toString());
        System.out.println(stuTwo.toString());

        reviseStuById(stu);
        freeStuById(stu);

    }

}

* 5. jdao.dao==>DaoException.java 如下:*
只是简单的继承了运行时异常、并没有做具体的处理;

package jdao.dao;

public class DaoException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public DaoException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
        // TODO Auto-generated constructor stub
    }

    public DaoException(String message, Throwable cause) {
        super(message, cause);
        // TODO Auto-generated constructor stub
    }

    public DaoException(String message) {
        super(message);
        // TODO Auto-generated constructor stub
    }

    public DaoException(Throwable cause) {
        super(cause);
        // TODO Auto-generated constructor stub
    }
}
3.2 对上面代码的进一步修正;

首先在命名上说明一点:我们将 StuDaoImpl.java重命名成: StuDaoJDBCImpl.java 为了强调这个接口的实现是通过JDBC技术来实现的。
其次 我们发现在业务逻辑层还存在下面这个问题,使得业务层不单单与StuDao接口打交道并且与具体的StuDao的实现耦合上了。如下:

这里写图片描述
注释:因为改名了这里应该是:

private static StuDao stuDao = new StuDaoJDBCImpl();

由于截图是先做得,这里不在更改了。
我们通过工程设计模式来解决这个问题:

新的 src 文件结构如下:

这里写图片描述

jdao.property==>StuDao.property:

stuDaoClass=jdao.dao.impl.StuDaoJDBCImpl

jdao.dao==>DaoFactory.java:

package jdao.dao;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public final class DaoFactory {
    /**
     * 注意这两个静态引用的初始化先后顺序,因为我们把
     * 类似静态代码块的东西放在了构造方法中了;
     */
    private static StuDao stuDao = null;

    private static DaoFactory instance = new DaoFactory();

    //私有了自己的构造方法、让不能被创建
    private DaoFactory(){
        //实现类似静态代码块的功能
        try {
            Properties property = new Properties();

            //property.load(new FileInputStream(new File("src/jdao/property/StuDao.property")));

            InputStream ips = DaoFactory.class.getClassLoader().
            getResourceAsStream("jdao/property/StuDao.property");
            property.load(ips);

            String stuDaoInitialClass = property.getProperty("stuDaoClass");
            stuDao = (StuDao) Class.forName(stuDaoInitialClass).newInstance();
        } catch (IOException 
        | InstantiationException 
        | IllegalAccessException 
        | ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public StuDao getStuDao(){
        return stuDao;
    }

    public static DaoFactory getInstance() {
        // TODO Auto-generated method stub
        return instance;
    }
}

4. 不错的参考文档:

1. 以系统登录界面解析三层架构 :
http://www.cnblogs.com/javawebsoa/archive/2013/05/21/3091747.html

2. JAVA三层架构 SSH:
http://www.cnblogs.com/nin-w/p/5959038.html

3. JAVA类加载过程的几篇文章:
http://wiki.jikexueyuan.com/project/java-vm/class-loading-mechanism.html


路漫漫其修远兮、愿你我不忘初心、继续前行!!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值