Selecting a Linked Entity View

Learn how to link an entity to another entity in a Spring application with Thymeleaf.

We'll cover the following...

Implementation

A common requirement in an application is the ability to select an entity from a list of entities to link that entity to another entity. Let’s make it practical.

We’ll create a Team entity. Each Team has a coach. When we create a form to create or edit a Team, we will have a combobox to select a coach. That combobox will contain all users of the application.

Creating a team will look like this:

We’ll start by creating our Team entity using JPearl:

mvn jpearl:generate -Dentity=Team

By expanding on that generated code, we have our Team entity like this:

Press + to interact
package com.tamingthymeleaf.application.team;
import com.tamingthymeleaf.application.user.User;
import io.github.wimdeblauwe.jpearl.AbstractVersionedEntity;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotBlank;
@Entity
public class Team extends AbstractVersionedEntity<TeamId> {
@NotBlank
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private User coach;
/**
* Default constructor for JPA
*/
protected Team() {
}
public Team(TeamId id,
String name,
User coach) {
super(id);
this.name = name;
this.coach = coach;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getCoach() {
return coach;
}
public void setCoach(User coach) {
this.coach = coach;
}
}

On lines 17 and 18, we’ll create a link between Team and User by using the many-to-one relationship. This allows for a single coach to coach different teams.

    @ManyToOne(fetch = FetchType.LAZY)
    private User coach; 

To support this Team entity, we need to create a database table:

Press + to interact
CREATE TABLE team
(
id UUID NOT NULL,
version BIGINT NOT NULL,
name VARCHAR NOT NULL,
coach_id UUID NOT NULL,
PRIMARY KEY (id)
);
ALTER TABLE team
ADD CONSTRAINT FK_team_to_user FOREIGN KEY (coach_id) REFERENCES tt_user;

We will also create a TeamService:

Press + to interact
package com.tamingthymeleaf.application.team;
import com.tamingthymeleaf.application.user.User;
import com.tamingthymeleaf.application.user.UserId;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.Optional;
public interface TeamService {
Page<TeamSummary> getTeams(Pageable pageable);
Team createTeam(String name, User coach);
Team createTeam(String name, UserId coachId);
Optional<Team> getTeam(TeamId teamId);
Team editTeam(TeamId teamId, long version, String name, UserId coachId);
void deleteTeam(TeamId teamId);
void deleteAllTeams();
}

The TeamServiceImpl will use the TeamRepository for the database interaction:

Press + to interact
package com.tamingthymeleaf.application.team;
import com.tamingthymeleaf.application.user.User;
import com.tamingthymeleaf.application.user.UserId;
import com.tamingthymeleaf.application.user.UserNotFoundException;
import com.tamingthymeleaf.application.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
@Transactional
public class TeamServiceImpl implements TeamService {
private static final Logger LOGGER = LoggerFactory.getLogger(TeamServiceImpl.class);
private final TeamRepository repository;
private final UserService userService;
public TeamServiceImpl(TeamRepository repository, UserService userService) {
this.repository = repository;
this.userService = userService;
}
@Override
@Transactional(readOnly = true)
public Page<TeamSummary> getTeams(Pageable pageable) {
return repository.findAllSummary(pageable);
}
@Override
public Team createTeam(String name, User coach) {
LOGGER.info("Creating team {} with coach {} ({})", name, coach.getUserName().getFullName(), coach.getId());
return repository.save(new Team(repository.nextId(), name, coach));
}
@Override
public Team createTeam(String name, UserId coachId) {
User coach = getCoach(coachId);
return createTeam(name, coach);
}
@Override
public Optional<Team> getTeam(TeamId teamId) {
return repository.findById(teamId);
}
@Override
public Team editTeam(TeamId teamId, long version, String name, UserId coachId) {
Team team = getTeam(teamId)
.orElseThrow(() -> new TeamNotFoundException(teamId));
if (team.getVersion() != version) {
throw new ObjectOptimisticLockingFailureException(User.class, team.getId().asString());
}
team.setName(name);
team.setCoach(getCoach(coachId));
return team;
}
@Override
public void deleteTeam(TeamId teamId) {
repository.deleteById(teamId);
}
@Override
public void deleteAllTeams() {
repository.deleteAll();
}
private User getCoach(UserId coachId) {
return userService.getUser(coachId)
.orElseThrow(() -> new UserNotFoundException(coachId));
}
}

The TeamRepository is a normal ...