第四十一天学习心得

昨天的JDBC学习让我掌握了基础的CRUD操作,今天的内容则更加深入------动态SQL拼接、结果集自动封装工具类以及多表关联插入。这些知识点已经非常接近企业级开发的实际场景,让我对JDBC的应用有了更完整的认识。

一、动态SQL拼接------应对复杂查询

问题背景

实际开发中,查询条件往往是动态变化的。用户可能只填用户名,也可能同时填用户名、年龄范围、时间范围,甚至什么都不填。如果为每一种组合都写一条SQL,代码会变得极其臃肿。

核心思路:动态拼接WHERE条件

```java

StringBuilder sql = new StringBuilder("SELECT * FROM user");

String username = searchParamVo.getUsername();

Integer minAge = searchParamVo.getMinAge();

String startTime = searchParamVo.getStartTime();boolean hasWhere = false;

if (username != null && !username.isBlank()) {

sql.append(" WHERE username LIKE ?");

hasWhere = true;

}

if (minAge != null) {

if (!hasWhere) {

sql.append(" WHERE ");

hasWhere = true;

}

sql.append(" AND age BETWEEN ? AND ?");

}

// 处理更多条件...

sql.append(" LIMIT ?, ?");

关键技术细节

  1. WHERE的添加时机

第一个条件前加`WHERE`,后续条件前加`AND`。使用`flag`变量标记是否已添加过WHERE。

  1. 末尾多余的AND处理

```java

if (hasWhere) {

sql.delete(sql.lastIndexOf("AND"), sql.length());

}

  1. ?占位符的动态赋值

```java

int count = 1;

if (username条件成立) {

preparedStatement.setString(count++, "%" + username + "%");

}

if (minAge条件成立) {

preparedStatement.setInt(count++, minAge);

preparedStatement.setInt(count++, maxAge);

}

// 最后设置分页参数

preparedStatement.setInt(count++, (page - 1) * pageSize);

preparedStatement.setInt(count, pageSize);

分层设计

将SQL拼接和参数赋值分离到两个独立方法中:

| 方法 | 职责 |

|------|------|

| `appendSql()` | 根据条件动态拼接SQL语句 |

| `setParamValue()` | 按顺序为所有`?`占位符赋值 |

这种设计让代码逻辑清晰,便于维护和调试。

二、ResultSetUtils------反射封装结果集

痛点分析

在昨天的查询代码中,每次从`ResultSet`取值都要手动调用`getInt()`、`getString()`、`getObject()`等,再逐个`set`到对象中,代码重复度极高。

解决方案:反射+工具类

```java

public class ResultSetUtils {

public static <T> T toBean(ResultSet rs, Class<T> clazz) throws Exception {

// 1. 创建对象(调用无参构造)

T bean = clazz.newInstance();

// 2. 获取所有列信息

ResultSetMetaData metaData = rs.getMetaData();

int columnCount = metaData.getColumnCount();

for (int i = 1; i <= columnCount; i++) {

// 3. 获取列名和列值

String columnName = metaData.getColumnName(i);

Object value = rs.getObject(i);

// 4. 列名转驼峰:role_name → roleName

String fieldName = underlineToCamel(columnName);

// 5. 反射调用setter方法

String setterName = "set" + firstCharUpperCase(fieldName);

Method setter = findSetterMethod(clazz, setterName);

if (setter != null) {

setter.invoke(bean, value);

}

}

return bean;

}

}

核心工具方法

  1. 下划线转驼峰

```java

private static String underlineToCamel(String columnName) {

StringBuilder sb = new StringBuilder();

boolean nextUpper = false;

for (char c : columnName.toCharArray()) {

if (c == '_') {

nextUpper = true;

} else {

sb.append(nextUpper ? Character.toUpperCase(c) : c);

nextUpper = false;

}

}

return sb.toString();

}

  1. 反射查找setter方法

```java

private static <T> Method findSetterMethod(Class<T> clazz, String setterName) {

for (Method method : clazz.getMethods()) {

if (method.getName().equals(setterName) && method.getParameterCount() == 1) {

return method;

}

}

return null;

}

使用效果

优化前:

```java

User user = new User();

user.setId(rs.getInt("id"));

user.setUsername(rs.getString("username"));

user.setAge(rs.getByte("age"));

// ... 更多字段

优化后:

```java

