最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring使用JdbcTemplate輕量完成數(shù)據(jù)庫增刪改查的實戰(zhàn)指南

 更新時間:2026年06月03日 08:39:42   作者:唐青楓  
JdbcTemplate?是?Spring?提供的輕量級數(shù)據(jù)庫操作工具,適合直接寫?SQL,又不想寫一堆?JDBC?模板代碼的場景,下面我們就來看看如何使用JdbcTemplate輕量完成數(shù)據(jù)庫增刪改查吧

簡介

JdbcTemplateSpring JDBC 模塊提供的數(shù)據(jù)庫訪問工具類。

它解決的是原生 JDBC 里很常見的一類重復工作:

  • 獲取連接
  • 創(chuàng)建 Statement
  • 綁定 SQL 參數(shù)
  • 遍歷 ResultSet
  • 關(guān)閉連接和結(jié)果集
  • 處理 SQLException

原生 JDBC 能做的事情,JdbcTemplate 基本都能做。

區(qū)別在于,JdbcTemplate 把連接管理、資源釋放、異常轉(zhuǎn)換這些固定流程封裝好了,業(yè)務(wù)代碼只需要關(guān)注:

  • 執(zhí)行什么 SQL
  • 傳什么參數(shù)
  • 結(jié)果怎么映射

一句話概括:JdbcTemplate 是 Spring 提供的輕量級數(shù)據(jù)庫操作工具,適合直接寫 SQL,又不想寫一堆 JDBC 模板代碼的場景。

原生 JDBC 寫法

先看一段原生 JDBC 查詢代碼:

String sql = "select id, username, email, age from users where id = ?";

Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;

try {
    connection = dataSource.getConnection();
    statement = connection.prepareStatement(sql);
    statement.setLong(1, 1L);
    resultSet = statement.executeQuery();

    if (resultSet.next()) {
        User user = new User();
        user.setId(resultSet.getLong("id"));
        user.setUsername(resultSet.getString("username"));
        user.setEmail(resultSet.getString("email"));
        user.setAge(resultSet.getInt("age"));
        return user;
    }

    return null;
} finally {
    if (resultSet != null) {
        resultSet.close();
    }
    if (statement != null) {
        statement.close();
    }
    if (connection != null) {
        connection.close();
    }
}

真正和業(yè)務(wù)有關(guān)的只有幾件事:

  • SQL 是什么
  • 參數(shù)是什么
  • 每一列怎么放進 User

其他部分基本都是固定流程。

JdbcTemplate 寫法

換成 JdbcTemplate 后:

String sql = "select id, username, email, age from users where id = ?";

User user = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
    User item = new User();
    item.setId(rs.getLong("id"));
    item.setUsername(rs.getString("username"));
    item.setEmail(rs.getString("email"));
    item.setAge(rs.getInt("age"));
    return item;
}, 1L);

代碼明顯少很多。

連接什么時候拿、什么時候關(guān),ResultSet 怎么關(guān)閉,異常怎么轉(zhuǎn)換,都交給 JdbcTemplate 處理。

JdbcTemplate 做了什么

JdbcTemplate 大致處在這個位置:

業(yè)務(wù)代碼
  |
  v
JdbcTemplate
  |
  v
DataSource
  |
  v
數(shù)據(jù)庫

它本身不負責連接池。

連接來自 DataSource。

Spring Boot 項目里,只要配置了數(shù)據(jù)源,并引入 spring-boot-starter-jdbc,通??梢灾苯幼⑷耄?/p>

@Repository
public class UserRepository {

    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

常見能力有這些:

  • update:執(zhí)行新增、修改、刪除
  • queryForObject:查詢一條記錄或一個值
  • query:查詢列表
  • batchUpdate:批量執(zhí)行
  • execute:執(zhí)行 DDL 或普通 SQL
  • RowMapper:把一行結(jié)果映射成一個對象
  • NamedParameterJdbcTemplate:使用命名參數(shù)寫 SQL

Maven 依賴

Spring Boot 項目常用依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

如果是本地演示,也可以使用 H2 數(shù)據(jù)庫:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

數(shù)據(jù)源配置

MySQL 為例:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

Spring Boot 會根據(jù)這些配置創(chuàng)建 DataSource。

同時,JdbcTemplate 也會自動注冊成 Bean。

準備演示表

下面的示例圍繞用戶表展開。

DROP TABLE IF EXISTS users;
CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100) NOT NULL,
  age INT NOT NULL,
  status VARCHAR(20) NOT NULL,
  created_at DATETIME NOT NULL
);
INSERT INTO users (username, email, age, status, created_at) VALUES
('張三', 'zhangsan@example.com', 20, 'ACTIVE', '2026-01-01 10:00:00'),
('李四', 'lisi@example.com', 25, 'ACTIVE', '2026-01-02 10:00:00'),
('王五', 'wangwu@example.com', 17, 'DISABLED', '2026-01-03 10:00:00');

