文章目录
1、将DAO实现的方法重复部分抽取到父类
下面的代码是我们之前手写实现的DAOUser的方法:如更新一个User,与其他的如删除一个User等含有重复代码以及灵活性很差,只能用于User类。
public void updateUser(User user) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConnect();
String sql = "upada bank set name=?,set money=? where id=?";
ps = conn.prepareStatement(sql);
ps.setFloat(1,user.getMoney());
ps.setString(2,user.getName());
ps.executeUpdate();
} catch (SQLException e) {
throw new DaoException(e.getMessage());//千万不能随便打印堆栈跟踪或者抛出编译时异常
//不利于将来的换其他数据库或者要修改各个接口。不知道那里出了问题。
}finally {
JDBCUtils.free(conn,ps,rs);
}
}
public void deleteUser(String userName) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConnect();
String sql = "delete from bank where name=?";
ps = conn.prepareStatement(sql);
ps.setString(1,userName);
ps.executeUpdate();
} catch (SQLException e) {
throw new DaoException(e.getMessage());//千万不能随便打印堆栈跟踪或者抛出编译时异常
//不利于将来的换其他数据库或者要修改各个接口。不知道那里出了问题。
}finally {
JDBCUtils.free(conn,ps,rs);
}
}
1、我们将该重复的代码提取到一个抽象父类Abstract,将可变代码进行修改
public abstract class AbstractDaoObjectImpl {
/**
*
* @param sql 执行的sql语句
* @param args sql命令中占位符的实际参数值
*/
public void upDate(String sql, Object[] args) {
Connection conn = null;
PreparedStatement ps = null;
// ResultSet rs = null;
try {
conn = JDBCUtils.getConnect();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
ps.executeUpdate();
} catch (SQLException e) {
throw new DaoException(e.getMessage());//千万不能随便打印堆栈跟踪或者抛出编译时异常
//不利于将来的换其他数据库或者要修改各个接口。不知道那里出了问题。
} finally {
JDBCUtils.free(conn, ps, null);
}
}
}
2、让原来的DaoImpl继承该抽象类,则可以修改DaoImpl的updateUser方法为:
@Override
public void updateUser(User user) {
String sql = "update bank set name=? set money=? where id=?";
Object[]args = new Object[]{user.getName(),user.getMoney(),user.getId()};
super.upDate(sql,args);
}
那么这个时候,我们所写的Update方法就不仅仅可以用于User类的更新操作了。还可以是其他的对象表映射,如Book等。只要对应的Book类能继承该类,重写该方法,输入正确的sql语句和args参数即可。
其他类似的方法都可以这样修改。如:
public void deleteUser(String userName) {
String sql = "delete from bank where name=?";
Object[]args = new Object[]{userName};
super.upDate(sql,args);
}
下面测试代码:
小结:这种方法通过抽取公共代码形成一个抽象父类和可变代码的修改实现了提高代码灵活性的可重用性。但是灵活性还是不够,当需要给特定的类添加一些操作方法时,需要修改很多处的代码。如给User添加一个按照id来查找name的方法,不要返回money,我们则需要修改sql语句
比如:findUser方法里面查找的字段无法放到抽象类里处理。那么我们需要另外一个办法。具体看第二点。
public User findUser(String userName) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConnect();
String sql = "select * from bank where name=?";
ps = conn.prepareStatement(sql);
ps.setString(1,userName);
rs = ps.executeQuery();
while(rs.next()){
user = new User();
user.setId(rs.getInt("id"));
user.setMoney(rs.getFloat("money"));
user.setName(rs.getString("name"));
}
return user;
} catch (SQLException e) {
throw new DaoException(e.getMessage());//千万不能随便打印堆栈跟踪或者抛出编译时异常
//不利于将来的换其他数据库或者要修改各个接口。不知道那里出了问题。
}finally {
JDBCUtils.free(conn,ps,rs);
}
}
2、使用模板设计模式处理DAO中的查询处理。
2.1、模板模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
介绍
- 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
- 何时使用:有一些通用的方法。
- 如何解决:将这些通用算法抽象出来。
- 关键代码:在抽象类实现,其他步骤在子类实现。
- 应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。
- 优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
- 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
- 使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
2.2、模板设计模式处理DAO中的查询处理。
我们的findUser方法可以变化,不同的功能查找,其实现也不同,这就无法直接抽象出方法到父类里边了。我们的处理方法是提供一个模板方法,这个方法在父类里边是抽象的方法,继承的子类来实现。即可解决问题。
在AbstractDaoObjectImpl 类里边添加find方法,模板抽象方法
public Object find(String sql,Object[]args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConnect();
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
rs = ps.executeQuery();
Object object=null;
while(rs.next()){
object = rowMapper(rs);
}
return object;
} catch (SQLException e) {
throw new DaoException(e.getMessage());//千万不能随便打印堆栈跟踪或者抛出编译时异常
//不利于将来的换其他数据库或者要修改各个接口。不知道那里出了问题。
}finally {
JDBCUtils.free(conn,ps,rs);
}
}
/**
* 模板查找方法
* @param rs 结果集
* @return 返回的对象类
*/
protected abstract Object rowMapper(ResultSet rs) throws SQLException;
则DAO的DaoUserImp类中的查询方法修改如下:
@Override
public User findUser(String userName) {//按名字查找
String sql = "select id,name, money from bank where name=?";
Object[] args = new Object[]{userName};
return (User) super.find(sql, args);
}
@Override
protected Object rowMapper(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setMoney(rs.getFloat("money"));
user.setName(rs.getString("name"));
return user;
}
也就是说我们不知道具体的查询数据如何处理,查询的是什么,但是我们提供了一个抽象方法,谁要用,谁负责实现该方法。
测试代码:
User user = new User();
Dao daoImpl = DaoFactory.getInstance();
user = daoImpl.findUser("王二小");
System.out.println(user.toString());
缺点:当findUser方法需求发生改变,则rowMapper()方法也需要跟着变化。
则AbstractDaoObjectImpl里面有得写一个抽象方法。也就导致了不灵活性,代码难维护。
我们第一种情况,是因为sql变化,所以将sql当作参数传进来,第二种情况因为对结果集的处理方式不同,而选择将处理抽象为一个模板方法,谁用谁来实现。但是带来的麻烦:每一种变化都需要添加一个抽象处理方法。解决办法:策略模式
3、使用策略模式将模板设计模式代码改进
3.1、策略模式
策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
介绍
- 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
- 主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
- 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
- 如何解决:将这些算法封装成一个一个的类,任意地替换。
- 关键代码:
实现同一个接口。
- 应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。
- 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
- 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
- 使用场景: 1、
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。 注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
3.2、策略模式将模板设计模式代码改进
我们按照上面的分析,我们可以将对结果集的处理抽象成一个接口。怎么处理结果集,里面有一个方法来规定。
1、定义接口,包含结果集的处理方法
public interface RowMapper {
Object rowMapper(ResultSet rs) throws SQLException;
}
2、然后将该接口作为一个函数的参数,同sql语句也是一个参数,同时传入
/**
* 策略模式对应的类
*/
public class DaoTemplateRowMap {
/**
*
* @param sql 传入的sql参数
* @param args 占位符参数
* @param rowMapper 结果集处理接口
* @return 需要查询的结果
*/
public Object find(String sql, Object[] args, RowMapper rowMapper) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnect();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
Object object = null;
while (rs.next()) {
object = rowMapper.rowMapper(rs);
}
return object;
} catch (SQLException e) {
throw new DaoException(e.getMessage());//千万不能随便打印堆栈跟踪或者抛出编译时异常
//不利于将来的换其他数据库或者要修改各个接口。不知道那里出了问题。
} finally {
JDBCUtils.free(conn, ps, rs);
}
}
}
3、修改DAO实现方法,内部有一个templateRowMap,我们的任务都由它来实现
public class DaoUserImp2 {
private DaoTemplateRowMap templateRowMap=new DaoTemplateRowMap();
public User findUser(String userName) {
String sql = "select id,name, money from bank where name=?";
Object[] args = new Object[]{userName};
return (User) templateRowMap.find(sql, args, new RowMapper() {
@Override
public Object rowMapper(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setMoney(rs.getFloat("money"));
user.setName(rs.getString("name"));
return user;
}
});
}
public void addUser(User user) {
String sql = "insert into bank(name,money) values(?,?)";
Object[] args = new Object[]{user.getName(), user.getMoney()};
int id = Integer.parseInt(templateRowMap.add(sql, args).toString());
user.setId(id);
}
.....
.....
4、注意以下代码
通过匿名类new RowMapper(),创建我们结果集的处理策略
。
return (User) templateRowMap.find(sql, args, new RowMapper() {
@Override
public Object rowMapper(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setMoney(rs.getFloat("money"));
user.setName(rs.getString("name"));
return user;
}
});
测试代码:
User user = new User();
Dao daoImpl = new DaoUserImp2();
user = daoImpl.findUser("王二小");
System.out.println(user.toString());
现在新加需求按照name来查找,返回其财产money就好,不用返回name,添加一个方法,同时需要修改一下结果集的处理即可。如:
public Float findByName(String userName){
String sql = "select money from bank where name=?";
Object[] args = new Object[]{userName};
return (Float)templateRowMap.find(sql, args, rs -> {
Object object = rs.getFloat("money");
return object;
});
}
测试代码:
DaoUserImp2 daoUserImp2 = new DaoUserImp2();
System.out.println(daoUserImp2.findByName("王二小"));
4、spring中的JDBCTemplate
JdbcTemplate
Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
JdbcTemplate主要提供以下五类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法用于执行新增、修改、删除等语句;
- batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
参考连接:
我们上面所做的操作都是类似于JdbcTemplate内部的封装过程。如RowMapper,Rowmap方法(JdbcTemplate是mapRow方法)