-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Cloud SQL MySQL Servlet connectivity sample. #1231
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice -- my comments and questions aren't required, just consider them. A few places that might benefit from a bit of explanation and I pointed you to shutdown hooks. Only the password should be passed as a param, but I'm an advocate of setting things once and moving on. You can actually grab envVars and pass them to appengine-web.xml in maven.
Consider asking Thea about password's in EnvVar's. AWS is ok w/ it, so maybe we should be, but it's often frowned on around here. That said, I tried to make it so that if we set them in appengine-web.xml that we could pass them as arguments to mvn, which then used them to set the appengine-web.xml values.
1. If you haven't already, set up a Java Development Environment (including google-cloud-sdk and | ||
maven utilities) by following the [java setup guide](https://cloud.google.com/java/docs/setup). | ||
|
||
1. Create a 2nd Gen Cloud SQL Instance by following these |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also point them at the create a project doc as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service/account/key.json | ||
export CLOUD_SQL_CONNECTION_NAME='<MY-PROJECT>:<INSTANCE-REGION>:<MY-DATABASE>' | ||
export DB_USER='my-db-user' | ||
export DB_PASS='my-db-pass' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is often bad practice. Letting folks pass it in on the mvn / gradle command line is usually considered better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a warning to the sections with environment variables that they aren't secure with a link to KMS as a secure solution. I would prefer to avoid using mvn / gradle to configure properties because it's permanent and forces the user to rebuild rather than restart the application. We are planning on adding instructions for GKE/GCE as well, which could mean a lot of repeated steps if they have to rebuild to change instance name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1; Definitely do not use Maven/Gradle configuration properties for runtime environment values. Those are not idiomatic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might argue that, but I'm ok with it.
import javax.servlet.annotation.WebListener; | ||
import javax.sql.DataSource; | ||
|
||
@WebListener |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to explain why we do this here.
cloud-sql/mysql/servlet/src/main/java/com/example/cloudsql/ConnectionPoolContextListener.java
Show resolved
Hide resolved
DataSource pool = new HikariDataSource(config); | ||
// [END cloud_sql_mysql_connection_pool] | ||
return pool; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might you want to create a Shutdown Hook to close any currently inactive instances and mark active ones to be closed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly in contextInitialized()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to skip this for now as well - we want to expand the sample to additional run-times soon and I want to avoid any run-time specific options.
cloud-sql/mysql/servlet/src/main/webapp/WEB-INF/appengine-web.xml
Outdated
Show resolved
Hide resolved
cloud-sql/mysql/servlet/src/main/webapp/WEB-INF/appengine-web.xml
Outdated
Show resolved
Hide resolved
cloud-sql/mysql/servlet/README.md
Outdated
|
||
Navigate towards `http://127.0.0.1:8080` to verify your application is running correctly. | ||
|
||
## Google AppEngine-Standard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Google AppEngine-Standard" or "Google App Engine Standard"? Want to make sure we are consistent with official naming/documentation.
pool.unwrap(HikariDataSource.class).close(); | ||
} catch (SQLException e) { | ||
// Handle exception | ||
System.out.println("Any error occurred while the application was shutting down: " + e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use logger, JUL if we don't already use anything. Always log exceptions in its own argument (so that the stacktrace will show).
private static final String DB_PASS = System.getenv("DB_PASS"); | ||
private static final String DB_NAME = System.getenv("DB_NAME"); | ||
|
||
private DataSource mysqlConnectionPool() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
method name should indicate the action, e.g. createConnectionPool
|
||
// PreparedStatements can also be executed multiple times with different arguments. This can | ||
// improve efficiency, and project a query from being vulnerable to an SQL injection. | ||
PreparedStatement voteCtStmt = conn.prepareStatement( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd encourage verbose variable names; Ct
is unclear. I believe it means voteCount
?
<div class="section"> | ||
<div class="center"> | ||
<h4> | ||
<% if(voteDiff != 0) { %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would encourage using standard taglibs, <c:if ...>
In fact, when using tags, we don't need so much <%= ... %>
scripts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you provide more context on this? When should I use a taglib over the <= ... %>
? Is <%= ... &>
unsafe or inefficient?
Is the advised taglib to use the JSTL core library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSTL core library works. The taglibs are idiomatic in the JSP world, and you don't need to use request.getAttribute()
to get the variables, since taglibs will be able to get the data directly from the attribute. It'll be cleaner, less code, and safer since it'll escape tags so you don't end up w/ script injections.
Just for example (and probably don't work as is since it's been a while since I had to write JSP :):
<c:choose>
<c:when test="${tabVoteCt == spaceVoteCt}">
TABS and SPACES are tied
</c:when>
<c:when test="${tabVoteCt > spaceVoteCt}">
TABS won
</c:when>
<c:when test="${tabVoteCt < spaceVoteCt}">
SPACES won
</c:when>
</c:choose>
To iterate through the list:
<c:forEach items="${recentVotes}" var="vote">
<li class="collection-item avatar">
<c:choose>
...
</c:choose>
</li>
</c:forEach>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the background - I've made the adjustments.
); | ||
createTableStatement.execute(); | ||
} catch (SQLException e) { | ||
throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do not throw Error. Also, don't use e.toString() or any of its string form when propagating exceptions.
It's actually better to simply propagate the SQLException upwards and declare the throw for this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the preferred way to stop the application if this context fails to initialize correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't use Error. Instead, throw a new RuntimeException
w/ the cause. throw new RuntimeException("oops", e);
try (Connection conn = pool.getConnection()) { | ||
PreparedStatement createTableStatement = conn.prepareStatement( | ||
"CREATE TABLE IF NOT EXISTS votes ( " | ||
+ "vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, canidate CHAR(6) NOT NULL, " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does canidate
mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
canidate
is the subject of the vote (e.g. tabs
or spaces
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it candidate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry yes - my bad. Fixed.
// If something goes wrong, the application needs to react appropriately. This might mean | ||
// getting a new connection and executing the query again, or it might mean redirecting the | ||
// user to a different page to let them know something went wrong. | ||
throw new ServletException("Unable to successfully connect to the database. Please check the " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls propagate e
in ServletException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IRL you'd probably log the event, maybe add to some monitor value as well before propagating.
team = team.toLowerCase(); | ||
} | ||
Timestamp now = new Timestamp(new Date().getTime()); | ||
if (team == null || !team.equals("tabs") && !team.equals("spaces")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be clear on the operation precedence, if (team == null || ("tabs".equals(team) && !"spaces".equals(team))
// If something goes wrong, handle the error in this section. This might involve retrying or | ||
// adjusting parameters depending on the situation. | ||
// [START_EXCLUDE] | ||
System.out.println("An SQL error occurred during executions: \n" + e.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comments as above regarding exception handling.
DataSource pool = (DataSource) event.getServletContext().getAttribute("my-pool"); | ||
if (pool != null) { | ||
try { | ||
pool.unwrap(HikariDataSource.class).close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we know this is already an HikariDataSource
, it should simply cast to it.
`HikariDataSource pool = (HikariDataSource) ...
voteCtStmt.setString(1, "spaces"); | ||
ResultSet spacesResult = voteCtStmt.executeQuery(); | ||
spacesResult.next(); // Move to the first result | ||
voteCt = spacesResult.getInt(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be spaceCt
?
</ul> | ||
</div> | ||
</body> | ||
<footer> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
footer
tag should be part of the body
tag. And if that's done, we should remove footer
tag altogether and simply use script
in the end.
<html>
<head></head>
<body>
...
<script>...</script>
</body>
</html>
|
||
public class Vote { | ||
|
||
private String candiate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
candidate?
…4.0 (#1231) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.cloud:libraries-bom](https://cloud.google.com/java/docs/bom) ([source](https://togithub.com/GoogleCloudPlatform/cloud-opensource-java)) | `25.3.0` -> `25.4.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/compatibility-slim/25.3.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/confidence-slim/25.3.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. ⚠ **Warning**: custom changes will be lost. --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-asset).
…4.0 (#1231) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.cloud:libraries-bom](https://cloud.google.com/java/docs/bom) ([source](https://togithub.com/GoogleCloudPlatform/cloud-opensource-java)) | `25.3.0` -> `25.4.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/compatibility-slim/25.3.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/confidence-slim/25.3.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. ⚠ **Warning**: custom changes will be lost. --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-asset).
…4.0 (#1231) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [com.google.cloud:libraries-bom](https://cloud.google.com/java/docs/bom) ([source](https://togithub.com/GoogleCloudPlatform/cloud-opensource-java)) | `25.3.0` -> `25.4.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/compatibility-slim/25.3.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/25.4.0/confidence-slim/25.3.0)](https://docs.renovatebot.com/merge-confidence/) | --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Renovate will not automatically rebase this PR, because other commits have been found. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. ⚠ **Warning**: custom changes will be lost. --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-asset).
This is the canonical sample for a new series of samples highlighting connection best practices for Cloud SQL users. It's a simple web app that allows voting between two different groups ("Tabs" vs "Spaces") and records the votes into a MySQL backend.
@saturnism PTAL