實體類

import java.time.LocalDateTime;

public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private String status;
    private LocalDateTime createdAt;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }
}

RowMapper:結(jié)果集映射

JdbcTemplate 查詢對象時,需要告訴它:ResultSet 的一行數(shù)據(jù)怎么變成 Java 對象。

這件事由 RowMapper 完成。

import org.springframework.jdbc.core.RowMapper;

import java.sql.Timestamp;

private static final RowMapper<User> USER_ROW_MAPPER = (rs, rowNum) -> {
    User user = new User();
    user.setId(rs.getLong("id"));
    user.setUsername(rs.getString("username"));
    user.setEmail(rs.getString("email"));
    user.setAge(rs.getInt("age"));
    user.setStatus(rs.getString("status"));

    Timestamp createdAt = rs.getTimestamp("created_at");
    if (createdAt != null) {
        user.setCreatedAt(createdAt.toLocalDateTime());
    }

    return user;
};

rowNum 表示當前行號,從 0 開始。

update:新增數(shù)據(jù)

update 可以執(zhí)行 insertupdate、delete。

新增用戶:

public int save(User user) {
    String sql = """
            insert into users (username, email, age, status, created_at)
            values (?, ?, ?, ?, ?)
            """;

    return jdbcTemplate.update(
            sql,
            user.getUsername(),
            user.getEmail(),
            user.getAge(),
            user.getStatus(),
            user.getCreatedAt()
    );
}

返回值是受影響行數(shù)。

返回 1,表示插入成功 1 行。

如果項目還在使用低版本 Java,不支持文本塊,可以寫成普通字符串:

String sql = "insert into users (username, email, age, status, created_at) values (?, ?, ?, ?, ?)";

update:修改數(shù)據(jù)

public int updateEmail(Long id, String email) {
    String sql = "update users set email = ? where id = ?";
    return jdbcTemplate.update(sql, email, id);
}

update:刪除數(shù)據(jù)

public int deleteById(Long id) {
    String sql = "delete from users where id = ?";
    return jdbcTemplate.update(sql, id);
}

queryForObject:查詢單個值

統(tǒng)計用戶數(shù)量:

public long countAll() {
    String sql = "select count(*) from users";
    Long count = jdbcTemplate.queryForObject(sql, Long.class);
    return count == null ? 0L : count;
}

查詢某個字段:

public String findEmailById(Long id) {
    String sql = "select email from users where id = ?";
    return jdbcTemplate.queryForObject(sql, String.class, id);
}

queryForObject 查詢單個值時,第二個參數(shù)可以寫目標類型:

Long.class
Integer.class
String.class
BigDecimal.class

queryForObject:查詢單個對象

public User findById(Long id) {
    String sql = """
            select id, username, email, age, status, created_at
            from users
            where id = ?
            """;

    return jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id);
}

這段代碼適合“數(shù)據(jù)必須存在”的場景。

如果查不到數(shù)據(jù),queryForObject 會拋出:

EmptyResultDataAccessException

如果查出了多條數(shù)據(jù),會拋出:

IncorrectResultSizeDataAccessException

所以 queryForObject 的語義很明確:期望結(jié)果必須是一條。

查詢可能不存在的數(shù)據(jù)

如果查詢結(jié)果可能不存在,可以用 query 返回列表,再取第一條。

import java.util.List;
import java.util.Optional;

public Optional<User> findOptionalById(Long id) {
    String sql = """
            select id, username, email, age, status, created_at
            from users
            where id = ?
            """;

    List<User> users = jdbcTemplate.query(sql, USER_ROW_MAPPER, id);

    if (users.isEmpty()) {
        return Optional.empty();
    }

    return Optional.of(users.get(0));
}

這種寫法不會把“查不到”當成異常。

業(yè)務(wù)層可以這樣處理:

User user = userRepository.findOptionalById(id)
        .orElseThrow(() -> new IllegalArgumentException("用戶不存在"));

query:查詢列表

查詢?nèi)坑脩簦?/p>

public List<User> findAll() {
    String sql = """
            select id, username, email, age, status, created_at
            from users
            order by id desc
            """;

    return jdbcTemplate.query(sql, USER_ROW_MAPPER);
}

按狀態(tài)查詢:

public List<User> findByStatus(String status) {
    String sql = """
            select id, username, email, age, status, created_at
            from users
            where status = ?
            order by id desc
            """;

    return jdbcTemplate.query(sql, USER_ROW_MAPPER, status);
}

分頁查詢:

public List<User> findPage(int page, int size) {
    int offset = (page - 1) * size;

    String sql = """
            select id, username, email, age, status, created_at
            from users
            order by id desc
            limit ? offset ?
            """;

    return jdbcTemplate.query(sql, USER_ROW_MAPPER, size, offset);
}

BeanPropertyRowMapper:自動映射

手寫 RowMapper 最清楚,也最可控。

如果表字段和實體屬性比較規(guī)整,可以使用 BeanPropertyRowMapper。

import org.springframework.jdbc.core.BeanPropertyRowMapper;

public List<User> findAllByBeanMapper() {
    String sql = "select id, username, email, age, status, created_at from users";

    return jdbcTemplate.query(
            sql,
            new BeanPropertyRowMapper<>(User.class)
    );
}

它可以處理常見的下劃線轉(zhuǎn)駝峰:

created_at -> createdAt

不過它依賴字段名和屬性名的匹配。

如果 SQL 字段比較復雜,或者結(jié)果來自多表關(guān)聯(lián),手寫 RowMapper 通常更直觀。

BeanPropertyRowMapper 配合別名

如果字段名和屬性名不一致,可以在 SQL 里使用別名。

String sql = """
        select
          id,
          username as username,
          email as email,
          created_at as created_at
        from users
        """;

對于復雜查詢,別名寫清楚,可以減少映射問題。

返回自增主鍵

新增數(shù)據(jù)后,如果需要拿到數(shù)據(jù)庫生成的主鍵,可以使用 KeyHolder。

import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import java.sql.PreparedStatement;
import java.sql.Statement;

public Long saveAndReturnId(User user) {
    String sql = """
            insert into users (username, email, age, status, created_at)
            values (?, ?, ?, ?, ?)
            """;

    KeyHolder keyHolder = new GeneratedKeyHolder();

    jdbcTemplate.update(connection -> {
        PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        ps.setString(1, user.getUsername());
        ps.setString(2, user.getEmail());
        ps.setInt(3, user.getAge());
        ps.setString(4, user.getStatus());
        ps.setObject(5, user.getCreatedAt());
        return ps;
    }, keyHolder);

    Number key = keyHolder.getKey();
    return key == null ? null : key.longValue();
}

這種寫法常用于:新增主表后,繼續(xù)插入子表。

batchUpdate:批量寫入

批量插入可以減少多次數(shù)據(jù)庫往返。

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.BatchPreparedStatementSetter;

public int[] batchSave(List<User> users) {
    String sql = """
            insert into users (username, email, age, status, created_at)
            values (?, ?, ?, ?, ?)
            """;

    return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            User user = users.get(i);
            ps.setString(1, user.getUsername());
            ps.setString(2, user.getEmail());
            ps.setInt(3, user.getAge());
            ps.setString(4, user.getStatus());
            ps.setObject(5, user.getCreatedAt());
        }

        @Override
        public int getBatchSize() {
            return users.size();
        }
    });
}

返回值是每條 SQL 影響的行數(shù)。

還有一種更短的寫法:

public int[] batchUpdateStatus(List<Long> ids, String status) {
    String sql = "update users set status = ? where id = ?";

    List<Object[]> args = ids.stream()
            .map(id -> new Object[]{status, id})
            .toList();

    return jdbcTemplate.batchUpdate(sql, args);
}

如果項目使用 Java 8,可以改成:

List<Object[]> args = ids.stream()
        .map(id -> new Object[]{status, id})
        .collect(Collectors.toList());

execute:執(zhí)行普通 SQL

execute 常用于執(zhí)行不返回結(jié)果集的 SQL。

