一、DAO的设计思想
1.没有DAO的情况
当应用中没有DAO设计的时候,会出现什么问题.-->为了解决功能重复问题.
- 1.当只有一个客户端处理数据时,如下图,此时的设计,在客户端中编写了操作JDBC的代码.这从功能上分析,是没有问题的.
- 2.但是上面的方法存在功能代码重复的问题。因为把功能代码编写在客户端中,此时若有3个客户端,则功能代码会重复3次.效果如下图:
- 3.该问题的解决方案,我们从之前的开发经验入手去分析:
数组 :存储数据,把数据存储到内存中.
数据库:存储数据,把数据存储到磁盘中.
数组的相关操作:
1):把数据存储到数组.
2):取出数组中的数据.
3):修改数组中的数据.
4):删除数组中的数据.
此时,也是在每一个客户端中编写代码,如此一来,在每一个客户端中代码依然重复.编写一个数组的工具类:ArrayList.把所有和数组相关的CRUD操作封装在ArrayList类中.以后,客户端需要什么功能,就只需要创建ArrayList对象,并调用相应的方法即可(如下图).
2.DAO的介绍
DAO(Data Access Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道。夹在业务逻辑与数据库资源中间。
在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象封装在一个公共API中。
用程序设计的语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。
DAO中的主要操作:增删改查(CRUD).引入DAO之后,此时设计如下图:
上面的设计图是使用了DAO思想之后的设计,
从功能的正确与否的角度分析没有问题,但是考虑设计save和get方法:
public void save(String name,Integer age){}
若参数过多,此时save方法的参数就会出现爆炸式增长.
public XXX get(Long id){}
此时查询指定ID的某一个学生信息,问题:XXX表示什么类型.
此时:若XXX表示String,就只能查询学生的名字.
若XXX表示Integer,就只能查询学生的名字.
那如果,我同时想得到学生的名字和年龄,怎么办?????
解决方案:把学生的信息封装成一个对象(Student).
二、DAO的规范
DAO其实是一个组件(可以重复使用),包括:
1.分包规范:
域名倒写.项目模块名.组件;
com.song.pss.domain; //装pss模块的domain类,模型对象.
com.song.pss.dao; //装pss模块的dao接口.
com.song.pss.dao.impl;//装pss模块的dao接口的实现类.
com.song.pss.test; //暂时存储DAO的测试类,以后的测试类不应该放这里.
2.声明类
dao对象的名字:xxxDAO,比如:employeeDAO/employeeDao,
- Domain数据对象:以下的,Xxx都表示一个对象比如Employee,Department.
- DAO 接口: IXxxDAO/IXxxDao, IEmployeeDAO/IEmployeeDao:仅仅是表示对Employee对象的CRUD的封装
- DAO实现类: XxxDAOImpl/XxxDaoImpl,EmployeeDAOImpl/EmployeeDaoImpl:仅仅表示IEmployeeDAO的实现.
- DAO测试类: XxxDAOTest:表名就是XxxDAO组件的测试类,应该测试DAO组件中的所有方法.
开发建议:面向接口编程,声明DAO对象:
(1) 传统的做法 :EmployeeDAOImpl dao = new EmployeeDAOImpl();
(2) 面向接口编程:IEmployeeDAO dao = new EmployeeDAOImpl();
把实现类赋给接口类型,体现多态的特性:可以屏蔽不同子类之间实现的差异.
public void show(List list){}
3.一般开发的顺序:
1):先建立模型对象:domain
2):编写DAO接口.
3):定义DAO实现类.
4):生产DAO测试类.
5):实现DAO实现类.
6):在DAO测试类中测试DAO方法.
4.调用:
show(new ArrayList()); //YES
show(new LinkedList()); //YES
三、DAO的实现
Student
public class Student {
private Long id;
private String name;
private int age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- IStudentDAO
public interface IStudentDAO {
/**
* 保存指定学生对象
* @param stu
*/
public void save(Student stu);
/**
* 更新指定id的学生对象
* @param stu
*/
public void update(Student stu);
/**
* 删除指定id的学生
* @param id
*/
public void delete(Long id);
/**
* 获取指定id学生的对象
* @param id
* @return
*/
public Student getSingle(Long id);
/**
* 获取全部学生对象
* @return
*/
public List<Student> list();
}
- StudentDAOImpl
public class StudentDAOImpl implements IStudentDAO {
public void save(Student stu) {
StringBuilder sb = new StringBuilder(100);
sb.append("INSERT INTO t_student(name,age) VALUES(");
sb.append("'").append(stu.getName()).append("'");
sb.append(",").append(stu.getAge()).append(")");
String sql = sb.toString();
Connection conn = null;
Statement st = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 获取语句对象
st = conn.createStatement();
// 执行
System.out.println("save:" + sql);
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
public void update(Student stu) {
StringBuilder sb = new StringBuilder(100);
sb.append("UPDATE t_student SET name =");
sb.append("'").append(stu.getName()).append("'");
sb.append(",age = ").append(stu.getAge());
sb.append(" WHERE id = ").append(stu.getId());
String sql = sb.toString();
Connection conn = null;
Statement st = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 获取语句对象
st = conn.createStatement();
// 执行
System.out.println("update:" + sql);
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
public void delete(Long id) {
String sql = "DELETE t_student WHERE id = " + id;
Connection conn = null;
Statement st = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 获取语句对象
st = conn.createStatement();
// 执行
System.out.println("delete:" + sql);
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
public Student getSingle(Long id) {
String sql = "SELECT * FROM t_student WHERE id = " + id;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 获取语句对象
st = conn.createStatement();
// 执行
System.out.println("getSingle:" + sql);
rs = st.executeQuery(sql);
if (rs.next()) {
Student stu = new Student();
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
stu.setId(rs.getLong("id"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (rs != null) {
rs.close();
}
} catch (Exception e2) {
e2.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
return null;
}
public List<Student> list() {
String sql = "SELECT * FROM t_student";
List<Student> list = new ArrayList<Student>();
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
// 获取语句对象
st = conn.createStatement();
// 执行
rs = st.executeQuery(sql);
while (rs.next()) {
Student stu = new Student();
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
stu.setId(rs.getLong("id"));
list.add(stu);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (rs != null) {
rs.close();
}
} catch (Exception e2) {
e2.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e3) {
e3.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e3) {
e3.printStackTrace();
}
}
}
}
return null;
}
}
四、重构设计
1.问题1:在DAO组件中每一个DAO方法,都得加载注册驱动,都得获取Connection对象.
存在问题:
每一个DAO方法的代码都重复了.如果要切换MySQL为Oracle,就得修改每一个DAO方法的代码.
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql:///jarry", "root", "123456");
解决方案:
把DAO方法中的这四个连接数据库的基本信息(驱动名,url,账号,密码)作为DAO类的成员变量.
private String driverClassName="com.mysql.jdbc.Driver";
private String url="jdbc:mysql:///jarry";
private String userName="root";
private String password="123456";
提取之后,DAO方法的代码,只需要直接引用成员变量即可:
// 加载驱动
Class.forName(driverClassName);
// 获取连接
conn = DriverManager.getConnection(url, userName, password);
2.问题2:问题1的解决方案,已经把四个连接数据库的基本信息(驱动名,url,账号,密码)作为DAO类的成员变量.
这本身是没有问题的,但是:在实际开发中,往往不止一个DAO类,难道每一个DAO实现类都得编写.
解决方案:
定义一个Jdbc的工具类:JdbcUtil,把上述四行代码定义在JdbcUtil类中,然后各个DAO类直接调用即可.
public class JdbcUtil {
public static String driverClassName="com.mysql.jdbc.Driver";
public static String url="jdbc:mysql:///jarry";
public static String userName="root";
public static String password="123456";
}
此时在DAO方法中的调用代码如下:
// 加载驱动
Class.forName(JdbcUtil.driverClassName);
// 获取连接
conn = DriverManager.getConnection(JdbcUtil.url, JdbcUtil.userName, JdbcUtil.password);
3.问题3:问题2在JdbcUtil中的使用public来修饰变量,破坏封装了.
在问题2:其实每一个DAO方法,只需要获取Connection对象即可,是不需要关系如何创建Connection的.
解决方案:
在JdbcUtil类中向外暴露一个getConn方法,用于向调用者返回Connection对象.
public class JdbcUtil {
private static String driverClassName="com.mysql.jdbc.Driver";
private static String url="jdbc:mysql:///jarry";
private static String userName="root";
private static String password="123456";
public static Connection getConn(){
Connection conn = null;
try {
// 获取连接
Class.forName(JdbcUtil.driverClassName);
// 加载驱动
conn = DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
此时在DAO方法中的调用代码如下:
// 获取连接
conn = JdbcUtil.getConn();
4.在DAO方法中都会调用JdbcUtil.getConn()方法,但是在getConn方法底层,每次调用都会重新加载注册一次驱动,而这是没有必要的.
解决方案:如何保证加载注册驱动只会执行一次?把加载注册驱动代码,存放到JdbcUtil的静态代码块中。
public class JdbcUtil {
private static String driverClassName="com.mysql.jdbc.Driver";
private static String url="jdbc:mysql:///jarry";
private static String userName="root";
private static String password="123456";
public JdbcUtil() {
//在JdbcUtil的字节码被加载进JVM就执行,只是执行一次
try {
// 获取连接
Class.forName(JdbcUtil.driverClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
Connection conn = null;
try {
// 加载驱动
conn = DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
5.问题5:每一个DAO方法都得关闭资源(没有任何技术含量,但是又必须得写).
解决方案:在JdbcUtil类中定义close方法,专门用于关闭资源.
此时在DAO方法中的调用代码如下:
JdbcUtil.close(conn, st, rs); // 如果是查询方法需要关闭资源
JdbcUtil.close(conn, st, null); // 如果是增删改方法需要关闭资源
6.问题6:在JdbcUtil类中,定义了连接数据库的四个基本信息,虽然即使以后需要切换数据库,只需要修改一次.
但是,需要修改源代码(因为我们在代码中写死了信息(硬编码)).
解决方案:把数据库的连接信息抽取到一个配置文件(properties/xml)中去.
db.properties放在代码根目录下:
drvierClassName = com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbcdemo
username=root
password=admin
新的问题:如何在Java代码中读取db.properties文件,并获取其中的信息.
以后,实施人员需要修改数据库的连接信息,只需要修改db.properties文件即可.
public class JdbcUtil {
private static Properties properties=new Properties();
static {
//在JdbcUtil的字节码被加载进JVM就执行,只是执行一次
try {
InputStream inStream=Thread.currentThread().getContextClassLoader()
.getResourceAsStream("db.properties");
properties.load(inStream);
Class.forName(properties.getProperty("driverClassName"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConn(){
Connection conn = null;
try {
// 加载驱动
conn = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("userName"), properties.getProperty("password"));
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
7.问题7:在DAO方法中拼接SQL,太恶心了.
解决方案:预编译语句对象来解决.
8.问题8:此时,每一个Connection对象,获取之后只使用一次就关闭了(获取连接/关闭连接比较耗性能),没有充分利用Connection对象.
解决方案:连接池思想.
9.问题9:在DAO实现类中,增删改操作的代码模板相同,查询操作代码模板相同,存在了相同的代码结构.
解决方案:JdbcTemplate.
10.问题10:我不想拼SQL,拼SQL不爽,能不能一行代码就搞定操作呢.
解决方案:期待Hibernate框架.
五.预编译语句对象-PreparedStatement
1.PreparedStatement的实现
public class PreParedStatementTest extends TestCase{
//statement
public void testStatement() throws SQLException{
String sql="INSERT INTO t_student (name,age) VALUES ('marry',18)";
Connection conn=JdbcUtil.getConn();
Statement st = st=conn.createStatement();
st.execute(sql);
JdbcUtil.close(conn, st, null);
}
//preparedStatement
public void testPreparedStatement() throws SQLException{
String sql="INSERT INTO t_student (name,age) VALUES (?,?)";
Connection conn=JdbcUtil.getConn();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "kity");
ps.setInt(2, 23);
ps.executeUpdate();
JdbcUtil.close(conn, ps, null);
}
}
2.Statement和PreparedStatement的区别:
PreparedStatement存在的优势:
1):更好的可读性,可维护性.
2):可以提供更好的性能(预编译).MySQL不支持PreparedStatement性能优化.
3):更安全,可以防止SQL注入的问题.