針對 Cassandra Java Driver 查詢結果 ResultSet 到實體對象的映射痛點,設計實現一套類似 Hibernate/MyBatis 的通用 RowMapper 框架,支持基本類型、集合、UDT、命名策略適配、PreparedStatement 參數綁定等企業級特性。
public class RowMapperUtils {
// === 查詢映射: Row → Entity ===
public static <T> T mapRow(Row row, Class<T> entityClass) { ... }
public static <T> T mapRow(Row row, Class<T> entityClass,
Map<String, Class<?>> collectionValueTypes,
Map<String, Class<?>> mapKeyTypes) { ... }
public static <T> Map<String, T> mapRowToMap(Row row) { ... }
public static <T> Map<String, T> mapRowToMap(Row row,
Map<String, Class<?>> collectionValueTypes,
Map<String, Class<?>> mapKeyTypes) { ... }
// === 寫入綁定: Entity/Map → BoundStatement ===
public static <T> BoundStatement bind(PreparedStatement stmt, T entity) { ... }
public static <T> BoundStatement bind(PreparedStatement stmt, Map<String, T> params) { ... }
}
private static final Map<Class<?>, Map<String, PropertyDescriptor>> PROPERTY_CACHE =
new ConcurrentHashMap<>();
private static Map<String, PropertyDescriptor> getPropertyDescriptors(Class<?> clazz) {
return PROPERTY_CACHE.computeIfAbsent(clazz, c -> {
Map<String, PropertyDescriptor> map = new HashMap<>();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(c)) {
if (pd.getWriteMethod() != null) {
String name = pd.getName().toLowerCase();
map.put(name, pd);
// 同時支持下劃線命名
String underscore = toUnderscoreCase(name).toLowerCase();
if (!name.equals(underscore)) map.put(underscore, pd);
}
}
return Collections.unmodifiableMap(map);
});
}
private static Object extractValue(Row row, DataType type, String column,
Map<String, Class<?>> collValTypes,
Map<String, Class<?>> mapKeyTypes) {
// 基本類型直取
if (type == DataType.cint()) return row.getInt(column);
if (type == DataType.bigint()) return row.getLong(column);
if (type == DataType.varchar() || type == DataType.text()) return row.getString(column);
if (type == DataType.uuid() || type == DataType.timeuuid()) return row.getUUID(column);
if (type == DataType.timestamp()) return row.getDate(column);
if (type == DataType.cboolean()) return row.getBool(column);
if (type == DataType.cfloat()) return row.getFloat(column);
if (type == DataType.cdouble()) return row.getDouble(column);
if (type == DataType.decimal()) return row.getDecimal(column);
if (type == DataType.varint()) return row.getVarint(column);
if (type == DataType.inet()) return row.getInet(column);
if (type == DataType.blob()) return row.getBytes(column);
// 集合類型:需要泛型類型參數
if (type.isCollection()) {
Class<?> valueType = collValTypes != null ? collValTypes.get(column) : null;
if (type.asJavaClass() == List.class)
return row.getList(column, valueType);
if (type.asJavaClass() == Set.class)
return row.getSet(column, valueType);
if (type.asJavaClass() == Map.class) {
Class<?> keyType = mapKeyTypes != null ? mapKeyTypes.get(column) : null;
return row.getMap(column, keyType, valueType);
}
}
return null;
}
// 駝峰 → 下劃線: userName → user_name, userID → user_id
public static String toUnderscoreCase(String camel) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < camel.length(); i++) {
char c = camel.charAt(i);
if (Character.isUpperCase(c)) {
if (i > 0) sb.append('_');
sb.append(Character.toLowerCase(c));
} else sb.append(c);
}
return sb.toString();
}
// 下劃線 → 駝峰: user_name → userName, USER_ID → userId
public static String toCamelCase(String underscore) {
if (!underscore.contains("_"))
return underscore.substring(0, 1).toLowerCase() + underscore.substring(1).toLowerCase();
StringBuilder sb = new StringBuilder();
String[] parts = underscore.toLowerCase().split("_");
for (int i = 0; i < parts.length; i++) {
if (parts[i].isEmpty()) continue;
if (i == 0) sb.append(parts[i]);
else sb.append(Character.toUpperCase(parts[i].charAt(0))).append(parts[i].substring(1));
}
return sb.toString();
}
public static <T> BoundStatement bind(PreparedStatement stmt, T entity) {
Map<String, Object> params = new LinkedHashMap<>();
Map<String, PropertyDescriptor> props = getPropertyDescriptors(entity.getClass());
for (Definition def : stmt.getVariables()) {
String column = def.getName().toLowerCase();
if (props.containsKey(column)) {
try {
Object val = props.get(column).getReadMethod().invoke(entity);
params.put(column, val);
} catch (Exception e) { log.warn("Get property failed: {}", column, e); }
} else {
log.debug("Entity missing property for column: {}", column);
}
}
return stmt.bind(params.values().toArray());
}
public class User {
private UUID userId;
private String userName;
private String email;
private Set<String> tags; // Cassandra Set<Text>
private Map<String, String> attrs; // Cassandra Map<Text, Text>
private List<Address> addresses; // Cassandra List<UDT:address>
// Getter/Setter...
}
// 指定集合泛型類型
Map<String, Class<?>> collTypes = new HashMap<>();
collTypes.put("tags", String.class);
collTypes.put("addresses", Address.class);
Map<String, Class<?>> mapKeyTypes = new HashMap<>();
mapKeyTypes.put("attrs", String.class);
User user = RowMapperUtils.mapRow(row, User.class, collTypes, mapKeyTypes);
PreparedStatement ps = session.prepare(
"INSERT INTO users (user_id, user_name, email, tags, attrs) VALUES (?, ?, ?, ?, ?)");
BoundStatement bs = RowMapperUtils.bind(ps, user);
session.execute(bs);
| 場景 | 解決方案 |
|---|---|
| UDT (用戶定義類型) | 實現 UDTMapper<T> 接口,註冊到 MappingManager |
| Tuple 類型 | 映射為 List/Object[] 或專用 Tuple 類 |
| 自定義類型編解碼 | 實現 TypeCodec<T> 註冊到 CodecRegistry |
| 異步查詢 | 配合 ResultSetFuture + CompletableFuture 異步映射 |
| 分頁查詢 | 使用 PagingState 實現深分頁 |
| 批量寫入 | BatchStatement + 批量 bind,單次網絡往返 |
PreparedStatementCache 避免重複 prepare