首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Cassandra 數據映射實戰:RowMapper 通用綁定框架設計與實現

     編輯:Java 相關知識

針對 Cassandra Java Driver 查詢結果 ResultSet 到實體對象的映射痛點,設計實現一套類似 Hibernate/MyBatis 的通用 RowMapper 框架,支持基本類型、集合、UDT、命名策略適配、PreparedStatement 參數綁定等企業級特性。

一、核心設計目標

  • 零侵入:實體類無需註解、繼承、實現接口
  • 命名策略適配:自動轉換 數據庫下劃線 (user_name) ↔ Java 駝峰 (userName)
  • 類型安全:支持基本類型、包裝類、集合、Map、UDT、Tuple、時間、UUID、InetAddress 等
  • 泛型保留:解決集合泛型擦除,通過額外參數傳遞 Value/Key Class
  • 雙向綁定:ResultSet → Entity (查詢)、Entity/Map → BoundStatement (寫入)
  • 高性能:PropertyDescriptor 緩存、反射對象復用、最小化對象創建

二、核心 API 設計

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) { ... }
}

三、關鍵實現細節

3.1 PropertyDescriptor 緩存策略

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);
    });
}

3.2 智能數據提取:extractValue


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;
}

3.3 雙向命名轉換工具


// 駝峰 → 下劃線: 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();
}

3.4 參數綁定:Entity → BoundStatement


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());
}

四、使用示例

4.1 實體定義


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...
}

4.2 查詢映射調用


// 指定集合泛型類型
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);

4.3 寫入綁定調用


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,單次網絡往返

六、性能優化建議

  1. 預熱緩存:應用啟動時掃描實體包,預熱 PropertyDescriptor 緩存
  2. 避免反射開銷:熱路徑考慮代碼生成或手寫 Mapper
  3. 合理使用 PreparedStatement 緩存:PreparedStatementCache 避免重複 prepare
  4. 異步非阻塞:高併發場景使用 Driver 異步 API + 反應式編程
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved