欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-13 12:51:51
...

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


1. DAO简介

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

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


2. JDBC 示例;

2.1 文件结构:

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

注释:

  • 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()} 说明;

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

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

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

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

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

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

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


3. 完整的代码如下:

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

  • 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的实现耦合上了。如下:

JAVA-JDBC: (4) DAO设计思想及骨架搭建
注释:因为改名了这里应该是:

private static StuDao stuDao = new StuDaoJDBCImpl();

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

新的 src 文件结构如下:

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

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


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