In this post, I am going to explain how to update one-to-many mapping entities using spring jpa.
Table’s structure
Projects
ID |
DESCRIPTION |
NAME |
|
|
|
|
|
|
Roles
ID |
ACTIVE |
ROLE_NAME |
PROJECT_ID |
|
|
|
|
|
|
|
|
Project entity is modeled like below
@Entity
@Table(name = "PROJECTS")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Integer id;
@Column(name = "NAME")
private String name;
@Column(name = "DESCRIPTION")
private String description;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "PROJECT_ID")
@JsonManagedReference
private Set<Role> roles;
.......
.......
}
Role entity class is modeled like below.
@Entity
@Table(name = "ROLES")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Integer id;
@Column(name = "ROLE_NAME")
private String roleName;
@Column(name = "ACTIVE")
private Integer active;
@JsonBackReference
@ManyToOne
@JoinColumn(name = "PROJECT_ID")
private Project project;
......
......
}
How to create a project along with role names?
Request payload
{
"description": "pjt1",
"name": "pjt1",
"roles": [
"ADMIN", "EDITOR"
]
}
tables will be updated like below.
PROJECTS table
ROLES table
As you see above snippet, ADMIN and EDITOR role names are associated with the project id 1.
How to update the project along with rolenames?
Request payload
{
"roles": [
"ADMIN", "SERVICE_USER"
]
}
We need to remove the EDITOR role for the project id 1 and add new role SERVICE_USER.
After we update the project, it should deactivate the role EDITOR (ACTIVE indicator is set to value 0).
Steps to be taken while updating the One-to-Many entity
Step 1: Get the persisted project and
roles.
Optional<Project> projectOpt = projectRepository.findById(projectId);
if (!projectOpt.isPresent()) {
throw new AppException("Project not found", HttpStatus.NOT_FOUND);
}
Project project = projectOpt.get();
Set<Role> roles = project.getRoles();
Step 2: Make all the persisted roles to INACTIVE and populate the mapping table, where key is the role name and value is the persisted Role.
Map<String, Role> rolesMap = new HashMap<>();
for (Role role : roles) {
role.setActive(AppConstants.INACTIVE);
rolesMap.put(role.getRoleName(), role);
}
Set<String> existingRoleNames = rolesMap.keySet();
Step 3: Iterate over new roles
a. if the existing role do not contain this new role, create new Role instance, and add it to the existing roles.
b. If the existing role is still valid, set it back to ACTIVE
List<String> roleNames = projectCreateRequestDto.getRoles();
for (String newRoleName : roleNames) {
if (!existingRoleNames.contains(newRoleName)) {
Role newRoleToCreate = new Role();
newRoleToCreate.setProject(project);
newRoleToCreate.setActive(AppConstants.ACTIVE);
newRoleToCreate.setRoleName(newRoleName);
roles.add(newRoleToCreate);
} else {
Role persistedRole = rolesMap.get(newRoleName);
persistedRole.setActive(AppConstants.ACTIVE);
}
}
Save the updated project entity.
project = projectRepository.save(project);
that’s it, you are done….:)
Find the below working application.
Step 1: Create new maven project ‘one-to-many-update-demo’.
Step 2: Update pom.xml with maven dependencies.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sample.app</groupId>
<artifactId>one-to-many-update-demo</artifactId>
<version>1</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
</project>
Step 3: Create application.properties file under src/main/resources folder.
application.properties
# Setting log level to DEBUG logging.level.org.springframework.web=DEBUG ## H2 specific properties spring.h2.console.enabled=true spring.h2.console.path=/h2 spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1; spring.datasource.username=krishna spring.datasource.password=password123 spring.datasource.driverClassName=org.h2.Driver ## JPA specific properties spring.jpa.hibernate.ddl-auto=create spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true ## Database connection pooling properties # Number of ms to wait before throwing an exception if no connection is available. spring.datasource.max-wait=10000 # Maximum number of active connections that can be allocated from this pool at the same time. spring.datasource.tomcat.max-active=10 spring.datasource.tomcat.max-idle=5 spring.datasource.tomcat.min-idle=3
Step 4: Define entity classes.
Project.java
package com.sample.app.entity;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonManagedReference;
@Entity
@Table(name = "PROJECTS")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Integer id;
@Column(name = "NAME")
private String name;
@Column(name = "DESCRIPTION")
private String description;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "PROJECT_ID")
@JsonManagedReference
private Set<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Role.java
package com.sample.app.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonBackReference;
@Entity
@Table(name = "ROLES")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Integer id;
@Column(name = "ROLE_NAME")
private String roleName;
@Column(name = "ACTIVE")
private Integer active;
@JsonBackReference
@ManyToOne
@JoinColumn(name = "PROJECT_ID")
private Project project;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public Integer getActive() {
return active;
}
public void setActive(Integer active) {
this.active = active;
}
}
Step 5: Define repository interfaces.
ProjectRepository.java
package com.sample.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.sample.app.entity.Project;
public interface ProjectRepository extends JpaRepository<Project, Integer>{
}
RoleRepository.java
package com.sample.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.sample.app.entity.Role;
public interface RoleRepository extends JpaRepository<Role, Integer>{
}
Step 6: Define service interfaces.
ProjectService.java
package com.sample.app.service;
import java.util.List;
import com.sample.app.dto.ProjectRequestDto;
import com.sample.app.dto.ProjectResponseDto;
import com.sample.app.exceptions.AppException;
public interface ProjectService {
ProjectResponseDto createProject(ProjectRequestDto projectCreateRequestDto);
ProjectResponseDto updateProject(Integer projectId, ProjectRequestDto projectCreateRequestDto) throws AppException;
List<ProjectResponseDto> all();
}
Step 7: Define Service implementation class.
ProjectServiceImpl.java
package com.sample.app.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import com.sample.app.constants.AppConstants;
import com.sample.app.dto.ProjectRequestDto;
import com.sample.app.dto.ProjectResponseDto;
import com.sample.app.entity.Project;
import com.sample.app.entity.Role;
import com.sample.app.exceptions.AppException;
import com.sample.app.repository.ProjectRepository;
import com.sample.app.service.ProjectService;
@Service
public class ProjectServiceImpl implements ProjectService {
@Autowired
private ProjectRepository projectRepository;
@Override
public ProjectResponseDto createProject(ProjectRequestDto projectCreateRequestDto) {
Project project = new Project();
project.setName(projectCreateRequestDto.getName());
project.setDescription(projectCreateRequestDto.getDescription());
List<Role> roles = new ArrayList<>();
if (projectCreateRequestDto.getRoles() != null) {
for (String role : projectCreateRequestDto.getRoles()) {
Role roleToCreate = new Role();
roleToCreate.setRoleName(role);
roleToCreate.setProject(project);
roleToCreate.setActive(AppConstants.ACTIVE);
roles.add(roleToCreate);
}
}
project.setRoles(new HashSet(roles));
project = projectRepository.save(project);
return convert(project);
}
@Override
public ProjectResponseDto updateProject(Integer projectId, ProjectRequestDto projectCreateRequestDto)
throws AppException {
Optional<Project> projectOpt = projectRepository.findById(projectId);
if (!projectOpt.isPresent()) {
throw new AppException("Project not found", HttpStatus.NOT_FOUND);
}
Project project = projectOpt.get();
Set<Role> roles = project.getRoles();
Map<String, Role> rolesMap = new HashMap<>();
for (Role role : roles) {
role.setActive(AppConstants.INACTIVE);
rolesMap.put(role.getRoleName(), role);
}
Set<String> existingRoleNames = rolesMap.keySet();
List<String> roleNames = projectCreateRequestDto.getRoles();
for (String newRoleName : roleNames) {
if (!existingRoleNames.contains(newRoleName)) {
Role newRoleToCreate = new Role();
newRoleToCreate.setProject(project);
newRoleToCreate.setActive(AppConstants.ACTIVE);
newRoleToCreate.setRoleName(newRoleName);
roles.add(newRoleToCreate);
} else {
Role persistedRole = rolesMap.get(newRoleName);
persistedRole.setActive(AppConstants.ACTIVE);
}
}
project = projectRepository.save(project);
return convert(project);
}
@Override
public List<ProjectResponseDto> all() {
List<Project> projects = projectRepository.findAll();
return convert(projects);
}
private ProjectResponseDto convert(Project project) {
ProjectResponseDto projectResponseDto = new ProjectResponseDto();
projectResponseDto.setId(project.getId());
projectResponseDto.setName(project.getName());
projectResponseDto.setDescription(project.getDescription());
projectResponseDto.setRoles(project.getRoles());
return projectResponseDto;
}
private List<ProjectResponseDto> convert(List<Project> projects) {
List<ProjectResponseDto> finalList = new ArrayList<>();
for (Project project : projects) {
finalList.add(convert(project));
}
return finalList;
}
}
Step 8: Define exception handling classes.
AppException.java
package com.sample.app.exceptions;
import org.springframework.http.HttpStatus;
public class AppException extends Exception {
private String message;
private HttpStatus errorCode;
public AppException(String message, HttpStatus errorCode) {
super();
this.message = message;
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public HttpStatus getErrorCode() {
return errorCode;
}
public void setErrorCode(HttpStatus errorCode) {
this.errorCode = errorCode;
}
}
AppExceptionHandler.java
package com.sample.app.exceptions;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import com.sample.app.controller.ProjectController;
@ControllerAdvice(basePackageClasses = { ProjectController.class })
@ResponseBody
public class AppExceptionHandler {
@org.springframework.web.bind.annotation.ExceptionHandler(AppException.class)
public ResponseEntity<ExceptionResult> handleThrowable(AppException ex) {
return new ResponseEntity<>(new ExceptionResult(ex), ex.getErrorCode());
}
}
ExceptionResult.java
package com.sample.app.exceptions;
public class ExceptionResult {
private String message;
public ExceptionResult(AppException ex) {
this.message = ex.getMessage();
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Step 9: Define request and response dto classes.
ProjectRequestDto.java
package com.sample.app.dto;
import java.util.List;
public class ProjectRequestDto {
private String name;
private String description;
private List<String> roles;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
ProjectResponseDto.java
package com.sample.app.dto;
import java.util.Set;
import com.sample.app.entity.Role;
public class ProjectResponseDto {
private Integer id;
private String name;
private String description;
private Set<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Step 10: Define application constants class.
AppConstants.java
package com.sample.app.constants;
public class AppConstants {
public static final int ACTIVE = 1;
public static final int INACTIVE = 0;
}
Step 11: Define swagger configuration class.
SwaggerConfig.java
package com.sample.app.config;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Component
@EnableAutoConfiguration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket userApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().paths(PathSelectors.any())
.apis(RequestHandlerSelectors.basePackage("com.sample.app.controller")).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Query builder").description("Query builder using spring specification")
.version("2.0").build();
}
}
Step 12: Define project controller class.
ProjectController.java
package com.sample.app.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.sample.app.dto.ProjectRequestDto;
import com.sample.app.dto.ProjectResponseDto;
import com.sample.app.exceptions.AppException;
import com.sample.app.service.ProjectService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@RestController
@RequestMapping(value = "/v1/projects")
@Api(tags = "Projects", description = "This section contains APIs for Projects")
public class ProjectController {
@Autowired
private ProjectService projectService;
@ApiOperation(value = "Get all Projects")
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<ProjectResponseDto>> getProjects() {
List<ProjectResponseDto> projects = projectService.all();
return new ResponseEntity<>(projects, HttpStatus.OK);
}
@ApiOperation(value = "Create Project")
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ProjectResponseDto> createProject(
@ApiParam(name = "ProjectDetails", value = "Project Details Object", required = true) @RequestBody ProjectRequestDto projectRequestDto) {
ProjectResponseDto projectResponseDto = projectService.createProject(projectRequestDto);
return new ResponseEntity<>(projectResponseDto, HttpStatus.CREATED);
}
@ApiOperation(value = "Update Project")
@PutMapping(value = "/{projectId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ProjectResponseDto> updateProject(
@ApiParam(name = "projectId", value = "Numerical denoting "
+ "pro id, for ex. 1.", required = true) @PathVariable(name = "projectId") Integer projectId,
@ApiParam(name = "ProjectDetails", value = "Project Details Object", required = true) @RequestBody ProjectRequestDto projectRequestDto) throws AppException {
ProjectResponseDto projectResponseDto = projectService.updateProject(projectId, projectRequestDto);
return new ResponseEntity<>(projectResponseDto, HttpStatus.CREATED);
}
}
Step 13: Define main application class.
App.java
package com.sample.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String args[]) {
SpringApplication.run(App.class, args);
}
}
Run App.java class file.
Open the url ‘http://localhost:8080/swagger-ui.html#/’ to see the swagger user interface.
Open the url ‘http://localhost:8080/h2’ to see the H2 console.
Total project structure looks like below.
You can download complete working application from below link.
https://github.com/harikrishna553/springboot/tree/master/jpa/one-to-many-update-demo
Previous Next Home
No comments:
Post a Comment