1、为什么要封装Dao层工具类?
在Dao层中,每次对数据库的增、删、改、查操作存在代码冗余,可以将共有代码抽取封装。
2、哪些代码需要封装?
- 在修改、删除、添加等操作方法中,每次都需要获取连接对象、获取PreparedStatement、给占位符赋值,释放连接等等。
- 在查询操作方法中(查询全部和查询所有),也需要获取连接对象,PreparedStatement、ResultSet等对象,还需要循环ResultSet,取值赋值等等。
3、DaoUtils具体实现
- 修改、删除、新增使用一个通用方法来实现。
- 查询单个和查询所有用一个通用方法来实现。
3.1、增删改通用方法commonsUpdate()
方法有两个参数,第一个是sql语句、第二个是参数列表。
- 第二个变量里存放的值是不固定的,因为增删改语句中的参数数量不同,比如:修改需要整张表,删除只需要一个,新增也需要整张表,所以使用Object... args、也就是可变参数。
- 调用者在调用通用函数、要事先将参数列表定义好,每一个值和sql语句中那一个占位符相对应。
/**
* 增删改通用方法
* @param sql 增删改sql语句
* @param args 参数列表
* @return 受影响行数
*/
public int commonUpdate(String sql, Object... args)
{
Connection conn = null;
PreparedStatement preparedStatement = null;
try
{
conn = DBUtils.getConnection();
preparedStatement = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++)
{
//第一个参数是设置的值下标,从1开始,第二个是从参数列表中取值的下标,所以从0开始
preparedStatement.setObject(i + 1, args[i]);
}
int result = preparedStatement.executeUpdate();
return result;
}
catch (SQLException sqlException)
{
sqlException.printStackTrace();
}
finally
{
DBUtils.closeDb(null, preparedStatement, null);
}
return 0;
}
- service层调用者、修改数据
public int update(Account account)
{
String sql = "update Account set pwd=?,balance=?,name=? where id=?";
Object[] arr = {account.getPwd(), account.getBalance(), account.getName(), account.getId()};
return daoUtils.commonUpdate(sql, arr);
}
数组里的值和SQL语句中占位符一一对应。
3.2、查询通用方法commonsSelect()
查询通用方法相对比较麻烦,因为查询的是单个、还是多个不知道,取值时候也不清楚是哪张表、表中有哪些字段,取出来后赋值给谁、接收用什么类型接收等等。
- 首先、对于查询多个还是单个,可以使用集合,查询单个、集合中就一条数据,查询多个集合中就多条数据。
- 对于不知道是那张表、有哪些字段。遍历取值赋值给那个实体类,可以新建一个回调函数来赋值。
- 回调函数接收的参数就是在通用方法里传来的ResultSet,取出里边的值,给泛型里的实体类赋值,回调函数里的泛型类型就是调用者接收的类型,也就是某个实体类。这样、在通用方法里每循环一次,就调用一次回调函数,而回调函数里知道是那张表,赋值完成后返回,通用函数里使用泛型类型接收后、再添加到list即可。
- 回调函数要使用面向接口方式、定义接口、针对不同的类、实现接口,传入的泛型类型也是不同的实体类。
3.3、查询通用方法代码实现
- 新建一个泛型接口、RowMapper
public interface RowMapper<T>
{
public T getRow(ResultSet resultSet);
}
- 实现该接口、要遍历那个类、就针对那个类传入对应的泛型类型,比如我这里要遍历Account表、所以:
public class AccountRowMapperImpl implements RowMapper<Account>
{
@Override
public Account getRow(ResultSet resultSet)
{
Account account = null;
try
{
int id = resultSet.getInt(1);
String pwd = resultSet.getString(2);
Double balance = resultSet.getDouble(3);
String name = resultSet.getString(4);
account = new Account(id,pwd,balance,name);
return account;
}
catch (SQLException sqlException)
{
sqlException.printStackTrace();
}
return null;
}
}
如果是其它表、比如User,那么就再新建一个实现类,泛型参数是User、返回类型也是User。
- 编写通用方法
/**
* 查询通用方法
* @param sql 查询sql语句
* @param rowMapper 回调接口、调用者要传入接口实现类
* @param args 参数列表
* @return 返回值(集合)
*/
public List<T> commomSelect(String sql, RowMapper<T> rowMapper, Object... args)
{
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try
{
conn = DBUtils.getConnection();
preparedStatement = conn.prepareStatement(sql);
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
preparedStatement.setObject(i + 1, args[i]);
}
rs = preparedStatement.executeQuery();
while (rs.next())
{
T t = rowMapper.getRow(rs);//回调 -->调用者提供的一个封装方法ORM
list.add(t);
}
}
}
catch (SQLException sqlException)
{
sqlException.printStackTrace();
}
finally
{
DBUtils.closeDb(null, preparedStatement, rs);
}
return list;
}
- service层调用者、因为这里是查询单个、所以直接list.get(0)即可。
public Account select(int id)
{
String sql = "select id,pwd,balance,name from Account where id=?";
List<Account> list = daoUtils.commomSelect(sql, new AccountRowMapperImpl(), id);
if (!list.isEmpty())
{
return list.get(0);
}
return null;
}
概括来讲、增删改比较简单,返回的就只是受影响行数,而查询比较复杂,不知道查询的是那张表、有哪些字段、就更不知道用那个实体类来接收。添加一个回调函数、通过泛型传入相应的实体类,遍历赋值完成后返回给通用方法,通用方法只负责查询和返回,回调函数赋值取值和赋值。