Spring Boot 连接 SQL Server 实战:从依赖配置到分页查询

举报
yd_236998400 发表于 2026/10/28 09:50:50 2026/10/28
【摘要】 Spring Boot 连接 SQL Server 实战:从依赖配置到分页查询环境基线:Java 17 · Spring Boot 3.x · mssql-jdbc 12.x · SQL Server 2019/2022 一、动手前先确认三件事项目建议Java17+(Spring Boot 3.x 强制要求)驱动mssql-jdbc 12.x(JDBC 4.3)SQL Server2012...

Spring Boot 连接 SQL Server 实战:从依赖配置到分页查询

环境基线:Java 17 · Spring Boot 3.x · mssql-jdbc 12.x · SQL Server 2019/2022

一、动手前先确认三件事

项目 建议
Java 17+(Spring Boot 3.x 强制要求)
驱动 mssql-jdbc 12.x(JDBC 4.3)
SQL Server 2012 及以上(2019/2022 最佳)

SQL Server 是最容易被「连不上」击倒的数据库,因为它的失败通常不在代码层,而在这三处:TCP/IP 协议没开、端口没通、只开了 Windows 身份验证。建议先看完第七节的排查表再写代码。

二、用 Docker 起一个可用的实例

docker run -d --name sqlserver -p 1433:1433 \
  -e "ACCEPT_EULA=Y" \
  -e "MSSQL_SA_PASSWORD=YourStrong@Passw0rd" \
  mcr.microsoft.com/mssql/server:2022-latest

密码必须满足复杂度要求(8 位以上、含大小写/数字/符号),否则容器会静默启动失败。Apple Silicon 或 ARM 环境请改用 mcr.microsoft.com/azure-sql-edge。

建库建表:

CREATE DATABASE demo;
GO
USE demo;
GO
CREATE TABLE sys_user (
    id         BIGINT IDENTITY(1,1) PRIMARY KEY,
    username   NVARCHAR(50)  NOT NULL,
    email      NVARCHAR(100) NULL,
    created_at DATETIME2(3)  NOT NULL DEFAULT SYSDATETIME()
);

字段统一用 NVARCHAR 和 DATETIME2,这是后面避免中文乱码和时间精度丢失的关键。

三、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <scope>runtime</scope>
</dependency>

Spring Boot 的 BOM 已经统一管理 mssql-jdbc 版本,不必自己指定;需要锁定版本时写 <version>12.8.1.jre11</version>。

注意:Spring Boot 没有 SQL Server 专属 starter。用 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa 加驱动即可,Spring Boot 会自动识别 SQLServerDriver。

四、配置文件

spring:
  datasource:
    url: "jdbc:sqlserver://localhost:1433;databaseName=demo;encrypt=true;trustServerCertificate=true;loginTimeout=30"
    username: sa
    password: "YourStrong@Passw0rd"
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2
      connection-timeout: 30000
      connection-test-query: SELECT 1

  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
    properties:
      hibernate:
        format_sql: true

两个参数必须重点说明:

  • encrypt=true:从 JDBC 10.2 起默认就是 true,连接串里写出来只是让意图更明确。
  • trustServerCertificate=true:本地自签证书场景必须加,否则握手直接失败。生产环境不建议开启,应改用受信任证书并设置 trustStore。

如果连接的是 SQL Server 2012/2014 这类老版本,TLS 版本过低会报 TLS10 is not accepted,临时方案是加 encrypt=false(仅限内网),根本方案是升级数据库或打补丁。

五、写代码

1. 实体

@Entity
@Table(name = "sys_user")
public class SysUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String username;

    @Column(length = 100)
    private String email;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    // getter / setter 省略
}

IDENTITY 是 SQL Server 自增列的唯一正确映射方式,用 AUTO 在某些版本下会退化成 hilo 策略,出现雪花式的跳跃 ID。

2. Repository

public interface SysUserRepository extends JpaRepository<SysUser, Long> {

