MTI TEK
Spring Framework | Spring JPA

Spring Persistence with JPA

Spring Data JPA sits on top of JPA (Jakarta Persistence API), which itself sits on top of a JPA provider (Hibernate by default in Spring Boot). spring-boot-starter-data-jpa pulls in Hibernate, Spring Data JPA, and Spring ORM. The autoconfiguration wires a DataSource, a LocalContainerEntityManagerFactoryBean, and a JpaTransactionManager — all without any explicit configuration in this project.

Maven Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-h2console</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

DataSource Configuration

spring.application.name=mtitek-spring-jpa
spring.datasource.name=mtitek-spring-jpa
spring.datasource.generate-unique-name=false

H2 Console Access

URL:      http://localhost:8080/h2-console
JDBC URL: jdbc:h2:mem:mtitek-spring-jpa
Driver:   org.h2.Driver
User:     sa
Password: (empty)

JPA Entities

AppProfile — String PK, No @GeneratedValue

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class AppProfile {
    @Id
    private String id;
    private String name;
    private Role role;

    public enum Role {
        USER, ADMIN, SUPPORT;
    }
}

AppUser — Auto-generated PK, @ManyToMany

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
public class AppUser {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Size(min = 3, message = "Name must be at least 3 characters long")
    private String name;

    @Size(min = 1, message = "You must choose at least 1 appProfile")
    @ManyToMany()
    private List<AppProfile> appProfiles = new ArrayList<>();

    public void addAppProfile(AppProfile appProfile) {
        this.appProfiles.add(appProfile);
    }
}

Spring Data Repositories

public interface AppUserRepository extends CrudRepository<AppUser, Long> {
}

public interface AppProfileRepository extends CrudRepository<AppProfile, String> {
}

Data Initialization with CommandLineRunner

@Bean
public CommandLineRunner saveAppProfiles(AppProfileRepository repo) {
    return new CommandLineRunner() {
        @Override
        public void run(String... args) throws Exception {
            repo.save(new AppProfile("1", "AppProfile 1 USER", Role.USER));
            repo.save(new AppProfile("2", "AppProfile 2 ADMIN", Role.ADMIN));
            repo.save(new AppProfile("3", "AppProfile 3 SUPPORT", Role.SUPPORT));
        }
    };
}

Repository Usage in Controllers

AppProfileController — @ModelAttribute and Session Scope

@Controller
@RequestMapping("/appProfiles")
@SessionAttributes("appUser")
public class AppProfileController {
    @ModelAttribute
    public void addAppProfilesToModel(Model model) {
        Iterable<AppProfile> appProfiles = appProfileRepository.findAll();
        Role[] roles = AppProfile.Role.values();
        for (Role role : roles) {
            model.addAttribute(role.toString().toLowerCase(), filterByRole(appProfiles, role));
        }
    }

    @ModelAttribute(name = "appUser")
    public AppUser appUser() {
        return new AppUser();
    }

    @PostMapping
    public String addAppProfile(@RequestParam(required = false) String appProfileId, @ModelAttribute AppUser appUser, Model model) {
        Optional<AppProfile> appProfile = appProfileRepository.findById(appProfileId);
        appUser.addAppProfile(appProfile.get());
        return "redirect:/appUsers/user";
    }
}

AppUserController — Saving with @SessionAttributes and SessionStatus

@Controller
@RequestMapping("/appUsers")
@SessionAttributes("appUser")
public class AppUserController {
    @PostMapping
    public String processAppUser(@Valid AppUser appUser, Errors errors, SessionStatus sessionStatus) {
        if (errors.hasErrors()) {
            return "appUserForm";
        }
        appUserRepository.save(appUser);
        sessionStatus.setComplete();
        return "redirect:/appProfiles";
    }
}