Spring boot and spring data jpa tutorial - A sample application using spring 4.0 spring boot and JPA (part 2/2)

This is the second part of the Spring boot and spring data jpa tutorial - A sample application using spring 4.0 spring boot and JPA

At the end of the tutorial I promised you that we will be removing one of the steps when running the application, that is merge the package and run in a maven command and make him do the work

5.- Making maven run the JAR directly

We're going to make maven run the runnable JAR for us after it has been packaged, for this purpose we're going to use one of org.codehaus.mojo maven plugins



....

        
            
                org.springframework.boot
                spring-boot-maven-plugin
            

            
                org.codehaus.mojo
                exec-maven-plugin
                1.2.1
                
                    com.ufasoli.tutorials.spring.boot.HelloWorldApp
                
            
        
    
...

So what's happening here ? :

  • By adding the exec plugin to the build section of our pom.xml and providing a mainClass when we run the appropriate maven goals the application will be packaged and run directly from maven

You can try this by running :

    mvn clean package exec:java

By running this on your console the application will be packaged and run directly using maven

Now let's add some more interesting stuff to our app

6.- Adding Spring Data and JPA support

We're now going to add some Spring Data and JPA support to our app.

The first thing you need to do is add the additional spring boot starter module (spring-boot-starter-data-jpa) to your maven dependencies and since we will be using an embedded HSQL database the dependency for the HSQL driver too:

 
 ...
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        

         
            hsqldb
            hsqldb
            ${version.hsqldb}
        
...


The next thing you need to do is tell spring to enable Spring Data and its repository features this is done by simply adding the @EnableJpaRepositories to your Spring App class

@ComponentScan 
@EnableAutoConfiguration
@EnableJpaRepositories
public class HelloWorldApp {

    public static void main(String[] args) throws Exception {

        SpringApplication.run(HelloWorldApp.class, args);

    }
}

And that's it if you now build and run your application you will have a Spring web application with Spring Data and JPA support even though the app won't do much for the moment ;).

So what's happening here?

  • Annotating the Spring Application class with the @EnableJpaRepositories will cause a lot of "magic" to happen and a lot of things will be configured/initialized for you such as an Entity Manager, DataSource (see next item), JPA Vendor adapter ..
  • Since we haven't defined a DataSource if Spring finds an in memory JDBC driver in the Classpath it will automatically create one for us using a set of predefined conventions

Now let's add some logic to our app :

7.- Adding some Spring data and JPA logic

The first thing we're going to be doing is adding a simple JPA entity to perform some CRUD operations later on



import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Book {


    @Id
    protected String isbn;
    protected String title;
    protected String author;
    protected String description;
    // omitting setters/getters/constructors
}

So now that we have our entity lets use one of the great features of Spring Data : The Repositories Abstraction :

The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.

By annotating an interface with @Repository and making this interface extend CrudRepositoryYou will get among other things 2 pretty nifty goodies of the box:

  • Some utility CRUD methods such as findAll() or count()
  • Auto-implementation of methods based on naming convention such as findBooksByAuthor()

And the best part is you get all of this without writing any code logic for these methods (of course you can override these methods, and write some custom methods) as shown in the doc, you can even avoid yet to write some code by using for example the @Query annotation

So now lets create our CRUD repository for our entity with a custom method to find the books by their author


import com.ufasoli.tutorials.spring.boot.model.Book;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;


@Repository
public interface BookRepository extends CrudRepository {

    public Iterable findBooksByAuthor(@Param("author") String author);
}

This interface will be picked up by Spring an instantiated accordingly.

We're almost done with our JPA/database logic, one last thing I would like to do is to have some data inserted automatically into the database when the application starts (especially since we're dealing with an in-memory database

Once again we can take advantage of another Spring convention, if you place a data.sql file in your maven resources directory the script will be automatically executed when the database will be bootstrapped and after reverse engineering your entities into the database

INSERT INTO book(isbn, title, author,description) VALUES ('1111111', 'The Spirit Thief (The Legend of Eli Monpress #1)', 'Rachel Aaron',
                         'Eli Monpress is talented. He''s charming. And he''s a thief.But not just any thief. He''s the greatest thief of the age - and he''s also a wizard. And with the help of his partners - a swordsman with the most powerful magic sword in the world but no magical ability of his own, and a demonseed who can step through shadows and punch through walls - he''s going to put his plan into effect.The first step is to increase the size of the bounty on his head, so he''ll need to steal some big things. But he''ll start small for now. He''ll just steal something that no one will miss - at least for a while. Like a king');

