第四十一天学习心得

昨天的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`中恢复自动提交并释放资源。

明天也继续加油。

相关推荐
sunflower#2 小时前
Go学习第五阶段:接口、泛型与包设计,实现可替换的通讯录存储层
学习·oracle·golang
dadaobusi2 小时前
RV设备透传:主线尚未支持
学习
xqqxqxxq2 小时前
AI Agent学习:主动工具发现(李博杰《深入理解 AI Agent》4.8观后总结)
人工智能·学习
爱吃苹果的日记本4 小时前
数据结构第一课
c语言·数据结构·数据库·学习·c#
孫治AllenSun4 小时前
【LangChain4J-09】开发学习知识点
spring boot·后端·学习
边境悍匪5 小时前
蜗牛学苑 Java 智能体学习 Day38|Spring AI Alibaba2 思维导图复盘
java·学习·spring
摘星星的屋顶6 小时前
2026年8月31日~2026年9月13日周报
人工智能·学习
泡泡鱼(敲代码中)7 小时前
Python语法技术学习笔记
开发语言·笔记·python·学习·pycharm
Yanjun2i8 小时前
Agent学习记录三:完成 Agent Loop
python·学习·agent