1

we have a huge database application, which must get refactored (there are so many reasons for this. biggest one: security).

What we already have:

  • MySQL Database
  • JPA2 (Eclipselink) classes for over 100 tables
  • Client application that accesses the database directly

What needs to be there:

  • REST interface
  • Login/Logout with roles via database

What I've done so far:

  • Set up Spring MVC 3.2.1 with Spring Security 3.1.1
  • Using a custom UserDetailsService (contains just static data for testing atm)
  • Created a few Controllers for testing (simply receiving/providing data)

Design Problems:

  • We have maaaaany @OneToMany and @ManyToMany relations in our database

1.: (important)

If I'd send the whole object tree with all child objects as a response, I could probably send the whole database at once.

So I need a way to request for example 'all Articles'. But it should omit all the child objects. I've tried this yesterday and the objects I received were tons of megabytes:

@PersistenceContext
private EntityManager em;


@RequestMapping(method=RequestMethod.GET)
public @ResponseBody List<Article> index() {        
    List<Article> a = em.createQuery("SELECT a FROM Article a", Article.class).getResultList(); 
    return a;
}

2.: (important)

If the client receives an Article, at the moment we can simply call article.getAuthor() and JPA will do a SELECT a FROM Author a JOIN Article ar WHERE ar.author_id = ?.

With REST we could make a request to /authors/{id}. But: This way we can't use our old JPA models on the client side, because the model contains Author author and not Long author_id.

Do we have to rewrite every model or is there a simpler approach?

3.: (less important)

Authentication: Make it stateless or not? I've never worked with stateless auth so far, but Spring seems to have some kind of support for it. When I look at some sample implementations on the web I have security concerns: With every request they send username and password. This can't be the right way.

If someone knows a nice solution for that, please tell me. Else I'd just go with standard HTTP Sessions.

4.:

What's the best way to design the client side model?

public class Book {
    int id;

    List<Author> authors; //option1
    List<Integer> authorIds; //option2
    Map<Integer, Author> idAuthorMap; //option3
}

(This is a Book which has multiple authors). All three options have different pros and cons:

  1. I could directly access the corresponding Author model, but if I request a Book model via REST, I maybe don't want the model now, but later. So option 2 would be better:
  2. I could request a Book model directly via REST. And use the authorIds to afterwards fetch the corresponding author(s). But now I can't simply use myBook.getAuthors().
  3. This is a mixture of 1. and 2.: If I just request the Books with only the Author ids included, I could do something like: idAuthorMap.put(authorId, null).

But maybe there's a Java library that handles all the stuff for me?!


That's it for now. Thank you guys :)


The maybe solution(s):

Problem: Select only the data I need. This means more or less to ignore every @ManyToMany, @OneToMany, @ManyToOne relations.

Solution: Use @JsonIgnore and/or @JsonIgnoreProperties.


Problem: Every ignored relation should get fetched easily without modifying the data model.

Solution: Example models:

class Book {
  int bId;
  Author author; // has @ManyToOne
}

class Author {
  int aId;
  List<Book> books; // has @OneToMany
}

Now I can fetch a book via REST: GET /books/4 and the result will look like that ('cause I ignore all relations via @JsonIgnore): {"bId":4}

Then I have to create another route to receive the related author: GET /books/4/author. Will return: {"aId":6}.

Backwards: GET /authors/6/books -> [{"bId":4},{"bId":42}].

There will be a route for every @ManyToMany, @OneToMany, @ManyToOne, but nothing more. So this will not exist: GET /authors/6/books/42. The client should use GET /books/42.

Benjamin M
  • 23,599
  • 32
  • 121
  • 201

1 Answers1

1

First, you will want to control how the JPA layer handles your relationships. What I mean is using Lazy Loading vs. Eager loading. This can easily be controller via the "fetch" option on the annotation like thus:

@OneToMany(fetch=FetchType.Lazy)

What this tells JPA is that, for this related object, only load it when some code requests it. Behind the scenes, what is happening is that a dynamic "proxy" object is being made/created. When you try to access this proxy, it's smart enough to go out and do another SQL to gather that needed bit. In the case of Collection, its even smart enough to grab the underlying objects in batches are you iterate over the items in the Collection. But, be warned: access to these proxies has to happen all within the same general Session. The underlying ORM framework (don't know how Eclipselink works...I am a Hybernate user) will not know how to associate the sub-requests with the proper domain object. This has a bigger effect when you use transportation frameworks like Flex BlazeDS, which tries to marshal objects using bytecode instead of the interface, and usually gets tripped up when it sees these proxy objects.