jdbcTemplate.execute("""
        create table if not exists operation_logs (
          id bigint primary key auto_increment,
          content varchar(200) not null
        )
        """);

實際業(yè)務(wù)里,建表更常交給遷移工具,比如 Flyway、Liquibase

execute 更適合臨時初始化、測試場景或少量管理類 SQL。

NamedParameterJdbcTemplate

JdbcTemplate 默認使用 ? 占位符。

參數(shù)少時很清楚:

String sql = "select * from users where status = ? and age >= ?";

參數(shù)一多,可讀性就會下降。

NamedParameterJdbcTemplate 支持命名參數(shù):

String sql = """
        select id, username, email, age, status, created_at
        from users
        where status = :status
          and age >= :minAge
        order by id desc
        """;

完整示例:

import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

public class UserQueryRepository {

    private final NamedParameterJdbcTemplate namedJdbcTemplate;

    public UserQueryRepository(NamedParameterJdbcTemplate namedJdbcTemplate) {
        this.namedJdbcTemplate = namedJdbcTemplate;
    }

    public List<User> findByCondition(String status, int minAge) {
        String sql = """
                select id, username, email, age, status, created_at
                from users
                where status = :status
                  and age >= :minAge
                order by id desc
                """;

        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("status", status)
                .addValue("minAge", minAge);

        return namedJdbcTemplate.query(sql, params, USER_ROW_MAPPER);
    }
}

IN 查詢

NamedParameterJdbcTemplate 處理 IN 條件很方便。

public List<User> findByIds(List<Long> ids) {
    String sql = """
            select id, username, email, age, status, created_at
            from users
            where id in (:ids)
            """;

    MapSqlParameterSource params = new MapSqlParameterSource()
            .addValue("ids", ids);

    return namedJdbcTemplate.query(sql, params, USER_ROW_MAPPER);
}

如果使用普通 JdbcTemplate,IN 參數(shù)需要自己拼出對應(yīng)數(shù)量的 ?,代碼會更啰嗦。

事務(wù)控制

JdbcTemplate 可以直接配合 @Transactional 使用。

常見寫法是:

  • Repository 負責 SQL
  • Service 負責事務(wù)和業(yè)務(wù)流程

示例:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public Long register(User user) {
        Long userId = userRepository.saveAndReturnId(user);

        userRepository.insertRegisterLog(userId, "用戶注冊成功");

        return userId;
    }
}

如果 insertRegisterLog 拋出運行時異常,前面的用戶新增也會回滾。

完整實戰(zhàn) Demo:用戶 Repository

下面是一份較完整的 Repository 示例,包含新增、返回主鍵、查詢、分頁、批量更新、刪除。

package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.List;
import java.util.Optional;

@Repository
public class UserRepository {

    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    private static final RowMapper<User> USER_ROW_MAPPER = (rs, rowNum) -> {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setUsername(rs.getString("username"));
        user.setEmail(rs.getString("email"));
        user.setAge(rs.getInt("age"));
        user.setStatus(rs.getString("status"));

        Timestamp createdAt = rs.getTimestamp("created_at");
        if (createdAt != null) {
            user.setCreatedAt(createdAt.toLocalDateTime());
        }

        return user;
    };

    public Long saveAndReturnId(User user) {
        String sql = """
                insert into users (username, email, age, status, created_at)
                values (?, ?, ?, ?, ?)
                """;

        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, user.getUsername());
            ps.setString(2, user.getEmail());
            ps.setInt(3, user.getAge());
            ps.setString(4, user.getStatus());
            ps.setObject(5, user.getCreatedAt());
            return ps;
        }, keyHolder);

        Number key = keyHolder.getKey();
        return key == null ? null : key.longValue();
    }

    public int updateEmail(Long id, String email) {
        String sql = "update users set email = ? where id = ?";
        return jdbcTemplate.update(sql, email, id);
    }

    public int deleteById(Long id) {
        String sql = "delete from users where id = ?";
        return jdbcTemplate.update(sql, id);
    }

    public Optional<User> findById(Long id) {
        String sql = """
                select id, username, email, age, status, created_at
                from users
                where id = ?
                """;

        try {
            User user = jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, id);
            return Optional.ofNullable(user);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }

    public List<User> findByStatus(String status) {
        String sql = """
                select id, username, email, age, status, created_at
                from users
                where status = ?
                order by id desc
                """;

        return jdbcTemplate.query(sql, USER_ROW_MAPPER, status);
    }

    public List<User> findPage(int page, int size) {
        int offset = (page - 1) * size;

        String sql = """
                select id, username, email, age, status, created_at
                from users
                order by id desc
                limit ? offset ?
                """;

        return jdbcTemplate.query(sql, USER_ROW_MAPPER, size, offset);
    }

    public int[] batchUpdateStatus(List<Long> ids, String status) {
        String sql = "update users set status = ? where id = ?";

        return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setString(1, status);
                ps.setLong(2, ids.get(i));
            }

            @Override
            public int getBatchSize() {
                return ids.size();
            }
        });
    }
}

