在很多传统的项目中使用的框架比较古老,没有使用持久层的框架(如hibernate、mybatis等),在读取数据库的时候还用的是基础的jdbc连接,查询到结果根据表中字段列表的顺序来一个个获取解析字段,这样不仅代码不够简洁,重复工作量大,且返回的结果不够直观,返回结果一旦改动就需要从数据库读取层面逐步向上修改,与数据库表的耦合度高,不利于程序的扩展。本文对jdbc返回结果ResultSet转化为javabean的方式做了封装,将我们从数据库读取出来的属性转化为javabean的属性,使开发工作更加直观高效。
1、反射机制加注解的方式
可以将以下代码放入数据库读取类的基类中,所有表的读取都可以直接调用下述封装好的方法而不做任何改动。如我们在BaseApiManager中增加如下方法:
1.1 根据条件查询列表
该方法供业务层直接调用,DbSearchDTO封装了相关查询参数。
/**
* 根据条件查询列表
*
* @param dbSearchDTO 查询条件
* @return
*/
public <T> List<T> listPojo(DbSearchDTO dbSearchDTO) throws BaseJdbcException {
String tableName = dbSearchDTO.getTableName();
String selectField = dbSearchDTO.getSelectField();
Class<T> clazz = dbSearchDTO.getClazz();
if (StringUtils.isBlank(tableName) || StringUtils.isBlank(selectField) || clazz == null) {
return null;
}
StringBuilder selectSql = new StringBuilder();
selectSql.append("select ");
selectSql.append(selectField);
selectSql.append(" from ");
selectSql.append(tableName);
String condition = dbSearchDTO.getCondition();
Object[] params = dbSearchDTO.getParams();
if (org.apache.commons.lang3.StringUtils.isNotBlank(condition)) {
selectSql.append(" where ");
selectSql.append(condition);
} else {
params = null;
dbSearchDTO.setParams(null);
}
String orderby = dbSearchDTO.getOrderby();
if (org.apache.commons.lang3.StringUtils.isNotBlank(orderby)) {
selectSql.append(" order by ");
selectSql.append(orderby);
}
Integer pageNum = dbSearchDTO.getPageNum();
Integer pageSize = dbSearchDTO.getPageSize();
int begin = 0;
int offset = 0;
if (pageNum != null && pageSize != null && pageNum != 0 && pageSize != 0) {
begin = (pageNum - 1) * pageSize;
offset = pageSize;
}
String sql = selectSql.toString();
// 根据条件查询数据列表(分页/不分页)
List<T> list = getDocList(begin, offset, sql, params, clazz);
return list;
}
附DbSearchDTO类
/**
* 数据库查询对象DTO
*
* @Author kongzi
* @Date 2022/7/22 17:46
* @Version 1.0
*/
public class DbSearchDTO<T> {
/** 动态查询sql表名 */
private String tableName;
/** sql语句查询字段 */
private String selectField;
/** 动态查询sql条件 */
private String condition;
/** 动态查询sql条件参数 */
private Object[] params;
/** 动态查询sql排序条件 */
private String orderby;
/** 查询页码 **/
private Integer pageNum;
/** 每页条数 **/
private Integer pageSize;
/** 结果集对应的java类 **/
private Class<T> clazz;
public DbSearchDTO(String tableName, String selectField) {
this.tableName = tableName;
this.selectField = selectField;
}
// getter、setter方法略
}
1.2 数据列表查询
/**
* 数据列表查询(分页/不分页)
*
* @param begin 开始
* @param offset 查询数量
* @param sql 查询脚本
* @param params 查询条件
* @return
* @throws BaseJdbcException
*/
public <T> List<T> getDocList(int begin, int offset, String sql, Object[] params, Class<T> clazz) throws BaseJdbcException {
DBSession db = null;
IResultSet rs = null;
List<T> list = null;
try {
// 获取数据库连接对象
db = Context.getDBSession();
if (offset != 0) {
// 查询数量不为0表示分页查询,否则不分页
// 根据方言增加分页条件
sql = db.getDialect().getLimitString(sql, begin, offset);
}
rs = db.executeQuery(sql, params);
list = resultSetToBean(rs, clazz);
} catch (Exception e) {
throw new BaseJdbcException(e);
} finally {
// 关闭rs
ResourceMgr.closeQuietly(rs);
// 关闭Connection
ResourceMgr.closeQuietly(db);
}
return list;
}
对于上述代码中的几处封装做了简单的截图,不是我们讨论的重点:
-
根据数据库相关配置参数获取连接
-
根据数据库类型获取数据库连接对象
-
mysql的分页实现
-
关闭rs
-
关闭Connection
1.3 rs结果集转java对象
上述代码中的,将resultSet结果集转化为java对象的集合。
/**
* resultSet结果集转化为java对象的集合
*
* @param rs 结果集对象
* @param clazz 转化的class对象
* @param <T>
* @return
*/
private <T> List<T> resultSetToBean(IResultSet rs, Class<T> clazz) throws E5Exception {
List<T> list = null;
try {
if (rs != null && clazz != null) {
// 根据表实体注解TableEntity确认需转换的java对象正确
if (clazz.isAnnotationPresent(TableEntity.class)) {
ResultSetMetaData rsmd = rs.getMetaData();
// 利用反射获取java类的属性
Field[] fields = clazz.getDeclaredFields();
while (rs.next()) {
T bean = (T) clazz.newInstance();
for (int _iterator = 0; _iterator < rsmd.getColumnCount(); _iterator++) {
String columnName = rsmd.getColumnName(_iterator + 1);
Object columnValue = rs.getObject(_iterator + 1);
for (Field field : fields) {
// 根据TableField注解确认要解析的数据库字段和java属性的映射关系
if (field.isAnnotationPresent(TableField.class)) {
TableField column = field.getAnnotation(TableField.class);
if (column != null && columnValue != null
&& column.value().equalsIgnoreCase(columnName)) {
BeanUtils.setProperty(bean, field.getName(), columnValue);
break;
}
}
}
}
if (list == null) {
list = new ArrayList<>();
}
list.add(bean);
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw new E5Exception(e);
}
return list;
}
1.4 java类及注解的代码
这里的java类就是我们将rs结果集转化为java对象的目标类,根据需要自定义即可。下面举个例子。
@TableEntity
public class ArticleDO {
@TableField("SYS_DOCUMENTID")
private Long id;
@TableField("SYS_TOPIC")
private String title;
@TableField("a_url")
private String url;
@TableField("a_realPubTime")
private String publishTime;
@TableField("a_picBig")
private String picBig;
@TableField("a_picMiddle")
private String picMiddle;
@TableField("a_picSmall")
private String picSmall;
@TableField("a_columnID")
private Long columnId;
@TableField("col_name")
private String columnName;
// getter、setter方法略
}
@TableEntity注解定义:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据表映射实体注解
*
* @Author kongzi
* @Date 2022/7/27 10:13
* @Version 1.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TableEntity {
String value() default "";
}
@TableField注解定义:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 数据表字段映射实体属性注解
*
* @Author kongzi
* @Date 2022/7/27 10:14
* @Version 1.0
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
String value() default "";
}
至此,我们就完成了数据库列表查询结果转化为javabean的操作,业务层只需要调用listPojo接口即可完成列表的分页/不分页的查询。
1.5 数据总数的查询
如果是分页查询,还涉及到数据的总条数,这里也一并附上代码。
/**
* 查询数据条数
*
* @param dbSearchDTO 数据查询条件
* @return
* @throws BaseJdbcException
*/
public int getTotal(DbSearchDTO dbSearchDTO) throws BaseJdbcException {
String tableName = dbSearchDTO.getTableName();
String condition = dbSearchDTO.getCondition();
Object[] params = dbSearchDTO.getParams();
String sql = "select count(1) as count from ".concat(tableName);
if (org.apache.commons.lang3.StringUtils.isNotBlank(condition)) {
sql = sql.concat(" where ").concat(condition);
} else {
params = null;
dbSearchDTO.setParams(null);
}
int count = getTotal(sql, params);
return count;
}
1.6 调用接口示例
String tableName = "article a, column b";
StringBuilder selectField = new StringBuilder("a.SYS_DOCUMENTID , a.a_columnID, a.SYS_TOPIC");
selectField.append(", a.a_url, a.a_pubTime, a.a_realPubTime");
selectField.append(", a.a_picBig, a.a_picMiddle, a.a_picSmall");
selectField.append(", b.col_name");
// 设置查询条件
DbSearchDTO dbSearchDTO = new DbSearchDTO(tableName, selectField.toString());
dbSearchDTO.setCondition("a.a_columnID = b.SYS_DOCUMENTID and a.a_columnID = ? and a.a_status = 1 and a.SYS_DELETEFLAG=0");
dbSearchDTO.setParams(new Object[]{columnId});
dbSearchDTO.setOrderby("SYS_DOCUMENTID ASC");
dbSearchDTO.setPageNum(pageResult.getPageNum());
dbSearchDTO.setPageSize(pageResult.getPageSize());
dbSearchDTO.setClazz(ArticleDO.class);
try {
// 获取总条数
int total = apiManager.getTotal(dbSearchDTO);
// 获取分页结果
List list = apiManager.listPojo(dbSearchDTO);
} catch (E5Exception e) {
throw new RuntimeException(e);
}
2、反射机制封装工具类
下面是一个简单的反射工具类,直接将rs结果集转化为对应的java类对象,要求数据库表的字段和java属性满足驼峰命名规范。
package util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RsToBeanUtil {
// 根据类名将查询结果resultSet封装成对象
// 属性名必须严格遵守驼峰命名,该类会将属性驼峰转为数据库_ 去与数据库字段进行匹配
public static <T> T resultToBean(ResultSet rs, Class<T> t) {
T result = null; // 封装数据完返回的Bean对象
try {
// 无参构造 创建bean对象
result = t.newInstance();
// 获得bean的属性数组
Field[] fields = t.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
// 取出属性
Field field = fields[i];
// 获取类属性名
String fieldName = field.getName();
// 属性名转数据库字段,驼峰转下划线
Pattern humpPattern = Pattern.compile("[A-Z]");
Matcher matcher = humpPattern.matcher(fieldName);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
String sqlName = sb.toString();
// System.out.println(sqlName);
// 用属性名得出set方法,set+将首字母大写
String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
// 获取字段类型
String type = field.getType().toString();
// // 测试
// System.out.println(setMethodName);
// System.out.println(type);
// 通过调用set方法进行属性注入
if ("class java.lang.Integer".equals(type)) {
// 类型匹配 则获取对应属性的set方法 方法名,类型
Method method = t.getMethod(setMethodName, Integer.class);
// 执行set方法 被执行的对象,需要注入的数据
method.invoke(result, rs.getObject(sqlName));
} else if ("class java.lang.String".equals(type)) {
Method method = t.getMethod(setMethodName, String.class);
method.invoke(result, rs.getObject(sqlName));
} else if ("class java.lang.Double".equals(type)) {
Method method = t.getMethod(setMethodName, Double.class);
method.invoke(result, rs.getObject(sqlName));
} else if ("class java.sql.Timestamp".equals(type)) {
Method method = t.getMethod(setMethodName, Timestamp.class);
method.invoke(result, rs.getObject(sqlName));
} else {
throw new RuntimeException("类型异常");
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
// 返回该对象
return result;
}
}
使用方法示例:
Class<Cost> costClass = Cost.class;
while (rs.next()) {
Cost cost = RsToBeanUtil.resultToBean(rs, costClass);
list.add(cost);
}
3、反射机制封装,和上例类似
使用反射把ResultSet对象得到的单条结果放入到java对象中,返回Object对象
public static Object singleResultset(ResultSet rs,String str){
try {
//加载类
Class clazz = Class.forName(str);
//创建此 Class对象所表示的类的一个新实例。
Object obj = clazz.newInstance();
//获取此 ResultSet对象的列的编号、类型和属性。
ResultSetMetaData rsmd = rs.getMetaData();
//ResultSetMetaData.getColumnCount()方法获取此 ResultSet对象中的列数。
int count = rsmd.getColumnCount();
//遍历该ResultSet对象的行数据
while(rs.next()){
//遍历ResultSet对象的列数据
for(int i=1;i<count+1;i++){
//ResultSetMetaData.getColumnName()方法获取指定列的名称。
String columnName = rsmd.getColumnName(i);
//将指定列的列名首字母转换成大写
columnName = columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
//根据指定列的名称获取指定的get方法
Method methodGet = clazz.getMethod("get"+columnName, null);
//Method.getGenericReturnType()获取该方法的返回类型。(获取该实例的get方法的返回类型)
Class type = (Class)methodGet.getGenericReturnType();
//根据指定列的名称获取指定的set方法
Method methodSet = clazz.getMethod("set"+columnName, type);
//判断并转换该实例中的set方法中参数的类型
if(type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)){
//将传给该实例的set方法的参数值转换成int类型,然后执行该实例的set方法将数据放入Po类的字段中
methodSet.invoke(obj, Integer.parseInt(rs.getString(columnName)));
}
if(type.isAssignableFrom(String.class)){
methodSet.invoke(obj, rs.getString(columnName));
}
if(type.isAssignableFrom(double.class) || type.isAssignableFrom(Double.class)){
methodSet.invoke(obj, Double.parseDouble(rs.getString(columnName)));
}
if(type.isAssignableFrom(boolean.class) || type.isAssignableFrom(Boolean.class)){
methodSet.invoke(obj, Boolean.parseBoolean(rs.getString(columnName)));
}
}
}
//返回该Class对象的实例
return obj;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
调用该方法示例:
//参数一是查询到的ResultSet对象,参数二是反射得到的结果集放入的java对象(应返回的是一个Object对象所以需要强转一下)
AssetPo ap = (AssetPo)singleResultset(rs,"com.wlx.po.AssetPo");
使用反射把ResultSet对象得到的多条结果放入到List 集合中,和上面的方法没有什么区别,唯一不同的是下面的结果集放入的是一个集合,返回List 集合
public static List listResultset(ResultSet rs,String str){
List list = new ArrayList();
try {
//加载类
Class clazz = Class.forName(str);
//获取此 ResultSet 对象的列的编号、类型和属性。
ResultSetMetaData rsmd = rs.getMetaData();
//返回此 ResultSet 对象中的列数。
int count = rsmd.getColumnCount();
while(rs.next()){
//创建此 Class对象所表示的类的一个新实例。
Object obj = clazz.newInstance();
for(int i=1;i<count+1;i++){
//获取指定列的名称。
String columnName = rsmd.getColumnName(i);
//将指定列的列名首字母转换成大写
columnName = columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
//获取该实例中的get方法
Method methodGet = clazz.getMethod("get"+columnName, null);
//获取该实例中get方法的返回类型
Class type = methodGet.getReturnType();
//获取该实例中的set方法
Method methodSet = clazz.getMethod("set"+columnName, type);
//判断并转换该实例中的set方法中参数的类型
if(type.isAssignableFrom(int.class) || type.isAssignableFrom(Integer.class)){
//将传给该实例的set方法的参数值转换成int类型,然后执行该实例的set方法将数据放入Po类的字段中
methodSet.invoke(obj, Integer.parseInt(rs.getString(columnName)));
}
if(type.isAssignableFrom(String.class)){
methodSet.invoke(obj, rs.getString(columnName));
}
if(type.isAssignableFrom(double.class) || type.isAssignableFrom(Double.class)){
methodSet.invoke(obj, Double.parseDouble(rs.getString(columnName)));
}
if(type.isAssignableFrom(boolean.class) || type.isAssignableFrom(Boolean.class)){
methodSet.invoke(obj, Boolean.parseBoolean(rs.getString(columnName)));
}
}
list.add(obj);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return list;
}
调用该方法示例:
//参数一是查询到的ResultSet对象,参数二是反射得到的结果集放入的java对象
List list = listResultset(rs, "com.wlx.po.AssetPo");
本例中使用java的反射机制来获得Po类中get方法的返回类型,来设置set的参数类型,并把查询得到的ResultSet对象中的结果通过执行set方法设入Po类中。
评论区