INSERT INTO book(isbn, title, author,description) VALUES ('2222222', 'The Spirit Rebellion (The Legend of Eli Monpress, #2)', 'Rachel Aaron',
                         'Eli Monpress is brilliant. He''s incorrigible. And he''s a thief.He''s also still at large, which drives Miranda Lyonette crazy. While she''s been kicked out of the Spirit Court, Eli''s had plenty of time to plan his next adventure. But now the tables have turned, because Miranda has a new job -- and an opportunity to capture a certain thief. Things are about to get exciting for Eli. He''s picked a winner for his newest heist. His target: the Duke of Gaol''s famous ''thief-proof'' citadel. Eli knows Gaol is a trap, but what''s life without challenges? Except the Duke is one of the wealthiest men in the world, a wizard who rules his duchy with an iron fist, and an obsessive perfectionist with only one hobby: Eli. It seems that everyone is hunting for Eli Monpress');

INSERT INTO book(isbn, title, author,description) VALUES ('3333333', 'The Spirit Eater (The Legend of Eli Monpress, #3)', 'Rachel Aaron',
                         'With the pressure on after his success in Gaol, Eli Monpress, professional thief and degenerate, decides it''s time to lie low for a bit. Taking up residence in a tiny seaside village, Eli and his companions seize the chance for some fun and relaxation. Nico, however, is finding it a bit hard. Plagued by a demon''s voice in her head and feeling powerless, she only sees herself as a burden. Everyone''s holiday comes to an untimely close, though, when Pele arrives to beg Eli''s help for finding her missing father. But there are larger plans afoot than even Eli can see, and the real danger, and the solution, may lie with one of his own and her forgotten past. If only Nico could remember whose side she''s on.');

INSERT INTO book(isbn, title, author,description) VALUES ('4444444', 'The Spirit War (The Legend of Eli Monpress, #4)', 'Rachel Aaron',
                         'Eli Monpress is vain. He''s cocky. And he''s a thief. But he''s a thief who has just seen his bounty topped and he''s not happy about it. The bounty topper, as it turns out, is his best friend, bodyguard, and master swordsman, Josef. Who has been keeping secrets from Eli. Apparently, he''s the only prince of a rather feisty country and his mother (a formidable queen who''s every bit as driven and stubborn as he is) wants him to come home and do his duty, which means throwing over personal ambitions like proving he''s the greatest swordsman who ever lived. Family drama aside, Eli and Josef have their hands full. The Spirit Court has been usurped by the Council of Thrones and someone calling herself the Immortal Empress is staging a massive invasion. But it''s not just politics --- the Immortal Empress has a specific target in mind: Eli Monpress, the greatest thief in the world.');

INSERT INTO book(isbn, title, author,description) VALUES ('5555555', 'Spirit''s End (The Legend of Eli Monpress #5)', 'Rachel Aaron',
                         'First rule of thievery: don''t be a hero. When Eli broke the rules and saved the Council Kingdoms, he thought he knew the price, but resuming his place as the Shepherdess''s favorite isn''t as simple as bowing his head. Now that she has her darling back, Benehime is setting in motion a plan that could destroy everything she was created to protect, and even Eli''s charm might not be enough to stop her. But Eli Monpress always has a plan, and with disaster rapidly approaching, he''s pulling in every favor he can think of to make it work, including the grudging help of the Spirit Court''s new Rector, Miranda Lyonette. But with the world in panic, the demon stirring, and the Lord of Storms back on the hunt, it''s going to take more than luck and charm to pull Eli through this time. He''s going to have to break a few more rules and work with some old enemies if he''s going to survive');

8.- Wrapping things up with a REST controller

To wrap things up we'll add a simple RestController that will interact with our freshly created Repository


import com.ufasoli.tutorials.spring.boot.model.Book;
import com.ufasoli.tutorials.spring.boot.repositories.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController // assumes @ResponseBody annotation in methods annotated with  @RequestMapping
@RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_VALUE)
public class BookController {

    @Autowired
    protected BookRepository bookRepository;

    @RequestMapping
    public Iterable books() {
        return bookRepository.findAll(); // uses the findAll() method inherited from CrudRepository
    }


    @RequestMapping(value = "/{isbn}")
    public Book book(@PathVariable("isbn") String isbn) {
        return bookRepository.findOne(isbn);// uses the findOne() method inherited from CrudRepository
    }


    @RequestMapping(value = "/{isbn}", method = RequestMethod.DELETE)
    public String deleteBook(@PathVariable("isbn") String isbn) {

        try {
            bookRepository.delete(isbn);
            return String.format("Book [%s] successfully deleted", isbn);// uses the delete() method inherited from CrudRepository
        } catch (Exception e) {
            return String.format("A problem occurred when deleting Book [%s]", e.getMessage());
        }
    }

    @RequestMapping("/author/{author}")
    public Iterable booksByAuthor(@PathVariable("author") String author) {

        return bookRepository.findBooksByAuthor(author);// uses the custom method defined in our BookRepository interface
    }


}

So that's it you can re-run your application now using the maven goal

     mvn clean package exec:java

You should see your application bootstrapping your database and inserting data, and once it's fully started you should be able to play with your Web Service

9.-Some final thoughts

I must say that I quite enjoyed playing with Spring boot, as long as you're using the full spring stack it helps you get things running quickly and has some very useful starter modules and conventions