Controller 示例

配合 Spring MVC 可以很快寫出一個簡單接口。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserRepository userRepository;

    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @PostMapping
    public Long create(@RequestBody User user) {
        user.setStatus("ACTIVE");
        user.setCreatedAt(LocalDateTime.now());
        return userRepository.saveAndReturnId(user);
    }

    @GetMapping("/{id}")
    public User detail(@PathVariable Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("用戶不存在"));
    }

    @GetMapping
    public List<User> list() {
        return userRepository.findByStatus("ACTIVE");
    }
}

參數(shù)綁定和 SQL 注入

JdbcTemplate 支持參數(shù)綁定:

String sql = "select * from users where email = ?";
User user = jdbcTemplate.queryForObject(sql, USER_ROW_MAPPER, email);

這里的 email 不會直接拼到 SQL 字符串里,而是作為參數(shù)傳給數(shù)據(jù)庫驅(qū)動。

這種寫法比字符串拼接更穩(wěn)妥:

String sql = "select * from users where email = '" + email + "'";

常見原則:

  • 值用參數(shù)綁定
  • 表名、字段名、排序字段不能直接接收外部輸入

如果排序字段來自前端,可以做白名單映射:

public String resolveSortColumn(String sort) {
    if ("age".equals(sort)) {
        return "age";
    }
    if ("createdAt".equals(sort)) {
        return "created_at";
    }
    return "id";
}

異常處理

JdbcTemplate 會把底層 SQLException 轉(zhuǎn)成 Spring 的 DataAccessException 體系。

常見異常有:

異常常見原因
EmptyResultDataAccessExceptionqueryForObject 沒查到數(shù)據(jù)
IncorrectResultSizeDataAccessException期望一條,實際多條
DuplicateKeyException唯一鍵沖突
BadSqlGrammarExceptionSQL 語法錯誤、表名字段名錯誤
DataIntegrityViolationException非空約束、外鍵約束、字段長度等問題

在業(yè)務(wù)層通常不需要捕獲所有數(shù)據(jù)庫異常。

更常見的做法是:

  • Repository 層處理“查不到”這種正常分支
  • 全局異常處理統(tǒng)一處理其他數(shù)據(jù)庫異常

JdbcTemplate 和 MyBatis 的區(qū)別

JdbcTemplateMyBatis 都是直接操作 SQL 的方案。

差異大致可以這樣理解:

對比點JdbcTemplateMyBatis
SQL 位置多數(shù)寫在 Java 代碼里常寫在 XML 或注解里
映射能力輕量,常用 RowMapper更完整,支持復雜映射
學習成本較低中等
動態(tài) SQL手寫字符串或命名參數(shù)動態(tài) SQL 支持更成熟
適合場景簡單 CRUD、報表查詢、小型模塊復雜 SQL、較多數(shù)據(jù)訪問層代碼

JdbcTemplate 的優(yōu)勢是直接、輕量、接近 JDBC。

如果項目里 SQL 很復雜、映射關(guān)系很多,MyBatis 通常會更省維護成本。

如果只是少量 SQL、內(nèi)部工具、簡單后臺、數(shù)據(jù)修復腳本接口,JdbcTemplate 用起來很順手。

常見使用建議

Repository 層統(tǒng)一放 SQL

SQL 通常放在 RepositoryDao 層。

@Repository
public class UserRepository {
}

Service 層負責業(yè)務(wù)流程,少直接拼 SQL。

復雜映射優(yōu)先手寫 RowMapper

BeanPropertyRowMapper 很省事,但它更適合簡單對象。

