Showing posts with label JPA. Show all posts
Showing posts with label JPA. Show all posts

Monday, August 12, 2013

spring-data-rest in Action

What is spring-data-rest?

spring-data-rest, a recent addition to the spring-data project, is a framework that helps you expose your entities directly as RESTful webservice endpoints. Unlike rails, grails or roo it does not generate any code achieving this goal. spring data-rest supports JPA, MongoDB, JSR-303 validation, HAL and many more. It is really innovative and lets you setup your RESTful webservice within minutes. In this example i'll give you a short overview of what spring-data-rest is capable of.

Initial Configuration 

I'm gonna use the new Servlet 3 Java Web Configuration instead of an ancient web.xml. Nothing really special here.

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{AppConfiguration.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
WebAppInitializer.java on github

I am initializing hibernate as my database abstraction layer in the AppConfiguration class. I'm using an embedded database (hsql), since i want to keep this showcase simple stupid. Still, nothing special here.

@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
public class AppConfiguration {

    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.HSQL).build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.HSQL);
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan(getClass().getPackage().getName());
        factory.setDataSource(dataSource());

        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }
}
AppConfiguration.java on github

Now to the application servlet configuration: WebConfiguration

@Configuration
public class WebConfiguration extends RepositoryRestMvcConfiguration {
}
WebConfiguration.java on github

Oh, well thats a bit short isnt it? Not a single line of code required for a complete setup. This is a really nice application of the convention over configuration paradigm. We can now start creating spring-data-jpa repositories and they will be exposed as RESTful resources automatically. And we can still add custom configuration to the WebConfiguration class later if needed.

The Initialization was really short and easy. We didn't have to code anything special. The only thing we did was setting up a database connection and hibernate, which is obviously inevitable. Now, that we have setup our "REST Servlet" and persistence, lets move on to the application itself, starting with the model.

The Model

I'll keep it really simple creating only two related entities.
@Entity
public class Book {

    @Id
    private String isbn;

    private String title;

    private String language;

    @ManyToMany
    private List<Author> authors;

}
Book.java on github

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Integer id;

    private String name;

    @ManyToMany(mappedBy = "authors")
    private List<Book> books;

}
Author.java on github

To finally make the entities persistent and exposed as a RESTful webservice, we need spring-data repositories. A repository is basically a DAO. It offers CRUD functionality for our entities. spring-data takes away most of your programming effort creating such repositories. We just have to define an empty interface, spring-data does everything else out of the box. Still, it is easily customizable thanks to its design by convention over configuration!

The Actual Repositories


@RestResource(path = "books", rel = "books")
public interface BookRepository extends PagingAndSortingRepository<Book, Long> {
}
BookRepository.java on github


@RestResource(path = "authors", rel = "authors")
public interface AuthorRepository extends PagingAndSortingRepository<Author, Integer> {
}
AuthorRepository.java on github

Again, there is nearly no code needed. Even the @RestResource annotation could be left out. But if i did, the path and rel would be named after the entity, which i dont want. A REST resource that contains multiple children should be named plural though.

Accessing The Result

Our RESTful webservice is now ready for deployment. Once run, it lists all available resources on the root, so you can navigate from there.

GET http://localhost:8080/
{
  "links" : [ {
    "rel" : "books",
    "href" : "http://localhost:8080/books"
  }, {
    "rel" : "authors",
    "href" : "http://localhost:8080/authors"
  } ],
  "content" : [ ]
}

Fine! Now lets create an author and a book.

POST http://localhost:8080/authors
{"name":"Uncle Bob"}

Response
201 Created
Location: http://localhost:8080/authors/1
PUT http://localhost:8080/books/0132350882
{
  "title": "Clean Code",
  "authors": [
      {
          "rel": "authors",
          "href": "http://localhost:8080/authors/1"
      }
  ]
}

Response
201 Created
Noticed how i used PUT to create the book? This is because its id is the actual isbn. I have to tell the server which isbn to use since he cant guess it. I used POST for the author as his id is just an incremental number that is generated automatically. Also, i used a link to connect both, the book (/books/0132350882) and the author (/authors/1). This is basically what hypermedia is all about: Links are used for navigation and relations between entities.

Now, lets see if the book was created accordingly.

GET http://localhost:8080/books
{
  "links" : [ ],
  "content" : [ {
    "links" : [ {
      "rel" : "books.Book.authors",
      "href" : "http://localhost:8080/books/0132350882/authors"
    }, {
      "rel" : "self",
      "href" : "http://localhost:8080/books/0132350882"
    } ],
    "title" : "Clean Code"
  } ],
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 1
  }
}

