By default, we use the public schema when we connect to PostgreSQL. Some applications use multiple schemas to store data in the database. Therefore, they need to specify explicitly which PostgreSQL schemas to use. This post shows how to connect to a database schema other than the public schema.
Environment Setup
We will use Docker for Windows, a PostgreSQL Docker image, Java Persistence API (JPA), Sprint Boot, and Java Development Kit (JDK) 8. This post assumes we already installed Docker.
First, we need a PostgreSQL database. We can create it using the PostgreSQL Docker image. Then we create two schemas – public and myapp_schema. The public schema has the pets table, while the myapp_schema schema has the persons table.
Consider the following PostgreSQL table definitions. We can use them to create tables in their respective PostgreSQL schemas.
1 2 3 4 5 6 7 8 9 10 11 12 13 | create table myapp_schema.persons ( person_id serial not null, person_name varchar(100) ); alter table myapp_schema.persons owner to turreta; create table pets ( pet_id serial not null, pet_name varchar(100) ); alter table pets owner to turreta; |
The persons and pets tables have the following data.
1 2 3 4 5 6 | +---------+-----------+ |person_id|person_name| +---------+-----------+ |1 |Karl | |2 |KL | +---------+-----------+ |
1 2 3 4 5 6 | +------+--------+ |pet_id|pet_name| +------+--------+ |1 |Brownie | |2 |Lazzie | +------+--------+ |
Our Sprint Boot Application
We use the Spring Initlzr to generate the first Spring Boot codes. Then, we modify the codes. For the pom.xml file, we have the following important Maven dependencies.
1 2 3 4 5 6 7 8 9 10 11 12 | ... <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> ... |
For the Java source code files, we have the JPA entity, JPA Repository, and main class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.turreta.postgresql.defaultschema2.defaultschema2demo; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "persons") public class PersonEntity { @Id private Long personId; private String personName; public Long getPersonId() { return personId; } public void setPersonId(Long personId) { this.personId = personId; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } } |
Then, the JPA Repository.
1 2 3 4 5 6 7 8 9 | package com.turreta.postgresql.defaultschema2.defaultschema2demo; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface PersonRepository extends JpaRepository<PersonEntity, Long>{ } |
Finally, we have the main class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.turreta.postgresql.defaultschema2.defaultschema2demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import java.util.List; @SpringBootApplication public class DefaultSchema2DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DefaultSchema2DemoApplication.class, args); PersonRepository personRepository = context.getBean(PersonRepository.class); List<PersonEntity> all = personRepository.findAll(); for (PersonEntity personEntity : all) { System.out.printf(personEntity.getPersonId() + "|" + personEntity.getPersonName()); } } } |
Default PostgreSQL Schema in application.properties
To use the myapp_schema schema instead of the public schema, we will fiddle with the application.properties file. First, we start with the usual configuration that uses the default public schema.
The property spring.datasource.url has a JDBC URL that specifies only the PostgreSQL database – turreta. We will change that property’s value a bit later.
1 2 3 4 5 6 7 8 9 10 | #server.port=9090 spring.jpa.database=POSTGRESQL spring.datasource.platform=postgres spring.datasource.url=jdbc:postgresql://localhost:5442/turreta spring.datasource.username=turreta spring.datasource.password=a1128f69-e6f7-4e93-a2df-3d4db6030abc spring.jpa.show-sql=true #spring.jpa.generate-ddl=true #spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true |
When we run our application, we get the following error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 2020-08-14 19:57:45.935 INFO 7656 --- [ main] c.t.p.d.d.DefaultSchema2DemoApplication : Started DefaultSchema2DemoApplication in 3.772 seconds (JVM running for 5.139) Hibernate: select personenti0_.person_id as person_i1_0_, personenti0_.person_name as person_n2_0_ from persons personenti0_ 2020-08-14 19:57:46.090 WARN 7656 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42P01 2020-08-14 19:57:46.090 ERROR 7656 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: relation "persons" does not exist Position: 94 Exception in thread "main" org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:281) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) at com.sun.proxy.$Proxy69.findAll(Unknown Source) at com.turreta.postgresql.defaultschema2.defaultschema2demo.DefaultSchema2DemoApplication.main(DefaultSchema2DemoApplication.java:17) Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:103) at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:67) at org.hibernate.loader.Loader.getResultSet(Loader.java:2341) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2094) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2056) at org.hibernate.loader.Loader.doQuery(Loader.java:953) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:350) at org.hibernate.loader.Loader.doList(Loader.java:2887) at org.hibernate.loader.Loader.doList(Loader.java:2869) at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2701) at org.hibernate.loader.Loader.list(Loader.java:2696) at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:506) at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:400) at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219) at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1415) at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1565) at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1533) at org.hibernate.query.Query.getResultList(Query.java:165) at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getResultList(CriteriaQueryTypeQueryAdapter.java:76) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:355) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:549) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:155) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ... 8 more Caused by: org.postgresql.util.PSQLException: ERROR: relation "persons" does not exist Position: 94 at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2532) at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2267) at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:312) at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:448) at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:369) at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:153) at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:103) at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:57) ... 44 more |
The error indicates that the persons table does not exist (in the public schema).
Specify PostgreSQL Schema
Now, let us change the value on line 4 in the application.properties file to the following.
4 | spring.datasource.url=jdbc:postgresql://localhost:5442/turreta?currentSchema=myapp_schema |
When we rerun the Spring Boot application, we get the following output.
1 2 3 4 5 6 | ... 2020-08-14 20:04:44.568 INFO 11288 --- [ task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2020-08-14 20:04:44.826 INFO 11288 --- [ main] DeferredRepositoryInitializationListener : Spring Data repositories initialized! 2020-08-14 20:04:44.834 INFO 11288 --- [ main] c.t.p.d.d.DefaultSchema2DemoApplication : Started DefaultSchema2DemoApplication in 3.986 seconds (JVM running for 5.448) Hibernate: select personenti0_.person_id as person_i1_0_, personenti0_.person_name as person_n2_0_ from persons personenti0_ 1|Karl2|KL |
In summary, we need to use PostgreSQL URL parameter currentSchema to specify explicitly which PostgreSQL schema to use.