多表關(guān)聯(lián)、字段別名、枚舉轉(zhuǎn)換、時間轉(zhuǎn)換、金額轉(zhuǎn)換等場景,手寫 RowMapper 會更清楚。

查詢可能為空時返回 Optional

對于按 ID 查詢、按唯一字段查詢這類方法,可以返回 Optional

public Optional<User> findByEmail(String email) {
    String sql = """
            select id, username, email, age, status, created_at
            from users
            where email = ?
            """;

    List<User> users = jdbcTemplate.query(sql, USER_ROW_MAPPER, email);

    if (users.isEmpty()) {
        return Optional.empty();
    }

    return Optional.of(users.get(0));
}

方法簽名本身就表達了:這個用戶可能存在,也可能不存在。

大批量數(shù)據(jù)注意分批

batchUpdate 適合批量操作,但不是一次塞得越多越好。

如果一次處理幾十萬條數(shù)據(jù),通常需要拆批:每 500 條或 1000 條提交一批。

具體大小要看數(shù)據(jù)庫、連接池、SQL 復雜度和網(wǎng)絡(luò)情況。

動態(tài)條件的參數(shù)綁定

動態(tài)查詢經(jīng)常會寫成這樣:

String sql = "select * from users where 1 = 1";

if (status != null) {
    sql += " and status = '" + status + "'";
}

這種寫法不利于參數(shù)綁定。

更合適的是保留參數(shù)列表:

StringBuilder sql = new StringBuilder("""
        select id, username, email, age, status, created_at
        from users
        where 1 = 1
        """);

List<Object> args = new ArrayList<>();

if (status != null) {
    sql.append(" and status = ?");
    args.add(status);
}

if (minAge != null) {
    sql.append(" and age >= ?");
    args.add(minAge);
}

List<User> users = jdbcTemplate.query(
        sql.toString(),
        USER_ROW_MAPPER,
        args.toArray()
);

如果動態(tài)條件很多,NamedParameterJdbcTemplate 會更容易維護。

常用方法匯總

方法作用常見場景
update(sql, args...)執(zhí)行新增、修改、刪除insert/update/delete
queryForObject(sql, Class<T>, args...)查詢單個值統(tǒng)計、單字段
queryForObject(sql, RowMapper<T>, args...)查詢單個對象按主鍵查詢且必須存在
query(sql, RowMapper<T>, args...)查詢列表列表頁、條件查詢
batchUpdate(sql, batchArgs)批量執(zhí)行批量插入、批量更新
execute(sql)執(zhí)行普通 SQLDDL、初始化腳本
NamedParameterJdbcTemplate命名參數(shù) SQL多條件查詢、IN 查詢
RowMapper<T>單行結(jié)果映射對象自定義實體映射
BeanPropertyRowMapper<T>自動屬性映射簡單表和簡單實體

總結(jié)

JdbcTemplate 的定位很清楚:保留 SQL 的直接控制感,同時減少 JDBC 模板代碼。

它適合這些場景:

  • SQL 數(shù)量不多
  • 查詢邏輯比較直接
  • 不想引入完整 ORM 或 Mapper 框架
  • 需要快速寫內(nèi)部工具、管理后臺、數(shù)據(jù)處理邏輯
  • 希望直接控制 SQL 和參數(shù)

實際使用時,重點放在幾件事上:

  • 用參數(shù)綁定,不拼接外部輸入
  • RowMapper 管好結(jié)果映射
  • queryOptional 表達可能查不到的數(shù)據(jù)
  • NamedParameterJdbcTemplate 處理多參數(shù)和 IN 查詢
  • @Transactional 管住多個 SQL 的一致性

掌握這些內(nèi)容后,JdbcTemplate 已經(jīng)可以覆蓋大多數(shù)輕量數(shù)據(jù)庫訪問場景。