Fine!

Here is an Integration Test, following these steps automatically. It is also available in the example on github.

public class BookApiIT {

    private final RestTemplate restTemplate = new RestTemplate();

    private final String authorsUrl = "http://localhost:8080/authors";
    private final String booksUrl = "http://localhost:8080/books";

    @Test
    public void testCreateBookWithAuthor() throws Exception {
        final URI authorUri = restTemplate.postForLocation(authorsUrl, sampleAuthor()); // create Author

        final URI bookUri = new URI(booksUrl + "/" + sampleBookIsbn);
        restTemplate.put(bookUri, sampleBook(authorUri.toString())); // create Book linked to Author

        Resource<Book> book = getBook(bookUri);
        assertNotNull(book);

        final URI authorsOfBookUri = new URI(book.getLink("books.Book.authors").getHref());
        Resource<List<Resource<Author>>> authors = getAuthors(authorsOfBookUri);
        assertNotNull(authors.getContent());
        assertFalse(authors.getContent().isEmpty()); // check if /books/0132350882/authors contains an author
    }

    private String sampleAuthor() {
        return "{\"name\":\"Robert C. Martin\"}";
    }

    private final String sampleBookIsbn = "0132350882";

    private String sampleBook(String authorUrl) {
        return "{\"title\":\"Clean Code\",\"authors\":[{\"rel\":\"authors\",\"href\":\"" + authorUrl + "\"}]}";
    }

    private Resource<Book> getBook(URI uri) {
        return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Book>>() {
        }).getBody();
    }

    private Resource<List<Resource<Author>>> getAuthors(URI uri) {
        return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<List<Resource<Author>>>>() {
        }).getBody();
    }
}
BookApiIT.java on github

Conclusion

We have created a complete RESTful webservice without much coding effort. We just defined our entities and database connection. spring-data-rest stated that everything else is just boilerplate, and i agree.

To consume the webservices manually, consider the rest-shell. It is a command-shell making the navigation in your webservice as easy and fun as it could be. Here is a screenshot:


The complete example is available on my github
https://github.com/gregorriegler/babdev-spring/tree/master/spring-data-rest
https://github.com/gregorriegler/babdev-spring


Wednesday, April 10, 2013

I18n of JPA Entities

Sadly the support for i18n for Entities in JPA is actually very poor to non-existent. There's just basically no out of the box solution provided by Hibernate & Co, although i18n is a feature that could be considered mandatory for a powerful ORM Framework. If you take a look at PHP for example Doctrine supports i18n very well. So besides some really unpopular i18n extensions on forums/github you are basically on your own.

So how do you do it?

There isn't really a general solution to achieve i18n. There are many different ways and you have to choose which is actually best for you. If you do have a relatively small amount of data and ordering by translations is not necessary you can store "keys" in your tables that are being translated by Spring i18n for example. Spring i18n could store the translations in separate property files. Since you wont have to worry about performance, this is a very easy way to achieve i18n.

But lets say you have lots of data, and you need to order it on the database side. With such requirements you are better off storing translations inside your database tables. While there are guides, and also an opensource framework on the internet that implement i18n with one centralized translation-table that holds all translations i highly recommend not to follow that idea. The problem with this solution is, that you will have to add one more join for every field that has to be translated, whereas multiple joins for a single table with lots of data can easily become a performanceissue.
Its better to follow the approach to create a separate translation-table for every entity. This means that you would have one entity containing simple data, and another one containing only the fields that require translation. Like so:

This schema is pretty normalized and you only have a single join on fetching an entity, while you can still order appropriate.

Your entities would look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Entity
public class Book {

    @Id
    private int isbn;

    private String author;

    private Date releaseDate;

    @OneToMany
    @JoinColumn(name = "isbn")
    private List<BookTranslation> translations;

    // constructor, getters and setters -...

}

and that

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Entity
public class BookTranslation implements Serializable {

    @Id
    private int isbn;

    @Id
    private String locale;

    private String title;

    private String description;

    // constructor, getters and setters -...

}

You could then fetch a book with its translations with:

select b from Book b join fetch b.translations bt where bt.locale = 'de';

If you cannot accept your ServiceLayer to deliver two entities for every Book only because of translations, i suggest you to implement some DTO like this BookTO which holds all values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class BookTO {

    private int isbn;

    private String author;

    private Date releaseDate;

    private String title;

    private String description;

}

You'd just need to map the values of Book and BookTranslation to BookTO within your ServiceLayer and also return BookTO instead of Book in your ServiceLayer.

This solution might not always be the most appropriate, but it definitely comes in handy sometimes.