下面的代码是我们之前手写实现的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);
}
}
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
介绍
注意事项:为防止恶意操作,一般模板方法都加上 final 关键词。
我们的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当作参数传进来,第二种情况因为对结果集的处理方式不同,而选择将处理抽象为一个模板方法,谁用谁来实现。但是带来的麻烦:每一种变化都需要添加一个抽象处理方法。解决办法:策略模式
策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
介绍
实现同一个接口。
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
我们按照上面的分析,我们可以将对结果集的处理抽象成一个接口。怎么处理结果集,里面有一个方法来规定。
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("王二小"));
JdbcTemplate
Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
JdbcTemplate主要提供以下五类方法:
参考连接:
我们上面所做的操作都是类似于JdbcTemplate内部的封装过程。如RowMapper,Rowmap方法(JdbcTemplate是mapRow方法)