    Page<SysUser> findByUsernameContaining(String keyword, Pageable pageable);
}

3. Controller

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

    private final SysUserRepository repository;

    public SysUserController(SysUserRepository repository) {
        this.repository = repository;
    }

    @GetMapping
    public Page<SysUser> list(@RequestParam(defaultValue = "0") int page,
                              @RequestParam(defaultValue = "10") int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
        return repository.findAll(pageable);
    }

    @PostMapping
    public SysUser create(@RequestBody SysUser user) {
        user.setCreatedAt(LocalDateTime.now());
        return repository.save(user);
    }
}

分页时一定要带 Sort。SQL Server 的分页语法是 OFFSET ... FETCH NEXT,它强制要求 ORDER BY,缺了会直接抛异常,而不是像 MySQL 那样能「侥幸跑通」。

4. 不想用 JPA?JdbcTemplate 版本

@Repository
public class UserJdbcDao {

    private final JdbcTemplate jdbcTemplate;

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

    public List<SysUser> findAll(int offset, int limit) {
        String sql = "SELECT id, username, email, created_at FROM sys_user " +
                     "ORDER BY id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
        return jdbcTemplate.query(sql, (rs, i) -> {
            SysUser u = new SysUser();
            u.setId(rs.getLong("id"));
            u.setUsername(rs.getString("username"));
            u.setEmail(rs.getString("email"));
            u.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
            return u;
        }, offset, limit);
    }
}

如果你的项目用 MyBatis,把 mybatis-spring-boot-starter 加上,其余配置完全一样,SQL 里注意 NVARCHAR 字段的 #{} 参数即可。

六、验证

启动后直接请求:

curl -X POST localhost:8080/users -H "Content-Type: application/json" \
  -d '{"username":"张三","email":"zhangsan@example.com"}'

curl "localhost:8080/users?page=0&size=10"

能插入中文并原样查回来,说明编码链路(NVARCHAR + sendStringParametersAsUnicode=true,后者默认开启)是通的。

七、常见问题对照表

报错/现象 真实原因 处理方式
Connection refused / 连接超时 TCP/IP 未启用,或 1433 未放行 SQL Server 配置管理器启用 TCP/IP 并重启实例;检查防火墙和端口
The server selected protocol version TLS10 is not accepted 旧版数据库 + 驱动默认加密 内网临时 encrypt=false,长期方案升级数据库或证书
Login failed for user 'sa' 实例只开了 Windows 身份验证 改为「混合模式」并重启 SQL Server 服务
Cannot open database "demo" 库名写错或未创建 先建库;本地可加 createDatabaseIfNotExist=true
中文变成 ??? 字段用了 VARCHAR 改为 NVARCHAR,排序规则选中文兼容的 collation
分页查询抛异常 缺少 ORDER BY PageRequest.of(..., Sort.by("id"))
时间差 8 小时 时区链路不一致 统一用 LocalDateTime + DATETIME2,勿混用 datetime/Timestamp
Incorrect syntax near 'user' 撞上保留字 表名/列名避开 user、order、key,或加方括号 [user]
批量插入极慢 JDBC 逐条 round-trip 连接串加 useBulkCopyForBatchInsert=true

八、几条生产建议

  1. ddl-auto 保持 none,表结构交给 Flyway 或 Liquibase 管理。
  2. 应用不要用 sa 连库,为业务建独立账号并只授予 db_datareader、db_datawriter。
  3. 连接池上限要参照 SQL Server 的 max worker threads,默认 10 条连接对中小应用足够。
  4. 大字段用 @Lob 前先确认字段类型是 VARBINARY(MAX) / NVARCHAR(MAX),否则会出现类型转换异常。

一句话总结:SQL Server 的连接问题九成出在环境,不在代码——把 TCP/IP、混合认证、DATETIME2/NVARCHAR 这三处对齐,剩下的和连 MySQL 没有区别。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。