├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── cicd-jenkins.md ├── data-sources ├── README.md ├── complete │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── example │ │ │ │ └── springframework │ │ │ │ ├── SpringBootPostgreSQLApplication.java │ │ │ │ ├── commands │ │ │ │ └── ProductForm.java │ │ │ │ ├── controllers │ │ │ │ └── ProductController.java │ │ │ │ ├── converters │ │ │ │ ├── ProductFormToProduct.java │ │ │ │ └── ProductToProductForm.java │ │ │ │ ├── domain │ │ │ │ └── Product.java │ │ │ │ ├── repositories │ │ │ │ └── ProductRepository.java │ │ │ │ └── services │ │ │ │ ├── ProductService.java │ │ │ │ └── ProductServiceImpl.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── templates │ │ │ └── product │ │ │ ├── list.html │ │ │ ├── productform.html │ │ │ └── show.html │ │ └── test │ │ └── java │ │ └── example │ │ └── springframework │ │ ├── SpringBootPostgreSQLApplicationTests.java │ │ └── repositories │ │ └── ProductRepositoryTest.java └── initial │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── example │ │ │ └── springframework │ │ │ ├── SpringBootPostgreSQLApplication.java │ │ │ ├── commands │ │ │ └── ProductForm.java │ │ │ ├── controllers │ │ │ └── ProductController.java │ │ │ ├── converters │ │ │ ├── ProductFormToProduct.java │ │ │ └── ProductToProductForm.java │ │ │ ├── domain │ │ │ └── Product.java │ │ │ ├── repositories │ │ │ └── ProductRepository.java │ │ │ └── services │ │ │ ├── ProductService.java │ │ │ └── ProductServiceImpl.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ └── product │ │ ├── list.html │ │ ├── productform.html │ │ └── show.html │ └── test │ └── java │ └── example │ └── springframework │ ├── SpringBootPostgreSQLApplicationTests.java │ └── repositories │ └── ProductRepositoryTest.java ├── key-vault ├── README.md └── complete │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── example │ │ │ └── springframework │ │ │ ├── SpringBootPostgreSQLApplication.java │ │ │ ├── commands │ │ │ └── ProductForm.java │ │ │ ├── controllers │ │ │ └── ProductController.java │ │ │ ├── converters │ │ │ ├── ProductFormToProduct.java │ │ │ └── ProductToProductForm.java │ │ │ ├── domain │ │ │ └── Product.java │ │ │ ├── repositories │ │ │ └── ProductRepository.java │ │ │ └── services │ │ │ ├── ProductService.java │ │ │ └── ProductServiceImpl.java │ └── resources │ │ ├── application-dev.properties │ │ ├── application-prod.properties │ │ ├── application.properties │ │ └── templates │ │ └── product │ │ ├── list.html │ │ ├── productform.html │ │ └── show.html │ └── test │ └── java │ └── example │ └── springframework │ ├── SpringBootPostgreSQLApplicationTests.java │ └── repositories │ └── ProductRepositoryTest.java └── maven-deployment ├── README.md ├── complete ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── microsoft │ │ └── azure │ │ └── samples │ │ └── view │ │ ├── Application.java │ │ └── HelloController.java │ ├── resources │ ├── application.properties │ └── static │ │ ├── css │ │ └── main.css │ │ └── js │ │ └── main.js │ └── webapp │ └── WEB-INF │ └── jsp │ └── hello.jsp └── initial ├── pom.xml └── src └── main ├── java └── com │ └── microsoft │ └── azure │ └── samples │ └── view │ ├── Application.java │ └── HelloController.java ├── resources ├── application.properties └── static │ ├── css │ └── main.css │ └── js │ └── main.js └── webapp └── WEB-INF └── jsp └── hello.jsp /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report 10 | - [ ] documentation issue or request 11 | - [ ] regression (a behavior that used to work and stopped in a new release) 12 | ``` 13 | 14 | ### Which tutorial is this for? 15 | > 16 | 17 | ### Minimal steps to reproduce 18 | > 19 | 20 | ### Any log messages given by the failure 21 | > 22 | 23 | ### Expected/desired behavior 24 | > 25 | 26 | ### OS and Version? 27 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 28 | 29 | ### Mention any other details that might be useful 30 | 31 | > --------------------------------------------------------------- 32 | > Thanks! We'll be in touch soon. 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Code style update (formatting, local variables) 19 | [ ] Refactoring (no functional changes, no api changes) 20 | [ ] Documentation content changes 21 | [ ] Other... Please describe: 22 | ``` 23 | 24 | ## Other Information 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java ### 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # Target directory 22 | **/target/* 23 | 24 | ### end Java ### 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | 30 | ### Intellij ### 31 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 32 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 33 | 34 | # User-specific stuff 35 | .idea/**/workspace.xml 36 | .idea/**/tasks.xml 37 | .idea/**/usage.statistics.xml 38 | .idea/**/dictionaries 39 | .idea/**/shelf 40 | 41 | # Generated files 42 | .idea/**/contentModel.xml 43 | 44 | # Sensitive or high-churn files 45 | .idea/**/dataSources/ 46 | .idea/**/dataSources.ids 47 | .idea/**/dataSources.local.xml 48 | .idea/**/sqlDataSources.xml 49 | .idea/**/dynamic.xml 50 | .idea/**/uiDesigner.xml 51 | .idea/**/dbnavigator.xml 52 | 53 | # Gradle 54 | .idea/**/gradle.xml 55 | .idea/**/libraries 56 | 57 | # Gradle and Maven with auto-import 58 | # When using Gradle or Maven with auto-import, you should exclude module files, 59 | # since they will be recreated, and may cause churn. Uncomment if using 60 | # auto-import. 61 | # .idea/modules.xml 62 | # .idea/*.iml 63 | # .idea/modules 64 | 65 | # CMake 66 | cmake-build-*/ 67 | 68 | # Mongo Explorer plugin 69 | .idea/**/mongoSettings.xml 70 | 71 | # File-based project format 72 | *.iws 73 | 74 | # IntelliJ 75 | out/ 76 | 77 | # mpeltonen/sbt-idea plugin 78 | .idea_modules/ 79 | 80 | # JIRA plugin 81 | atlassian-ide-plugin.xml 82 | 83 | # Cursive Clojure plugin 84 | .idea/replstate.xml 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | 92 | # Editor-based Rest Client 93 | .idea/httpRequests 94 | 95 | # Android studio 3.1+ serialized cache file 96 | .idea/caches/build_file_checksums.ser 97 | 98 | # JetBrains templates 99 | **___jb_tmp___ 100 | 101 | ### Intellij Patch ### 102 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 103 | 104 | # *.iml 105 | # modules.xml 106 | # .idea/misc.xml 107 | # *.ipr 108 | 109 | # Sonarlint plugin 110 | .idea/sonarlint 111 | 112 | ### end IntelliJ ### 113 | 114 | ### Eclipse ### 115 | .metadata 116 | bin/ 117 | tmp/ 118 | *.tmp 119 | *.bak 120 | *.swp 121 | *~.nib 122 | local.properties 123 | .settings/ 124 | .loadpath 125 | .recommenders 126 | 127 | # External tool builders 128 | .externalToolBuilders/ 129 | 130 | # Locally stored "Eclipse launch configurations" 131 | *.launch 132 | 133 | # PyDev specific (Python IDE for Eclipse) 134 | *.pydevproject 135 | 136 | # CDT-specific (C/C++ Development Tooling) 137 | .cproject 138 | 139 | # CDT- autotools 140 | .autotools 141 | 142 | # Java annotation processor (APT) 143 | .factorypath 144 | 145 | # PDT-specific (PHP Development Tools) 146 | .buildpath 147 | 148 | # sbteclipse plugin 149 | .target 150 | 151 | # Tern plugin 152 | .tern-project 153 | 154 | # TeXlipse plugin 155 | .texlipse 156 | 157 | # STS (Spring Tool Suite) 158 | .springBeans 159 | 160 | # Code Recommenders 161 | .recommenders/ 162 | 163 | # Annotation Processing 164 | .apt_generated/ 165 | 166 | # Scala IDE specific (Scala & Java development for Eclipse) 167 | .cache-main 168 | .scala_dependencies 169 | .worksheet 170 | 171 | ### Eclipse Patch ### 172 | # Eclipse Core 173 | .project 174 | 175 | # JDT-specific (Eclipse Java Development Tools) 176 | .classpath 177 | 178 | # Annotation Processing 179 | .apt_generated 180 | 181 | .sts4-cache/ 182 | 183 | #### End of Eclipse ### 184 | 185 | ### Visual Studio Code ### 186 | .vscode/* 187 | .vscode 188 | !.vscode/settings.json 189 | !.vscode/tasks.json 190 | !.vscode/launch.json 191 | !.vscode/extensions.json 192 | 193 | ### VisualStudioCode Patch ### 194 | # Ignore all local history of files 195 | .history 196 | 197 | ### End of VS Code ### -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Java Scenarios on App Service 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - java 5 | - html 6 | products: 7 | - azure 8 | description: "Each Java project in this directory addresses a specific scenario on App Service. See the table of contents below for links to each project." 9 | urlFragment: java-on-app-service 10 | --- 11 | 12 | # Java Scenarios on App Service 13 | 14 | Each Java project in this directory addresses a specific scenario on App Service. See the table of contents below for links to each project. 15 | 16 | ## Contents 17 | 18 | 1. [Deploying with Maven](/maven-deployment) 19 | 1. Deploying from the command line 20 | 1. [Connecting to data sources](/data-sources) 21 | 1. [Using Key Vault References](/key-vault) 22 | 1. Integrating with Redis Cache 23 | 1. Outsourcing user sessions to Redis 24 | 1. Setting up an APM 25 | 1. EasyAuth and Tomcat 26 | 1. Configuring deployment slots 27 | 1. CI/CD 28 | 1. [with Jenkins](cicd-jenkins.md) 29 | 1. with Azure DevOps 30 | 1. Using the Java Flight Recorder 31 | 32 | ## Getting Started 33 | 34 | ### Prerequisites 35 | 36 | You will need the following tools installed locally to complete these tutorials. 37 | 38 | - [Maven 3](https://maven.apache.org/download.cgi) 39 | - [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) 40 | - Java 8 41 | - The [Java Extension Pack](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack) for VS Code makes it easy to download and install the JDK from a variety of JDK providers. 42 | 43 | ## Resources 44 | 45 | - [Developer Guide for Java on App Service Linux](https://docs.microsoft.com/en-us/azure/app-service/containers/app-service-linux-java) 46 | - [App Service Linux docs](https://docs.microsoft.com/en-us/azure/app-service/containers/) 47 | - [App Service Windows docs](https://docs.microsoft.com/en-us/azure/app-service/) 48 | -------------------------------------------------------------------------------- /cicd-jenkins.md: -------------------------------------------------------------------------------- 1 | # CI/CD with Jenkins 2 | 3 | Deploying to App Service from Jenkins is made easy with the App Service plugin. If you already have a Jenkins server, follow [these instructions](https://docs.microsoft.com/en-us/azure/jenkins/deploy-jenkins-app-service-plugin) to get started with the plugin. 4 | 5 | If you do not already have a Jenkins server, you can [create one from the Azure Marketplace](https://docs.microsoft.com/en-us/azure/jenkins/install-jenkins-solution-template). Once your Jenkins server is up and running, follow the previous link to configure the App Service plugin. 6 | 7 | ## Overview 8 | 9 | To use the Jenkins plugin for App Service, you will need to create a service principal (also known as an App Registration) with Contributor rights. The Jenkins plugin will use this principal to authenticate with Azure and deploy your Java app to App Service. -------------------------------------------------------------------------------- /data-sources/README.md: -------------------------------------------------------------------------------- 1 | # Configuring Data Sources 2 | 3 | These instructions will walk through the process of configuring a PostgreSQL database for a Spring Boot app on App Service Linux. Our dependencies are managed using Spring Starters, which abstract the necessary dependencies for common development scenarios (such as cache, data access, or messaging). This tutorial starts at the `initial/` directory and works towards the `complete/` directory. 4 | 5 | ## Run Locally 6 | 7 | Our Spring Boot application uses Spring Data JPA to map our Java objects to relational objects in the PostgreSQL database. Under the hood, Spring Data JPA uses Hibernate, an implementation of the JPA specification. This means we use Spring's annotations and interfaces instead of explicitly writing SQL commands in our application. This project is configured with an in-memory H2 database for local development and testing. To run the app locally, run the following commands from the `initial/` directory. 8 | 9 | ```bash 10 | mvn clean package 11 | java -jar target/app.jar 12 | ``` 13 | 14 | Open a browser to `http://localhost:8080/` and you will see a simple link to create a new product. Try creating a new product with an example description, price, and image URL. You can see the SQL commands in the terminal. If you kill the app and restart it, the contents will be lost because H2 is an embedded, in-memory database. You can see all the items in the database by browsing to `http://localhost:8080/product/list` 15 | 16 | > When we deploy to App Service, the application will instead listen on port 80. 17 | 18 | ## Provision the Server 19 | 20 | You can provision a Postgres database through the Portal by going to Create a Resource and searching for "Azure Database for PostgreSQL". In the following screen enter your desired server name, admin username, and password. 21 | 22 | Alternatively, you can use the following Azure CLI command to create the database by replacing the placeholder values. To get a full list of all the Azure locations (aka "regions"), run `az account list-locations`. Don't forget your username and password! 23 | 24 | ```bash 25 | az postgres server create -n -g --sku-name B_Gen5_1 -u -p -l centralus 26 | ``` 27 | 28 | ### Configure the Server 29 | 30 | Right now the server requires SSL to connect, does not allow access to Azure services, and does not allow access from our client machine. In the portal, navigate to the "Connection Security" portion of your PostgreSQL Server and update the following: 31 | 32 | - "Allow access to Azure services" should be set to **on**. 33 | - Click "Add client IP" (near the Save and Discard buttons). 34 | - Finally set "Enforce SSL connection" to **disabled**. 35 | 36 | Don't forget to click the save button! 37 | 38 | ### Create a Database 39 | 40 | We have created a PostgreSQL Sever, and now we need to create a database for our application to connect to. If your client computer has psql installed, you can use a local instance of psql, or the Azure Cloud Console to connect to an Azure PostgreSQL server. Let's now use the psql command-line utility to connect to the Azure Database for PostgreSQL server. 41 | 42 | 1. Run the following psql command to connect to an Azure Database for PostgreSQL database: 43 | 44 | ```bash 45 | psql --host= --port= --username=@ --dbname= 46 | ``` 47 | 48 | For example, the following command connects to the default database called postgres on your PostgreSQL server mydemoserver.postgres.database.azure.com using access credentials. Enter the `` you chose when prompted for password. 49 | 50 | ```bash 51 | psql --host=mydemoserver.postgres.database.azure.com --port=5432 --username=myadmin@mydemoserver --dbname=postgres 52 | ``` 53 | 54 | 2. Once you are connected to the server, create a blank database at the prompt: 55 | 56 | ```sql 57 | CREATE DATABASE postgres_demo; 58 | ``` 59 | 60 | If you do **not** have psql installed locally, you can run the same commands from the Azure Cloudshell in the Portal. 61 | 62 | ## Configure the App 63 | 64 | To get this application working with Azure PostgreSQL, we just need to configure Spring Data JPA via the `application.properties` file. This project already has a second Maven profile configured for use with Postgres. This allows us to build our app with a different configuration for when we deploy to App Service. 65 | 66 | ### Connection Strings 67 | 68 | It is bad practice to put connection strings in source control, so we will put this information in environment variables and inject them into our application at build time. 69 | 70 | 1. First, create the following environment variables with the corresponding values. 71 | - `POSTGRES_URL`: The full URL of the PostgreSQL database: `.postgres.database.azure.com:5432/postgres_demo?sslmode=require` 72 | - `POSTGRES_USERNAME`: The username you used appended with `@`. For example: `username@mydemoserver` 73 | - `POSTGRES_PASSWORD`: The password you used when provisioning the PostgreSQL server 74 | 75 | > See these instructions to set environment variables on [Windows](https://www.techjunkie.com/environment-variables-windows-10/) and [Mac](http://osxdaily.com/2015/07/28/set-enviornment-variables-mac-os-x/). 76 | 77 | 1. Paste the following snippet into the `production` profile of the `pom.xml`. We will use this profile to build our application before deploying to App Service. 78 | 79 | ```xml 80 | ${POSTGRES_URL} 81 | ${POSTGRES_USERNAME} 82 | ${POSTGRES_PASSWORD} 83 | ``` 84 | 85 | ## Build and Deploy 86 | 87 | We will now build our app and deploy it to App Service Linux. 88 | 89 | 1. Replace the resource group and application name in the App Service Maven plugin with your own values. (You can also change the location field to a region closer to you.) 90 | 91 | ```xml 92 | ... 93 | YOUR RESOURCE GROUP 94 | YOUR APP NAME 95 | ... 96 | ``` 97 | 98 | 1. Build your app with the "production" profile and deploy it with `mvn clean package -Pproduction`. If you navigate to `target/classes/application.properties` you should see the Postgres username, password, and URL. 99 | 100 | 1. Finally, deploy the application with `mvn azure-webapp:deploy`. In the future, you can chain these commands by running, `mvn clean package -Pproduction azure-webapp:deploy`. 101 | 102 | Browse to your application and you will see the same app now running on App Service using Azure PostgreSQL! You can confirm the application is using Postgres by querying the database with psql or pgadmin. 103 | 104 | ## Next steps 105 | 106 | Follow [this guide](../key-vault) to learn how to store your connection strings in Key Vault, thus providing easy secret management and rotation! 107 | 108 | ## Helpful Links 109 | 110 | - [Instructions for installing the Postgres certificate on your local machine](https://docs.microsoft.com/en-us/azure/postgresql/concepts-ssl-connection-security#applications-that-require-certificate-verification-for-ssl-connectivity) 111 | - [Getting Started with Azure PostgreSQL](https://docs.microsoft.com/en-us/azure/postgresql/tutorial-design-database-using-azure-cli) 112 | - [Full list of Spring Boot Starters](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-starters) 113 | - [Relationship between Spring Data JPA, JPA, and Hibernate](https://thoughts-on-java.org/what-is-spring-data-jpa-and-why-should-you-use-it/) 114 | -------------------------------------------------------------------------------- /data-sources/complete/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | example.springframework 6 | spring-boot-postgres 7 | 0.0.1-SNAPSHOT 8 | jar 9 | spring-boot-postgres 10 | 11 | Demo project for Spring Boot and PostgreSQL 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.0.0.RELEASE 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 23 | 24 | 25 | org.postgresql 26 | postgresql 27 | 9.4-1206-jdbc42 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-jpa 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-thymeleaf 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | 48 | com.h2database 49 | h2 50 | runtime 51 | 52 | 53 | 54 | 55 | 56 | development 57 | 58 | true 59 | 60 | 61 | jdbc:h2:mem:testdb 62 | sa 63 | 64 | org.h2.Driver 65 | org.hibernate.dialect.H2Dialect 66 | update 67 | 8080 68 | 69 | 70 | 71 | 72 | production 73 | 74 | ${POSTGRES_URL} 75 | ${POSTGRES_USERNAME} 76 | ${POSTGRES_PASSWORD} 77 | org.postgresql.Driver 78 | org.hibernate.dialect.PostgreSQLDialect 79 | create 80 | 80 81 | 82 | 83 | 84 | 85 | app 86 | 87 | 88 | com.microsoft.azure 89 | azure-webapp-maven-plugin 90 | 1.5.4 91 | 92 | V2 93 | example-resource-group 94 | example-app-name 95 | westus 96 | P1V2 97 | 98 | linux 99 | jre8 100 | jre8 101 | 102 | 103 | 104 | 105 | ${project.basedir}/target 106 | 107 | *.jar 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-maven-plugin 117 | 118 | 119 | 120 | 121 | 122 | spring-snapshots 123 | Spring Snapshots 124 | https://repo.spring.io/snapshot 125 | 126 | true 127 | 128 | 129 | 130 | spring-milestones 131 | Spring Milestones 132 | https://repo.spring.io/milestone 133 | 134 | false 135 | 136 | 137 | 138 | maven-central 139 | Maven Central 140 | https://mvnrepository.com/repos/central 141 | 142 | 143 | 144 | 145 | spring-snapshots 146 | Spring Snapshots 147 | https://repo.spring.io/snapshot 148 | 149 | true 150 | 151 | 152 | 153 | spring-milestones 154 | Spring Milestones 155 | https://repo.spring.io/milestone 156 | 157 | false 158 | 159 | 160 | 161 | maven-central 162 | Maven Central 163 | https://mvnrepository.com/repos/central 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/SpringBootPostgreSQLApplication.java: -------------------------------------------------------------------------------- 1 | package example.springframework; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootPostgreSQLApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootPostgreSQLApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/commands/ProductForm.java: -------------------------------------------------------------------------------- 1 | package example.springframework.commands; 2 | 3 | 4 | import java.math.BigDecimal; 5 | 6 | 7 | public class ProductForm { 8 | private Long id; 9 | private String description; 10 | private BigDecimal price; 11 | private String imageUrl; 12 | 13 | public Long getId() { 14 | return id; 15 | } 16 | 17 | public void setId(Long id) { 18 | this.id = id; 19 | } 20 | 21 | public String getDescription() { 22 | return description; 23 | } 24 | 25 | public void setDescription(String description) { 26 | this.description = description; 27 | } 28 | 29 | public BigDecimal getPrice() { 30 | return price; 31 | } 32 | 33 | public void setPrice(BigDecimal price) { 34 | this.price = price; 35 | } 36 | 37 | public String getImageUrl() { 38 | return imageUrl; 39 | } 40 | 41 | public void setImageUrl(String imageUrl) { 42 | this.imageUrl = imageUrl; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/controllers/ProductController.java: -------------------------------------------------------------------------------- 1 | package example.springframework.controllers; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.converters.ProductToProductForm; 5 | import example.springframework.domain.Product; 6 | import example.springframework.services.ProductService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | 15 | import javax.validation.Valid; 16 | 17 | @Controller 18 | public class ProductController { 19 | private ProductService productService; 20 | 21 | private ProductToProductForm productToProductForm; 22 | 23 | @Autowired 24 | public void setProductToProductForm(ProductToProductForm productToProductForm) { 25 | this.productToProductForm = productToProductForm; 26 | } 27 | 28 | @Autowired 29 | public void setProductService(ProductService productService) { 30 | this.productService = productService; 31 | } 32 | 33 | @RequestMapping("/") 34 | public String redirToList(){ 35 | return "redirect:/product/list"; 36 | } 37 | 38 | @RequestMapping({"/product/list", "/product"}) 39 | public String listProducts(Model model){ 40 | model.addAttribute("products", productService.listAll()); 41 | return "product/list"; 42 | } 43 | 44 | @RequestMapping("/product/show/{id}") 45 | public String getProduct(@PathVariable String id, Model model){ 46 | model.addAttribute("product", productService.getById(Long.valueOf(id))); 47 | return "product/show"; 48 | } 49 | 50 | @RequestMapping("product/edit/{id}") 51 | public String edit(@PathVariable String id, Model model){ 52 | Product product = productService.getById(Long.valueOf(id)); 53 | ProductForm productForm = productToProductForm.convert(product); 54 | 55 | model.addAttribute("productForm", productForm); 56 | return "product/productform"; 57 | } 58 | 59 | @RequestMapping("/product/new") 60 | public String newProduct(Model model){ 61 | model.addAttribute("productForm", new ProductForm()); 62 | return "product/productform"; 63 | } 64 | 65 | @RequestMapping(value = "/product", method = RequestMethod.POST) 66 | public String saveOrUpdateProduct(@Valid ProductForm productForm, BindingResult bindingResult){ 67 | 68 | if(bindingResult.hasErrors()){ 69 | return "product/productform"; 70 | } 71 | 72 | Product savedProduct = productService.saveOrUpdateProductForm(productForm); 73 | 74 | return "redirect:/product/show/" + savedProduct.getId(); 75 | } 76 | 77 | @RequestMapping("/product/delete/{id}") 78 | public String delete(@PathVariable String id){ 79 | productService.delete(Long.valueOf(id)); 80 | return "redirect:/product/list"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/converters/ProductFormToProduct.java: -------------------------------------------------------------------------------- 1 | package example.springframework.converters; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.StringUtils; 8 | 9 | @Component 10 | public class ProductFormToProduct implements Converter { 11 | 12 | @Override 13 | public Product convert(ProductForm productForm) { 14 | Product product = new Product(); 15 | if (productForm.getId() != null && !StringUtils.isEmpty(productForm.getId())) { 16 | product.setId(new Long(productForm.getId())); 17 | } 18 | product.setDescription(productForm.getDescription()); 19 | product.setPrice(productForm.getPrice()); 20 | product.setImageUrl(productForm.getImageUrl()); 21 | return product; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/converters/ProductToProductForm.java: -------------------------------------------------------------------------------- 1 | package example.springframework.converters; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class ProductToProductForm implements Converter { 10 | @Override 11 | public ProductForm convert(Product product) { 12 | ProductForm productForm = new ProductForm(); 13 | productForm.setId(product.getId()); 14 | productForm.setDescription(product.getDescription()); 15 | productForm.setPrice(product.getPrice()); 16 | productForm.setImageUrl(product.getImageUrl()); 17 | return productForm; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/domain/Product.java: -------------------------------------------------------------------------------- 1 | package example.springframework.domain; 2 | 3 | 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import java.math.BigDecimal; 9 | 10 | 11 | @Entity 12 | public class Product { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long _id; 17 | private String description; 18 | private BigDecimal price; 19 | private String imageUrl; 20 | 21 | public Long getId() { 22 | return _id; 23 | } 24 | 25 | public void setId(Long id) { 26 | this._id = id; 27 | } 28 | 29 | public String getDescription() { 30 | return description; 31 | } 32 | 33 | public void setDescription(String description) { 34 | this.description = description; 35 | } 36 | 37 | public BigDecimal getPrice() { 38 | return price; 39 | } 40 | 41 | public void setPrice(BigDecimal price) { 42 | this.price = price; 43 | } 44 | 45 | public String getImageUrl() { 46 | return imageUrl; 47 | } 48 | 49 | public void setImageUrl(String imageUrl) { 50 | this.imageUrl = imageUrl; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/repositories/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package example.springframework.repositories; 2 | 3 | import example.springframework.domain.Product; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface ProductRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/services/ProductService.java: -------------------------------------------------------------------------------- 1 | package example.springframework.services; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | 6 | import java.util.List; 7 | 8 | public interface ProductService { 9 | 10 | List listAll(); 11 | 12 | Product getById(Long id); 13 | 14 | Product saveOrUpdate(Product product); 15 | 16 | void delete(Long id); 17 | 18 | Product saveOrUpdateProductForm(ProductForm productForm); 19 | } 20 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/java/example/springframework/services/ProductServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.springframework.services; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.converters.ProductFormToProduct; 5 | import example.springframework.domain.Product; 6 | import example.springframework.repositories.ProductRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Service 14 | public class ProductServiceImpl implements ProductService { 15 | 16 | private ProductRepository productRepository; 17 | private ProductFormToProduct productFormToProduct; 18 | 19 | @Autowired 20 | public ProductServiceImpl(ProductRepository productRepository, ProductFormToProduct productFormToProduct) { 21 | this.productRepository = productRepository; 22 | this.productFormToProduct = productFormToProduct; 23 | } 24 | 25 | 26 | @Override 27 | public List listAll() { 28 | List products = new ArrayList<>(); 29 | productRepository.findAll().forEach(products::add); //fun with Java 8 30 | return products; 31 | } 32 | 33 | @Override 34 | public Product getById(Long id) { 35 | return productRepository.findById(id).orElse(null); 36 | } 37 | 38 | @Override 39 | public Product saveOrUpdate(Product product) { 40 | productRepository.save(product); 41 | return product; 42 | } 43 | 44 | @Override 45 | public void delete(Long id) { 46 | productRepository.deleteById(id); 47 | 48 | } 49 | 50 | @Override 51 | public Product saveOrUpdateProductForm(ProductForm productForm) { 52 | Product savedProduct = saveOrUpdate(productFormToProduct.convert(productForm)); 53 | 54 | System.out.println("Saved Product Id: " + savedProduct.getId()); 55 | return savedProduct; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # =============================== 2 | # = DATA SOURCE 3 | # =============================== 4 | # Set here configurations for the database connection 5 | spring.datasource.url=@spring.datasource.url@ 6 | spring.datasource.username=@spring.datasource.username@ 7 | spring.datasource.password=@spring.datasource.password@ 8 | spring.datasource.driver-class-name=@spring.datasource.driver-class-name@ 9 | 10 | # Keep the connection alive if idle for a long time (needed in production) 11 | spring.datasource.testWhileIdle=true 12 | spring.datasource.validationQuery=SELECT 1 13 | 14 | # =============================== 15 | # = JPA / HIBERNATE 16 | # =============================== 17 | # Show or not log for each sql query 18 | spring.jpa.show-sql=true 19 | 20 | # Hibernate ddl auto (create, create-drop, update): with "create-drop" the database 21 | # schema will be automatically created afresh for every start of application 22 | spring.jpa.hibernate.ddl-auto=create 23 | 24 | # Naming strategy 25 | spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl 26 | spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 27 | 28 | # Allows Hibernate to generate SQL optimized for a particular DBMS 29 | spring.jpa.properties.hibernate.dialect=@spring.jpa.properties.hibernate.dialect@ 30 | 31 | # App Service 32 | server.port=@server.port@ -------------------------------------------------------------------------------- /data-sources/complete/src/main/resources/templates/product/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - List Products 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 |
20 |

