PlanetScale’s distributed nature means your standard Hibernate hibernate.cfg.xml or application.properties needs a few specific tweaks to play nice, especially around connection pooling and statement caching.

Let’s see how this looks in practice. Imagine a simple Spring Boot application needing to talk to PlanetScale.

@Configuration
public class JpaConfig {

    @Bean
    public DataSource dataSource() {
        // We'll configure this with PlanetScale-specific properties
        // For now, imagine a HikariDataSource
        return new HikariDataSource();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.yourapp.model"); // Your JPA entity package

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);

        Properties properties = new Properties();
        // PlanetScale specific properties below
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect"); // Or appropriate MySQL dialect
        properties.put("hibernate.show_sql", "true");
        properties.put("hibernate.format_sql", "true");
        // ... other Hibernate properties
        em.setJpaProperties(properties);

        return em;
    }

    // ... other beans like transaction manager
}

The real magic happens when we configure the DataSource and its underlying connection pool. For PlanetScale, we’ll likely be using HikariCP as it’s the default in Spring Boot and highly performant.

Here’s what your application.properties (or application.yml) might look like for a PlanetScale connection:

# application.properties example
spring.datasource.url=jdbc:mysql://<your-planetscale-host>:3306/<your-database-name>?sslMode=VERIFY_IDENTITY&sslCa=/path/to/your/ca.pem
spring.datasource.username=<your-planetscale-username>
spring.datasource.password=<your-planetscale-password>

# HikariCP settings
spring.datasource.hikari.poolName=PlanetScalePool
spring.datasource.hikari.maximumPoolSize=10 # Adjust based on your workload and PlanetScale plan
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.connectionTimeout=30000 # 30 seconds
spring.datasource.hikari.idleTimeout=600000 # 10 minutes
spring.datasource.hikari.maxLifetime=1800000 # 30 minutes

# Hibernate specific settings for PlanetScale
# Disable prepared statement caching to avoid issues with schema changes in PlanetScale
spring.jpa.properties.hibernate.jdbc.use_get_generated_keys=true
spring.jpa.properties.hibernate.jdbc.batch_size=100
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.cache.use_query_cache=false
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
spring.jpa.properties.hibernate.connection.provider_class=com.zaxxer.hikari.HikariDataSource
spring.jpa.properties.hibernate.globally_quoted_identifiers=false
spring.jpa.properties.hibernate.id.optimizer.pooled=true # Essential for performance

# PlanetScale specific: Disable prepared statement caching.
# PlanetScale's sharding and dynamic schema aspects can sometimes lead to
# prepared statement cache invalidation issues or performance degradations.
# Disabling it forces new statements to be prepared more often, but avoids
# potential subtle bugs. For very high-throughput scenarios, you might
# revisit this and profile carefully.
spring.jpa.properties.hibernate.stmt.cache.size=0

The most surprising true thing about PlanetScale and ORMs like Hibernate is that you often need to disable features that are normally performance boosters in traditional RDBMS setups. Specifically, prepared statement caching is a prime candidate for disabling. PlanetScale’s architecture, which involves sharding and potentially dynamic routing of queries, can sometimes conflict with how ORMs cache prepared statements. When a statement is cached, the ORM might assume the underlying execution plan or statement structure remains identical across connections or even across different but related database instances. In a distributed system like PlanetScale, this assumption can break. Disabling the cache (hibernate.stmt.cache.size=0) forces Hibernate to prepare statements more frequently, but it sidesteps potential issues where a cached statement might be invalid or inefficient for the specific shard or connection being used at that moment.

Let’s look at the connection string itself. For PlanetScale, you’ll need to enable SSL and provide the CA certificate. The sslMode=VERIFY_IDENTITY is crucial for security, ensuring you’re connecting to the legitimate PlanetScale endpoint. You’ll download the ca.pem file from PlanetScale’s documentation or your connection details.

// Example of how the datasource URL might be constructed programmatically
String planetScaleHost = "your-planetscale-host.region.planetscale.com";
String databaseName = "your-database-name";
String username = "your-username";
String password = "your-password";
String caCertPath = "/path/to/your/ca.pem";

String jdbcUrl = String.format(
    "jdbc:mysql://%s:3306/%s?sslMode=VERIFY_IDENTITY&sslCa=%s",
    planetScaleHost,
    databaseName,
    caCertPath
);

// Then use this jdbcUrl in your DataSource configuration.

The hibernate.id.optimizer.pooled=true setting is vital. PlanetScale, like many modern databases, handles auto-increment IDs efficiently. This Hibernate optimizer tells Hibernate to rely on the database’s pooled next-value generation for primary keys, rather than using a separate sequence or table, which is much more performant in a distributed environment.

Another key area is connection pooling. PlanetScale’s connections are stateful and have resource limits. Over-provisioning your connection pool (maximumPoolSize) can lead to connection errors or performance degradation. Start with a conservative number, like 10-15, and monitor your application’s performance and PlanetScale’s connection metrics. The connectionTimeout, idleTimeout, and maxLifetime are also important. Setting maxLifetime to a value less than PlanetScale’s potential idle connection timeout (often around 30 minutes for unattended connections) can help prevent stale connections from being held in your pool.

You’ll also notice settings like hibernate.order_inserts=true and hibernate.order_updates=true. These, along with hibernate.jdbc.batch_size, are generally good practices for bulk operations. They tell Hibernate to group multiple INSERT or UPDATE statements together and send them in batches, reducing network round trips and improving throughput. While not strictly PlanetScale-specific, they are crucial for efficient data manipulation in any database.

Disabling second-level cache (hibernate.cache.use_second_level_cache=false) and query cache (hibernate.cache.use_query_cache=false) is often recommended for PlanetScale, especially if you’re not carefully managing cache invalidation. Distributed databases can make cache invalidation complex. For many applications, relying on the database itself for caching (e.g., through its buffer pool) and using application-level caching where necessary is a simpler and more robust approach.

The hibernate.globally_quoted_identifiers=false setting is a minor optimization. It tells Hibernate not to quote all identifiers (table names, column names), which can be slightly more performant. If you have identifiers that are reserved keywords or contain special characters, you might need to set this to true and quote them explicitly in your entity mappings.

The one thing most people don’t know is that hibernate.jdbc.use_get_generated_keys=true is a critical hint for how Hibernate should retrieve auto-generated primary keys. When you insert an entity with an auto-increment ID, Hibernate needs to get that ID back from the database. Setting this to true tells Hibernate to use the getGeneratedKeys() method of the JDBC PreparedStatement, which is generally the most efficient and database-agnostic way to retrieve such keys. This avoids the need for a separate SELECT last_insert_id() query after each insert, which can be slower and less reliable in certain scenarios, especially with complex transaction management.

After you’ve got your Hibernate and PlanetScale configuration dialed in, the next hurdle you’ll likely encounter is managing connection churn and understanding PlanetScale’s query latency under load.

Want structured learning?

Take the full Planetscale course →