User user = ResultSetUtils.toBean(rs, User.class);

一行代码替代了原来的8行,而且适用于所有实体类!这正是框架的雏形------封装重复代码,提取通用逻辑。

三、多表关联插入------事务的雏形

业务需求

新增角色时,需要同时完成两件事:

  1. 向`role`表插入角色基本信息

  2. 向中间表`role_menu`插入角色和菜单的关联关系

核心要求

两步操作必须**同时成功或同时失败**。这就是**事务(Transaction)**的雏形。

```java

Connection conn = DBUtil.getConnection();

try {

// 关闭自动提交------开启事务

conn.setAutoCommit(false);

// 1. 插入角色

String roleSql = "INSERT INTO role (role_name, role_desc) VALUES (?, ?)";

// 执行插入...

// 2. 插入角色-菜单关联

String relSql = "INSERT INTO role_menu (rid, mid) VALUES (?, ?)";

// 批量执行多条关联插入...

// 提交事务

conn.commit();

} catch (SQLException e) {

// 回滚事务

conn.rollback();

throw new RuntimeException(e);

} finally {

conn.setAutoCommit(true);

DBUtil.release(conn);

}

关键点:

  • `conn.setAutoCommit(false)`:关闭自动提交,手动控制事务

  • `conn.commit()`:全部成功才提交

  • `conn.rollback()`:任何一步失败则回滚全部

获取自增ID

```sql

-- 插入角色后获取自增的role_id

SELECT LAST_INSERT_ID();

在JDBC中:

```java

preparedStatement.executeUpdate();

ResultSet rs = preparedStatement.getGeneratedKeys();

if (rs.next()) {

int roleId = rs.getInt(1);

}

需要在使用`prepareStatement`时指定返回生成的键:

```java

PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

四、今日总结

今天的内容让我从"会用JDBC"进入了"用好JDBC"的阶段:

| 知识点 | 核心要点 |

|--------|----------|

| 动态SQL拼接 | StringBuilder + flag标记 + 末尾清理 |

| 占位符赋值 | 维护count索引,按顺序匹配 |

| 反射封装 | ResultSetMetaData + 反射调用setter |

| 下划线转驼峰 | 遍历字符数组,处理_标志 |

| 事务控制 | setAutoCommit(false) + commit + rollback |

| 获取自增ID | getGeneratedKeys() |

| 批量插入 | 多条SQL在事务中统一执行 |

三点感悟:

  1. 框架的本质是封装:像`ResultSetUtils`这样的工具类,通过反射+约定(列名与字段名对应),将重复的"结果集→对象"转换封装成通用方法。这就是MyBatis等ORM框架的底层逻辑。

  2. 动态SQL是后端开发的常态:企业级应用中,查询条件几乎都是动态组合的。学会用Java拼接SQL是必备技能,但拼接时务必使用`PreparedStatement`防止SQL注入。

  3. 事务是数据一致性的保障:多表操作必须使用事务。`setAutoCommit(false)` + `commit/rollback` 是标准模式,务必在`finally`中恢复自动提交并释放资源。

明天也继续加油。

相关推荐
dadaobusi6 小时前
学习: PCIe 6.0 的 L0p
学习
燕落南枝8 小时前
CSS 学习:类型选择器与 AI 注释实战
css·人工智能·学习
Swilderrr12 小时前
论文精读:《Directory-Aware Query and Maintenance in Vector Databases》
笔记·学习
kkkkkkkkkk_Z12 小时前
新手自学嵌入式 | 学习日记:LCD显示原理、PWM控制、电容触摸屏(GT9147)驱动开发与项目实战
驱动开发·学习
LXT71213 小时前
每日英语一读|为什么夏天的雷雨,总爱在傍晚出现?
深度学习·学习
知识分享小能手13 小时前
C学习教程,从入门到精通,C语言文件 — 完整知识点详解(14)
c语言·网络·学习
羊羊一洋13 小时前
ROM&RAM
c语言·stm32·学习
优化Henry14 小时前
LTE小区光路降质告警排查与处理实录
运维·网络·学习·5g·tdd
暖馒14 小时前
运动控制框架相关知识
学习
stereohomology15 小时前
我对DeepSeek4.1flash能力变化的感觉和第三方评估相当
人工智能·学习·llm