Skip to content

Commit a18a8d5

Browse files
authored
Add sample for Cloud SQL for SQL Server (GoogleCloudPlatform#4282)
* add sqlserver jdbc sample * update versions for driver and socket factory * update version of JDBC socket factory * update readme * update copyright headers * update artifact ID * change project id to placeholder value * fixes to IndexServlet.java * add sample overview and cleanup instructions to README * change logging level back to warning * update app engine configuration * add note about supported Java versions for GAE dev server * Fix typos in README.md
1 parent 66c4531 commit a18a8d5

7 files changed

Lines changed: 644 additions & 0 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Connecting to Cloud SQL - SQL Server
2+
3+
This is a sample application that inserts and reads votes for two options (tabs and spaces) in a Cloud SQL database. The application demonstrates the reommended method of connecting to Cloud SQL from a Java application using the [Cloud SQL Java Connector](https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory)
4+
5+
## Before you begin
6+
7+
1. If you haven't already, set up a Java Development Environment (including google-cloud-sdk and
8+
maven utilities) by following the [java setup guide](https://cloud.google.com/java/docs/setup) and
9+
[create a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project).
10+
11+
1. Create a 2nd Gen Cloud SQL Instance by following these
12+
[instructions](https://cloud.google.com/sql/docs/sqlserver/create-instance). Note the connection string,
13+
database user, and database password that you create.
14+
15+
1. Create a database for your application by following these
16+
[instructions](https://cloud.google.com/sql/docs/sqlserver/create-manage-databases). Note the database
17+
name.
18+
19+
1. Create a service account with the 'Cloud SQL Client' permissions by following these
20+
[instructions](https://cloud.google.com/sql/docs/sqlserver/connect-external-app#4_if_required_by_your_authentication_method_create_a_service_account).
21+
Download a JSON key to use to authenticate your connection.
22+
23+
1. Use the information noted in the previous steps:
24+
```bash
25+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service/account/key.json
26+
export CLOUD_SQL_CONNECTION_NAME='<MY-PROJECT>:<INSTANCE-REGION>:<MY-DATABASE>'
27+
export DB_USER='my-db-user'
28+
export DB_PASS='my-db-pass'
29+
export DB_NAME='my_db'
30+
```
31+
Note: Saving credentials in environment variables is convenient, but not secure - consider a more
32+
secure solution such as [Cloud KMS](https://cloud.google.com/kms/) to help keep secrets safe.
33+
34+
## Deploying locally
35+
36+
To run this application locally, run the following command inside the project folder:
37+
38+
```bash
39+
mvn jetty:run
40+
```
41+
42+
Navigate towards `http://127.0.0.1:8080` to verify your application is running correctly.
43+
44+
## Google App Engine Standard
45+
46+
To run on GAE-Standard, create an AppEngine project by following the setup for these
47+
[instructions](https://cloud.google.com/appengine/docs/standard/java/quickstart#before-you-begin)
48+
and verify that
49+
[appengine-maven-plugin](https://cloud.google.com/java/docs/setup#optional_install_maven_or_gradle_plugin_for_app_engine)
50+
has been added in your build section as a plugin.
51+
52+
53+
### Development Server
54+
55+
The following command will run the application locally in the the GAE-development server:
56+
```bash
57+
mvn clean package appengine:run
58+
```
59+
60+
Note: if the GAE development server fails to start, check that you are using a supported version of Java. Supported versions are Java 8 and Java 11.
61+
### Deploy to Google Cloud
62+
63+
First, update `src/main/webapp/WEB-INF/appengine-web.xml` with the correct values to pass the
64+
environment variables into the runtime.
65+
66+
Next, the following command will deploy the application to your Google Cloud project:
67+
```bash
68+
mvn clean package appengine:deploy
69+
```
70+
71+
### Cleanup
72+
To avoid incurring any charges, navigate to your project's [App Engine settings](https://console.cloud.google.com/appengine/settings) and click `Disable Application`. Also [delete your Cloud SQL Instance](https://cloud.google.com/sql/docs/mysql/delete-instance) if you no longer need it.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<!--
2+
Copyright 2020 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
<project>
17+
<modelVersion>4.0.0</modelVersion>
18+
<packaging>war</packaging>
19+
<version>1.0-SNAPSHOT</version>
20+
<groupId>com.example.cloudsql</groupId>
21+
<artifactId>tabs-vs-spaces-sqlserver</artifactId>
22+
23+
<!--
24+
The parent pom defines common style checks and testing strategies for our samples.
25+
Removing or replacing it should not affect the execution of the samples in anyway.
26+
-->
27+
<parent>
28+
<groupId>com.google.cloud.samples</groupId>
29+
<artifactId>shared-configuration</artifactId>
30+
<version>1.0.15</version>
31+
</parent>
32+
33+
<properties>
34+
<maven.compiler.target>1.8</maven.compiler.target>
35+
<maven.compiler.source>1.8</maven.compiler.source>
36+
<failOnMissingWebXml>false</failOnMissingWebXml>
37+
</properties>
38+
39+
<dependencies>
40+
<dependency>
41+
<groupId>javax.servlet</groupId>
42+
<artifactId>javax.servlet-api</artifactId>
43+
<version>3.1.0</version>
44+
<type>jar</type>
45+
<scope>provided</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>javax.servlet</groupId>
49+
<artifactId>jstl</artifactId>
50+
<version>1.2</version>
51+
</dependency>
52+
<dependency>
53+
<groupId>com.microsoft.sqlserver</groupId>
54+
<artifactId>mssql-jdbc</artifactId>
55+
<version>9.1.0.jre8-preview</version>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.google.cloud.sql</groupId>
59+
<artifactId>cloud-sql-connector-jdbc-sqlserver</artifactId>
60+
<version>1.2.0</version>
61+
</dependency>
62+
<dependency>
63+
<groupId>com.zaxxer</groupId>
64+
<artifactId>HikariCP</artifactId>
65+
<version>3.4.2</version>
66+
</dependency>
67+
</dependencies>
68+
69+
<build>
70+
<plugins>
71+
<plugin>
72+
<groupId>org.eclipse.jetty</groupId>
73+
<artifactId>jetty-maven-plugin</artifactId>
74+
<version>9.4.27.v20200227</version>
75+
<configuration>
76+
<scanIntervalSeconds>1</scanIntervalSeconds>
77+
</configuration>
78+
</plugin>
79+
<!-- Only required for AppEngine Deployments -->
80+
<plugin>
81+
<groupId>com.google.cloud.tools</groupId>
82+
<artifactId>appengine-maven-plugin</artifactId>
83+
<version>2.4.0</version>
84+
<configuration>
85+
<deploy.projectId>GCLOUD_CONFIG</deploy.projectId>
86+
<deploy.version>GCLOUD_CONFIG</deploy.version>
87+
</configuration>
88+
</plugin>
89+
</plugins>
90+
</build>
91+
</project>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2020 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.cloudsql;
18+
19+
import com.zaxxer.hikari.HikariConfig;
20+
import com.zaxxer.hikari.HikariDataSource;
21+
import java.sql.Connection;
22+
import java.sql.PreparedStatement;
23+
import java.sql.SQLException;
24+
import java.util.logging.Logger;
25+
import javax.servlet.ServletContextEvent;
26+
import javax.servlet.ServletContextListener;
27+
import javax.servlet.annotation.WebListener;
28+
import javax.sql.DataSource;
29+
30+
@WebListener("Creates a connection pool that is stored in the Servlet's context for later use.")
31+
public class ConnectionPoolContextListener implements ServletContextListener {
32+
33+
private static final Logger LOGGER = Logger.getLogger(IndexServlet.class.getName());
34+
35+
// Saving credentials in environment variables is convenient, but not secure - consider a more
36+
// secure solution such as https://cloud.google.com/kms/ to help keep secrets safe.
37+
private static final String CLOUD_SQL_CONNECTION_NAME = System.getenv(
38+
"CLOUD_SQL_CONNECTION_NAME");
39+
private static final String DB_USER = System.getenv("DB_USER");
40+
private static final String DB_PASS = System.getenv("DB_PASS");
41+
private static final String DB_NAME = System.getenv("DB_NAME");
42+
43+
private DataSource createConnectionPool() {
44+
// [START cloud_sql_sqlserver_servlet_create]
45+
// The configuration object specifies behaviors for the connection pool.
46+
HikariConfig config = new HikariConfig();
47+
48+
// Configure which instance and what database user to connect with.
49+
config
50+
.setDataSourceClassName(String.format("com.microsoft.sqlserver.jdbc.SQLServerDataSource"));
51+
config.setUsername(DB_USER); // e.g. "root", "sqlserver"
52+
config.setPassword(DB_PASS); // e.g. "my-password"
53+
config.addDataSourceProperty("databaseName", DB_NAME);
54+
55+
// For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections.
56+
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
57+
config.addDataSourceProperty("socketFactoryClass",
58+
"com.google.cloud.sql.sqlserver.SocketFactory");
59+
config.addDataSourceProperty("socketFactoryConstructorArg", CLOUD_SQL_CONNECTION_NAME);
60+
61+
// ... Specify additional connection properties here.
62+
63+
// [START_EXCLUDE]
64+
65+
// [START cloud_sql_sqlserver_servlet_limit]
66+
// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
67+
// values for this setting are highly variable on app design, infrastructure, and database.
68+
config.setMaximumPoolSize(5);
69+
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
70+
// Additional connections will be established to meet this value unless the pool is full.
71+
config.setMinimumIdle(5);
72+
// [END cloud_sql_sqlserver_servlet_limit]
73+
74+
// [START cloud_sql_sqlserver_servlet_timeout]
75+
// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
76+
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
77+
// SQLException.
78+
config.setConnectionTimeout(10000); // 10 seconds
79+
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
80+
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
81+
config.setIdleTimeout(600000); // 10 minutes
82+
// [END cloud_sql_sqlserver_servlet_timeout]
83+
84+
// [START cloud_sql_sqlserver_servlet_backoff]
85+
// Hikari automatically delays between failed connection attempts, eventually reaching a
86+
// maximum delay of `connectionTimeout / 2` between attempts.
87+
// [END cloud_sql_sqlserver_servlet_backoff]
88+
89+
// [START cloud_sql_sqlserver_servlet_lifetime]
90+
// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
91+
// live longer than this many milliseconds will be closed and reestablished between uses. This
92+
// value should be several minutes shorter than the database's timeout value to avoid unexpected
93+
// terminations.
94+
config.setMaxLifetime(1800000); // 30 minutes
95+
// [END cloud_sql_sqlserver_servlet_lifetime]
96+
97+
// [END_EXCLUDE]
98+
99+
// Initialize the connection pool using the configuration object.
100+
DataSource pool = new HikariDataSource(config);
101+
// [END cloud_sql_sqlserver_servlet_create]
102+
return pool;
103+
}
104+
105+
private void createTable(DataSource pool) throws SQLException {
106+
// Safely attempt to create the table schema.
107+
try (Connection conn = pool.getConnection()) {
108+
PreparedStatement createTableStatement = conn.prepareStatement(
109+
"IF NOT EXISTS ("
110+
+ "SELECT * FROM sysobjects WHERE name='votes' and xtype='U')"
111+
+ "CREATE TABLE votes ("
112+
+ "vote_id INT NOT NULL IDENTITY,"
113+
+ "time_cast DATETIME NOT NULL,"
114+
+ "candidate VARCHAR(6) NOT NULL,"
115+
+ "PRIMARY KEY (vote_id));"
116+
);
117+
createTableStatement.execute();
118+
}
119+
}
120+
121+
@Override
122+
public void contextDestroyed(ServletContextEvent event) {
123+
// This function is called when the Servlet is destroyed.
124+
HikariDataSource pool = (HikariDataSource) event.getServletContext().getAttribute("my-pool");
125+
if (pool != null) {
126+
pool.close();
127+
}
128+
}
129+
130+
@Override
131+
public void contextInitialized(ServletContextEvent event) {
132+
// This function is called when the application starts and will safely create a connection pool
133+
// that can be used to connect to.
134+
DataSource pool = (DataSource) event.getServletContext().getAttribute("my-pool");
135+
if (pool == null) {
136+
pool = createConnectionPool();
137+
event.getServletContext().setAttribute("my-pool", pool);
138+
}
139+
try {
140+
createTable(pool);
141+
} catch (SQLException ex) {
142+
throw new RuntimeException("Unable to verify table schema. Please double check the steps"
143+
+ "in the README and try again.", ex);
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)