I also added the actuator module which gives you some metrics and stats (as REST endpoints) on your application which is pretty cool to have when running the app in production

Check it out here

As usual you can find the code source for this application over at my github account here

16 comments:

  1. I tried to use @Query annotation:

    @Repository
    public interface WorkflowVariableInstanceRepository extends JpaRepository {
    @Query("SELECT v FROM WorkflowVariableInstance v WHERE v.workflowVariableId = :workflowVariableId AND v.workflowInstanceId = :workflowInstanceId")
    WorkflowVariableInstance findByCustomParams(@Param("workflowVariableId") Long workflowVariableId,
    @Param("workflowInstanceId") Long workflowInstanceId);

    }

    but I get:

    org.hibernate.hql.internal.ast.QuerySyntaxException: WorkflowVariableInstance is not mapped

    ReplyDelete
    Replies
    1. Hi it would seem hibernate is not picking up WorkflowVariableInstance as a JPA/Hibernate entity.
      Is your class properly annotated with @Entity?

      Delete
    2. Hi Ulises,
      thx for this tutorial. Really.

      But I want to point out that in the mvn quotes "groupId" is written as "groupid" which causes mvn to fail.

      cheers,
      Achim

      Delete
    3. Sorry, I was referring to your part one of this subject...

      cheers,
      Achim

      Delete
    4. Hi , thanks for the comment!
      Regarding the xml tags being wrong it has something to do with the highlighter plugin that removes camel casing, I don't know why..

      Delete
  2. The code generation from the database... you were referring to JOOQ, right?

    ReplyDelete
    Replies
    1. Hi,
      No I'm not using JooQ in this tutorial.
      Only JPA/Hibernate

      Delete
  3. Hi!

    Thank you for your post, great job. I'd like to ask you something. Did you manage to create the database using the file system? (I mean, no tables in memory).

    I'm asking because when I set my application.properties to use jdbc:hsqldb:file:PATH, some files are created (log, properties...) but no .data file appears.

    I have used HSQLDB in other projects and usually there is a binary file (the real database), having all the info.

    But file databases dont seem to work with Spring Boot. Even if I open my database using a SQL client (such as Squirrel), no tables appear.

    Any idea?

    Thank you very much in advance!

    ReplyDelete
    Replies
    1. Hi,
      yes I've managed to successfully create a file database.
      this are the values I've used in the application.properties file

      spring.datasource.url=jdbc:hsqldb:file:${project.basedir}/db/mydb;shutdown=true
      spring.datasource.username=SA
      spring.datasource.password=
      spring.jpa.database=HSQL

      with ${project.basedir} being filtered by maven

      Ulises

      Delete
    2. Hi,

      thank you for your answer. I have tested your properties (and removed mine first) but the database is not created in the file system.

      I mean, some files are created:

      - test.properties (some properties for the db)
      - test.log (the last queries executed)
      - test.script (a file containing the queries that will "restore" your database when the system is restarted).

      If I modify the .script file and start the app, the "database" is modified too. This means that this script is loaded in memory, and thus the database is actually stored in memory.

      No test.data file (the real binary file, the database in the filesystem) appears.

      (You can have a look at this in case it clarifies: http://www.hsqldb.org/doc/1.8/guide/apc.html)

      Maybe I'm doing something wrong. Did you manage to get the "your_db_name".data file? I hope you did, so I can learn how to do it :-)

      Once more, thanks for your help!

      Delete
    3. Hi, could you put your code on github or something so I can take a look?

      Delete
    4. Hi,

      the project is hosted in Bitbucket, because it's still private. I'd like to finish a stable version and clean the code before making it public (I will, in the near future, maybe it can be usefull for others).

      Can I add you to my contacts in Google plus or something like that? I will send you a link so that you can check the code (or even send you the code in a zip file, there is no problem at all).


      Delete
    5. Hi!

      I have tested your sample project to use HSQLDB filesystem database and your properties, and the same problem happens. No database (.data file) is created.

      I have read that this is imposible to work because in the SpringBoot code the use of memory databases is hardcoded:

      http://stackoverflow.com/questions/32398935/spring-boot-does-not-use-hsqldbs-file-based-database-when-configured-to-do-so

      I will try the solution proposed there and will tell you.

      Thanks!

      Delete
  4. Hi, I have a question regarding your github repository. Can you please give steps to run batch-diff-generator project?

    Thanks,
    Nirupa

    ReplyDelete
  5. Hi,
    This is an old project I wrote specifically for 1 use case in my previous company.
    I'm pretty sure it will not run in the current state, however you can get the code and adapt it to make it work.
    Basically you use the config.properties to tell the program the "left" and "right" files to compare as well as the output folder, this will the use the diff library to generate a HTML diff report; hope this helps

    Ulises

    ReplyDelete
  6. Hi,

    I'm a noob but let me figure out. I'm basically trying to build an app that takes in URL and generate a report on the given URL's.

    Thank you for your response.

    Nirupa

    ReplyDelete