封装好后,我们称这个类为BaseDao.以后任何实现类要操作数据库,都需要继承这个BaseDao.而且,这个BaseDao并不仅限这3个方法,在这个BaseDao类里,其他常用操作应该在这基本的增删改查方法的基础上进行再封装,这样会减少我们很多重复性的操作.
/**
*@Author: zsb
*@Description: 封装的增删改方法
*@params: String sql, Object...params
*@Date: 2018/6/1
*/
public int update(String sql, Object...params){
int result = 0;
//try-with-resouece可以在操作后自动关闭资源
//dbUtils是对JDBC进行简单的封装,通常使用数据库池
try(Connection conn = dbUtils.getConnection(); PreparedStatement ps = conn.prepareStatement(sql);){
//向ps预编译的sql语句中添加对应的属性
setParams(ps,params);
//执行sql语句,并获得影响的数据库条数
result = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
看到过使用 List 的封装方式,感觉并不方便.
这里使用反射+泛型的方式去封装这个查询方法,通过这次封装,也明白了一些ORM框架为什么需要各种映射文件,表列名与实体类属性名应该保证相同.
/**
*@Author: zsb
*@Description: 封装的查询方法,使用泛型获取保存所有的查询信息
*@params:
*@Date: 2018/6/1
*/
public List queryForList(Class clazz, String sql, Object... params){
List list = new ArrayList<>();
try(Connection conn = dbUtils.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)){
//占位符赋值
setParams(ps, params);
//执行查询
ResultSet rs = ps.executeQuery();
//获得对应类的所有属性值
Field[] fields = clazz.getDeclaredFields();
while(rs.next()){
//根据传入类的class生成一个对应的实例,用于保存数据库中对应的每一条数据
T newObject = clazz.newInstance();
//结果集每一条数据,都需要找到对应的setter方法遍历赋值一次,这里使用反射来应对不同的返回值情况
//特别注意,需要表属性名和实体类属性名相同
for (Field field : fields) {
//获取对应变量在数据库中的内容
Object columnValue = rs.getObject(field.getName());
//获得属性名,并转换为首字母大写,主要是因为set方法后是首字母大写的属性名
String UpperFieldName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
//找到对应的set方法, 这个getType()弄了好一阵,参数需要的是方法参数类型的.class对象
Method method = clazz.getMethod("set" + UpperFieldName, field.getType());
//执行方法
method.invoke(newObject,columnValue);
}
list.add(newObject);
}
} catch (SQLException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
return list;
}
/**
*@Author: zsb
*@Description: 向预编译PrepareStatement的sql语句里添加对应的属性
*@params:
*@Date: 2018/6/1
*/
public void setParams(PreparedStatement ps, Object... params) throws SQLException {
if(params != null){
for(int i = 0; i < params.length; i++){
ps.setObject(i+1,params[i]);
}
}
}
通过使用以上封装的增删改查方法,在处理各种数据库操作时,确实方便很多.在这个基础上,再进行多层的封装,可以减少各种表名,参数的填写,直接可以通过方法名识别出当前需要操作的表格,以及可以去除过多的参数.
以下举例
现在有一个Article类,对应的数据库表名为article,通常,我们需要获取前几个article进行显示(可能是要求是最新的,或者评论数最多的,等等)
因此在以上封装好的查询方法的基础上,我们再封装一个用于获取按照某种排序规则排序后的数据
/**
*@Author: zsb
*@Description: 用于将table表里的数据进行定制排序输出指定内容,这种语句很常用
*@params:
*@Date: 2018/6/4
*/
public List queryRankingByCondition(Class clazz, String table, String condition, String asc, int beginRow, int rowNumber){
String sql = "SELECT * FROM "+ table + " ORDER BY " + condition +" "+ asc + " LIMIT " + beginRow +","+ + rowNumber;
return queryForList(clazz, sql,null);
}
这个方法的参数很多,但是这个方法很常用,有封装的价值.操作不同的表的时候,我们会有不同的实现类,而这个基本的增删改查类(称为baseDao)会是它们的父类,
/**
* @Author zsb@myblog
* @Description: 包含各种查询方法, 使用单例模式设计
* @Modified By:
* @Date 2018/6/2
*/
public class ArticleDaoImpl extends BaseDao implements ArticleDao {
private static ArticleDaoImpl articleDaoImpl = null;
private ArticleDaoImpl(){ }
//单例模式,用于获得实例
public static synchronized ArticleDaoImpl getInstance() {
if(articleDaoImpl == null){
articleDaoImpl = new ArticleDaoImpl();
}
return articleDaoImpl;
}
/**
*@Author: zsb
*@Description: 查询最新的文章,最新文章排行
*@params: articleNum 列表的长度
*@Date: 2018/6/4
*/
@Override
public List queryUpToDateArticle(int articleNum) {
return queryRankingByCondition(Article.class,"article","id","DESC",0,articleNum);
}
}
这样,在实际使用时,就只需要填写需要显示多少个article就可以了.虽然我们的其他实现类继承了BaseDao,但是我并不建议直接使用BaseDao里封装好的方法,因为参数太多,而且在实际使用是,目的不明确,不能通过方法名直接判断具体的相关的操作信息.