Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

« Quizzo - it's THURSDAY? Must go into the Dojo and meditate | Main | Quizzo Saturday - testing web flows »
Monday
Feb272012

Quizzo in Roo case of the Mondays. Or, how you can be a JPA doofus 

This morning I took a look at my schema, and started to cry. Well, not really, but in that way people say LOL, I say COL. Here was my original schema (click to view the picture in full resolution):

Wait, this guy knows JPA? Sure he does!

Now, I know some of you have my back here. You say, "look, JPA can be confusing, you have to work with it." The thing is, I wrote a bloody chapter on JPA relationships, I teach JPA, my head just wasn't in the game, OK?

Just kidding. You know by now I'm razzing you all...

Anyhoo, I didn't bother taking any time to pay attention to my relationships (sounds like somebody's going to have cold soup for dinner). Turns out, I was abusing my @JoinColumn and mappedBy settings.

How to do a bi-directional one-to-many relationship

Do it up, Rimp. Here's how. First off, you need to define your one-sided entity and allow it to contain your Set (don't get me started about alternative options for collection types, that'll take us all day):

@RooJavaBean
@RooToString
@RooJpaEntity
public class Quiz {

    @NotNull
    @Size(max = 200)
    private String title;

    @NotNull
    @Size(max = 500)
    private String description;

    @OneToMany(mappedBy = "quiz",
        cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Set<Question> questions = new HashSet<Question>();

}

Now, I know we're all adults in here, so that's why I put the harder stuff in the cabinet. You see, there are several key items of critical information here:

  • The collection is of type Set, and I'm creating a prototype (for new entities before I save them) of the real type of HashSet. Don't assume they will be HashSet instances when you fetch them again, they likely could be proxies that hydrate when you touch them (wear coding gloves).
  • I've decided to use the cascade option to tell JPA to cascade changes to the questions if I persist or update (merge) the quiz.
  • The mappedBy element is all-important. This tells us that the property pointing back to our Quiz instance in the class named Question will be called quiz. This establishes what side owns the relationship. As one of my students once said, "if you see mappedBy, it means this class does NOT own the relationship." Good rule to live by.

Whew! I'm feeling weak and dizzy. A lot of information hiding in there. Let's look at the Question class for the other side of this thing:

@RooJavaBean
@RooToString
@RooJpaEntity
public class Question {
  @ManyToOne
  @JoinColumn(name = "quiz_id")
  private Quiz quiz;
}

Wow, that's it??? Huh. Ok, well erm let's give this a shake. This class is in charge of the relationship, meaning that if it is touched, it will handle the insert or update of the foreign key. So:

  • The quiz variable name matches the mappedBy element in the Quiz table. Think of this as the role of the relationship. You may use the same table twice with different roles (think sourceAccount, destinationAccount and you get the idea. They would each have different @OneToMany relationships with different mappedBy elements, thereby different roles and variables.
  • The foreign key column that handles the database relationship is called quiz_id, and so we'll get a table called Question with a foreign key in it called quiz_id

Easy, peasy! Now, if you look at the relationship between Question and Quiz in the image above, it looks like there are three tables. But in the one below, the relationships are all simplified. That's because I paid attention to @JoinColumn and mappedBy. Click the image to see the full copy:

Mmmm... Low fat data models. Delicious and 1/2 the tables of your normal schema.

What else did I change recently?

I also got the state machine 99% working, with just a hitch on the test that determines whether the quiz is over or not. I'll work on that one tonight, now that I can actually understand my data model without getting sick to my stomach.

I moved the QuizRunState tracking state into memory, as it was failing me. Oh, one more thing - don't assume that IntelliJ catches Java Language assert statements. I was guarding my code with assertions to catch any invalid states in the state machine. Turns out, Maven turns on assertion checking when testing but IntelliJ does not. I'm not talking about JUnit assertions, but the Java System assertion language statement, such as I use in this fragment of QuizRunStateMachineInMemory:


  @Transactional
  public void startQuiz() {
    assert (runState == QuizRunState.NOT_STARTED);
    runState = QuizRunState.IN_PROGRESS;
  }

Oh, it burns us baggins! To fix this, you edit your JUnit runner and add the -ea -esa flags to your vm settings. And, ladies and gentlemen, that was the reason I was getting errors in my Maven code that weren't showing up in IntelliJ. Just sayin'

See ya tomorrow (or maybe later tonight).

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>