AgentSkillsCN

spring-supabase-integration

将 Spring Boot 与 Supabase PostgreSQL 集成,涵盖 DataSource 配置、JPA 设置,以及生产就绪的连接池管理。

SKILL.md
--- frontmatter
name: spring-supabase-integration
description: Spring Boot integration with Supabase PostgreSQL including DataSource configuration, JPA setup, and production-ready connection pooling.
version: "1.0.0"
author: GazApps
tags: [supabase, spring-boot, postgresql, jpa, datasource]
dependencies: [java-fundamentals, spring-boot-core, spring-data-jpa, supabase-jdbc]
compatibility: [antigravity, claude-code, gemini-cli]

Spring Boot + Supabase Integration

Complete Spring Boot configuration for Supabase PostgreSQL.

Use this skill when

  • Configuring Spring Boot to use Supabase database
  • Setting up JPA/Hibernate with Supabase
  • Need production-ready database configuration
  • User mentions "Spring Boot with Supabase"

Dependencies

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- Optional: Load .env files -->
<dependency>
    <groupId>me.paulschwarz</groupId>
    <artifactId>spring-dotenv</artifactId>
    <version>4.0.0</version>
</dependency>

Configuration

application.yml

yaml
spring:
  datasource:
    url: jdbc:postgresql://${SUPABASE_HOST}:${SUPABASE_PORT:5432}/${SUPABASE_DB:postgres}?sslmode=require
    username: ${SUPABASE_USER:postgres}
    password: ${SUPABASE_PASSWORD}
    driver-class-name: org.postgresql.Driver
    
    # HikariCP settings
    hikari:
      pool-name: SupabaseHikariPool
      maximum-pool-size: ${DB_POOL_SIZE:10}
      minimum-idle: ${DB_POOL_MIN_IDLE:2}
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
      
      # Connection test
      connection-test-query: SELECT 1

  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: ${JPA_DDL_AUTO:validate}
    show-sql: ${JPA_SHOW_SQL:false}
    open-in-view: false
    properties:
      hibernate:
        format_sql: true
        jdbc:
          time_zone: UTC
          batch_size: 20
        order_inserts: true
        order_updates: true
        default_batch_fetch_size: 20

application-dev.yml

yaml
spring:
  datasource:
    hikari:
      maximum-pool-size: 5
      minimum-idle: 1
      
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true

logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql: TRACE

application-prod.yml

yaml
spring:
  datasource:
    # Use connection pooler for production
    url: jdbc:postgresql://${SUPABASE_POOLER_HOST}:${SUPABASE_POOLER_PORT:6543}/${SUPABASE_DB:postgres}?sslmode=require
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

Environment Variables (.env)

properties
# Direct connection
SUPABASE_HOST=db.xxxxx.supabase.co
SUPABASE_PORT=5432
SUPABASE_DB=postgres
SUPABASE_USER=postgres
SUPABASE_PASSWORD=your-password

# Connection pooler (for production)
SUPABASE_POOLER_HOST=xxxxx.pooler.supabase.com
SUPABASE_POOLER_PORT=6543

# JPA settings
JPA_DDL_AUTO=validate
JPA_SHOW_SQL=false

# Pool settings
DB_POOL_SIZE=10
DB_POOL_MIN_IDLE=2

JPA Configuration

java
@Configuration
@EnableJpaAuditing
@EnableJpaRepositories(basePackages = "com.example.app.repository")
public class JpaConfig {

    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.ofNullable(SecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .filter(Authentication::isAuthenticated)
            .map(Authentication::getName);
    }
}

Health Check

Spring Boot Actuator automatically adds database health check:

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info
  endpoint:
    health:
      show-details: when_authorized
      probes:
        enabled: true
  health:
    db:
      enabled: true

Response at /actuator/health:

json
{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "PostgreSQL",
        "validationQuery": "isValid()"
      }
    }
  }
}

Custom Health Indicator

java
@Component
public class SupabaseHealthIndicator implements HealthIndicator {

    private final DataSource dataSource;

    public SupabaseHealthIndicator(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Health health() {
        try (Connection conn = dataSource.getConnection()) {
            if (conn.isValid(5)) {
                return Health.up()
                    .withDetail("database", "Supabase PostgreSQL")
                    .withDetail("connection", "valid")
                    .build();
            }
        } catch (SQLException e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
        return Health.unknown().build();
    }
}

Connection Pool Metrics

java
@Configuration
public class MetricsConfig {

    @Bean
    public MeterBinder hikariMetrics(DataSource dataSource) {
        if (dataSource instanceof HikariDataSource hikari) {
            return new HikariCPMetrics(hikari.getHikariPoolMXBean());
        }
        return registry -> {};
    }
}

Metrics available at /actuator/metrics:

  • hikaricp.connections.active
  • hikaricp.connections.idle
  • hikaricp.connections.pending
  • hikaricp.connections.timeout

Database Migrations with Flyway

xml
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
yaml
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true

Migration files in src/main/resources/db/migration/:

sql
-- V1__create_users_table.sql
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);

Row Level Security (RLS)

If using Supabase RLS, configure the connection to set the user context:

java
@Component
@RequiredArgsConstructor
public class SupabaseRlsInterceptor implements HandlerInterceptor {

    private final DataSource dataSource;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.isAuthenticated()) {
            String userId = auth.getName();
            try (Connection conn = dataSource.getConnection();
                 Statement stmt = conn.createStatement()) {
                stmt.execute("SET app.current_user_id = '" + userId + "'");
            } catch (SQLException e) {
                // Handle error
            }
        }
        return true;
    }
}

RLS Policy example in Supabase:

sql
CREATE POLICY "Users can only see their own data"
ON users
FOR SELECT
USING (id::text = current_setting('app.current_user_id', true));

Testing with Testcontainers

xml
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
java
@SpringBootTest
@Testcontainers
class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveUser() {
        User user = User.builder()
            .name("Test User")
            .email("test@example.com")
            .build();
        
        User saved = userRepository.save(user);
        
        assertThat(saved.getId()).isNotNull();
    }
}

Troubleshooting

Connection Issues

java
// Add this to debug connection issues
@EventListener(ApplicationReadyEvent.class)
public void testConnection(ApplicationReadyEvent event) {
    try (Connection conn = dataSource.getConnection()) {
        log.info("Database connection successful: {}", conn.getCatalog());
    } catch (SQLException e) {
        log.error("Database connection failed: {}", e.getMessage());
    }
}

Pool Exhaustion

yaml
# Increase pool size or reduce connection timeout
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      connection-timeout: 10000
      leak-detection-threshold: 30000

Code Quality Checklist

  • Environment variables for all credentials
  • Different configs for dev/prod profiles
  • Connection pooler for production
  • JPA auditing enabled
  • Health check configured
  • Flyway for migrations
  • Tests use Testcontainers
  • open-in-view disabled

References

  • See references/troubleshooting.md for common issues
  • See examples/ for complete examples