以下是结合 Spring Boot + PostgreSQL + MyBatis 的具体示例,涵盖配置、代码、事务、测试等关键环节。
1. 项目结构与依赖
pom.xml 关键依赖
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- PostgreSQL 驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis 集成 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>
</dependencies>
2. 数据模型与 Mapper
实体类 User.java
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
}
Mapper 接口 UserMapper.java
package com.example.demo.mapper;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
// 注解方式:简单查询
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);
// XML 方式:复杂查询
List<User> findByName(String name);
// 插入并返回自增 ID(PostgreSQL 使用 RETURNING)
@Insert("INSERT INTO users (name, email, created_at) " +
"VALUES (#{name}, #{email}, #{createdAt}) " +
"RETURNING id")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(User user);
// 动态 SQL 更新(通过 XML 实现)
void update(User user);
}
XML 映射文件 UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<!-- 根据名称查询(支持动态条件) -->
<select id="findByName" resultType="User">
SELECT * FROM users
WHERE name = #{name}
<if test="status != null">
AND status = #{status}
</if>
</select>
<!-- 动态更新 -->
<update id="update">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
<if test="createdAt != null">created_at = #{createdAt},</if>
</set>
WHERE id = #{id}
</update>
</mapper>
3. Service 层与事务管理
UserService.java
package com.example.demo.service;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
// 事务管理示例
@Transactional
public User createUser(User user) {
userMapper.insert(user); // 插入并获取自增 ID
return user;
}
// 动态更新
@Transactional
public void updateUser(User user) {
userMapper.update(user);
}
}
4. Controller 层
UserController.java
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
@PutMapping("/{id}")
public void updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
userService.updateUser(user);
}
}
5. 配置文件 application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: postgres
password: mysecretpassword
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
connection-timeout: 30000
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.model
configuration:
map-underscore-to-camel-case: true
6. 测试示例
集成测试 UserServiceTest.java
package com.example.demo.service;
import com.example.demo.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest
@Testcontainers
class UserServiceTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Autowired
private UserService userService;
@Test
void testCreateUser() {
User user = new User();
user.setName("Alice");
user.setEmail("alice@example.com");
user.setCreatedAt(LocalDateTime.now());
User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
}
}
7. PostgreSQL 表结构
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
关键点说明
-
注解与 XML 混合使用:
- 简单查询用
@Select/@Insert注解,复杂动态 SQL 用 XML。 - PostgreSQL 的
RETURNING子句配合@Options获取自增 ID。
- 简单查询用
-
事务管理:
- 在 Service 层方法上添加
@Transactional,确保原子性操作。
- 在 Service 层方法上添加
-
测试:
- 使用 Testcontainers 启动真实 PostgreSQL 容器,避免 Mock 数据库的局限性。
-
性能优化:
- HikariCP 连接池配置(
maximum-pool-size、connection-timeout)。 - MyBatis 的
map-underscore-to-camel-case自动转换字段名。
- HikariCP 连接池配置(
常见问题解决
-
字段名映射失败:
- 确认
map-underscore-to-camel-case: true已开启,或使用@Result注解手动映射。
- 确认
-
事务不生效:
- 确保
@Transactional注解添加在public方法上,且调用来自外部类(Spring 代理机制)。
- 确保
-
XML 文件未加载:
- 检查
mybatis.mapper-locations路径是否匹配实际 XML 文件位置。
- 检查
通过以上具体示例,可以快速搭建一个符合最佳实践的 Spring Boot + PostgreSQL + MyBatis 应用。