├── .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 | Id
24 | Description
25 | Price
26 | Image URL
27 | List
28 | Edit
29 | Delete
30 |
31 |
32 |
33 |
34 |
35 |
36 | View
37 | Edit
38 | Delete
39 |
40 |
41 |
42 |
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 |
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 |
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 | Id
24 | Description
25 | Price
26 | Image URL
27 | List
28 | Edit
29 | Delete
30 |
31 |
32 |
33 |
34 |
35 |
36 | View
37 | Edit
38 | Delete
39 |
40 |
41 |
42 |
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 |
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 |
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 | Id
24 | Description
25 | Price
26 | Image URL
27 | List
28 | Edit
29 | Delete
30 |
31 |
32 |
33 |
34 |
35 |
36 | View
37 | Edit
38 | Delete
39 |
40 |
41 |
42 |
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 |
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 |
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 |
--------------------------------------------------------------------------------