







使用 NamedParameteredJdbcTemplate 而不是常用的 JdbcTemplate, 因为它可以更容易灵活地为SQL传参. 尤其要将不定数量查询词参数传入的In clause, 不需要客户端拼sql, 只需要将一个数组传入即可.
可以通过 outputFieldNames 参数自行设定输出字段名, 也可以让程序自动推导, 两种实现方式:
where 1=0 这样的查询中, 使用Spring的ResultSetExtractor<ResultSetMetaData> 获取元数据集信息.SqlRowSet rowSet = jdbcTemplate.queryForRowSet(request.getSql(), paramSource);
// 直接从结果集中提取列名(元数据), 因为结果 rowSet 是已断开连接的 SqlRowSet, 所以无法直接使用 BeanPropertyRowMapper 完成结果对象赋值, 下面使用 columnNames 完成赋值.
SqlRowSetMetaData metaData = rowSet.getMetaData();
List<String> columnNames = Arrays.asList(metaData.getColumnNames());
List<Object> results = mapRowSetToObjects(rowSet, dynamicClazz, columnNames);
private List<Object> mapRowSetToObjects(SqlRowSet rowSet, Class<?> clazz, List<String> columns) throws Exception {
List<Object> list = new ArrayList<>();
while (rowSet.next()) {
Object obj = clazz.getDeclaredConstructor().newInstance();
for (String col : columns) {
Object value = rowSet.getObject(col);
// 通过反射调用 setter,Javassist 生成的类包含 public 字段也可直接赋值
Field field = clazz.getField(col);
field.set(obj, value);
}
list.add(obj);
}
return list;
}
因为是通用SQL执行, 结果集结构不能实现确定, 也就无法实现编写对应的结果Class类, 这里使用 javassist 类库, 动态创建一个结果行 Class。
C_{Pid}_{Tid}_{CurrentTimeMills} , 即包含进程Id和线程Id和距今的毫秒数。但这不是一个很好的设计,每次查询都创建一个类, 最终会导致JVM发生OOM, 更好的做法是根据 outputFieldNames MD5签名作为类名, 再结合 ClassLoader 来检查MD5摘要类是否存在。import javassist.*;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class DynamicClassGenerator {
// 静态缓存,防止类重复创建导致 Metaspace OOM
private static final Map<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();
public static Class<?> getOrCreateClass(List<String> fields) throws Exception {
// 1. 将字段排序并合并,生成唯一的 MD5 签名
String fingerprint = generateMD5(fields.stream().sorted().collect(Collectors.joining(",")));
String className = "com.gen.DynamicRow_" + fingerprint;
// 2. 检查缓存
if (CLASS_CACHE.containsKey(className)) {
return CLASS_CACHE.get(className);
}
// 3. 同步块内二次检查并创建(DCL 模式)
synchronized (CLASS_CACHE) {
if (CLASS_CACHE.containsKey(className)) return CLASS_CACHE.get(className);
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(className);
for (String field : fields) {
// 定义公共属性
CtField ctField = new CtField(pool.get("java.lang.Object"), field, ctClass);
ctField.setModifiers(Modifier.PUBLIC);
ctClass.addField(ctField);
// 生成标准的 Getter 和 Setter
ctClass.addMethod(CtNewMethod.getter("get" + capitalize(field), ctField));
ctClass.addMethod(CtNewMethod.setter("set" + capitalize(field), ctField));
}
Class<?> clazz = ctClass.toClass();
CLASS_CACHE.put(className, clazz);
return clazz;
}
}
private static String generateMD5(String input) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashInBytes = md.digest(input.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : hashInBytes) sb.append(String.format("%02x", b));
return sb.toString();
}
private static String capitalize(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。