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:
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;@Entitypublic class Team extends AbstractVersionedEntity<TeamId> {@NotBlankprivate 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:
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 teamADD CONSTRAINT FK_team_to_user FOREIGN KEY (coach_id) REFERENCES tt_user;
We will also create a TeamService
:
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:
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@Transactionalpublic 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);}@Overridepublic 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));}@Overridepublic Team createTeam(String name, UserId coachId) {User coach = getCoach(coachId);return createTeam(name, coach);}@Overridepublic Optional<Team> getTeam(TeamId teamId) {return repository.findById(teamId);}@Overridepublic 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;}@Overridepublic void deleteTeam(TeamId teamId) {repository.deleteById(teamId);}@Overridepublic void deleteAllTeams() {repository.deleteAll();}private User getCoach(UserId coachId) {return userService.getUser(coachId).orElseThrow(() -> new UserNotFoundException(coachId));}}
The TeamRepository
is a normal ...