Skip to content

Commit ffdb971

Browse files
kurtisvglesv
authored andcommitted
Added Cloud SQL MySQL Servlet connectivity sample. (GoogleCloudPlatform#1231)
* Added MySQL Servlet connectivity sample. Fix checkstyle violations. * Partially address lesv feedback. * Address rest of lesv's feedback. * Address additional feedback.
1 parent 2766bda commit ffdb971

File tree

8 files changed

+628
-1
lines changed

8 files changed

+628
-1
lines changed

cloud-sql/mysql/servlet/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Connecting to Cloud SQL - MySQL
2+
3+
## Before you begin
4+
5+
1. If you haven't already, set up a Java Development Environment (including google-cloud-sdk and
6+
maven utilities) by following the [java setup guide](https://cloud.google.com/java/docs/setup) and
7+
[create a project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project).
8+
9+
1. Create a 2nd Gen Cloud SQL Instance by following these
10+
[instructions](https://cloud.google.com/sql/docs/mysql/create-instance). Note the connection string,
11+
database user, and database password that you create.
12+
13+
1. Create a database for your application by following these
14+
[instructions](https://cloud.google.com/sql/docs/mysql/create-manage-databases). Note the database
15+
name.
16+
17+
1. Create a service account with the 'Cloud SQL Client' permissions by following these
18+
[instructions](https://cloud.google.com/sql/docs/mysql/connect-external-app#4_if_required_by_your_authentication_method_create_a_service_account).
19+
Download a JSON key to use to authenticate your connection.
20+
21+
1. Use the information noted in the previous steps:
22+
```bash
23+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service/account/key.json
24+
export CLOUD_SQL_CONNECTION_NAME='<MY-PROJECT>:<INSTANCE-REGION>:<MY-DATABASE>'
25+
export DB_USER='my-db-user'
26+
export DB_PASS='my-db-pass'
27+
export DB_NAME='my_db'
28+
```
29+
Note: Saving credentials in environment variables is convenient, but not secure - consider a more
30+
secure solution such as [Cloud KMS](https://cloud.google.com/kms/) to help keep secrets safe.
31+
32+
## Deploying locally
33+
34+
To run this application locally, run the following command inside the project folder:
35+
36+
```bash
37+
mvn jetty:run
38+
```
39+
40+
Navigate towards `http://127.0.0.1:8080` to verify your application is running correctly.
41+
42+
## Google App Engine Standard
43+
44+
To run on GAE-Standard, create an AppEngine project by following the setup for these
45+
[instructions](https://cloud.google.com/appengine/docs/standard/java/quickstart#before-you-begin)
46+
and verify that
47+
[appengine-maven-plugin](https://cloud.google.com/java/docs/setup#optional_install_maven_or_gradle_plugin_for_app_engine)
48+
has been added in your build section as a plugin.
49+
50+
51+
### Development Server
52+
53+
The following command will run the application locally in the the GAE-development server:
54+
```bash
55+
mvn appengine:run
56+
```
57+
58+
### Deploy to Google Cloud
59+
60+
First, update `src/main/webapp/WEB-INF/appengine-web.xml` with the correct values to pass the
61+
environment variables into the runtime.
62+
63+
Next, the following command will deploy the application to your Google Cloud project:
64+
```bash
65+
mvn appengine:deploy
66+
```

cloud-sql/mysql/servlet/pom.xml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<!--
2+
Copyright 2018 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</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.10</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>mysql</groupId>
54+
<artifactId>mysql-connector-java</artifactId>
55+
<version>8.0.11</version>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.google.cloud.sql</groupId>
59+
<artifactId>mysql-socket-factory-connector-j-8</artifactId>
60+
<version>1.0.11</version>
61+
</dependency>
62+
<dependency>
63+
<groupId>com.zaxxer</groupId>
64+
<artifactId>HikariCP</artifactId>
65+
<version>3.1.0</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.10.v20180503</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>1.3.2</version>
84+
</plugin>
85+
</plugins>
86+
</build>
87+
</project>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2018 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_INSTANCE_NAME = System.getenv("CLOUD_SQL_INSTANCE_NAME");
38+
private static final String DB_USER = System.getenv("DB_USER");
39+
private static final String DB_PASS = System.getenv("DB_PASS");
40+
private static final String DB_NAME = System.getenv("DB_NAME");
41+
42+
private DataSource createConnectionPool() {
43+
// [START cloud_sql_mysql_connection_pool]
44+
// The configuration object specifies behaviors for the connection pool.
45+
HikariConfig config = new HikariConfig();
46+
47+
// Configure which instance and what database user to connect with.
48+
config.setJdbcUrl(String.format("jdbc:mysql:///%s", DB_NAME));
49+
config.setUsername(DB_USER); // e.g. "root", "postgres"
50+
config.setPassword(DB_PASS); // e.g. "my-password"
51+
52+
// For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections.
53+
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
54+
config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory");
55+
config.addDataSourceProperty("cloudSqlInstance", CLOUD_SQL_INSTANCE_NAME);
56+
config.addDataSourceProperty("useSSL", "false");
57+
58+
// ... Specify additional connection properties here.
59+
60+
// [START_EXCLUDE]
61+
62+
// [START cloud_sql_limit_connections]
63+
// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
64+
// values for this setting are highly variable on app design, infrastructure, and database.
65+
config.setMaximumPoolSize(5);
66+
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
67+
// Additional connections will be established to meet this value unless the pool is full.
68+
config.setMinimumIdle(5);
69+
// [END cloud_sql_limit_connections]
70+
71+
// [START cloud_sql_connection_timeout]
72+
// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
73+
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
74+
// SQLException.
75+
config.setConnectionTimeout(10000); // 10 seconds
76+
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
77+
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
78+
config.setIdleTimeout(600000); // 10 minutes
79+
// [END cloud_sql_connection_timeout]
80+
81+
// [START cloud_sql_connection_backoff]
82+
// Hikari automatically delays between failed connection attempts, eventually reaching a
83+
// maximum delay of `connectionTimeout / 2` between attempts.
84+
// [END cloud_sql_connection_backoff]
85+
86+
// [START cloud_sql_connection_lifetime]
87+
// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
88+
// live longer than this many milliseconds will be closed and reestablished between uses. This
89+
// value should be several minutes shorter than the database's timeout value to avoid unexpected
90+
// terminations.
91+
config.setMaxLifetime(1800000); // 30 minutes
92+
// [END cloud_sql_connection_lifetime]
93+
94+
// [END_EXCLUDE]
95+
96+
// Initialize the connection pool using the configuration object.
97+
DataSource pool = new HikariDataSource(config);
98+
// [END cloud_sql_mysql_connection_pool]
99+
return pool;
100+
}
101+
102+
private void createTable(DataSource pool) throws SQLException {
103+
// Safely attempt to create the table schema.
104+
try (Connection conn = pool.getConnection()) {
105+
PreparedStatement createTableStatement = conn.prepareStatement(
106+
"CREATE TABLE IF NOT EXISTS votes ( "
107+
+ "vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, candidate CHAR(6) NOT NULL,"
108+
+ " PRIMARY KEY (vote_id) );"
109+
);
110+
createTableStatement.execute();
111+
}
112+
}
113+
114+
@Override
115+
public void contextDestroyed(ServletContextEvent event) {
116+
// This function is called when the Servlet is destroyed.
117+
HikariDataSource pool = (HikariDataSource) event.getServletContext().getAttribute("my-pool");
118+
if (pool != null) {
119+
pool.close();
120+
}
121+
}
122+
123+
@Override
124+
public void contextInitialized(ServletContextEvent event) {
125+
// This function is called when the application starts and will safely create a connection pool
126+
// that can be used to connect to.
127+
DataSource pool = (DataSource) event.getServletContext().getAttribute("my-pool");
128+
if (pool == null) {
129+
pool = createConnectionPool();
130+
event.getServletContext().setAttribute("my-pool", pool);
131+
}
132+
try {
133+
createTable(pool);
134+
} catch (SQLException ex) {
135+
throw new RuntimeException("Unable to verify table schema. Please double check the steps"
136+
+ "in the README and try again.", ex);
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)