Spring Boot 连接 SQL Server 实战:从依赖配置到分页查询
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 |
八、几条生产建议
ddl-auto保持none,表结构交给 Flyway 或 Liquibase 管理。- 应用不要用
sa连库,为业务建独立账号并只授予db_datareader、db_datawriter。 - 连接池上限要参照 SQL Server 的
max worker threads,默认 10 条连接对中小应用足够。 - 大字段用
@Lob前先确认字段类型是VARBINARY(MAX)/NVARCHAR(MAX),否则会出现类型转换异常。
一句话总结:SQL Server 的连接问题九成出在环境,不在代码——把 TCP/IP、混合认证、DATETIME2/NVARCHAR 这三处对齐,剩下的和连 MySQL 没有区别。
- 点赞
- 收藏
- 关注作者
评论(0)