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

Entries in service-and-repository (1)

Friday
Feb242012

TGIF - Ok, last Friday Post - Spring Data JPA Makes it Sweeter

Now that we have our service and repository layers, let's not give people too much rope to hang themselves. Let's get rid of the Active Record model.

To do this, change the annotation from @RooJpaActiveRecord to @RooJpaEntity:

@RooJavaBean
@RooToString
@RooJpaEntity
@RooSerializable
public class Team {
...
}

Now, watch Roo re-factor the configuration once we re-run the shell:

(create-repositories-and-services)⚡ [130] % roo                                                                                                  ~/git-repositories/quizzo
    ____  ____  ____  
   / __ \/ __ \/ __ \ 
  / /_/ / / / / / / / 
 / _, _/ /_/ / /_/ /  
/_/ |_|\____/\____/    1.2.1.RELEASE [rev 6eae723]


Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.
Deleted SRC_MAIN_JAVA/com/chariot/games/quizzo/model/Team_Roo_Configurable.aj - 
  not required for governor com.chariot.games.quizzo.model.Team
Deleted SRC_MAIN_JAVA/com/chariot/games/quizzo/model/Team_Roo_Jpa_ActiveRecord.aj - 
  not required for governor com.chariot.games.quizzo.model.Team
roo>

Pretty nifty, right? Just make sure to fix-up any classes that needed Active Record. I had two of them:

  • One place where I persisted the team and team members in the webflow service.
  • Another place in the state machine where I use Team's static entityManager() method to access the Jpa Entity Manager.

Two easy fixes.

First, removing the ActiveRecord code from the webflow method:

  // inject the service:
  @Autowired
  private TeamService teamService;
  
  ...
  
  // change from
  team.persist();
  
  // to
  teamService.saveTeam(team);

Now we can actually test our webflow and mock the service layer. YAY!

Single Responsibility Principal...

This lump of code is like an extension cord between the browser and the database... Let's refactor it and provide it in a TeamService method, and inject that service call into our state machine.

The original code:


@Override
public Map<String, BigDecimal> getScores() {
  Map<String, BigDecimal> scores = new HashMap<String, BigDecimal>();

  Query query = Team.entityManager().createQuery("select t from team " +
      "t where t.quizRun.id = :id", Team.class).setParameter("id", quizRun.getId());
  @SuppressWarnings("unchecked")
  List<Team> teams = query.getResultList();
  Iterator<Team> itTeams = teams.iterator();
  while (itTeams.hasNext()) {
    Team team = itTeams.next();
    scores.put(team.getName(), team.calculateTotalScore());
  }
  return scores;
}

First, create a Spring Data JPA Repository interface method to handle what was our entity manager query. Add the highlighted lines to the Repository interface.

@RooJpaRepository(domainType = Team.class)
public interface TeamRepository {

  @Query("select t from Team t where t.quizRun.id = ?1")
  public List<Team> teamsByQuizRun(Long quizRunId);
}

Yep, Spring Data JPA is super cool. Check with our own Gordon Dickens - he has two blog posts you'd like to read if you want to know more about it.

Now, on to writing the new service method. We'll make it transactional and also read only:

// TeamService method:
public Map<String, BigDecimal> calcScoresByQuizRun(Long quizRunId);

// TeamServiceBean implementation:
@Transactional(readOnly = true)
public Map<String, BigDecimal> calcScoresByQuizRun(Long quizRunId) {

  Map<String, BigDecimal> scores = new HashMap<String, BigDecimal>();
  List<Team> teams = teamRepository.teamsByQuizRun(quizRunId);
  Iterator<Team> itTeams = teams.iterator();
  while (itTeams.hasNext()) {
    Team team = itTeams.next();
    scores.put(team.getName(), team.calculateTotalScore());
  }
  return scores;
}

AND... Our call from the QuizRunStateMachineInMemory bean, which keeps track of state and lets us access the scores:

@Override
public Map<String, BigDecimal> getScores() {
  return teamService.calcScoresByQuizRun(quizRun.getId());
}
  • Note: even better- you can do this with one bit of logic - create a GROUP BY query in the Spring Data JPA method, and eliminate the middle tier. Adding to my refactoring list (broken windows!).

Now we've focused on responsibilities dealt with across this refactor:

  • QuizRunStateMachineInMemory - delivers information such as scores to web flows
  • TeamService - focuses on providing a calculation of scores, regardless of what datasource they use
  • TeamRepository - fetches teams from a JPA-backed data source

There. Now we've separated concerns, modularized our code a bit, and made it all much better to deal with.

Why not all in one heap?

Well, every framework looks great at first. You can just "get things done, quickly." The problem is, as you add developers, logic, fixes, complexity and the rest, it becomes a massive heap of code, with no visible organizing principles.

Final word (for this blog post)

Hey, let's flatten a Roo ActiveRecord finder into three lines!

// from roo's generated finder:

privileged aspect Answer_Roo_Finder {
    
    public static TypedQuery<Answer> Answer.findAnswersByTeamAndQuestion(Team team, Question question) {
        if (team == null) throw new IllegalArgumentException("The team argument is required");
        if (question == null) throw new IllegalArgumentException("The question argument is required");
        EntityManager em = Answer.entityManager();
        TypedQuery<Answer> q = em.createQuery("SELECT o FROM Answer AS o WHERE o.team = :team AND o.question = :question", Answer.class);
        q.setParameter("team", team);
        q.setParameter("question", question);
        return q;
    }
    
}

// to this:

@RooJpaRepository(domainType = Answer.class)
public interface AnswerRepository {
  @Query("SELECT a FROM Answer AS a WHERE a.team.id = ?1 AND a.question.id = ?2")
  public List<Answer> getAnswersByTeamIdAndQuestionId(Long teamId, Long questionId);
}

Ain't that sweet? Next post (unless I get derailed) on to testing the web flow methods.