Spring Boot + PostgreSQL + MyBatis

258 阅读2分钟

以下是结合 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
);

关键点说明

  1. 注解与 XML 混合使用

    • 简单查询用 @Select/@Insert 注解,复杂动态 SQL 用 XML。
    • PostgreSQL 的 RETURNING 子句配合 @Options 获取自增 ID。
  2. 事务管理

    • 在 Service 层方法上添加 @Transactional,确保原子性操作。
  3. 测试

    • 使用 Testcontainers 启动真实 PostgreSQL 容器,避免 Mock 数据库的局限性。
  4. 性能优化

    • HikariCP 连接池配置(maximum-pool-sizeconnection-timeout)。
    • MyBatis 的 map-underscore-to-camel-case 自动转换字段名。

常见问题解决

  1. 字段名映射失败

    • 确认 map-underscore-to-camel-case: true 已开启,或使用 @Result 注解手动映射。
  2. 事务不生效

    • 确保 @Transactional 注解添加在 public 方法上,且调用来自外部类(Spring 代理机制)。
  3. XML 文件未加载

    • 检查 mybatis.mapper-locations 路径是否匹配实际 XML 文件位置。

通过以上具体示例,可以快速搭建一个符合最佳实践的 Spring Boot + PostgreSQL + MyBatis 应用。