Product List

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
IdDescriptionPriceImage URLListEditDelete
View Edit Delete
41 |
42 |
43 |
44 | New Product 45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/resources/templates/product/productform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - Product Form 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 | 20 |

Product Details

21 |
22 |
23 | 24 |
25 |

Error Message

26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
    37 |
  • 38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 |
    50 |
  • 51 |
52 |
53 |
54 |
55 | 56 |
57 | 58 |
59 | 60 | 61 | 62 |
    63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /data-sources/complete/src/main/resources/templates/product/show.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - Show Product 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |

Show Product

23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |

Product Id

32 |
33 |
34 |
35 | 36 |
37 |

Description

38 |
39 |
40 |
41 | 42 |
43 |

Price

44 |
45 |
46 |
47 | 48 |
49 |

Image

50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /data-sources/complete/src/test/java/example/springframework/SpringBootPostgreSQLApplicationTests.java: -------------------------------------------------------------------------------- 1 | package example.springframework; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringBootPostgreSQLApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /data-sources/complete/src/test/java/example/springframework/repositories/ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package example.springframework.repositories; 2 | 3 | import example.springframework.domain.Product; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import java.math.BigDecimal; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringBootTest 16 | public class ProductRepositoryTest { 17 | 18 | private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100.00); 19 | private static final String PRODUCT_DESCRIPTION = "a cool product"; 20 | private static final String IMAGE_URL = "http://an-imageurl.com/image1.jpg"; 21 | 22 | @Autowired 23 | private ProductRepository productRepository; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | 28 | } 29 | 30 | @Test 31 | public void testPersistence() { 32 | //given 33 | Product product = new Product(); 34 | product.setDescription(PRODUCT_DESCRIPTION); 35 | product.setImageUrl(IMAGE_URL); 36 | product.setPrice(BIG_DECIMAL_100); 37 | 38 | //when 39 | productRepository.save(product); 40 | 41 | //then 42 | Assert.assertNotNull(product.getId()); 43 | Product newProduct = productRepository.findById(product.getId()).orElse(null); 44 | Assert.assertEquals((Long) 1L, newProduct.getId()); 45 | Assert.assertEquals(PRODUCT_DESCRIPTION, newProduct.getDescription()); 46 | Assert.assertEquals(BIG_DECIMAL_100.compareTo(newProduct.getPrice()), 0); 47 | Assert.assertEquals(IMAGE_URL, newProduct.getImageUrl()); 48 | } 49 | } -------------------------------------------------------------------------------- /data-sources/initial/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | example.springframework 6 | spring-boot-postgres 7 | 0.0.1-SNAPSHOT 8 | jar 9 | spring-boot-postgres 10 | 11 | Demo project for Spring Boot and PostgreSQL 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.0.0.RELEASE 17 | 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 23 | 24 | 25 | org.postgresql 26 | postgresql 27 | 9.4-1206-jdbc42 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-jpa 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-thymeleaf 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | 48 | com.h2database 49 | h2 50 | runtime 51 | 52 | 53 | 54 | 55 | 57 | development 58 | 59 | true 60 | 61 | 62 | jdbc:h2:mem:testdb 63 | sa 64 | 65 | org.h2.Driver 66 | org.hibernate.dialect.H2Dialect 67 | update 68 | 8080 69 | 70 | 71 | 72 | 73 | production 74 | 75 | 76 | org.postgresql.Driver 77 | org.hibernate.dialect.PostgreSQLDialect 78 | create 79 | 80 80 | 81 | 82 | 83 | 84 | app 85 | 86 | 87 | com.microsoft.azure 88 | azure-webapp-maven-plugin 89 | 1.5.4 90 | 91 | V2 92 | YOUR RESOURCE GROUP 93 | YOUR APP NAME 94 | westus 95 | P1V2 96 | 97 | linux 98 | jre8 99 | jre8 100 | 101 | 102 | 103 | 104 | ${project.basedir}/target 105 | 106 | *.jar 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.springframework.boot 115 | spring-boot-maven-plugin 116 | 117 | 118 | 119 | 120 | 121 | spring-snapshots 122 | Spring Snapshots 123 | https://repo.spring.io/snapshot 124 | 125 | true 126 | 127 | 128 | 129 | spring-milestones 130 | Spring Milestones 131 | https://repo.spring.io/milestone 132 | 133 | false 134 | 135 | 136 | 137 | maven-central 138 | Maven Central 139 | https://mvnrepository.com/repos/central 140 | 141 | 142 | 143 | 144 | spring-snapshots 145 | Spring Snapshots 146 | https://repo.spring.io/snapshot 147 | 148 | true 149 | 150 | 151 | 152 | spring-milestones 153 | Spring Milestones 154 | https://repo.spring.io/milestone 155 | 156 | false 157 | 158 | 159 | 160 | maven-central 161 | Maven Central 162 | https://mvnrepository.com/repos/central 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/SpringBootPostgreSQLApplication.java: -------------------------------------------------------------------------------- 1 | package example.springframework; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootPostgreSQLApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootPostgreSQLApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/commands/ProductForm.java: -------------------------------------------------------------------------------- 1 | package example.springframework.commands; 2 | 3 | 4 | import java.math.BigDecimal; 5 | 6 | 7 | public class ProductForm { 8 | private Long id; 9 | private String description; 10 | private BigDecimal price; 11 | private String imageUrl; 12 | 13 | public Long getId() { 14 | return id; 15 | } 16 | 17 | public void setId(Long id) { 18 | this.id = id; 19 | } 20 | 21 | public String getDescription() { 22 | return description; 23 | } 24 | 25 | public void setDescription(String description) { 26 | this.description = description; 27 | } 28 | 29 | public BigDecimal getPrice() { 30 | return price; 31 | } 32 | 33 | public void setPrice(BigDecimal price) { 34 | this.price = price; 35 | } 36 | 37 | public String getImageUrl() { 38 | return imageUrl; 39 | } 40 | 41 | public void setImageUrl(String imageUrl) { 42 | this.imageUrl = imageUrl; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/controllers/ProductController.java: -------------------------------------------------------------------------------- 1 | package example.springframework.controllers; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.converters.ProductToProductForm; 5 | import example.springframework.domain.Product; 6 | import example.springframework.services.ProductService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | 15 | import javax.validation.Valid; 16 | 17 | @Controller 18 | public class ProductController { 19 | private ProductService productService; 20 | 21 | private ProductToProductForm productToProductForm; 22 | 23 | @Autowired 24 | public void setProductToProductForm(ProductToProductForm productToProductForm) { 25 | this.productToProductForm = productToProductForm; 26 | } 27 | 28 | @Autowired 29 | public void setProductService(ProductService productService) { 30 | this.productService = productService; 31 | } 32 | 33 | @RequestMapping("/") 34 | public String redirToList(){ 35 | return "redirect:/product/list"; 36 | } 37 | 38 | @RequestMapping({"/product/list", "/product"}) 39 | public String listProducts(Model model){ 40 | model.addAttribute("products", productService.listAll()); 41 | return "product/list"; 42 | } 43 | 44 | @RequestMapping("/product/show/{id}") 45 | public String getProduct(@PathVariable String id, Model model){ 46 | model.addAttribute("product", productService.getById(Long.valueOf(id))); 47 | return "product/show"; 48 | } 49 | 50 | @RequestMapping("product/edit/{id}") 51 | public String edit(@PathVariable String id, Model model){ 52 | Product product = productService.getById(Long.valueOf(id)); 53 | ProductForm productForm = productToProductForm.convert(product); 54 | 55 | model.addAttribute("productForm", productForm); 56 | return "product/productform"; 57 | } 58 | 59 | @RequestMapping("/product/new") 60 | public String newProduct(Model model){ 61 | model.addAttribute("productForm", new ProductForm()); 62 | return "product/productform"; 63 | } 64 | 65 | @RequestMapping(value = "/product", method = RequestMethod.POST) 66 | public String saveOrUpdateProduct(@Valid ProductForm productForm, BindingResult bindingResult){ 67 | 68 | if(bindingResult.hasErrors()){ 69 | return "product/productform"; 70 | } 71 | 72 | Product savedProduct = productService.saveOrUpdateProductForm(productForm); 73 | 74 | return "redirect:/product/show/" + savedProduct.getId(); 75 | } 76 | 77 | @RequestMapping("/product/delete/{id}") 78 | public String delete(@PathVariable String id){ 79 | productService.delete(Long.valueOf(id)); 80 | return "redirect:/product/list"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/converters/ProductFormToProduct.java: -------------------------------------------------------------------------------- 1 | package example.springframework.converters; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.StringUtils; 8 | 9 | @Component 10 | public class ProductFormToProduct implements Converter { 11 | 12 | @Override 13 | public Product convert(ProductForm productForm) { 14 | Product product = new Product(); 15 | if (productForm.getId() != null && !StringUtils.isEmpty(productForm.getId())) { 16 | product.setId(new Long(productForm.getId())); 17 | } 18 | product.setDescription(productForm.getDescription()); 19 | product.setPrice(productForm.getPrice()); 20 | product.setImageUrl(productForm.getImageUrl()); 21 | return product; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/converters/ProductToProductForm.java: -------------------------------------------------------------------------------- 1 | package example.springframework.converters; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class ProductToProductForm implements Converter { 10 | @Override 11 | public ProductForm convert(Product product) { 12 | ProductForm productForm = new ProductForm(); 13 | productForm.setId(product.getId()); 14 | productForm.setDescription(product.getDescription()); 15 | productForm.setPrice(product.getPrice()); 16 | productForm.setImageUrl(product.getImageUrl()); 17 | return productForm; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/domain/Product.java: -------------------------------------------------------------------------------- 1 | package example.springframework.domain; 2 | 3 | 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import java.math.BigDecimal; 9 | 10 | 11 | @Entity 12 | public class Product { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long _id; 17 | private String description; 18 | private BigDecimal price; 19 | private String imageUrl; 20 | 21 | public Long getId() { 22 | return _id; 23 | } 24 | 25 | public void setId(Long id) { 26 | this._id = id; 27 | } 28 | 29 | public String getDescription() { 30 | return description; 31 | } 32 | 33 | public void setDescription(String description) { 34 | this.description = description; 35 | } 36 | 37 | public BigDecimal getPrice() { 38 | return price; 39 | } 40 | 41 | public void setPrice(BigDecimal price) { 42 | this.price = price; 43 | } 44 | 45 | public String getImageUrl() { 46 | return imageUrl; 47 | } 48 | 49 | public void setImageUrl(String imageUrl) { 50 | this.imageUrl = imageUrl; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/repositories/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package example.springframework.repositories; 2 | 3 | import example.springframework.domain.Product; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface ProductRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/services/ProductService.java: -------------------------------------------------------------------------------- 1 | package example.springframework.services; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | 6 | import java.util.List; 7 | 8 | public interface ProductService { 9 | 10 | List listAll(); 11 | 12 | Product getById(Long id); 13 | 14 | Product saveOrUpdate(Product product); 15 | 16 | void delete(Long id); 17 | 18 | Product saveOrUpdateProductForm(ProductForm productForm); 19 | } 20 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/java/example/springframework/services/ProductServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.springframework.services; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.converters.ProductFormToProduct; 5 | import example.springframework.domain.Product; 6 | import example.springframework.repositories.ProductRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Service 14 | public class ProductServiceImpl implements ProductService { 15 | 16 | private ProductRepository productRepository; 17 | private ProductFormToProduct productFormToProduct; 18 | 19 | @Autowired 20 | public ProductServiceImpl(ProductRepository productRepository, ProductFormToProduct productFormToProduct) { 21 | this.productRepository = productRepository; 22 | this.productFormToProduct = productFormToProduct; 23 | } 24 | 25 | 26 | @Override 27 | public List listAll() { 28 | List products = new ArrayList<>(); 29 | productRepository.findAll().forEach(products::add); //fun with Java 8 30 | return products; 31 | } 32 | 33 | @Override 34 | public Product getById(Long id) { 35 | return productRepository.findById(id).orElse(null); 36 | } 37 | 38 | @Override 39 | public Product saveOrUpdate(Product product) { 40 | productRepository.save(product); 41 | return product; 42 | } 43 | 44 | @Override 45 | public void delete(Long id) { 46 | productRepository.deleteById(id); 47 | 48 | } 49 | 50 | @Override 51 | public Product saveOrUpdateProductForm(ProductForm productForm) { 52 | Product savedProduct = saveOrUpdate(productFormToProduct.convert(productForm)); 53 | 54 | System.out.println("Saved Product Id: " + savedProduct.getId()); 55 | return savedProduct; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # =============================== 2 | # = DATA SOURCE 3 | # =============================== 4 | # Set here configurations for the database connection 5 | spring.datasource.url=@spring.datasource.url@ 6 | spring.datasource.username=@spring.datasource.username@ 7 | spring.datasource.password=@spring.datasource.password@ 8 | spring.datasource.driver-class-name=@spring.datasource.driver-class-name@ 9 | 10 | # Keep the connection alive if idle for a long time (needed in production) 11 | spring.datasource.testWhileIdle=true 12 | spring.datasource.validationQuery=SELECT 1 13 | 14 | # =============================== 15 | # = JPA / HIBERNATE 16 | # =============================== 17 | # Show or not log for each sql query 18 | spring.jpa.show-sql=true 19 | 20 | # Hibernate ddl auto (create, create-drop, update): with "create-drop" the database 21 | # schema will be automatically created afresh for every start of application 22 | spring.jpa.hibernate.ddl-auto=create 23 | 24 | # Naming strategy 25 | spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl 26 | spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 27 | 28 | # Allows Hibernate to generate SQL optimized for a particular DBMS 29 | spring.jpa.properties.hibernate.dialect=@spring.jpa.properties.hibernate.dialect@ 30 | 31 | # App Service 32 | server.port=@server.port@ -------------------------------------------------------------------------------- /data-sources/initial/src/main/resources/templates/product/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - List Products 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 |
20 |

Product List

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
IdDescriptionPriceImage URLListEditDelete
View Edit Delete
41 |
42 |
43 |
44 | New Product 45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/resources/templates/product/productform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - Product Form 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 | 20 |

Product Details

21 |
22 |
23 | 24 |
25 |

Error Message

26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
    37 |
  • 38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 |
    50 |
  • 51 |
52 |
53 |
54 |
55 | 56 |
57 | 58 |
59 | 60 | 61 | 62 |
    63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /data-sources/initial/src/main/resources/templates/product/show.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - Show Product 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |

Show Product

23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |

Product Id

32 |
33 |
34 |
35 | 36 |
37 |

Description

38 |
39 |
40 |
41 | 42 |
43 |

Price

44 |
45 |
46 |
47 | 48 |
49 |

Image

50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /data-sources/initial/src/test/java/example/springframework/SpringBootPostgreSQLApplicationTests.java: -------------------------------------------------------------------------------- 1 | package example.springframework; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringBootPostgreSQLApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /data-sources/initial/src/test/java/example/springframework/repositories/ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package example.springframework.repositories; 2 | 3 | import example.springframework.domain.Product; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import java.math.BigDecimal; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringBootTest 16 | public class ProductRepositoryTest { 17 | 18 | private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100.00); 19 | private static final String PRODUCT_DESCRIPTION = "a cool product"; 20 | private static final String IMAGE_URL = "http://an-imageurl.com/image1.jpg"; 21 | 22 | @Autowired 23 | private ProductRepository productRepository; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | 28 | } 29 | 30 | @Test 31 | public void testPersistence() { 32 | //given 33 | Product product = new Product(); 34 | product.setDescription(PRODUCT_DESCRIPTION); 35 | product.setImageUrl(IMAGE_URL); 36 | product.setPrice(BIG_DECIMAL_100); 37 | 38 | //when 39 | productRepository.save(product); 40 | 41 | //then 42 | Assert.assertNotNull(product.getId()); 43 | Product newProduct = productRepository.findById(product.getId()).orElse(null); 44 | Assert.assertEquals((Long) 1L, newProduct.getId()); 45 | Assert.assertEquals(PRODUCT_DESCRIPTION, newProduct.getDescription()); 46 | Assert.assertEquals(BIG_DECIMAL_100.compareTo(newProduct.getPrice()), 0); 47 | Assert.assertEquals(IMAGE_URL, newProduct.getImageUrl()); 48 | } 49 | } -------------------------------------------------------------------------------- /key-vault/README.md: -------------------------------------------------------------------------------- 1 | # Using KeyVault References 2 | 3 | This tutorial will walk through how to to use Key Vault references in our Java application without modifying our [data source example app](../data-sources) or adding any dependencies. Using Key Vault to store our secrets (such as database connection strings) give us a centralized storage location with management tools like role-based access control, secrets rotation, and encryption. 4 | 5 | ## Prerequisites 6 | 7 | This tutorial continues from the [data source configuration](../data-sources) example. You will need to complete that tutorial before doing this one. 8 | 9 | ## Setting up a Managed Identity 10 | 11 | A system-assigned identity is tied to your application and is deleted if your app is deleted. An app can only have one system-assigned identity. System-assigned identity support is generally available for Windows apps. 12 | 13 | ```bash 14 | az webapp identity assign --name --resource-group 15 | ``` 16 | 17 | Save the `principalId` 18 | 19 | ## Provisioning the Key Vault 20 | 21 | 1. Let's spin up a Key Vault named `java-app-key-vault`. For information about the parameters specified below, run `az keyvault create --help`. 22 | 23 | ```bash 24 | az keyvault create --name java-app-key-vault \ 25 | --resource-group \ 26 | --location \ 27 | --enabled-for-deployment true \ 28 | --enabled-for-disk-encryption true \ 29 | --enabled-for-template-deployment true \ 30 | --sku standard 31 | ``` 32 | 33 | 1. Give the System assigned identity `get` and `list` access to the Key Vault 34 | 35 | ```bash 36 | az keyvault set-policy --name java-app-key-vault \ 37 | --secret-permission get list \ 38 | --object-id 39 | ``` 40 | 41 | 1. Now we will add the Postgres username, password, and URL to the Key Vault. If you followed the tutorial on data sources, you should still have the secrets saved as environment variables on your machine. (If you are using Powershell, use the `$env:ENV_VAR` syntax to inject the environment variable into the command). 42 | 43 | ```bash 44 | az keyvault secret set --name POSTGRES-USERNAME \ 45 | --value $POSTGRES_USERNAME \ 46 | --vault-name java-app-key-vault 47 | az keyvault secret set --name POSTGRES-PASSWORD \ 48 | --value $POSTGRES_PASSWORD \ 49 | --vault-name java-app-key-vault 50 | az keyvault secret set --name POSTGRES-URL \ 51 | --value $POSTGRES_URL \ 52 | --vault-name java-app-key-vault 53 | ``` 54 | 55 | ## Configuring our App 56 | 57 | ### Key Vault References 58 | 59 | Our Spring app will be able to access these secrets when deployed on App Service through [Application Settings](https://docs.microsoft.com/en-us/azure/app-service/web-sites-configure#app-settings). We will now configure the App Service plugin to set the Application Settings when we deploy the app. 60 | 61 | 1. First, we need the URI's of our three secrets. Run the commands below and copy the `id` value in each. 62 | 63 | ```bash 64 | az keyvault secret show --vault-name java-app-key-vault --name POSTGRES-URL 65 | az keyvault secret show --vault-name java-app-key-vault --name POSTGRES-USERNAME 66 | az keyvault secret show --vault-name java-app-key-vault --name POSTGRES-PASSWORD 67 | ``` 68 | 69 | 1. Add the following to the Azure App Service plugin section of the `pom.xml`. Adding this configuration will create Application Settings with the given name and value. The value of each setting should be a Key Vault reference with the corresponding `id` from the previous step. 70 | 71 | ```xml 72 | 73 | 74 | SPRING_DATASOURCE_URL 75 | @Microsoft.KeyVault(SecretUri=YOUR_SECRET_UIR) 76 | 77 | 78 | SPRING_DATASOURCE_USERNAME 79 | @Microsoft.KeyVault(SecretUri=YOUR_SECRET_UIR) 80 | 81 | 82 | SPRING_DATASOURCE_PASSWORD 83 | @Microsoft.KeyVault(SecretUri=YOUR_SECRET_UIR) 84 | 85 | 86 | ``` 87 | 88 | A Key Vault reference is of the form `@Microsoft.KeyVault(SecretUri=)`, where `` is data-plane URI of a secret in Key Vault, including a version. There is an alternate syntax [documented here](https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references#reference-syntax). 89 | 90 | ### Environment Configuration 91 | 92 | The Key Vault references will be replaced with the actual secrets when our App Service boots up. This means our Spring Application needs to resolve the connection strings at runtime, but currently gets these strings at build time. We also want to be able to use our H2 database for development, and optionally connect to the production DB from our local machine to run tests. To fill all these requirements, we will create two new configuration files: `application-dev.properties`, and `application-prod.properties`. 93 | 94 | 1. Create a file under `src/main/resources` named `application-dev.properties`. Copy/paste the following into the file: 95 | 96 | ```txt 97 | # =============================== 98 | # = DATA SOURCE 99 | # =============================== 100 | # Set here configurations for the database connection 101 | spring.datasource.url=jdbc:h2:mem:testdb 102 | spring.datasource.username=sa 103 | spring.datasource.password= 104 | spring.datasource.driver-class-name=org.h2.Driver 105 | 106 | # =============================== 107 | # = JPA / HIBERNATE 108 | # =============================== 109 | 110 | # Allows Hibernate to generate SQL optimized for a particular DBMS 111 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 112 | 113 | # App Service 114 | server.port=8080 115 | ``` 116 | 117 | 1. Create a file under `src/main/resources` named `application-dev.properties`. Copy/paste the following into the file. We do not set the connection strings here. Instead, Spring will resolve them at runtime by looking for the uppercase and underscored versions of `spring.datasource.url`, `spring.datasource.username`, and `spring.datasource.password`. 118 | 119 | ```txt 120 | # =============================== 121 | # = DATA SOURCE 122 | # =============================== 123 | 124 | # The connection URL, username, and password will be sourced from environment variables 125 | # on App Service 126 | 127 | # Set here configurations for the database connection 128 | spring.datasource.driver-class-name=org.postgresql.Driver 129 | 130 | # =============================== 131 | # = JPA / HIBERNATE 132 | # =============================== 133 | 134 | # Allows Hibernate to generate SQL optimized for a particular DBMS 135 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 136 | 137 | # App Service 138 | server.port=80 139 | ``` 140 | 141 | 1. Now we can slim-down our original `application.properties` file. Replace the contents of `application.properties` with the following. 142 | 143 | ```txt 144 | # Active profile is set by Maven 145 | spring.profiles.active=@spring.profiles.active@ 146 | 147 | # =============================== 148 | # = DATA SOURCE 149 | # =============================== 150 | 151 | # Keep the connection alive if idle for a long time (needed in production) 152 | spring.datasource.testWhileIdle=true 153 | spring.datasource.validationQuery=SELECT 1 154 | 155 | # =============================== 156 | # = JPA / HIBERNATE 157 | # =============================== 158 | # Show or not log for each sql query 159 | spring.jpa.show-sql=true 160 | 161 | # Hibernate ddl auto (create, create-drop, update): with "create-drop" the database 162 | # schema will be automatically created afresh for every start of application 163 | spring.jpa.hibernate.ddl-auto=create 164 | 165 | # Naming strategy 166 | spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl 167 | spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 168 | ``` 169 | 170 | 1. Finally, we can also slim down our Maven profiles because we have moved th information to the new properties files. The profile section of your `pom.xml` should now be the following: 171 | 172 | ```xml 173 | 174 | 175 | 176 | dev 177 | 178 | true 179 | 180 | 181 | dev 182 | 183 | 184 | 185 | 186 | prod 187 | 188 | prod 189 | 190 | 191 | 192 | ``` 193 | 194 | ## Deploy and Test 195 | 196 | Check that the development profile works as expected by running the following commands and opening a browser to `http://localhost:8080/`. 197 | 198 | ```bash 199 | mvn clean package -Pdev 200 | java -jar target/app.jar 201 | ``` 202 | 203 | Before deploying to App Service, you can build your application with the production profile and test against your PostgreSQL DB from your local machine. To do so, simply rename the three environment variables beginning with `POSTGRES_` and rename them to `SPRING_DATASOURCE_URL`, `SPRING_DATASOURCE_USERNAME`, and `SPRING_DATASOURCE_PASSWORD` respectively. Run the following commands to build and start your app. Spring will resolve the connection strings in the environment variables at runtime. 204 | 205 | ```BASH 206 | mvn clean package -Pprod 207 | java -jar target/app.jar 208 | ``` 209 | 210 | > If you did not rename your local environment variables, skip the tests with `mvn clean package -Pprod -DskipTests` 211 | 212 | Finally, deploy the production app to App Service with `mvn azure-webapp:deploy`. Browse to the application and test that it works properly. 213 | 214 | ## Next Steps 215 | 216 | - Azure SDK 217 | 218 | ## Helpful Links 219 | 220 | - [You can use the Azure Spring Boot Stater to accomplish this scenario as well](https://github.com/microsoft/azure-spring-boot/tree/master/azure-spring-boot-samples/azure-keyvault-secrets-spring-boot-sample) -------------------------------------------------------------------------------- /key-vault/complete/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | guru.springframework 6 | spring-boot-postgres 7 | 0.0.1-SNAPSHOT 8 | jar 9 | spring-boot-postgres 10 | 11 | Demo project for Spring Boot and PostgreSQL 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.0.0.RELEASE 17 | 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 24 | 25 | 26 | org.postgresql 27 | postgresql 28 | 9.4-1206-jdbc42 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-data-jpa 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-thymeleaf 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-test 45 | test 46 | 47 | 48 | 49 | com.h2database 50 | h2 51 | runtime 52 | 53 | 54 | 55 | 56 | 57 | dev 58 | 59 | true 60 | 61 | 62 | dev 63 | 64 | 65 | 66 | 67 | prod 68 | 69 | prod 70 | 71 | 72 | 73 | 74 | app 75 | 76 | 77 | src/main/resources 78 | true 79 | 80 | 81 | 82 | 83 | 84 | com.microsoft.azure 85 | azure-webapp-maven-plugin 86 | 1.5.4 87 | 88 | V2 89 | YOUR RESOURCE GROUP 90 | YOUR RESOURCE GRUOP 91 | westus 92 | P1V2 93 | 94 | linux 95 | jre8 96 | jre8 97 | 98 | 99 | 100 | 101 | ${project.basedir}/target 102 | 103 | *.jar 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | SPRING_DATASOURCE_URL 112 | @Microsoft.KeyVault(SecretUri=YOUR_SECRET_UIR) 113 | 114 | 115 | SPRING_DATASOURCE_USERNAME 116 | @Microsoft.KeyVault(SecretUri=YOUR_SECRET_UIR) 117 | 118 | 119 | SPRING_DATASOURCE_PASSWORD 120 | @Microsoft.KeyVault(SecretUri=YOUR_SECRET_UIR) 121 | 122 | 123 | 124 | 125 | 126 | org.springframework.boot 127 | spring-boot-maven-plugin 128 | 129 | 130 | 131 | 132 | 133 | spring-snapshots 134 | Spring Snapshots 135 | https://repo.spring.io/snapshot 136 | 137 | true 138 | 139 | 140 | 141 | spring-milestones 142 | Spring Milestones 143 | https://repo.spring.io/milestone 144 | 145 | false 146 | 147 | 148 | 149 | maven-central 150 | Maven Central 151 | https://mvnrepository.com/repos/central 152 | 153 | 154 | 155 | 156 | spring-snapshots 157 | Spring Snapshots 158 | https://repo.spring.io/snapshot 159 | 160 | true 161 | 162 | 163 | 164 | spring-milestones 165 | Spring Milestones 166 | https://repo.spring.io/milestone 167 | 168 | false 169 | 170 | 171 | 172 | maven-central 173 | Maven Central 174 | https://mvnrepository.com/repos/central 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/SpringBootPostgreSQLApplication.java: -------------------------------------------------------------------------------- 1 | package example.springframework; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootPostgreSQLApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootPostgreSQLApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/commands/ProductForm.java: -------------------------------------------------------------------------------- 1 | package example.springframework.commands; 2 | 3 | 4 | import java.math.BigDecimal; 5 | 6 | 7 | public class ProductForm { 8 | private Long id; 9 | private String description; 10 | private BigDecimal price; 11 | private String imageUrl; 12 | 13 | public Long getId() { 14 | return id; 15 | } 16 | 17 | public void setId(Long id) { 18 | this.id = id; 19 | } 20 | 21 | public String getDescription() { 22 | return description; 23 | } 24 | 25 | public void setDescription(String description) { 26 | this.description = description; 27 | } 28 | 29 | public BigDecimal getPrice() { 30 | return price; 31 | } 32 | 33 | public void setPrice(BigDecimal price) { 34 | this.price = price; 35 | } 36 | 37 | public String getImageUrl() { 38 | return imageUrl; 39 | } 40 | 41 | public void setImageUrl(String imageUrl) { 42 | this.imageUrl = imageUrl; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/controllers/ProductController.java: -------------------------------------------------------------------------------- 1 | package example.springframework.controllers; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.converters.ProductToProductForm; 5 | import example.springframework.domain.Product; 6 | import example.springframework.services.ProductService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | 15 | import javax.validation.Valid; 16 | 17 | @Controller 18 | public class ProductController { 19 | private ProductService productService; 20 | 21 | private ProductToProductForm productToProductForm; 22 | 23 | @Autowired 24 | public void setProductToProductForm(ProductToProductForm productToProductForm) { 25 | this.productToProductForm = productToProductForm; 26 | } 27 | 28 | @Autowired 29 | public void setProductService(ProductService productService) { 30 | this.productService = productService; 31 | } 32 | 33 | @RequestMapping("/") 34 | public String redirToList(){ 35 | return "redirect:/product/list"; 36 | } 37 | 38 | @RequestMapping({"/product/list", "/product"}) 39 | public String listProducts(Model model){ 40 | model.addAttribute("products", productService.listAll()); 41 | return "product/list"; 42 | } 43 | 44 | @RequestMapping("/product/show/{id}") 45 | public String getProduct(@PathVariable String id, Model model){ 46 | model.addAttribute("product", productService.getById(Long.valueOf(id))); 47 | return "product/show"; 48 | } 49 | 50 | @RequestMapping("product/edit/{id}") 51 | public String edit(@PathVariable String id, Model model){ 52 | Product product = productService.getById(Long.valueOf(id)); 53 | ProductForm productForm = productToProductForm.convert(product); 54 | 55 | model.addAttribute("productForm", productForm); 56 | return "product/productform"; 57 | } 58 | 59 | @RequestMapping("/product/new") 60 | public String newProduct(Model model){ 61 | model.addAttribute("productForm", new ProductForm()); 62 | return "product/productform"; 63 | } 64 | 65 | @RequestMapping(value = "/product", method = RequestMethod.POST) 66 | public String saveOrUpdateProduct(@Valid ProductForm productForm, BindingResult bindingResult){ 67 | 68 | if(bindingResult.hasErrors()){ 69 | return "product/productform"; 70 | } 71 | 72 | Product savedProduct = productService.saveOrUpdateProductForm(productForm); 73 | 74 | return "redirect:/product/show/" + savedProduct.getId(); 75 | } 76 | 77 | @RequestMapping("/product/delete/{id}") 78 | public String delete(@PathVariable String id){ 79 | productService.delete(Long.valueOf(id)); 80 | return "redirect:/product/list"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/converters/ProductFormToProduct.java: -------------------------------------------------------------------------------- 1 | package example.springframework.converters; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.StringUtils; 8 | 9 | @Component 10 | public class ProductFormToProduct implements Converter { 11 | 12 | @Override 13 | public Product convert(ProductForm productForm) { 14 | Product product = new Product(); 15 | if (productForm.getId() != null && !StringUtils.isEmpty(productForm.getId())) { 16 | product.setId(new Long(productForm.getId())); 17 | } 18 | product.setDescription(productForm.getDescription()); 19 | product.setPrice(productForm.getPrice()); 20 | product.setImageUrl(productForm.getImageUrl()); 21 | return product; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/converters/ProductToProductForm.java: -------------------------------------------------------------------------------- 1 | package example.springframework.converters; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class ProductToProductForm implements Converter { 10 | @Override 11 | public ProductForm convert(Product product) { 12 | ProductForm productForm = new ProductForm(); 13 | productForm.setId(product.getId()); 14 | productForm.setDescription(product.getDescription()); 15 | productForm.setPrice(product.getPrice()); 16 | productForm.setImageUrl(product.getImageUrl()); 17 | return productForm; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/domain/Product.java: -------------------------------------------------------------------------------- 1 | package example.springframework.domain; 2 | 3 | 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import java.math.BigDecimal; 9 | 10 | 11 | @Entity 12 | public class Product { 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.IDENTITY) 16 | private Long _id; 17 | private String description; 18 | private BigDecimal price; 19 | private String imageUrl; 20 | 21 | public Long getId() { 22 | return _id; 23 | } 24 | 25 | public void setId(Long id) { 26 | this._id = id; 27 | } 28 | 29 | public String getDescription() { 30 | return description; 31 | } 32 | 33 | public void setDescription(String description) { 34 | this.description = description; 35 | } 36 | 37 | public BigDecimal getPrice() { 38 | return price; 39 | } 40 | 41 | public void setPrice(BigDecimal price) { 42 | this.price = price; 43 | } 44 | 45 | public String getImageUrl() { 46 | return imageUrl; 47 | } 48 | 49 | public void setImageUrl(String imageUrl) { 50 | this.imageUrl = imageUrl; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/repositories/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package example.springframework.repositories; 2 | 3 | import example.springframework.domain.Product; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | public interface ProductRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/services/ProductService.java: -------------------------------------------------------------------------------- 1 | package example.springframework.services; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.domain.Product; 5 | 6 | import java.util.List; 7 | 8 | public interface ProductService { 9 | 10 | List listAll(); 11 | 12 | Product getById(Long id); 13 | 14 | Product saveOrUpdate(Product product); 15 | 16 | void delete(Long id); 17 | 18 | Product saveOrUpdateProductForm(ProductForm productForm); 19 | } 20 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/java/example/springframework/services/ProductServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.springframework.services; 2 | 3 | import example.springframework.commands.ProductForm; 4 | import example.springframework.converters.ProductFormToProduct; 5 | import example.springframework.domain.Product; 6 | import example.springframework.repositories.ProductRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Service 14 | public class ProductServiceImpl implements ProductService { 15 | 16 | private ProductRepository productRepository; 17 | private ProductFormToProduct productFormToProduct; 18 | 19 | @Autowired 20 | public ProductServiceImpl(ProductRepository productRepository, ProductFormToProduct productFormToProduct) { 21 | this.productRepository = productRepository; 22 | this.productFormToProduct = productFormToProduct; 23 | } 24 | 25 | 26 | @Override 27 | public List listAll() { 28 | List products = new ArrayList<>(); 29 | productRepository.findAll().forEach(products::add); //fun with Java 8 30 | return products; 31 | } 32 | 33 | @Override 34 | public Product getById(Long id) { 35 | return productRepository.findById(id).orElse(null); 36 | } 37 | 38 | @Override 39 | public Product saveOrUpdate(Product product) { 40 | productRepository.save(product); 41 | return product; 42 | } 43 | 44 | @Override 45 | public void delete(Long id) { 46 | productRepository.deleteById(id); 47 | 48 | } 49 | 50 | @Override 51 | public Product saveOrUpdateProductForm(ProductForm productForm) { 52 | Product savedProduct = saveOrUpdate(productFormToProduct.convert(productForm)); 53 | 54 | System.out.println("Saved Product Id: " + savedProduct.getId()); 55 | return savedProduct; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | # =============================== 2 | # = DATA SOURCE 3 | # =============================== 4 | # Set here configurations for the database connection 5 | spring.datasource.url=jdbc:h2:mem:testdb 6 | spring.datasource.username=sa 7 | spring.datasource.password= 8 | spring.datasource.driver-class-name=org.h2.Driver 9 | 10 | # =============================== 11 | # = JPA / HIBERNATE 12 | # =============================== 13 | 14 | # Allows Hibernate to generate SQL optimized for a particular DBMS 15 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 16 | spring.jpa.hibernate.ddl-auto=update 17 | 18 | # App Service 19 | server.port=8080 -------------------------------------------------------------------------------- /key-vault/complete/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | # =============================== 2 | # = DATA SOURCE 3 | # =============================== 4 | 5 | spring.datasource.url=${SPRING_DATASOURCE_URL} 6 | spring.datasource.username=${SPRING_DATASOURCE_USERNAME} 7 | spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} 8 | 9 | # Set here configurations for the database connection 10 | spring.datasource.driver-class-name=org.postgresql.Driver 11 | 12 | # =============================== 13 | # = JPA / HIBERNATE 14 | # =============================== 15 | 16 | # Allows Hibernate to generate SQL optimized for a particular DBMS 17 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 18 | 19 | # App Service 20 | server.port=80 -------------------------------------------------------------------------------- /key-vault/complete/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Active profile is set by Maven 2 | spring.profiles.active=@spring.profiles.active@ 3 | 4 | # =============================== 5 | # = DATA SOURCE 6 | # =============================== 7 | 8 | # Keep the connection alive if idle for a long time (needed in production) 9 | spring.datasource.testWhileIdle=true 10 | spring.datasource.validationQuery=SELECT 1 11 | 12 | # =============================== 13 | # = JPA / HIBERNATE 14 | # =============================== 15 | # Show or not log for each sql query 16 | spring.jpa.show-sql=true 17 | 18 | # Hibernate ddl auto (create, create-drop, update): with "create-drop" the database 19 | # schema will be automatically created afresh for every start of application 20 | spring.jpa.hibernate.ddl-auto=create 21 | 22 | # Naming strategy 23 | spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl 24 | spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy -------------------------------------------------------------------------------- /key-vault/complete/src/main/resources/templates/product/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - List Products 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 |
20 |

Product List

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
IdDescriptionPriceImage URLListEditDelete
View Edit Delete
41 |
42 |
43 |
44 | New Product 45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/resources/templates/product/productform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - Product Form 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 | 20 |

Product Details

21 |
22 |
23 | 24 |
25 |

Error Message

26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
    37 |
  • 38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 |
    50 |
  • 51 |
52 |
53 |
54 |
55 | 56 |
57 | 58 |
59 | 60 | 61 | 62 |
    63 |
  • 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /key-vault/complete/src/main/resources/templates/product/show.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Core Online Tutorial - Show Product 5 | 6 | 7 | 10 | 11 | 13 | 14 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |

Show Product

23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |

Product Id

32 |
33 |
34 |
35 | 36 |
37 |

Description

38 |
39 |
40 |
41 | 42 |
43 |

Price

44 |
45 |
46 |
47 | 48 |
49 |

Image

50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /key-vault/complete/src/test/java/example/springframework/SpringBootPostgreSQLApplicationTests.java: -------------------------------------------------------------------------------- 1 | package example.springframework; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringBootPostgreSQLApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /key-vault/complete/src/test/java/example/springframework/repositories/ProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package example.springframework.repositories; 2 | 3 | import example.springframework.domain.Product; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import java.math.BigDecimal; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringBootTest 16 | public class ProductRepositoryTest { 17 | 18 | private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100.00); 19 | private static final String PRODUCT_DESCRIPTION = "a cool product"; 20 | private static final String IMAGE_URL = "http://an-imageurl.com/image1.jpg"; 21 | 22 | @Autowired 23 | private ProductRepository productRepository; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | 28 | } 29 | 30 | @Test 31 | public void testPersistence() { 32 | //given 33 | Product product = new Product(); 34 | product.setDescription(PRODUCT_DESCRIPTION); 35 | product.setImageUrl(IMAGE_URL); 36 | product.setPrice(BIG_DECIMAL_100); 37 | 38 | //when 39 | productRepository.save(product); 40 | 41 | //then 42 | Assert.assertNotNull(product.getId()); 43 | Product newProduct = productRepository.findById(product.getId()).orElse(null); 44 | Assert.assertEquals((Long) 1L, newProduct.getId()); 45 | Assert.assertEquals(PRODUCT_DESCRIPTION, newProduct.getDescription()); 46 | Assert.assertEquals(BIG_DECIMAL_100.compareTo(newProduct.getPrice()), 0); 47 | Assert.assertEquals(IMAGE_URL, newProduct.getImageUrl()); 48 | } 49 | } -------------------------------------------------------------------------------- /maven-deployment/README.md: -------------------------------------------------------------------------------- 1 | # Deploying with Maven 2 | 3 | These instructions will walk through deploying a Spring Boot JAR application to App Service. We will start at the `initial/` directory. If you run into problems, see the `complete/` directory. There are supplemental instructions to convert the application to a WAR and deploy that onto App Service. 4 | 5 | ## Run Locally 6 | 7 | First, test that the application builds and runs successfully. 8 | 9 | 1. Build the application with Maven. This project does not have any test classes. 10 | 11 | ```shell 12 | mvn clean package 13 | ``` 14 | 15 | 1. Run the application with the following command: 16 | 17 | ```shell 18 | java -jar ./target/app.jar 19 | ``` 20 | 21 | 1. Open your browser and navigate to `http://localhost:8080/`. (Try `http://127.0.0.1:8080/` if the first link does not work.) You should see a simple web page with green text displaying, "Hello App Service!" 22 | 23 | ## Deploying to App Service with Maven 24 | 25 | Now that we have confirmed the JAR runs locally, we will deploy and run this app on App Service Linux. 26 | 27 | 1. First, insert the [App Service Maven plugin](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) into the `` sections of the `pom.xml`. 28 | 29 | ```xml 30 | 31 | com.microsoft.azure 32 | azure-webapp-maven-plugin 33 | 34 | 1.5.3 35 | 36 | ``` 37 | 38 | 1. Next, we will use the plugin to generate the configuration for our webapp. Run the `mvn azure-webapp:config` command and select the default options in the following prompts. The command will generate a configuration similar to the one below. Feel free to change the resource group and app name to something more memorable. 39 | 40 | ```xml 41 | 42 | V2 43 | maven-deployment-1555354589298-rg 44 | maven-deployment-1555354589298 45 | westeurope 46 | P1V2 47 | 48 | linux 49 | jre8 50 | jre8 51 | 52 | 53 | 54 | 55 | ${project.basedir}/target 56 | 57 | *.jar 58 | 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 | 1. Now we will deploy to App Service! In the terminal, run `mvn clean package azure-webapp:deploy`. We are cleaning the target directory and repackaging in case there were any changes. You can also run `mvn azure-webapp:deploy` if you already packaged the webapp. 66 | 67 | > Note that the Maven plugin uses the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest), so you must be authenticated in the CLI for the deployment to succeed. 68 | 69 | When the command finishes, there will be a URL to your newly created web app. Copy/paste this URL into your browser and you will see the same simple web page and green text that we saw locally. 70 | 71 | ## Deploying a WAR application 72 | 73 | In its current state, the Spring Boot application uses an embedded Tomcat servlet and JSP compiler. To run this application on App Service's Tomcat 8.5 or 9.0, we will remove these dependencies from the project and package the application as a WAR. 74 | 75 | 1. In the `pom.xml`, change the packaging type to a JAR by replacing "war" with "jar" in the `` tags. 76 | 77 | 1. Next, we will mark the spring-boot-starter-tomcat and tomcat-embed-jasper dependencies as "provided". Adding these xml snippets will instruct the compiler that these dependencies will be provided at runtime, but we will not package them into our WAR. 78 | 79 | ```xml 80 | 81 | org.springframework.boot 82 | spring-boot-starter-tomcat 83 | provided 84 | 85 | 86 | org.apache.tomcat.embed 87 | tomcat-embed-jasper 88 | provided 89 | 90 | ``` 91 | 92 | 1. Lastly, update the App Service plugin configuration to deploy the WAR onto Tomcat. Change the webContainer from "jre-8" to "tomcat 8.5" (or "tomcat 9.0") and the include tag's content from ".jar" to ".war". 93 | 94 | ```xml 95 | tomcat 8.5 96 | ... 97 | *.war 98 | ``` 99 | 100 | 1. Now we can deploy to App Service by running `mvn clean package azure-webapp:deploy`. 101 | 102 | **If you ran into issues in this tutorial, please open an issue on the [GitHub repository](https://github.com/Azure-Samples/java-on-app-service).** 103 | -------------------------------------------------------------------------------- /maven-deployment/complete/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | com.microsoft.azure.samples.java-on-app-service 6 | maven-deployment 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.1.1.RELEASE 14 | 15 | 16 | 17 | UTF-8 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-tomcat 29 | 30 | 31 | org.apache.tomcat.embed 32 | tomcat-embed-jasper 33 | 34 | 35 | 36 | 37 | app 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-maven-plugin 42 | 43 | 44 | com.microsoft.azure 45 | azure-webapp-maven-plugin 46 | 47 | 1.5.3 48 | 49 | V2 50 | maven-deployment-1555354589298-rg 51 | maven-deployment-1555354589298 52 | westeurope 53 | P1V2 54 | 55 | linux 56 | jre8 57 | jre8 58 | 59 | 60 | 61 | 62 | ${project.basedir}/target 63 | 64 | *.jar 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /maven-deployment/complete/src/main/java/com/microsoft/azure/samples/view/Application.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.azure.samples.view; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 7 | 8 | @SpringBootApplication 9 | public class Application extends SpringBootServletInitializer { 10 | 11 | @Override 12 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 13 | return application.sources(Application.class); 14 | } 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(Application.class, args); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /maven-deployment/complete/src/main/java/com/microsoft/azure/samples/view/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.azure.samples.view; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | 8 | @Controller 9 | public class HelloController { 10 | @GetMapping({"/", "/hello"}) 11 | public String hello(Model model, @RequestParam(value="name", required=false, defaultValue="App Service") String name) { 12 | model.addAttribute("name", name); 13 | return "hello"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /maven-deployment/complete/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.mvc.view.prefix: /WEB-INF/jsp/ 2 | spring.mvc.view.suffix: .jsp 3 | # App Service Linux expects apps to listen on port 80 4 | server.port: 80 5 | -------------------------------------------------------------------------------- /maven-deployment/complete/src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | .hello-title{ 2 | color: darkgreen; 3 | } -------------------------------------------------------------------------------- /maven-deployment/complete/src/main/resources/static/js/main.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | console.log("Hello World!"); 3 | })(); -------------------------------------------------------------------------------- /maven-deployment/complete/src/main/webapp/WEB-INF/jsp/hello.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello ${name}! 6 | 7 | 8 | 9 |

Hello ${name}!

10 | 11 | 12 | -------------------------------------------------------------------------------- /maven-deployment/initial/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | com.microsoft.azure.samples.java-on-app-service 6 | maven-deployment 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | 11 | org.springframework.boot 12 | spring-boot-starter-parent 13 | 2.1.1.RELEASE 14 | 15 | 16 | 17 | UTF-8 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-tomcat 29 | 30 | 31 | org.apache.tomcat.embed 32 | tomcat-embed-jasper 33 | 34 | 35 | 36 | 37 | app 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-maven-plugin 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /maven-deployment/initial/src/main/java/com/microsoft/azure/samples/view/Application.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.azure.samples.view; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 7 | 8 | @SpringBootApplication 9 | public class Application extends SpringBootServletInitializer { 10 | 11 | @Override 12 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 13 | return application.sources(Application.class); 14 | } 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(Application.class, args); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /maven-deployment/initial/src/main/java/com/microsoft/azure/samples/view/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.azure.samples.view; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | 8 | @Controller 9 | public class HelloController { 10 | @GetMapping({"/", "/hello"}) 11 | public String hello(Model model, @RequestParam(value="name", required=false, defaultValue="App Service") String name) { 12 | model.addAttribute("name", name); 13 | return "hello"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /maven-deployment/initial/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.mvc.view.prefix: /WEB-INF/jsp/ 2 | spring.mvc.view.suffix: .jsp 3 | # App Service Linux expects the app to be listening on port 80 4 | server.port: 80 5 | -------------------------------------------------------------------------------- /maven-deployment/initial/src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | .hello-title{ 2 | color: darkgreen; 3 | } -------------------------------------------------------------------------------- /maven-deployment/initial/src/main/resources/static/js/main.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | console.log("Hello World!"); 3 | })(); -------------------------------------------------------------------------------- /maven-deployment/initial/src/main/webapp/WEB-INF/jsp/hello.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello ${name}! 6 | 7 | 8 | 9 |

Hello ${name}!

10 | 11 | 12 | --------------------------------------------------------------------------------