到此這篇關(guān)于Spring使用JdbcTemplate輕量完成數(shù)據(jù)庫增刪改查的實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Spring JdbcTemplate數(shù)據(jù)庫增刪改查內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot Actuator跟蹤HTTP請求和響應(yīng)的方法

    SpringBoot Actuator跟蹤HTTP請求和響應(yīng)的方法

    Spring Boot Actuator 是 Spring Boot 提供的生產(chǎn)級監(jiān)控和管理模塊,旨在幫助開發(fā)者實時監(jiān)控應(yīng)用狀態(tài)、收集運行時指標,并提供一系列管理端點,本文給大家介紹了SpringBoot Actuator跟蹤HTTP請求和響應(yīng)的方法,需要的朋友可以參考下
    2025-08-08
  • SpringBoot 動態(tài)定時器的使用方法

    SpringBoot 動態(tài)定時器的使用方法

    這篇文章主要介紹了SpringBoot 動態(tài)定時器的使用方法,非常不錯,具有一定的參考借鑒借鑒價值,需要的朋友可以參考下
    2018-05-05
  • 從JVM的內(nèi)存管理角度分析Java的GC垃圾回收機制

    從JVM的內(nèi)存管理角度分析Java的GC垃圾回收機制

    這篇文章主要介紹了從JVM的內(nèi)存管理角度分析Java的GC垃圾回收機制,帶有GC是Java語言的重要特性之一,需要的朋友可以參考下
    2015-11-11
  • 使用Spring Data MongoDB進行地理位置相關(guān)查詢的步驟和示例

    使用Spring Data MongoDB進行地理位置相關(guān)查詢的步驟和示例

    SpringData MongoDB是SpringData技術(shù)封裝了mongodb-driver技術(shù)之后的產(chǎn)物,它可以用更加簡單的方式操作MongoDB,本文給大家介紹了如何使用Spring Data MongoDB進行地理位置相關(guān)查詢的步驟和示例,需要的朋友可以參考下
    2025-05-05
  • 基于SpringBoot實現(xiàn)文件秒傳功能

    基于SpringBoot實現(xiàn)文件秒傳功能

    在開發(fā)Web應(yīng)用時,文件上傳是一個常見需求,然而,當用戶需要上傳大文件或相同文件多次時,會造成帶寬浪費和服務(wù)器存儲冗余,此時可以使用文件秒傳技術(shù)通過識別重復文件,本文就給大家介紹了如何基于SpringBoot實現(xiàn)文件秒傳功能,需要的朋友可以參考下
    2025-04-04
  • IDEA2020.3創(chuàng)建web工程的完整步驟

    IDEA2020.3創(chuàng)建web工程的完整步驟

    這篇文章主要給大家介紹了關(guān)于IDEA2020.3創(chuàng)建web工程的完整步驟,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • 在Spring?Boot中啟用HTTPS的方法

    在Spring?Boot中啟用HTTPS的方法

    本文介紹了在Spring Boot項目中啟用HTTPS的步驟,從生成SSL證書開始,到配置Spring Boot。HTTPS是保護Web應(yīng)用程序安全的基石之一,而Spring Boot則提供了相對簡易的途徑來配置它,感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • Java存儲數(shù)據(jù)之數(shù)組與集合的詳細操作

    Java存儲數(shù)據(jù)之數(shù)組與集合的詳細操作

    這篇文章主要介紹了Java中數(shù)組和集合的基本概念、優(yōu)勢和使用場景,并詳細探討了集合的各個子類及其特性,同時,文章還涵蓋了如何遍歷集合以及遍歷過程中的一些注意事項,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • Java中wait()與sleep()兩者的不同深入解析

    Java中wait()與sleep()兩者的不同深入解析

    在Java多線程編程中,wait()和sleep()是控制線程執(zhí)行和等待的兩個關(guān)鍵方法,但它們在應(yīng)用場景和實現(xiàn)上有顯著差異,這篇文章主要介紹了Java中wait()與sleep()兩者的不同,需要的朋友可以參考下
    2024-11-11
  • JavaEE中volatile、wait和notify詳解

    JavaEE中volatile、wait和notify詳解

    這篇文章主要給大家介紹了關(guān)于JavaEE中volatile、wait和notify的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2023-02-02

最新評論

富平县| 沂南县| 英吉沙县| 张家口市| 阿城市| 怀来县| 中山市| 德安县| 孝义市| 临颍县| 古浪县| 武夷山市| 东乡族自治县| 平遥县| 宁德市| 贵定县| 水富县| 元氏县| 鸡泽县| 苏尼特右旗| 房产| 潢川县| 五常市| 伊吾县| 仙游县| 汝州市| 吐鲁番市| 乌苏市| 海伦市| 呼伦贝尔市| 襄城县| 神农架林区| 武义县| 偃师市| 科技| 贡觉县| 福鼎市| 正定县| 广灵县| 前郭尔| 九寨沟县|