JDBC 编程
DAO设计
没有使用DAO存在的问题:多个地方都要都同时做CRUD操作时,重复的代码就会很多。
DAO:Data Access Object(数据存取对象)。
位于业务逻辑和持久化数据之间,实现对持久化数据的访问。
ORM
对象关系映射:
将关系数据库中表中的记录映射成为对象,以对象的形式展现。因此ORM的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
对应关系:
示意图:
domain
domain 就是一个符合JavaBean规范(一个类当中有字段和该字段的getter与Setter方法)的类。
作用:是用户与数据库交互的核心中转站。
示例:创建一个domain类
public class Student {
Integer id;
String name;
Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
保存数据:
获取数据:
DAO设计规范
编写DAO组件
-
定义DAO接口
-
编写对应DAO实现类
为什么要定义接口
- 接口就是只给出了函数声明,但是是没有函数体类。函数体在实现类中给出。
- 面向接口编程
- 根据客户提出的需求,定义接口,业务具体实现是通过实现类来完成。
- 当客户提出新的需求,只需要编写该业务逻辑新的实现类。
- 好处
- 业务逻辑更加清晰
- 增强代码的扩展性,可维护性
- 接口和实现相分离,适合团队协作开发
- 降低耦合度。便于以后升级扩展
包名的规范
整体规范:域名倒写.模块名称.组件名称。
DAO包规范:
- package com.jdbc.domain:存储所有的domain
- page com.jdbc.dao:存储所有的dao接口
- page com.jdbc.dao.impl:存储所有的Dao接口实现类
- page com.jdbc.dao.test:存储Dao组件的测试类
类名规范:
-
domain类:存储在domain包中。用于描述一个对象,是一个javaBean,写时要见名知意。
-
dao接口:存储在dao包中,用于表示某一个对象的CRUD声明。
起名规范:IDomainDao ,即 :接口-domain-dao
-
dao实现类:存储到dao.impl包中,用于表示DAO接口的实现类,要实现DAO接口。
起名规范:DomainDAOImpl ,即:domain-dao-impl
DAO代码优化
-
每一个DAO方法当中都会写驱动名称、url、用户名、密码
把公共的这些声明为成员变量,在一个类当中能够共享这些成员变量
-
每个DAO当中都会相同的4行代码。
抽取到一个公共类JdbcUtil当中。
-
每个dao方法每次操作只需要connection对象,至于是怎么样创建的不关心。
把创建Connection代码抽取到jdbcUtil当中,并提供一个getConn就能够获得连接对象。
-
每次调用getConn就会创建一个Connection对象,但不需要每次都注册驱动。
把加载驱动放到静态代码块当中,只会在类被加载到JVM时,才会执行一次。
-
每个dao方法都要关闭资源
在util当中提供一个方法专门关闭资源,在方法当中传入要关闭哪些资源。
-
DAO方法中,拼接SQL太麻烦。
要使用预编译语句对象。
-
DAO方法当中每次都创建一个connection对象,用完就关闭了,创建Connection成本很大。
通过数据库连接池来解决。
-
jdbcUtil当中的用户名,密码这些信息都写到了文件当中,不便于维护。
给写到一个单独的配置文件当中。
DAO代码重构
在DAO当中执行的保存方法,更新方法,删除这些DML操作有太多重复代码。
重构代码原则
- 同一个类中:在一个类当中有多个方法当中有太多相同的代码,不同的地方通过参数传递进去,把它们抽到一个方法当中。
- 不同类中:不同类当中有共同的代码给抽取到一个新类当中。大家同时共享该类中的内容。
抽取DML方法
设计一个方法,要求传入两个参数,第一个参数为sql语句模板,第二个参数为可变参数,设置语句参数值。返回值为int,为受影响的行数。
DAO重构示意图
调用示意图
public static int executeUpdate(String sql, Object... params) {
try (Connection c = DBUtil.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
// 遍历参数
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
// 执行语句
return ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
抽取DQL方法
规定:
- 表中的列名必须和对象中的属性名相同
- 规定表中的类型必须和Java中的类型匹配
想要完成此项工作,就要使用内省机制。
步骤:
- 创建一个类实现结果集处理器,实现的时候也不知道是什么类型,所以也可以指定为一个泛型。
- 提供一个字段,表示要封装成对象的类型。
- 提供一个构造器,在创建时,就指定是什么类型。
- 在实现方法当中,通过内省机制获取所有属性名称,做为列名。
- 通过传入的类字节码创建对象。
- 通过内省获取字节码中所有的信息。
- 获取所有的属性描述器。
- 遍历属性描述器,获取对象的属性名称 ,规定的属性名和列名相同。
- 通过列名获取结果集中的值。
- 调用属性的set方法把属性的值设置进去。
public interface IResultSetHandler<T> {
T handle(ResultSet rs) throws Exception;
}
public class BeanHandler<T> implements IResultSetHandler<T> {
private Class<T> classType;
public BeanHandler(Class<T> classType) {
this.classType = classType;
}
@Override
public T handle(ResultSet rs) throws Exception {
if (rs.next()) {
// 创建一个对象
T obj = this.classType.newInstance();
// 通过内省来拿属性 , Object.class
BeanInfo bf = Introspector.getBeanInfo(this.classType, Object.class);
// 获取所有属性描述器
PropertyDescriptor[] pds = bf.getPropertyDescriptors();
// 遍历每一个属性的描述 器
for (PropertyDescriptor pd : pds) {
if(pd.getName().equals("anonymousName"))
continue;
Object val = rs.getObject(pd.getName());
// 给对象设置属性值
pd.getWriteMethod().invoke(obj, val);
}
return obj;
}
return null;
}
}
public class BeanListHandler<T> implements IResultSetHandler<List<T>> {
private Class<T> classType;
public BeanListHandler(Class<T> classType) {
this.classType = classType;
}
@Override
public List<T> handle(ResultSet rs) throws Exception {
List<T> list = new ArrayList<>();
while (rs.next()) {
// 创建一个对象
T obj = this.classType.newInstance();
// 通过内省来拿属性
BeanInfo bf = Introspector.getBeanInfo(this.classType, Object.class);
// 获取所有属性描述器
PropertyDescriptor[] pds = bf.getPropertyDescriptors();
// 遍历每一个属性的描述 器
for (PropertyDescriptor pd : pds) {
if(pd.getName().equals("anonymousName"))
continue;
Object val = rs.getObject(pd.getName());
// 给对象设置属性值
pd.getWriteMethod().invoke(obj, val);
}
// 把对象存放 到集合当中
list.add(obj);
}
return list;
}
}
public class BeanCountHander implements IResultSetHandler<Integer> {
@Override
public Integer handle(ResultSet rs) throws Exception {
if (rs.next()) {
return rs.getInt("count");
}
return 0;
}
}
public static <T> T executeQuery(String sql, IResultSetHandler<T> rh, Object... params) {
ResultSet rs = null;
try (Connection c = DBUtil.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
// 遍历参数
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
// 执行语句
rs = ps.executeQuery();
return rh.handle(rs);
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.close(null, null, rs);
}
return null;
}
开发步骤
- 创建表
- 建立domain包和domain类
- 建立dao包和dao接口
- 建立dao.impl包和dao实现类
- 根据dao接口创建dao测试类
- 编写实现类当中dao的声明的方法体
- 每编写一个dao方法,进行测试功能是否正确