Appearance
Spring JDBC SimpleJdbc 类详解:让数据库操作变得简单优雅 🚀
概述
在传统的 JDBC 开发中,我们经常需要编写大量的样板代码来处理数据库操作。Spring Framework 提供了 SimpleJdbcInsert
和 SimpleJdbcCall
类,通过利用数据库元数据信息,大大简化了 JDBC 操作的配置和使用。
TIP
SimpleJdbc 类的核心优势在于自动化配置:它们能够通过 JDBC 驱动程序获取数据库元数据,自动推断表结构、参数类型等信息,让开发者专注于业务逻辑而非繁琐的配置。
为什么需要 SimpleJdbc 类?
传统 JDBC 的痛点
kotlin
// 传统 JDBC 插入数据的繁琐代码
fun addActor(actor: Actor) {
val sql = "INSERT INTO t_actor (first_name, last_name) VALUES (?, ?)"
val connection = dataSource.connection
val statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
try {
statement.setString(1, actor.firstName)
statement.setString(2, actor.lastName)
statement.executeUpdate()
val keys = statement.generatedKeys
if (keys.next()) {
actor.id = keys.getLong(1)
}
} finally {
statement.close()
connection.close()
}
}
kotlin
// 使用 SimpleJdbcInsert 的简洁代码
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun addActor(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName
)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
IMPORTANT
对比可以看出,SimpleJdbc 类消除了:
- 手动编写 SQL 语句
- 复杂的连接和语句管理
- 繁琐的参数设置
- 手动处理生成的键值
SimpleJdbcInsert:简化数据插入操作
基本用法
SimpleJdbcInsert
是专门用于简化数据插入操作的工具类。它通过数据库元数据自动构建 INSERT 语句。
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
// 在初始化时配置 SimpleJdbcInsert
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
fun addActor(actor: Actor) {
// 创建参数映射,键名必须与数据库列名匹配
val parameters = mapOf(
"id" to actor.id,
"first_name" to actor.firstName,
"last_name" to actor.lastName
)
// 执行插入操作
insertActor.execute(parameters)
}
}
NOTE
参数映射中的键名必须与数据库表的列名完全匹配,因为 SimpleJdbcInsert 会读取元数据来构建实际的 INSERT 语句。
自动获取生成的主键
在实际开发中,我们经常需要获取数据库自动生成的主键值:
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun addActor(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName
// 注意:这里不需要包含 id,因为它是自动生成的
)
// 执行插入并返回生成的主键
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
}
指定插入列
有时我们只想插入特定的列,可以使用 usingColumns
方法:
kotlin
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id")
TIP
使用 usingColumns
可以:
- 提高性能(只插入必要的列)
- 避免触发不必要的触发器
- 确保数据一致性
参数源:让参数传递更灵活
Spring 提供了多种 SqlParameterSource
实现,让参数传递更加灵活和便捷。
BeanPropertySqlParameterSource
当你的参数来自一个 JavaBean 对象时,这是最方便的选择:
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun addActor(actor: Actor): Actor {
// 直接使用对象作为参数源
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
}
MapSqlParameterSource
提供了链式调用的便利方法:
kotlin
fun addActor(actor: Actor): Actor {
val parameters = MapSqlParameterSource()
.addValue("first_name", actor.firstName)
.addValue("last_name", actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
SimpleJdbcCall:简化存储过程调用
基本存储过程调用
假设我们有以下存储过程:
sql
CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE
)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor WHERE id = in_id;
END;
使用 SimpleJdbcCall 调用:
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
fun readActor(id: Long): Actor {
val input = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(input)
return Actor(
id = id,
firstName = output["out_first_name"] as String,
lastName = output["out_last_name"] as String,
birthDate = output["out_birth_date"] as Date
)
}
}
处理大小写敏感问题
不同数据库对参数名的大小写处理不同,为了提高代码的可移植性:
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
private val procReadActor: SimpleJdbcCall
init {
val jdbcTemplate = JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}
procReadActor = SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
}
}
调用存储函数
存储函数与存储过程类似,但有返回值:
sql
CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT CONCAT(first_name, ' ', last_name)
INTO out_name
FROM t_actor WHERE id = in_id;
RETURN out_name;
END;
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
private val funcGetActorName = SimpleJdbcCall(dataSource)
.withFunctionName("get_actor_name")
fun getActorName(id: Long): String {
val input = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, input)
}
}
处理结果集返回
当存储过程返回结果集时:
sql
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date
FROM t_actor a;
END;
kotlin
@Repository
class ActorRepository(dataSource: DataSource) {
private val procReadAllActors = SimpleJdbcCall(dataSource)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
fun getAllActors(): List<Actor> {
val result = procReadAllActors.execute(emptyMap<String, Any>())
return result["actors"] as List<Actor>
}
}
显式参数声明
当需要更精确的控制或数据库不支持元数据查询时,可以显式声明参数:
kotlin
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SqlOutParameter("out_last_name", Types.VARCHAR),
SqlOutParameter("out_birth_date", Types.DATE)
)
完整的实战示例
让我们通过一个完整的 Actor 管理服务来展示 SimpleJdbc 的实际应用:
完整的 ActorService 实现
kotlin
@Service
@Transactional
class ActorService(dataSource: DataSource) {
// 插入操作
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
// 存储过程调用
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
// 存储函数调用
private val funcGetActorName = SimpleJdbcCall(dataSource)
.withFunctionName("get_actor_name")
// 批量查询
private val procReadAllActors = SimpleJdbcCall(dataSource)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
/**
* 添加新演员
*/
fun addActor(actor: Actor): Actor {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
/**
* 根据ID查询演员详情
*/
fun getActorById(id: Long): Actor? {
return try {
val input = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(input)
Actor(
id = id,
firstName = output["out_first_name"] as String,
lastName = output["out_last_name"] as String,
birthDate = output["out_birth_date"] as Date
)
} catch (e: Exception) {
null
}
}
/**
* 获取演员全名
*/
fun getActorFullName(id: Long): String {
val input = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, input)
}
/**
* 获取所有演员列表
*/
fun getAllActors(): List<Actor> {
val result = procReadAllActors.execute(emptyMap<String, Any>())
return result["actors"] as List<Actor>
}
}
// 数据类定义
data class Actor(
val id: Long = 0,
val firstName: String,
val lastName: String,
val birthDate: Date? = null
)
数据流程图
让我们通过时序图来理解 SimpleJdbc 的工作流程:
最佳实践与注意事项
1. 性能优化
性能建议
- 复用 SimpleJdbc 实例:在类初始化时创建,避免重复创建
- 使用连接池:确保 DataSource 配置了合适的连接池
- 批量操作:对于大量数据插入,考虑使用 JdbcTemplate 的批量操作
2. 错误处理
kotlin
fun addActorSafely(actor: Actor): Result<Actor> {
return try {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
Result.success(actor.copy(id = newId.toLong()))
} catch (e: DataAccessException) {
logger.error("Failed to insert actor: ${actor.firstName} ${actor.lastName}", e)
Result.failure(e)
}
}
3. 事务管理
kotlin
@Service
@Transactional
class ActorService(dataSource: DataSource) {
@Transactional(readOnly = true)
fun getActorById(id: Long): Actor? {
// 只读事务,提高性能
}
@Transactional(rollbackFor = [Exception::class])
fun addActor(actor: Actor): Actor {
// 任何异常都会回滚
}
}
总结
SimpleJdbc 类通过以下方式简化了数据库操作:
✅ 自动化配置:利用数据库元数据自动推断表结构和参数类型
✅ 流畅的 API:提供链式调用的配置方法
✅ 类型安全:减少手动类型转换的错误
✅ 减少样板代码:消除大量重复的 JDBC 代码
✅ 灵活的参数传递:支持多种参数源类型
IMPORTANT
SimpleJdbc 类特别适合:
- 简单的 CRUD 操作
- 存储过程和函数调用
- 需要获取自动生成主键的场景
- 希望减少 SQL 编写的项目
通过合理使用 SimpleJdbc 类,我们可以在保持代码简洁性的同时,享受 Spring JDBC 带来的便利和安全性。这正是 Spring Framework "约定优于配置" 哲学的完美体现! 🎉