Posted on 6 mins read

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.

Spring Boot CLI Docs

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"

Stack link

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

Link

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

link

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.

link

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