Beginning Spring Boot with Gradle
Spring Boot with Gradle because Maven is too verbose.
When I was looking for tutorials, it seemed like using gradle with Spring Boot is the lesser walked path. However, I think going down this path enables me to understand gradle better. This will be useful for Android development too.
It is to be noted that Spring has some integrations with the paid version of the IntelliJ IDEA IDE. As I am using the community edition, I will use the Spring CLI to setup the project. Furthermore, it simulates the scenario where you are a contractor and you have a one off project that requires Spring Boot. You might not like spending a few hundred dollars on a new IDE which you will have to spend time getting familiar with. IntelliJ IDEA Community Edition is great for writing code once you have the initial setup done.
Update: This post is much messier than I expected. I jumped ahead to build my photo blog. So much configuration and testing was involved in this side project. As such I do not think this post will make much sense for the general reader. If you happen to be trying something more than a hello world or step-by-step tutorial this might be of help.
References:
Setup
The assumption here is that you have sprint boot already installed to your commandline.
Using the spring init
command:
spring init --build=gradle --java-version=1.8 --dependencies=web,thymeleaf --package=codes.robin.spring --artifactId=codes.robin.spring --groupId=codes.robin.spring --packaging=war --name=spring-boot-web -x
spring init --build=gradle -d=jpa,h2 -g=codes.robin.spring -a=simple-jpa-app --package-name=code.robin.spring -name=simple-jpa-app --packaging=war
spring init --build=gradle -d=web,thymeleaf,data-jpa,data-rest -g=codes.robin.spring -a=spring-boot-journal --package-name=codes.robin.spring -name=spring-boot-journal -x
-x
extracts the project into your current directory if location is not specified.
Running & Configurations
gradle build
gradle bootRun
Try localhost:8080
.
1 way to change port:
myapp/
src/
gradle/
config/
application.properties # add a line "server.port=8090"
You can retrieve the values of the properties in the file by using the following annotation:
@Value("${server.port}")
private String port;
Spring also offers a way to configure separate profiles using application-{profile}.properties files. This is really useful if you have different environments. You really should have separate environments for dev and prod.
application-dev.properties
server.ip=localhost
application-prod.properties
server.ip=http//cool-server.com
gradle bootRun -Dspring.profiles.active=dev
(Not so) Minimum code
Folder structure
src/
domain # Models
repository # Repository, JPA/Custom Queries
web # Controllers
Controller
Using Spring @Controller
annotation.
@RestController
could be used too. link
A convenience annotation that is itself annotated with @Controller and @ResponseBody.
Types that carry this annotation are treated as controllers where @RequestMapping methods assume @ResponseBody semantics by default. Additionally, if you want to set response codes. Just pass HttpServletResponse response
to the method like someMethod(ttpServletResponse response)
and set response.setStatus(HttpServletResponse.SC_ACCEPTED);
. link
@ModelAttribute
tells Spring to use its default web data binder to populate an instance of something with data from the HttpServletRequest. Choosing to pass this data back to the view is up to the programmer. When you have a method annotated with @ModelAttribute, it is being called every time code hits that servlet. When you have @ModelAttribute as one of the method’s parameters, we are talking about incoming Http form data-binding. link link link very detailed link
- TODO:
ResponseEntity<?>
vs@ResponseBody
. When to use? - TODO: Pagination? Instead of loading all image posts at once, load them in stages.
@Controller
public class BasicController {
//This is similar to @Inject (part of the JSR-330)
//http://stackoverflow.com/questions/19414734/understanding-spring-autowired-usage
@Autowired
SomeRespository repo;
@RequestMapping("/")
@ResponseBody
public String index(){
//return NAME of html page (eg. index.html)
return "Greetings from spring boot!";
}
@RequestMapping(value = "/someResource", method = RequestMethod.GET)
public void someResource(HttpServletResponse response) {
//can change this to by default return a list?
response.setStatus(HttpServletResponse.SC_OK);
try {
response.getWriter().write("Some useful message.");
response.getWriter().flush();
response.getWriter().close();
} catch (Exception ex) {
log.error("Argh, the AIs are rebelling! ", ex);
}
}
@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<?> uploadFile(
@RequestParam("file") MultipartFile file, @RequestParam(value = "description", required = true) String description) {
try {
// process multipart file
} catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
@RequestMapping(value = "/submitInfo", method = RequestMethod.POST)
@ResponseBody
public String someInfoFromAForm(@ModelAttribute SomeObject someObject) {
//ModelAttribute is not actually required here
//use getters
return payload;
}
}
References: -davidmendoza -netgloo
Repository
public interface SomeRepository extends JpaRepository<SomeObject, Long> {
}
//examples
repo.save(SomeObject);
repo.findAll();
repo.getOne((long) id);
Extended from:
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
List<T> findAll();
List<T> findAll(Sort sort);
List<T> findAll(Iterable<ID> ids);
<S extends T> List<S> save(Iterable<S> entities);
void flush();
<S extends T> S saveAndFlush(S entity);
void deleteInBatch(Iterable<T> entities);
void deleteAllInBatch();
T getOne(ID id);
}
Domain
Using JPA you need to have @Entity
.
@Transient
means it won’t be added to database. Some metadata.
@Entity
public class SomeObject {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private String description;
private Date created;
@Transient
private SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
public SomeObject(String description, String date) throws ParseException{
this.description = description;
this.created = format.parse(date);
}
public SomeObject() {}
//getters & setters
}
Useful things
cURL to post json
curl -X POST -d '{"title":"Test Spring Boot","created":"06/18/2016","summary":"Create Unit
Test for Spring Boot"}' -H "Content-Type: application/json" http://localhost:8080/journal
Testing
If you do gradle test
it runs the tests and generates a html report in the build/tests
folder.
It would be nice to see the results immediately in terminal. link
Add the following to build.gradle
:
test {
//we want display the following test events
testLogging {
events "PASSED", "STARTED", "FAILED", "SKIPPED"
}
}
Accessing H2 In-Memory Test Database
Ensure application.properties
has:
spring.h2.console.enabled=true
Go to http://localhost:8080/h2-console/
.
By default it will have the org.h2.Driver driver class, which is the connection URL as
jdbc:h2:mem:testdb and the
username: sa
password: empty
to connect to the H2 engine.
Using cURL to upload post data with files
Use -F
option:
-F/--form <name=content> Specify HTTP multipart POST data (H)`
curl \
-F "userid=1" \
-F "filecomment=This is an image file" \
-F "image=@/home/user1/Desktop/test.jpg" \
localhost/uploader.php
curl -i -X POST -H "Content-Type: multipart/form-data"
-F "data=@test.mp3" http://mysuperserver/media/1234/upload/
# when userid is cached as part of form
curl -i -X POST -H "Content-Type: multipart/form-data" -F "data=@test.mp3;userid=1234" http://localhost:8080/upload/
curl -i -X POST -H "Content-Type: multipart/form-data" -F file=@phoduckdp.jpg -F description=blah http://localhost:8080/upload/
curl -v -F description=value1 -F file=@phoduckdp.jpg http://localhost:8080/upload/
# GET verbose
curl -v http://localhost:8080/photos
cURL with auth
curl -i localhost:8080/oauth/token -d "grant_type=password&scope=read&username=springb
oot&password=isawesome" -u acd167f6-04f8-4306-a118-03e2356f73aa:2dd4bec5-fe62-4568-94a1-
c31ac3c4eb4e
curl -i -H "Authorization: bearer f1d362f2-b167-41d9-a411-35f8ba7f0454" localhost:8080/api
MySQL
Assuming you have it installed on OSX.
brew info mysql
To have launchd start mysql now and restart at login:
brew services start mysql
Or, if you don't want/need a background service you can just run:
mysql.server start
To stop
mysql.server stop
Getting properties/config
@Value("${file.upload.directory}")
private String fileUploadDirectory;
//OR
String directory = env.getProperty("file.upload.directory");
Optional URL path
LiveReload setup
Update: In the end I just included the devtools
dependency and used the make
command in the IDE before refreshing in the browser. Static files are still updated. However, when it comes to javascript I prefer having a setup that enables me to see the changes immediately (actually livereloading) so I think developing the UI & frontend in a separate project would be better.
Add to build.gradle;
configurations {
dev
}
# under dependencies
dev("org.springframework.boot:spring-boot-devtools")
# hmm
bootRun {
// Use Spring Boot DevTool only when we run Gradle bootRun task
classpath = sourceSets.main.runtimeClasspath + configurations.dev
}
H2 Database config
If nothing is specified in application.properties
, h2 uses in mem.
spring.datasource.url = jdbc:h2:mem:testdb
A persistent db can be useful for testing. This setting doesn’t work nicely with gradle build --continuous
.
spring.datasource.url=jdbc:h2:~/test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.jpa.hibernate.ddl-auto=update
Sample application.properties
file.upload.directory=/home/someuser/tomcat-data-directory
spring.h2.console.enabled=true
#JPA-Hibernate
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.datasource.url=jdbc:h2:/home/someuser/tomcat-data-directory/testdb;DB_CLOSE_ON_EXIT=FALSE
#Spring DataSource SQL
#spring.datasource.url = jdbc:mysql://localhost:3306/cooldatabase
#spring.datasource.username = root
#spring.datasource.password =
spring.datasource.testWhileIdle = true
spring.datasource.validationQuery = SELECT 1