You may also want to set your cascade policy, which can be done via the "cascade" option like

@OneToMany(cascade=CascadeType.ALL)

Or you can give it a list like:

@OneToMany(cascade={CascadeType.MERGE, CascadeType.REMOVE})

Once you control what is getting pulled from your database, then you need to look at how you are marshalling your domain objects. Are you sending this via JSON, XML, a mixture depending on the request? What frameworks are you using (Jackson, FlexJSON, XStream, something else)? The problem is, even if you set the fetch type to Lazy, these frameworks will still go after the related objects, thus negating all the work you did telling it to lazily load. This is where things get more specific to the mashalling/serializing scheme: you will need to figure out how to tell your framework what to marshal and what not to marshal. Again, this will be highly dependent on whatever framework is in use.

CodeChimp
  • 8,016
  • 5
  • 41
  • 79
  • Hi and thanks for your detailed answer!! I use Spring MVC 3.2.1 and Jackson 2. Spring automatically detects Jackson and uses it, if there's a HTTP Request that want's to receive `application/json`. For now I have another (for me) hard question: How to design the model on the client side? (I'll explain it in more detail at the bottom of my question) – Benjamin M Feb 22 '13 at 15:49
  • I dont think you need to create data translation objects. You should be able to simply modify how Jackson uses your existing domain objects. This is an article from a few years ago, but it demonstrates one way to customize how Jackson serializes/marshals an object: http://texscribbles.blogspot.com/2010/07/custom-json-serialization-with-spring.html – CodeChimp Feb 22 '13 at 20:16
  • As for your security question, I assume that you are using AJAX from a web page? If so, then the simple answer would be to make the user authenticate via the website, then all requests to your app server after that will use that authentication. The AJAX calls are no more than a simple HTTP query. If, however, you have unauthenticated clients hitting these methods, you will have to either use Basic Auth or some other mechinism (Single Sign-on or some custom app-key mechinism) to authenticate and authorize the requests. – CodeChimp Feb 22 '13 at 20:19
  • As far as stateless auth, there could be other solutions. OAuth might be worth looking into, or some other authorization framework (SiteMinder for SSO, for instance). – CodeChimp Feb 22 '13 at 20:20
  • Thank you. I'll have a look at the Jackson website. ... Concerning authentication: At the moment we have Java/Swing clients, but later this year there'll be a second client using HTML/JavaScript. But these HTML clients are going to be some kind of Java app with an embedded firefox, which comes as stand alone application, thus using JSONP. – Benjamin M Feb 22 '13 at 20:28
  • The custom JSON serialization approach is very very ugly, I'd have to write over 100 new classes... But maybe I have no other choice. BTW: This seems to be related to the `FetchType` question: http://stackoverflow.com/questions/4805761/jpa2-hibernate-stop-lazy-loading – Benjamin M Feb 22 '13 at 20:36
  • I think, as long as you are using SSL, you are fine on the Security issue. I definately wouldn't recommend not using SSL, but that would be the same if you were doing straight Spring MVC w/ HTML/JSPs using AJAX. Heck, I would want some sort of encryption even if I were developing a custom protocol using Sockets. – CodeChimp Feb 22 '13 at 20:38
  • Yeah sure. SSL is planned, but before that everything else must be running ;) I think I've found the solution for the circular dependency thing using Jackson: Simply use `@JsonIgnore`: http://forum.springsource.org/showthread.php?92684-Exclude-bean-field-from-JSON-response&p=311041#post311041 – Benjamin M Feb 22 '13 at 20:42
  • I've updated my question to reflect what solutions I will use for specific problems. Looks pretty nice now :) – Benjamin M Feb 22 '13 at 22:29