├── .gitignore ├── README.adoc ├── pom.xml ├── spring-boot-yugabytedb-autoconfigure └── pom.xml ├── spring-data-yugabytedb-parent └── pom.xml ├── spring-data-yugabytedb-ysql ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── yugabyte │ │ │ └── data │ │ │ ├── jdbc │ │ │ ├── core │ │ │ │ ├── QueryOptions.java │ │ │ │ ├── YsqlOperations.java │ │ │ │ ├── YsqlTemplate.java │ │ │ │ └── convert │ │ │ │ │ ├── DefaultYsqlDataAccessStrategy.java │ │ │ │ │ └── YsqlDataAccessStrategy.java │ │ │ ├── datasource │ │ │ │ └── YugabyteTransactionManager.java │ │ │ └── repository │ │ │ │ ├── YsqlRepository.java │ │ │ │ ├── config │ │ │ │ ├── AbstractYugabyteJdbcConfiguration.java │ │ │ │ ├── EnableYsqlRepositories.java │ │ │ │ ├── YsqlRepositoriesRegistrar.java │ │ │ │ ├── YsqlRepositoryConfigExtension.java │ │ │ │ └── YugabyteDialectResolver.java │ │ │ │ └── support │ │ │ │ ├── SimpleYsqlRepository.java │ │ │ │ ├── YsqlQueryLookupStrategy.java │ │ │ │ ├── YsqlRepositoryFactory.java │ │ │ │ └── YsqlRepositoryFactoryBean.java │ │ │ └── relational │ │ │ └── core │ │ │ └── dialect │ │ │ └── YugabyteDialect.java │ └── resources │ │ └── META-INF │ │ └── spring.factories │ └── test │ └── java │ └── com │ └── yugabyte │ └── data │ └── jdbc │ ├── AbstractYugabyteConfigurationIntegrationTests.java │ ├── EnableYugabyteRepositoriesIntegrationTests.java │ ├── YugabyteDataSourceConfiguration.java │ ├── repository │ └── config │ │ └── SimpleYsqlRepositoryUnitTests.java │ └── testing │ ├── YugabyteDBTestImage.java │ └── YugbayteDataSourceConfiguration.java └── yugabytedb-ysql-spring-boot-starter └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Spring Data YugabyteDB 2 | 3 | Spring Data YugabyteDB brings the power of Distributed SQL to Spring developers by using the familar Spring Data paradigms, Spring Data YugabyteDB supports Spring template and Spring repositories for accessing the data from YugabyteDB. Spring Data YuabyteDB is based on the [Spring Data JDBC](https://github.com/spring-projects/spring-data-jdbc) project which is extended to support YugabyteDB distributed SQL features. 4 | 5 | YugabyteDB is a high-performance, cloud-native distributed SQL database that aims to support all PostgreSQL features. It is best to fit for cloud-native OLTP (i.e. real-time, business-critical) applications that need absolute data correctness and require at least one of the following: scalability, high tolerance to failures, or globally-distributed deployments. 6 | 7 | == Features 8 | 9 | In addition to providing most PostgreSQL features supported by Spring Data JDBC, this project aims to enable the following: 10 | 11 | * CRUD operations for YugabyteDB 12 | * `@EnableYsqlRepositories` annotation to enable `YsqlRepository` 13 | * Yugabyte Distributed SQL transaction manager 14 | * Support for Follower Reads 15 | * Eliminate Load Balancer from SQL (cluster-awareness) 16 | * Develop Geo-Distributed Apps (topology-awareness) 17 | * Row Level Geo-partitioning support (partition-awareness) 18 | 19 | === Getting Started 20 | 21 | A quick start example of getting started with Spring Data YugabyteDB in JAVA: 22 | 23 | [source, java] 24 | ---- 25 | public interface ShoppingCartRepository extends YsqlRepository { 26 | 27 | ShoppingCart findById(String id); 28 | 29 | List findByUserId(String userId); 30 | 31 | } 32 | 33 | @Service 34 | public class CartService { 35 | 36 | private final ShoppingCartRepository repository; 37 | 38 | public CartService(CartService repository) { 39 | this.repository = repository; 40 | } 41 | 42 | public void doWork() { 43 | 44 | repository.deleteAll(); 45 | 46 | ShoppingCart myShoppingCart = new ShoppingCart(); 47 | myShoppingCart.set("cart1") 48 | myShoppingCart.setUserId("u1001"); 49 | myShoppingCart.setProductId("asin1001"); 50 | myShoppingCart.setQuantity(1); 51 | 52 | repository.save(myShoppingCart); 53 | 54 | ShoppingCart savedCart = repository.findById("cart1"); 55 | List productsInCart = repository.findByUserId("u1001"); 56 | } 57 | } 58 | 59 | @Configuration 60 | @EnableYsqlRepositories 61 | public class YsqlConfig extends AbstractYugabyteJdbcConfiguration { 62 | 63 | @Bean 64 | DataSource dataSource() { 65 | 66 | String hostName = "127.0.0.1"; 67 | String port = "5433"; 68 | 69 | Properties poolProperties = new Properties(); 70 | poolProperties.setProperty("dataSourceClassName", "com.yugabyte.ysql.YBClusterAwareDataSource"); 71 | poolProperties.setProperty("dataSource.serverName", hostName); 72 | poolProperties.setProperty("dataSource.portNumber", port); 73 | poolProperties.setProperty("dataSource.user", "yugabyte"); 74 | poolProperties.setProperty("dataSource.password", ""); 75 | poolProperties.setProperty("dataSource.loadBalance", "true"); 76 | poolProperties.setProperty("dataSource.additionalEndpoints", 77 | "127.0.0.2:5433,127.0.0.3:5433"); 78 | 79 | HikariConfig hikariConfig = new HikariConfig(poolProperties); 80 | DataSource ybClusterAwareDataSource = new HikariDataSource(hikariConfig); 81 | return ybClusterAwareDataSource; 82 | } 83 | 84 | @Bean 85 | JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) { 86 | 87 | JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 88 | return jdbcTemplate; 89 | } 90 | 91 | @Bean 92 | NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource) { 93 | return new NamedParameterJdbcTemplate(dataSource); 94 | } 95 | 96 | @Bean 97 | TransactionManager transactionManager(DataSource dataSource) { 98 | return new YugabyteTransactionManager(dataSource); 99 | } 100 | 101 | } 102 | ---- 103 | 104 | === Maven configuration 105 | 106 | Add the Maven dependency: 107 | 108 | [source,xml] 109 | ---- 110 | 111 | com.yugabyte 112 | spring-data-yugabytedb-ysql 113 | 2.3.0 114 | 115 | 116 | com.yugabyte 117 | jdbc-yugabytedb 118 | 42.2.7-yb-5.beta.5 119 | 120 | ---- 121 | 122 | See also the https://github.com/yugabyte/spring-data-yugabytedb-example[spring-data-yugabytedb-example] app. 123 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 4.0.0 11 | 12 | com.yugabyte 13 | spring-data-yugabytedb 14 | 2.3.1-SNAPSHOT 15 | pom 16 | 17 | Spring Data Yugabyte 18 | Spring Data support for YugabyteDB YSQL and YCQL. 19 | https://projects.spring.io/spring-data-yugabytedb 20 | 21 | 22 | 23 | Yugabyte, Inc. 24 | https://yugabyte.com 25 | Yugabyte Development Team 26 | info@yugabyte.com 27 | -5 28 | 29 | 30 | 31 | 2021 32 | 33 | 34 | spring-data-yugabytedb-parent 35 | spring-data-yugabytedb-ysql 36 | 38 | 39 | 40 | 41 | 42 | Apache 2 43 | http://www.apache.org/licenses/LICENSE-2.0.txt 44 | repo 45 | Apache License Version 2.0 46 | 47 | 48 | 49 | 50 | scm:git:git@github.com:yugabyte/spring-data-yugabytedb.git 51 | scm:git:git@github.com:yugabyte/spring-data-yugabytedb.git 52 | https://github.com/yugabyte/spring-data-yugabytedb 53 | 2.3.0 54 | 55 | 56 | 57 | 58 | ${yugabyte.releases.repository.id} 59 | ${yugabyte.releases.repository.url} 60 | 61 | 62 | ${yugabyte.snapshots.repository.id} 63 | ${yugabyte.snapshots.repository.url} 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /spring-boot-yugabytedb-autoconfigure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.yugabyte 4 | spring-boot-yugabytedb-autoconfigure 5 | 2.6.0-SNAPSHOT 6 | spring-boot-yugabytedb-autoconfigure 7 | Auto-reconfiguration support for YugabyteDB. 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-parent/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 4.0.0 11 | 12 | org.springframework.data.build 13 | spring-data-parent 14 | 2.5.3 15 | 16 | 17 | com.yugabyte 18 | spring-data-yugabytedb-parent 19 | 2.3.1-SNAPSHOT 20 | pom 21 | 22 | Spring Data Relational Parent 23 | Parent module for Spring Data Relational repositories. 24 | 25 | https://projects.spring.io/spring-data-jdbc 26 | 27 | 28 | 29 | 30 | nchandrappa 31 | Nikhil Chandrappa 32 | nikhil(at)yugabyte.com 33 | Yugabyte, Inc. 34 | https://yugabyte.com 35 | 36 | Project Lead 37 | 38 | -5 39 | 40 | 41 | 42 | 43 | 2.5.3 44 | 2.2.3 45 | 2.2.3 46 | 42.2.19 47 | 1.0.0-beta.1 48 | 3.8.0 49 | 4.0.3 50 | 42.2.7-yb-5-beta.5 51 | 52 | 53 | 2021 54 | 55 | 58 | 59 | 60 | 61 | org.springframework.data 62 | spring-data-commons 63 | ${springdata.commons} 64 | 65 | 66 | 67 | org.springframework 68 | spring-core 69 | 70 | 71 | 72 | org.springframework 73 | spring-tx 74 | 75 | 76 | 77 | org.springframework 78 | spring-context 79 | 80 | 81 | 82 | org.springframework 83 | spring-beans 84 | 85 | 86 | 87 | com.yugabyte 88 | testcontainer 89 | ${yugabyte.testcontainer.version} 90 | test 91 | 92 | 93 | 94 | org.junit.jupiter 95 | junit-jupiter-engine 96 | test 97 | 98 | 99 | 100 | org.junit.platform 101 | junit-platform-runner 102 | test 103 | 104 | 105 | 106 | org.mockito 107 | mockito-core 108 | ${mockito.version} 109 | test 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-dependency-plugin 119 | 120 | 121 | org.sonatype.plugins 122 | nexus-staging-maven-plugin 123 | 1.6.8 124 | 125 | 126 | maven-gpg-plugin 127 | 1.5 128 | 129 | 130 | maven-release-plugin 131 | 2.5.3 132 | 133 | 134 | maven-install-plugin 135 | 2.4 136 | 137 | 138 | maven-source-plugin 139 | 3.0.1 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-surefire-plugin 144 | 145 | methods 146 | 10 147 | false 148 | 149 | **/*IntegrationTests.java 150 | 151 | 152 | yugabyte 153 | 154 | 155 | 156 | 157 | org.apache.maven.plugins 158 | maven-checkstyle-plugin 159 | 3.1.2 160 | 161 | google_checks.xml 162 | UTF-8 163 | true 164 | true 165 | false 166 | 167 | 168 | 169 | validate 170 | validate 171 | 172 | check 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | release 183 | 184 | 185 | 186 | maven-source-plugin 187 | 188 | 189 | attach-sources 190 | 191 | jar 192 | 193 | 194 | 195 | 196 | 197 | maven-javadoc-plugin 198 | 199 | 200 | attach-javadocs 201 | 202 | jar 203 | 204 | 205 | 206 | 207 | 208 | maven-gpg-plugin 209 | 210 | 211 | sign-artifacts 212 | verify 213 | 214 | sign 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | scm:git:git@github.com:yugabyte/spring-data-yugabytedb.git 226 | scm:git:git@github.com:yugabyte/spring-data-yugabytedb.git 227 | https://github.com/yugabyte/spring-data-yugabytedb 228 | 2.3.0 229 | 230 | 231 | 232 | 233 | ${yugabyte.releases.repository.id} 234 | ${yugabyte.releases.repository.url} 235 | 236 | 237 | ${yugabyte.snapshots.repository.id} 238 | ${yugabyte.snapshots.repository.url} 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 4.0.0 11 | 12 | spring-data-yugabytedb-ysql 13 | jar 14 | 15 | Spring Data YugabyteDB YSQL 16 | Spring Data support for YugabyteDB YSQL. 17 | 18 | 19 | com.yugabyte 20 | spring-data-yugabytedb-parent 21 | 2.3.1-SNAPSHOT 22 | ../spring-data-yugabytedb-parent/pom.xml 23 | 24 | 25 | 26 | ${basedir}/.. 27 | spring.data.yugabytedb.ysql 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.data 34 | spring-data-relational 35 | ${springdata.relational} 36 | 37 | 38 | 39 | org.springframework.data 40 | spring-data-jdbc 41 | ${springdata.jdbc} 42 | 43 | 44 | 45 | org.springframework 46 | spring-jdbc 47 | 48 | 49 | 50 | org.postgresql 51 | postgresql 52 | ${postgresql.version} 53 | test 54 | 55 | 56 | 57 | com.yugabyte 58 | jdbc-yugabytedb 59 | ${yugabyte.driver.version} 60 | 61 | 62 | 63 | 64 | 65 | scm:git:git@github.com:yugabyte/spring-data-yugabytedb.git 66 | scm:git:git@github.com:yugabyte/spring-data-yugabytedb.git 67 | https://github.com/yugabyte/spring-data-yugabytedb 68 | 2.3.0 69 | 70 | 71 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/core/QueryOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.core; 14 | 15 | import org.springframework.lang.Nullable; 16 | 17 | /** 18 | * Specifies QueryOptions specified at Statement level for each query performed on YugabyteDB. 19 | * 20 | * @author Nikhil Chandrappa 21 | */ 22 | public class QueryOptions { 23 | 24 | private static final QueryOptions EMPTY = QueryOptions.builder().build(); 25 | 26 | private final @Nullable Boolean deferrable; 27 | private final @Nullable Boolean multiRowInsert; 28 | private final @Nullable Boolean followerRead; 29 | 30 | protected QueryOptions (@Nullable Boolean isolationLevel, 31 | @Nullable Boolean multiRowInsert, @Nullable Boolean follwerRead) { 32 | this.deferrable = isolationLevel; 33 | this.multiRowInsert = multiRowInsert; 34 | this.followerRead = follwerRead; 35 | } 36 | 37 | public static QueryOptionsBuilder builder() { 38 | return new QueryOptionsBuilder(); 39 | } 40 | 41 | public static QueryOptions empty() { 42 | return EMPTY; 43 | } 44 | 45 | public Boolean isDeferrable() { 46 | return this.deferrable; 47 | } 48 | 49 | public Boolean getMutliRowInsert() { 50 | return this.multiRowInsert; 51 | } 52 | 53 | public Boolean getFollowerRead() { 54 | return this.followerRead; 55 | } 56 | 57 | public static class QueryOptionsBuilder { 58 | 59 | protected @Nullable Boolean deferrable; 60 | protected @Nullable Boolean multiRowInsert; 61 | protected @Nullable Boolean followerRead; 62 | 63 | QueryOptionsBuilder() { 64 | } 65 | 66 | QueryOptionsBuilder(QueryOptions queryOptions) { 67 | this.deferrable = queryOptions.deferrable; 68 | this.multiRowInsert = queryOptions.multiRowInsert; 69 | this.followerRead = queryOptions.followerRead; 70 | } 71 | 72 | public QueryOptionsBuilder deferrable(Boolean deferrable) { 73 | 74 | this.deferrable = deferrable; 75 | 76 | return this; 77 | } 78 | 79 | public QueryOptionsBuilder multiRowInsert(Boolean multiRowInsert) { 80 | 81 | this.multiRowInsert = multiRowInsert; 82 | 83 | return this; 84 | } 85 | 86 | public QueryOptionsBuilder followerRead(Boolean followerRead) { 87 | 88 | this.followerRead = followerRead; 89 | 90 | return this; 91 | } 92 | 93 | public QueryOptions build() { 94 | return new QueryOptions(this.deferrable, this.multiRowInsert, this.followerRead); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/core/YsqlOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.core; 14 | 15 | import java.sql.SQLException; 16 | 17 | import org.springframework.data.jdbc.core.JdbcAggregateOperations; 18 | 19 | /** 20 | * Specifies Database operations one can perform on YugabyteDB Distributed SQL, 21 | * based on an Domain Type. 22 | * 23 | * @author Nikhil Chandrappa 24 | */ 25 | public interface YsqlOperations extends JdbcAggregateOperations { 26 | 27 | /** 28 | * Counts the number of objects of a given type. 29 | * 30 | * Method uses below transaction isolation level for count(*) queries, 31 | * Transaction BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE 32 | * 33 | * @param domainType the type of the aggregates to be counted. 34 | * @return the number of instances stored in the YugabyteDB cluster. Guaranteed to be not {@code null}. 35 | */ 36 | long count(Class domainType, QueryOptions queryOptions) throws SQLException; 37 | 38 | // /** 39 | // * Load an aggregate from the database. 40 | // * 41 | // * @param id the id of the domain to load. Must not be {@code null}. 42 | // * @param domainType the type of the aggregate root. Must not be {@code null}. 43 | // * @param the type of the aggregate root. 44 | // * @param determines whether data can be read from follower copies. 45 | // * @return the loaded aggregate. Might return {@code null}. 46 | // */ 47 | // @Nullable 48 | // T findById(Object id, Class domainType, Boolean readFromFollower); 49 | // 50 | // /** 51 | // * Load all aggregates of a given type that are identified by the given ids. 52 | // * 53 | // * @param ids of the aggregate roots identifying the aggregates to load. Must not be {@code null}. 54 | // * @param domainType the type of the aggregate roots. Must not be {@code null}. 55 | // * @param the type of the aggregate roots. Must not be {@code null}. 56 | // * @param determines whether data can be read from follower copies. 57 | // * @return Guaranteed to be not {@code null}. 58 | // */ 59 | // Iterable findAllById(Iterable ids, Class domainType, Boolean readFromFollower); 60 | // 61 | // /** 62 | // * Load all aggregates of a given type. 63 | // * 64 | // * @param domainType the type of the aggregate roots. Must not be {@code null}. 65 | // * @param the type of the aggregate roots. Must not be {@code null}. 66 | // * @param determines whether data can be read from follower copies. 67 | // * @return Guaranteed to be not {@code null}. 68 | // */ 69 | // Iterable findAll(Class domainType, Boolean readFromFollower); 70 | // 71 | // /** 72 | // * Load all aggregates of a given type, sorted. 73 | // * 74 | // * @param domainType the type of the aggregate roots. Must not be {@code null}. 75 | // * @param the type of the aggregate roots. Must not be {@code null}. 76 | // * @param sort the sorting information. Must not be {@code null}. 77 | // * @param determines whether data can be read from follower copies. 78 | // * @return Guaranteed to be not {@code null}. 79 | // * @since 2.0 80 | // */ 81 | // Iterable findAll(Class domainType, Sort sort, Boolean readFromFollower); 82 | // 83 | // /** 84 | // * Load a page of (potentially sorted) aggregates of a given type. 85 | // * 86 | // * @param domainType the type of the aggregate roots. Must not be {@code null}. 87 | // * @param the type of the aggregate roots. Must not be {@code null}. 88 | // * @param pageable the pagination information. Must not be {@code null}. 89 | // * @param determines whether data can be read from follower copies. 90 | // * @return Guaranteed to be not {@code null}. 91 | // * @since 2.0 92 | // */ 93 | // Page findAll(Class domainType, Pageable pageable, Boolean readFromFollower); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/core/YsqlTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.core; 14 | 15 | import java.sql.SQLException; 16 | 17 | import org.springframework.context.ApplicationContext; 18 | import org.springframework.context.ApplicationEventPublisher; 19 | import org.springframework.data.jdbc.core.JdbcAggregateTemplate; 20 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 21 | import org.springframework.data.relational.core.mapping.RelationalMappingContext; 22 | import org.springframework.util.Assert; 23 | 24 | import com.yugabyte.data.jdbc.core.convert.YsqlDataAccessStrategy; 25 | 26 | /** 27 | * {@link YugabyteDbYsqlOperations} implementation, storing entity objects in and obtaining them 28 | * from a YugabyteDB cluster. 29 | * 30 | * @author Nikhil Chandrappa 31 | */ 32 | public class YsqlTemplate extends JdbcAggregateTemplate implements YsqlOperations { 33 | 34 | private YsqlDataAccessStrategy ysqlDataAccessStrategy; 35 | 36 | public YsqlTemplate(ApplicationContext publisher, RelationalMappingContext context, 37 | JdbcConverter converter, YsqlDataAccessStrategy dataAccessStrategy) { 38 | super(publisher, context, converter, dataAccessStrategy); 39 | this.ysqlDataAccessStrategy = dataAccessStrategy; 40 | } 41 | 42 | public YsqlTemplate(ApplicationEventPublisher publisher, RelationalMappingContext context, JdbcConverter converter, 43 | YsqlDataAccessStrategy dataAccessStrategy) { 44 | super(publisher, context, converter, dataAccessStrategy); 45 | this.ysqlDataAccessStrategy = dataAccessStrategy; 46 | } 47 | 48 | public long count(Class domainType, QueryOptions queryOptions) throws SQLException { 49 | 50 | Assert.notNull(domainType, "Domain type must not be null"); 51 | 52 | return ysqlDataAccessStrategy.count(domainType, queryOptions); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/core/convert/DefaultYsqlDataAccessStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.core.convert; 14 | 15 | import java.sql.Connection; 16 | import java.sql.ResultSet; 17 | import java.sql.SQLException; 18 | import java.sql.Statement; 19 | 20 | import javax.sql.DataSource; 21 | 22 | import org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy; 23 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 24 | import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; 25 | import org.springframework.data.relational.core.mapping.RelationalMappingContext; 26 | import org.springframework.data.relational.core.sql.IdentifierProcessing; 27 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 28 | import org.springframework.jdbc.datasource.DataSourceUtils; 29 | import org.springframework.transaction.support.TransactionSynchronizationManager; 30 | import org.springframework.util.Assert; 31 | 32 | import com.yugabyte.data.jdbc.core.QueryOptions; 33 | 34 | /** 35 | * The YugabyteDB 36 | * {@link org.springframework.data.jdbc.core.convert.DataAccessStrategy} is to 37 | * generate YSQL statements based on the metadata of the entity. 38 | * 39 | * @author Nikhil Chandrappa 40 | */ 41 | public class DefaultYsqlDataAccessStrategy extends DefaultDataAccessStrategy implements YsqlDataAccessStrategy { 42 | 43 | private static final String DEFERRABLE_TRANSACTION = "BEGIN ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE"; 44 | private final DataSource dataSource; 45 | private final RelationalMappingContext context; 46 | 47 | public DefaultYsqlDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, RelationalMappingContext context, 48 | JdbcConverter converter, NamedParameterJdbcOperations operations, DataSource dataSource) { 49 | super(sqlGeneratorSource, context, converter, operations); 50 | this.context = context; 51 | this.dataSource = dataSource; 52 | } 53 | 54 | @Override 55 | public long count(Class domainType, QueryOptions queryOptions) throws SQLException { 56 | 57 | String tableName = context.getRequiredPersistentEntity(domainType).getTableName() 58 | .toSql(IdentifierProcessing.ANSI); 59 | String sqlString = String.format("SELECT COUNT(*) FROM %s", tableName); 60 | 61 | // Determine transaction in flight before executing the DEFERRABLE transactions. 62 | Connection readConnection = DataSourceUtils.getConnection(dataSource); 63 | Boolean originalAutoCommit = readConnection.getAutoCommit(); 64 | if (queryOptions.isDeferrable() != null && queryOptions.isDeferrable()) { 65 | if (!TransactionSynchronizationManager.isActualTransactionActive()) { 66 | readConnection.setAutoCommit(false); 67 | Statement deferrableStatement = readConnection.createStatement(); 68 | deferrableStatement.executeUpdate(DEFERRABLE_TRANSACTION); 69 | } 70 | } 71 | 72 | Statement countStatement = readConnection.createStatement(); 73 | Long result = null; 74 | 75 | try { 76 | ResultSet rs = countStatement.executeQuery(sqlString); 77 | while (rs.next()) { 78 | result = rs.getLong(1); 79 | } 80 | } finally { 81 | readConnection.setAutoCommit(originalAutoCommit); 82 | readConnection.close(); 83 | } 84 | 85 | Assert.notNull(result, "The result of a count query must not be null."); 86 | return result; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/core/convert/YsqlDataAccessStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.core.convert; 14 | 15 | import java.sql.SQLException; 16 | 17 | import org.springframework.data.jdbc.core.convert.DataAccessStrategy; 18 | 19 | import com.yugabyte.data.jdbc.core.QueryOptions; 20 | 21 | public interface YsqlDataAccessStrategy extends DataAccessStrategy { 22 | 23 | 24 | /** 25 | * Counts the number of objects of a given type. 26 | * 27 | * Method uses below transaction isolation level for count(*) queries, 28 | * Transaction BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE 29 | * 30 | * @param domainType the type of the aggregates to be counted. 31 | * @return the number of instances stored in the YugabyteDB cluster. Guaranteed to be not {@code null}. 32 | */ 33 | long count(Class domainType, QueryOptions queryOptions) throws SQLException; 34 | 35 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/datasource/YugabyteTransactionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.datasource; 14 | 15 | import javax.sql.DataSource; 16 | 17 | import org.springframework.jdbc.support.JdbcTransactionManager; 18 | import org.springframework.lang.Nullable; 19 | 20 | @SuppressWarnings("serial") 21 | public class YugabyteTransactionManager extends JdbcTransactionManager { 22 | 23 | @Nullable 24 | private DataSource dataSource; 25 | 26 | public YugabyteTransactionManager() { 27 | super(); 28 | } 29 | 30 | public YugabyteTransactionManager(DataSource dataSource) { 31 | this(); 32 | setDataSource(dataSource); 33 | afterPropertiesSet(); 34 | } 35 | 36 | @Override 37 | protected boolean useSavepointForNestedTransaction() { 38 | return false; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/YsqlRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository; 14 | 15 | import org.springframework.data.repository.CrudRepository; 16 | import org.springframework.data.repository.NoRepositoryBean; 17 | import org.springframework.data.repository.PagingAndSortingRepository; 18 | 19 | /** 20 | * Extending {@link CrudRepository} to support additional methods specific to 21 | * YugabyteDB Ysql operations. 22 | * 23 | * @author Nikhil Chandrappa 24 | */ 25 | @NoRepositoryBean 26 | public interface YsqlRepository extends PagingAndSortingRepository { 27 | 28 | long count(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/config/AbstractYugabyteJdbcConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.config; 14 | 15 | import java.util.Optional; 16 | 17 | import javax.sql.DataSource; 18 | 19 | import org.springframework.context.ApplicationContext; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.context.annotation.Lazy; 23 | import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; 24 | import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; 25 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 26 | import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; 27 | import org.springframework.data.jdbc.core.convert.RelationResolver; 28 | import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; 29 | import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; 30 | import org.springframework.data.jdbc.repository.config.DialectResolver; 31 | import org.springframework.data.relational.core.dialect.Dialect; 32 | import org.springframework.data.relational.core.mapping.NamingStrategy; 33 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 34 | 35 | import com.yugabyte.data.jdbc.core.YsqlTemplate; 36 | import com.yugabyte.data.jdbc.core.convert.DefaultYsqlDataAccessStrategy; 37 | import com.yugabyte.data.jdbc.core.convert.YsqlDataAccessStrategy; 38 | 39 | /* 40 | * Beans registration for Spring Data YugabyteDB YSQL template and repository support. 41 | * 42 | * @author Nikhil Chandrappa 43 | * @since 2.3.0 44 | */ 45 | @Configuration(proxyBeanMethods = false) 46 | public class AbstractYugabyteJdbcConfiguration { 47 | 48 | @Bean 49 | public YsqlTemplate ysqlTemplate(ApplicationContext applicationContext, JdbcMappingContext mappingContext, 50 | JdbcConverter converter, YsqlDataAccessStrategy dataAccessStrategy) { 51 | return new YsqlTemplate(applicationContext, mappingContext, converter, dataAccessStrategy); 52 | } 53 | 54 | @Bean 55 | public YsqlDataAccessStrategy ysqlDataAccessStrategyBean(NamedParameterJdbcOperations operations, 56 | JdbcConverter jdbcConverter, JdbcMappingContext context, Dialect dialect, DataSource dataSource) { 57 | return new DefaultYsqlDataAccessStrategy(new SqlGeneratorSource(context, jdbcConverter, dialect), context, 58 | jdbcConverter, operations, dataSource); 59 | } 60 | 61 | @Bean 62 | public JdbcMappingContext jdbcMappingContext(Optional namingStrategy, 63 | JdbcCustomConversions customConversions) { 64 | 65 | JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE)); 66 | mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); 67 | 68 | return mappingContext; 69 | } 70 | 71 | @Bean 72 | public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations, 73 | @Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) { 74 | 75 | DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations()); 76 | 77 | return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory, 78 | dialect.getIdentifierProcessing()); 79 | } 80 | 81 | @Bean 82 | public JdbcCustomConversions jdbcCustomConversions() { 83 | return new JdbcCustomConversions(); 84 | } 85 | 86 | @Bean 87 | public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { 88 | return DialectResolver.getDialect(operations.getJdbcOperations()); 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/config/EnableYsqlRepositories.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.config; 14 | 15 | import java.lang.annotation.Documented; 16 | import java.lang.annotation.ElementType; 17 | import java.lang.annotation.Inherited; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | 22 | import org.springframework.context.annotation.ComponentScan.Filter; 23 | import org.springframework.context.annotation.Import; 24 | import org.springframework.data.repository.config.DefaultRepositoryBaseClass; 25 | 26 | import com.yugabyte.data.jdbc.repository.support.YsqlRepositoryFactoryBean; 27 | 28 | /** 29 | * Annotation to enable Spring Data YSQL repositories. 30 | * By Default, Will scan the package of the annotated configuration class for Spring Data repositories. 31 | * 32 | * @author Nikhil Chandrappa 33 | */ 34 | @Target(ElementType.TYPE) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Documented 37 | @Inherited 38 | @Import(YsqlRepositoriesRegistrar.class) 39 | public @interface EnableYsqlRepositories { 40 | 41 | /** 42 | * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: 43 | * {@code @EnableJdbcRepositories("org.my.pkg")} instead of 44 | * {@code @EnableJdbcRepositories(basePackages="org.my.pkg")}. 45 | */ 46 | String[] value() default {}; 47 | 48 | /** 49 | * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this 50 | * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. 51 | */ 52 | String[] basePackages() default {}; 53 | 54 | /** 55 | * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The 56 | * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in 57 | * each package that serves no purpose other than being referenced by this attribute. 58 | */ 59 | Class[] basePackageClasses() default {}; 60 | 61 | /** 62 | * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from 63 | * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. 64 | */ 65 | Filter[] includeFilters() default {}; 66 | 67 | /** 68 | * Specifies which types are not eligible for component scanning. 69 | */ 70 | Filter[] excludeFilters() default {}; 71 | 72 | /** 73 | * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So 74 | * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning 75 | * for {@code PersonRepositoryImpl}. 76 | */ 77 | String repositoryImplementationPostfix() default "Impl"; 78 | 79 | /** 80 | * Configures the location of where to find the Spring Data named queries properties file. Will default to 81 | * {@code META-INF/jdbc-named-queries.properties}. 82 | */ 83 | String namedQueriesLocation() default ""; 84 | 85 | /** 86 | * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to 87 | * {@link JdbcRepositoryFactoryBean}. 88 | */ 89 | Class repositoryFactoryBeanClass() default YsqlRepositoryFactoryBean.class; 90 | 91 | /** 92 | * Configure the repository base class to be used to create repository proxies for this particular configuration. 93 | * 94 | * @since 2.1 95 | */ 96 | Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; 97 | 98 | /** 99 | * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the 100 | * repositories infrastructure. 101 | */ 102 | boolean considerNestedRepositories() default false; 103 | 104 | /** 105 | * Configures the name of the {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations} bean 106 | * definition to be used to create repositories discovered through this annotation. Defaults to 107 | * {@code YugabyteDbYsqlJdbcTemplate}. 108 | */ 109 | String jdbcOperationsRef() default ""; 110 | 111 | /** 112 | * Configures the name of the {@link org.springframework.data.jdbc.core.convert.DataAccessStrategy} bean definition to 113 | * be used to create repositories discovered through this annotation. Defaults to {@code YugabyteDbdefaultDataAccessStrategy}. 114 | */ 115 | String dataAccessStrategyRef() default ""; 116 | 117 | /** 118 | * Configures the name of the {@link DataSourceTransactionManager} bean definition to be used to create repositories 119 | * discovered through this annotation. Defaults to {@code transactionManager}. 120 | * @since 2.1 121 | */ 122 | String transactionManagerRef() default "transactionManager"; 123 | 124 | } 125 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/config/YsqlRepositoriesRegistrar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.config; 14 | 15 | import java.lang.annotation.Annotation; 16 | 17 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 18 | import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; 19 | import org.springframework.data.repository.config.RepositoryConfigurationExtension; 20 | 21 | /** 22 | * {@link ImportBeanDefinitionRegistrar} to enable 23 | * {@link EnableYsqlRepositories} annotation. 24 | * 25 | * @author Nikhil Chandrappa 26 | */ 27 | public class YsqlRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { 28 | 29 | @Override 30 | protected Class getAnnotation() { 31 | return EnableYsqlRepositories.class; 32 | } 33 | 34 | @Override 35 | protected RepositoryConfigurationExtension getExtension() { 36 | return new YsqlRepositoryConfigExtension(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/config/YsqlRepositoryConfigExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.config; 14 | 15 | import java.lang.annotation.Annotation; 16 | import java.util.Collection; 17 | import java.util.Collections; 18 | import java.util.Locale; 19 | import java.util.Optional; 20 | 21 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 22 | import org.springframework.data.relational.core.mapping.Table; 23 | import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; 24 | import org.springframework.data.repository.config.RepositoryConfigurationSource; 25 | import org.springframework.util.StringUtils; 26 | 27 | import com.yugabyte.data.jdbc.repository.support.YsqlRepositoryFactoryBean; 28 | 29 | /** 30 | * {@link org.springframework.data.repository.config.RepositoryConfigurationExtension} extending the repository 31 | * registration process by registering YugabyteDB YSQL repositories. 32 | * 33 | * @author Nikhil Chandrappa 34 | */ 35 | public class YsqlRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport { 36 | 37 | private static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; 38 | 39 | 40 | @Override 41 | public String getModuleName() { 42 | return "YUGABYTEDB_YSQL"; 43 | } 44 | 45 | @Override 46 | public String getRepositoryFactoryBeanClassName() { 47 | 48 | return YsqlRepositoryFactoryBean.class.getName(); 49 | } 50 | 51 | @Override 52 | protected String getModulePrefix() { 53 | 54 | return getModuleName().toLowerCase(Locale.US); 55 | } 56 | 57 | @Override 58 | public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) { 59 | 60 | source.getAttribute("jdbcOperationsRef") // 61 | .filter(StringUtils::hasText) // 62 | .ifPresent(s -> builder.addPropertyReference("jdbcOperations", s)); 63 | 64 | source.getAttribute("dataAccessStrategyRef") // 65 | .filter(StringUtils::hasText) // 66 | .ifPresent(s -> builder.addPropertyReference("dataAccessStrategy", s)); 67 | 68 | Optional transactionManagerRef = source.getAttribute("transactionManagerRef"); 69 | builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)); 70 | } 71 | 72 | @Override 73 | protected Collection> getIdentifyingAnnotations() { 74 | return Collections.singleton(Table.class); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/config/YugabyteDialectResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.config; 14 | 15 | import java.sql.Connection; 16 | import java.sql.ResultSet; 17 | import java.sql.SQLException; 18 | import java.sql.Statement; 19 | import java.util.Optional; 20 | import java.util.regex.Pattern; 21 | 22 | import org.apache.commons.logging.Log; 23 | import org.apache.commons.logging.LogFactory; 24 | import org.postgresql.util.PSQLException; 25 | import org.springframework.data.jdbc.repository.config.DialectResolver.DefaultDialectProvider; 26 | import org.springframework.data.relational.core.dialect.Dialect; 27 | import org.springframework.jdbc.core.ConnectionCallback; 28 | import org.springframework.jdbc.core.JdbcOperations; 29 | import org.springframework.lang.Nullable; 30 | import org.springframework.util.StringUtils; 31 | 32 | import com.yugabyte.data.relational.core.dialect.YugabyteDialect; 33 | 34 | /** 35 | * An SQL dialect for YugabyteDB. 36 | * 37 | * @author Nikhil Chandrappa 38 | * @since 2.3 39 | */ 40 | public class YugabyteDialectResolver { 41 | 42 | static public class YugabyteDialectProvider extends DefaultDialectProvider { 43 | 44 | private static final Log LOG = LogFactory.getLog(YugabyteDialectProvider.class); 45 | private static final String YUGABYTE_SERVERS_QUERY = "select * from yb_servers()"; 46 | private static final String YB_SERVERS_FUNCTION_DOESNT_EXIST = "function yb_servers() does not exist"; 47 | 48 | @Override 49 | public Optional getDialect(JdbcOperations operations) { 50 | 51 | Optional yugabyteDialect = Optional.ofNullable( 52 | operations.execute((ConnectionCallback) YugabyteDialectProvider::getDialect)); 53 | 54 | if (yugabyteDialect.isPresent()) { 55 | return yugabyteDialect; 56 | } 57 | 58 | return super.getDialect(operations); 59 | } 60 | 61 | @Nullable 62 | private static Dialect getDialect(Connection connection) throws SQLException { 63 | 64 | Statement ybStatement = connection.createStatement(); 65 | Dialect dialect = null; 66 | 67 | LOG.debug("Executing query against YB_SERVERS() system function."); 68 | try { 69 | 70 | ResultSet rs = ybStatement.executeQuery(YUGABYTE_SERVERS_QUERY); 71 | if (!rs.next()) { 72 | LOG.debug( 73 | "Query for YB_SERVERS() system function returned null. Falling back on DefaultDialectProvider."); 74 | return null; 75 | } 76 | 77 | // Determine client application is connecting to a YugabyteDB cluster. 78 | String hostName = rs.getString("host"); 79 | if (StringUtils.hasText(hostName)) { 80 | dialect = YugabyteDialect.INSTANCE; 81 | } 82 | } catch (PSQLException psqlException) { 83 | 84 | Boolean functionNotExist = Pattern 85 | .compile(Pattern.quote(YB_SERVERS_FUNCTION_DOESNT_EXIST), Pattern.CASE_INSENSITIVE) 86 | .matcher(psqlException.getMessage()).find(); 87 | 88 | if (functionNotExist) { 89 | LOG.debug("YB_SERVERS() system function doesn't exist. Falling back on DefaultDialectProvider."); 90 | return null; 91 | } 92 | } 93 | 94 | LOG.debug("Using YugabyteDB YSQL Dialect."); 95 | return dialect; 96 | } 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/support/SimpleYsqlRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.support; 14 | 15 | import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; 16 | import org.springframework.data.mapping.PersistentEntity; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import com.yugabyte.data.jdbc.core.YsqlOperations; 20 | 21 | /** 22 | * The YugabyteDB repository implementation to support YSQL distributed SQL operations. 23 | * 24 | * @author Nikhil Chandrappa 25 | */ 26 | @Transactional(readOnly = true) 27 | public class SimpleYsqlRepository extends SimpleJdbcRepository { 28 | 29 | 30 | private final YsqlOperations entityOperations; 31 | private final PersistentEntity entity; 32 | 33 | public SimpleYsqlRepository(YsqlOperations entityOperations, PersistentEntity entity) { 34 | super(entityOperations, entity); 35 | this.entityOperations = entityOperations; 36 | this.entity = entity; 37 | } 38 | 39 | @Override 40 | public long count() { 41 | return entityOperations.count(entity.getType()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/support/YsqlQueryLookupStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.support; 14 | 15 | import java.lang.reflect.Method; 16 | import java.sql.ResultSet; 17 | import java.sql.SQLException; 18 | 19 | import org.springframework.beans.factory.BeanFactory; 20 | import org.springframework.context.ApplicationEventPublisher; 21 | import org.springframework.data.jdbc.core.convert.EntityRowMapper; 22 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 23 | import org.springframework.data.jdbc.repository.QueryMappingConfiguration; 24 | import org.springframework.data.jdbc.repository.query.JdbcQueryMethod; 25 | import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery; 26 | import org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery; 27 | import org.springframework.data.mapping.callback.EntityCallbacks; 28 | import org.springframework.data.projection.ProjectionFactory; 29 | import org.springframework.data.relational.core.dialect.Dialect; 30 | import org.springframework.data.relational.core.mapping.RelationalMappingContext; 31 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; 32 | import org.springframework.data.relational.core.mapping.event.AfterLoadCallback; 33 | import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; 34 | import org.springframework.data.repository.core.NamedQueries; 35 | import org.springframework.data.repository.core.RepositoryMetadata; 36 | import org.springframework.data.repository.query.QueryCreationException; 37 | import org.springframework.data.repository.query.QueryLookupStrategy; 38 | import org.springframework.data.repository.query.RepositoryQuery; 39 | import org.springframework.jdbc.core.RowMapper; 40 | import org.springframework.jdbc.core.SingleColumnRowMapper; 41 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 42 | import org.springframework.lang.Nullable; 43 | import org.springframework.util.Assert; 44 | 45 | /** 46 | * {@link QueryLookupStrategy} for YugabyteDB repositories. 47 | * 48 | * @author Nikhil Chandrappa 49 | */ 50 | public class YsqlQueryLookupStrategy implements QueryLookupStrategy { 51 | 52 | private final ApplicationEventPublisher publisher; 53 | private final @Nullable EntityCallbacks callbacks; 54 | private final RelationalMappingContext context; 55 | private final JdbcConverter converter; 56 | private final Dialect dialect; 57 | private final QueryMappingConfiguration queryMappingConfiguration; 58 | private final NamedParameterJdbcOperations operations; 59 | private final BeanFactory beanfactory; 60 | 61 | YsqlQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, 62 | RelationalMappingContext context, JdbcConverter converter, Dialect dialect, 63 | QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, 64 | BeanFactory beanfactory) { 65 | 66 | Assert.notNull(publisher, "ApplicationEventPublisher must not be null"); 67 | Assert.notNull(context, "RelationalMappingContextPublisher must not be null"); 68 | Assert.notNull(converter, "JdbcConverter must not be null"); 69 | Assert.notNull(dialect, "Dialect must not be null"); 70 | Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null"); 71 | Assert.notNull(operations, "NamedParameterJdbcOperations must not be null"); 72 | 73 | this.publisher = publisher; 74 | this.callbacks = callbacks; 75 | this.context = context; 76 | this.converter = converter; 77 | this.dialect = dialect; 78 | this.queryMappingConfiguration = queryMappingConfiguration; 79 | this.operations = operations; 80 | this.beanfactory = beanfactory; 81 | } 82 | 83 | @Override 84 | public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata, 85 | ProjectionFactory projectionFactory, NamedQueries namedQueries) { 86 | 87 | JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, 88 | context); 89 | 90 | try { 91 | if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) { 92 | 93 | RowMapper mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod); 94 | StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, operations, mapper, converter); 95 | query.setBeanFactory(beanfactory); 96 | return query; 97 | } else { 98 | return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, 99 | createMapper(queryMethod)); 100 | } 101 | } catch (Exception e) { 102 | throw QueryCreationException.create(queryMethod, e); 103 | } 104 | } 105 | 106 | @SuppressWarnings("unchecked") 107 | private RowMapper createMapper(JdbcQueryMethod queryMethod) { 108 | 109 | Class returnedObjectType = queryMethod.getReturnedObjectType(); 110 | 111 | RelationalPersistentEntity persistentEntity = context.getPersistentEntity(returnedObjectType); 112 | 113 | if (persistentEntity == null) { 114 | return (RowMapper) SingleColumnRowMapper.newInstance(returnedObjectType, 115 | converter.getConversionService()); 116 | } 117 | 118 | return (RowMapper) determineDefaultMapper(queryMethod); 119 | } 120 | 121 | private RowMapper determineDefaultMapper(JdbcQueryMethod queryMethod) { 122 | 123 | Class domainType = queryMethod.getReturnedObjectType(); 124 | RowMapper configuredQueryMapper = queryMappingConfiguration.getRowMapper(domainType); 125 | 126 | if (configuredQueryMapper != null) 127 | return configuredQueryMapper; 128 | 129 | EntityRowMapper defaultEntityRowMapper = new EntityRowMapper<>( // 130 | context.getRequiredPersistentEntity(domainType), // 131 | converter // 132 | ); 133 | 134 | return new PostProcessingRowMapper<>(defaultEntityRowMapper); 135 | } 136 | 137 | class PostProcessingRowMapper implements RowMapper { 138 | 139 | private final RowMapper delegate; 140 | 141 | PostProcessingRowMapper(RowMapper delegate) { 142 | this.delegate = delegate; 143 | } 144 | 145 | @Override 146 | public T mapRow(ResultSet rs, int rowNum) throws SQLException { 147 | 148 | T entity = delegate.mapRow(rs, rowNum); 149 | 150 | if (entity != null) { 151 | 152 | publisher.publishEvent(new AfterLoadEvent<>(entity)); 153 | 154 | if (callbacks != null) { 155 | return callbacks.callback(AfterLoadCallback.class, entity); 156 | } 157 | } 158 | 159 | return entity; 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/support/YsqlRepositoryFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.support; 14 | 15 | import java.util.Optional; 16 | 17 | import org.springframework.beans.factory.BeanFactory; 18 | import org.springframework.context.ApplicationEventPublisher; 19 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 20 | import org.springframework.data.jdbc.repository.QueryMappingConfiguration; 21 | import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; 22 | import org.springframework.data.mapping.callback.EntityCallbacks; 23 | import org.springframework.data.relational.core.dialect.Dialect; 24 | import org.springframework.data.relational.core.mapping.RelationalMappingContext; 25 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; 26 | import org.springframework.data.repository.core.EntityInformation; 27 | import org.springframework.data.repository.core.RepositoryInformation; 28 | import org.springframework.data.repository.core.RepositoryMetadata; 29 | import org.springframework.data.repository.core.support.PersistentEntityInformation; 30 | import org.springframework.data.repository.core.support.RepositoryFactorySupport; 31 | import org.springframework.data.repository.query.QueryLookupStrategy; 32 | import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; 33 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 34 | import org.springframework.lang.Nullable; 35 | import org.springframework.util.Assert; 36 | 37 | import com.yugabyte.data.jdbc.core.YsqlTemplate; 38 | import com.yugabyte.data.jdbc.core.convert.YsqlDataAccessStrategy; 39 | 40 | /** 41 | * Creates repository implementation based on YugabyteDB YSQL. 42 | * 43 | * @author Nikhil Chandrappa 44 | */ 45 | @SuppressWarnings("unused") 46 | public class YsqlRepositoryFactory extends JdbcRepositoryFactory { 47 | 48 | private final RelationalMappingContext context; 49 | private final JdbcConverter converter; 50 | private final ApplicationEventPublisher publisher; 51 | private final YsqlDataAccessStrategy ysqlDataAccessStrategy; 52 | private final NamedParameterJdbcOperations operations; 53 | private final Dialect dialect; 54 | @Nullable private BeanFactory beanFactory; 55 | 56 | private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; 57 | private EntityCallbacks entityCallbacks; 58 | 59 | public YsqlRepositoryFactory(YsqlDataAccessStrategy dataAccessStrategy, RelationalMappingContext context, 60 | JdbcConverter converter, Dialect dialect, ApplicationEventPublisher publisher, 61 | NamedParameterJdbcOperations operations) { 62 | 63 | super(dataAccessStrategy, context, converter, dialect, publisher, operations); 64 | 65 | Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null!"); 66 | Assert.notNull(context, "RelationalMappingContext must not be null!"); 67 | Assert.notNull(converter, "RelationalConverter must not be null!"); 68 | Assert.notNull(dialect, "Dialect must not be null!"); 69 | Assert.notNull(publisher, "ApplicationEventPublisher must not be null!"); 70 | 71 | this.publisher = publisher; 72 | this.context = context; 73 | this.converter = converter; 74 | this.dialect = dialect; 75 | this.ysqlDataAccessStrategy = dataAccessStrategy; 76 | this.operations = operations; 77 | } 78 | 79 | @Override 80 | protected Class getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) { 81 | return SimpleYsqlRepository.class; 82 | } 83 | 84 | @SuppressWarnings("deprecation") 85 | @Override 86 | protected Object getTargetRepository(RepositoryInformation repositoryInformation) { 87 | 88 | YsqlTemplate template = new YsqlTemplate(publisher, context, converter, ysqlDataAccessStrategy); 89 | 90 | if (entityCallbacks != null) { 91 | template.setEntityCallbacks(entityCallbacks); 92 | } 93 | 94 | RelationalPersistentEntity persistentEntity = context 95 | .getRequiredPersistentEntity(repositoryInformation.getDomainType()); 96 | 97 | return getTargetRepositoryViaReflection(repositoryInformation.getRepositoryBaseClass(), template, persistentEntity); 98 | } 99 | 100 | @Override 101 | protected Optional getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key, 102 | QueryMethodEvaluationContextProvider evaluationContextProvider) { 103 | 104 | return Optional.of(new YsqlQueryLookupStrategy(publisher, entityCallbacks, context, converter, dialect, 105 | queryMappingConfiguration, operations, beanFactory)); 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/jdbc/repository/support/YsqlRepositoryFactoryBean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.support; 14 | 15 | import java.io.Serializable; 16 | 17 | import javax.sql.DataSource; 18 | 19 | import org.springframework.beans.factory.BeanFactory; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.context.ApplicationEventPublisher; 22 | import org.springframework.context.ApplicationEventPublisherAware; 23 | import org.springframework.data.jdbc.core.convert.JdbcConverter; 24 | import org.springframework.data.jdbc.core.convert.SqlGeneratorSource; 25 | import org.springframework.data.jdbc.repository.QueryMappingConfiguration; 26 | import org.springframework.data.mapping.callback.EntityCallbacks; 27 | import org.springframework.data.relational.core.dialect.Dialect; 28 | import org.springframework.data.relational.core.mapping.RelationalMappingContext; 29 | import org.springframework.data.repository.Repository; 30 | import org.springframework.data.repository.core.support.RepositoryFactorySupport; 31 | import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; 32 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 33 | import org.springframework.util.Assert; 34 | 35 | import com.yugabyte.data.jdbc.core.convert.DefaultYsqlDataAccessStrategy; 36 | import com.yugabyte.data.jdbc.core.convert.YsqlDataAccessStrategy; 37 | 38 | /** 39 | * Special adapter for Springs 40 | * {@link org.springframework.beans.factory.FactoryBean} interface to allow easy 41 | * setup of repository factories via Spring configuration. 42 | * 43 | * @author Nikhil Chandrappa 44 | */ 45 | public class YsqlRepositoryFactoryBean, S, ID extends Serializable> 46 | extends TransactionalRepositoryFactoryBeanSupport implements ApplicationEventPublisherAware { 47 | 48 | private ApplicationEventPublisher publisher; 49 | private BeanFactory beanFactory; 50 | private RelationalMappingContext mappingContext; 51 | private JdbcConverter converter; 52 | private YsqlDataAccessStrategy ysqlDataAccessStrategy; 53 | private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; 54 | private NamedParameterJdbcOperations operations; 55 | private DataSource dataSource; 56 | private EntityCallbacks entityCallbacks; 57 | private Dialect dialect; 58 | 59 | protected YsqlRepositoryFactoryBean(Class repositoryInterface) { 60 | super(repositoryInterface); 61 | } 62 | 63 | @Override 64 | protected RepositoryFactorySupport doCreateRepositoryFactory() { 65 | 66 | YsqlRepositoryFactory ysqlRepositoryFactory = new YsqlRepositoryFactory(ysqlDataAccessStrategy, mappingContext, 67 | converter, dialect, publisher, operations); 68 | ysqlRepositoryFactory.setQueryMappingConfiguration(queryMappingConfiguration); 69 | ysqlRepositoryFactory.setEntityCallbacks(entityCallbacks); 70 | ysqlRepositoryFactory.setBeanFactory(beanFactory); 71 | 72 | return ysqlRepositoryFactory; 73 | } 74 | 75 | @Override 76 | public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { 77 | 78 | super.setApplicationEventPublisher(publisher); 79 | 80 | this.publisher = publisher; 81 | } 82 | 83 | @Autowired 84 | protected void setMappingContext(RelationalMappingContext mappingContext) { 85 | 86 | Assert.notNull(mappingContext, "MappingContext must not be null"); 87 | 88 | super.setMappingContext(mappingContext); 89 | this.mappingContext = mappingContext; 90 | } 91 | 92 | @Autowired 93 | protected void setDialect(Dialect dialect) { 94 | 95 | Assert.notNull(dialect, "Dialect must not be null"); 96 | 97 | this.dialect = dialect; 98 | } 99 | 100 | public void setDataAccessStrategy(YsqlDataAccessStrategy dataAccessStrategy) { 101 | 102 | Assert.notNull(dataAccessStrategy, "DataAccessStrategy must not be null"); 103 | 104 | this.ysqlDataAccessStrategy = dataAccessStrategy; 105 | } 106 | 107 | @Autowired(required = false) 108 | public void setQueryMappingConfiguration(QueryMappingConfiguration queryMappingConfiguration) { 109 | 110 | Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null"); 111 | 112 | this.queryMappingConfiguration = queryMappingConfiguration; 113 | } 114 | 115 | public void setJdbcOperations(NamedParameterJdbcOperations operations) { 116 | 117 | Assert.notNull(operations, "NamedParameterJdbcOperations must not be null"); 118 | 119 | this.operations = operations; 120 | } 121 | 122 | @Autowired 123 | public void setConverter(JdbcConverter converter) { 124 | 125 | Assert.notNull(converter, "JdbcConverter must not be null"); 126 | 127 | this.converter = converter; 128 | } 129 | 130 | @Override 131 | public void setBeanFactory(BeanFactory beanFactory) { 132 | 133 | super.setBeanFactory(beanFactory); 134 | 135 | this.beanFactory = beanFactory; 136 | } 137 | 138 | @Override 139 | public void afterPropertiesSet() { 140 | 141 | Assert.state(this.mappingContext != null, "MappingContext is required and must not be null!"); 142 | Assert.state(this.converter != null, "RelationalConverter is required and must not be null!"); 143 | 144 | if (this.operations == null) { 145 | 146 | Assert.state(beanFactory != null, "If no JdbcOperations are set a BeanFactory must be available."); 147 | 148 | this.operations = beanFactory.getBean(NamedParameterJdbcOperations.class); 149 | } 150 | 151 | if (this.dataSource == null) { 152 | 153 | Assert.state(beanFactory != null, "If no DataSource are set a BeanFactory must be available."); 154 | 155 | this.dataSource = beanFactory.getBean(DataSource.class); 156 | } 157 | 158 | if (this.ysqlDataAccessStrategy == null) { 159 | 160 | Assert.state(beanFactory != null, "If no DataAccessStrategy is set a BeanFactory must be available."); 161 | 162 | this.ysqlDataAccessStrategy = this.beanFactory.getBeanProvider(YsqlDataAccessStrategy.class) // 163 | .getIfAvailable(() -> { 164 | 165 | Assert.state(this.dialect != null, "Dialect is required and must not be null!"); 166 | 167 | SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(this.mappingContext, this.converter, 168 | this.dialect); 169 | return new DefaultYsqlDataAccessStrategy(sqlGeneratorSource, this.mappingContext, this.converter, 170 | this.operations, dataSource); 171 | }); 172 | } 173 | 174 | if (this.queryMappingConfiguration == null) { 175 | this.queryMappingConfiguration = QueryMappingConfiguration.EMPTY; 176 | } 177 | 178 | if (beanFactory != null) { 179 | entityCallbacks = EntityCallbacks.create(beanFactory); 180 | } 181 | 182 | super.afterPropertiesSet(); 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/java/com/yugabyte/data/relational/core/dialect/YugabyteDialect.java: -------------------------------------------------------------------------------- 1 | package com.yugabyte.data.relational.core.dialect; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.relational.core.dialect.LockClause; 6 | import org.springframework.data.relational.core.dialect.PostgresDialect; 7 | import org.springframework.data.relational.core.sql.IdentifierProcessing; 8 | import org.springframework.data.relational.core.sql.LockOptions; 9 | import org.springframework.data.relational.core.sql.SqlIdentifier; 10 | import org.springframework.data.relational.core.sql.Table; 11 | 12 | /** 13 | * An SQL dialect for YugabyteDB YSQL. 14 | * 15 | * @author Nikhil Chandrappa 16 | * @since 2.3 17 | */ 18 | public class YugabyteDialect extends PostgresDialect { 19 | 20 | public static final YugabyteDialect INSTANCE = new YugabyteDialect(); 21 | 22 | protected YugabyteDialect() {} 23 | 24 | private final LockClause LOCK_CLAUSE = new YugabyteLockClause(this.getIdentifierProcessing()); 25 | 26 | /* 27 | * (non-Javadoc) 28 | * @see org.springframework.data.relational.core.dialect.Dialect#lock() 29 | */ 30 | @Override 31 | public LockClause lock() { 32 | return LOCK_CLAUSE; 33 | } 34 | 35 | static class YugabyteLockClause implements LockClause { 36 | 37 | private final IdentifierProcessing identifierProcessing; 38 | 39 | YugabyteLockClause(IdentifierProcessing identifierProcessing) { 40 | this.identifierProcessing = identifierProcessing; 41 | } 42 | 43 | /* 44 | * (non-Javadoc) 45 | * @see org.springframework.data.relational.core.dialect.LockClause#getLock(LockOptions) 46 | */ 47 | @SuppressWarnings("unused") 48 | @Override 49 | public String getLock(LockOptions lockOptions) { 50 | 51 | List tables = lockOptions.getFrom().getTables(); 52 | if (tables.isEmpty()) { 53 | return ""; 54 | } 55 | 56 | // get the first table and obtain last part if the identifier is a composed one. 57 | SqlIdentifier identifier = tables.get(0).getName(); 58 | SqlIdentifier last = identifier; 59 | 60 | for (SqlIdentifier sqlIdentifier : identifier) { 61 | last = sqlIdentifier; 62 | } 63 | 64 | // without schema 65 | String tableName = last.toSql(this.identifierProcessing); 66 | 67 | switch (lockOptions.getLockMode()) { 68 | 69 | case PESSIMISTIC_WRITE: 70 | return ""; 71 | 72 | case PESSIMISTIC_READ: 73 | return ""; 74 | 75 | default: 76 | return ""; 77 | } 78 | } 79 | 80 | /* 81 | * (non-Javadoc) 82 | * @see org.springframework.data.relational.core.dialect.LockClause#getClausePosition() 83 | */ 84 | @Override 85 | public Position getClausePosition() { 86 | return Position.AFTER_ORDER_BY; 87 | } 88 | 89 | }; 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.jdbc.repository.support.YsqlRepositoryFactory 2 | org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=com.yugabyte.data.jdbc.repository.config.YugabyteDialectResolver.YugabyteDialectProvider -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/test/java/com/yugabyte/data/jdbc/AbstractYugabyteConfigurationIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package com.yugabyte.data.jdbc; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import javax.sql.DataSource; 7 | 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.data.relational.core.dialect.Dialect; 14 | import org.springframework.jdbc.core.JdbcOperations; 15 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; 16 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 17 | 18 | import com.yugabyte.data.jdbc.core.YsqlTemplate; 19 | import com.yugabyte.data.jdbc.core.convert.YsqlDataAccessStrategy; 20 | import com.yugabyte.data.jdbc.repository.config.AbstractYugabyteJdbcConfiguration; 21 | import com.yugabyte.data.relational.core.dialect.YugabyteDialect; 22 | 23 | public class AbstractYugabyteConfigurationIntegrationTests { 24 | 25 | AnnotationConfigApplicationContext testApplicationContext; 26 | 27 | @Before 28 | public void setup() { 29 | testApplicationContext = new AnnotationConfigApplicationContext(SampleYugabyteJdbcConfiguration.class); 30 | } 31 | 32 | @Test 33 | public void configureYugabyteRelatedBeans() { 34 | 35 | assertThat(testApplicationContext.getBean("jdbcDialect", Dialect.class)).isInstanceOf(YugabyteDialect.class); 36 | assertThat(testApplicationContext.getBean("ysqlTemplate", YsqlTemplate.class)).isNotNull(); 37 | assertThat(testApplicationContext.getBean("ysqlDataAccessStrategyBean", YsqlDataAccessStrategy.class)).isNotNull(); 38 | } 39 | 40 | @After 41 | public void close() { 42 | testApplicationContext.close(); 43 | } 44 | 45 | static class SampleYugabyteJdbcConfiguration extends AbstractYugabyteJdbcConfiguration { 46 | 47 | @Bean 48 | public NamedParameterJdbcOperations jdbcOperations() { 49 | 50 | JdbcOperations jdbcOperations = mock(JdbcOperations.class); 51 | return new NamedParameterJdbcTemplate(jdbcOperations); 52 | } 53 | 54 | @Bean 55 | public DataSource dataSource() { 56 | 57 | DataSource dataSource = mock(DataSource.class); 58 | return dataSource; 59 | } 60 | 61 | @Override 62 | @Bean 63 | public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { 64 | return YugabyteDialect.INSTANCE; 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/test/java/com/yugabyte/data/jdbc/EnableYugabyteRepositoriesIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package com.yugabyte.data.jdbc; 2 | 3 | public class EnableYugabyteRepositoriesIntegrationTests { 4 | 5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/test/java/com/yugabyte/data/jdbc/YugabyteDataSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.yugabyte.data.jdbc; 2 | 3 | public class YugabyteDataSourceConfiguration { 4 | 5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/test/java/com/yugabyte/data/jdbc/repository/config/SimpleYsqlRepositoryUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yugabyte, Inc. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 4 | * in compliance with the License. You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License 9 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 10 | * or implied. See the License for the specific language governing permissions and limitations 11 | * under the License. 12 | */ 13 | package com.yugabyte.data.jdbc.repository.config; 14 | 15 | /** 16 | * Unit Test for {@link SimpleYsqlRepository}. 17 | * 18 | * @author Nikhil Chandrappa 19 | */ 20 | public class SimpleYsqlRepositoryUnitTests { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/test/java/com/yugabyte/data/jdbc/testing/YugabyteDBTestImage.java: -------------------------------------------------------------------------------- 1 | package com.yugabyte.data.jdbc.testing; 2 | 3 | import org.testcontainers.utility.DockerImageName; 4 | 5 | public interface YugabyteDBTestImage { 6 | DockerImageName YUGABYTEDB_IMAGE = DockerImageName.parse("yugabytedb/yugabyte:2.7.0.0-b17"); 7 | } 8 | -------------------------------------------------------------------------------- /spring-data-yugabytedb-ysql/src/test/java/com/yugabyte/data/jdbc/testing/YugbayteDataSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.yugabyte.data.jdbc.testing; 2 | 3 | import static com.yugabyte.data.jdbc.testing.YugabyteDBTestImage.YUGABYTEDB_IMAGE; 4 | 5 | import javax.sql.DataSource; 6 | 7 | import org.postgresql.ds.PGSimpleDataSource; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.testcontainers.containers.YugabyteDBContainer; 12 | 13 | import com.yugabyte.ysql.YBClusterAwareDataSource; 14 | 15 | @Configuration 16 | public class YugbayteDataSourceConfiguration { 17 | 18 | private static final Logger LOG = LoggerFactory.getLogger(YugbayteDataSourceConfiguration.class); 19 | private static YugabyteDBContainer YUGABYTE_CONTAINER; 20 | 21 | protected DataSource createDataSource() { 22 | 23 | if (YUGABYTE_CONTAINER == null) { 24 | YugabyteDBContainer yugabyteDBContainer = new YugabyteDBContainer(YUGABYTEDB_IMAGE); 25 | yugabyteDBContainer.start(); 26 | YUGABYTE_CONTAINER = yugabyteDBContainer; 27 | LOG.info("Initialized YugabyteDB Test Container."); 28 | } 29 | 30 | PGSimpleDataSource dataSource = new PGSimpleDataSource(); 31 | dataSource.setUrl(YUGABYTE_CONTAINER.getJdbcUrl()); 32 | dataSource.setUser(YUGABYTE_CONTAINER.getUsername()); 33 | dataSource.setPassword(YUGABYTE_CONTAINER.getPassword()); 34 | LOG.info("Initialized PGSimpleDataSource for YugabyteDB."); 35 | 36 | return dataSource; 37 | 38 | } 39 | 40 | protected DataSource createClusterAwareDataSource() { 41 | 42 | if (YUGABYTE_CONTAINER == null) { 43 | YugabyteDBContainer yugabyteDBContainer = new YugabyteDBContainer(YUGABYTEDB_IMAGE); 44 | yugabyteDBContainer.start(); 45 | YUGABYTE_CONTAINER = yugabyteDBContainer; 46 | } 47 | 48 | YBClusterAwareDataSource dataSource = new YBClusterAwareDataSource(); 49 | dataSource.setUrl(YUGABYTE_CONTAINER.getJdbcUrl()); 50 | dataSource.setUser(YUGABYTE_CONTAINER.getUsername()); 51 | dataSource.setPassword(YUGABYTE_CONTAINER.getPassword()); 52 | LOG.info("Initialized YBClusterAwareDataSource for YugabyteDB."); 53 | 54 | return dataSource; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /yugabytedb-ysql-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.yugabyte 4 | yugabytedb-ysql-spring-boot-starter 5 | 2.6.0-SNAPSHOT 6 | yugabytedb-ysql-spring-boot-starter 7 | Spring Boot Starter for YugabyteDB YSQL. 8 | 9 | --------------------------------------------------------------------------------