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
路漫漫其修远兮、愿你我不忘初心、继续前行!!