I am trying to learn Spring framework and I have an idea to create REST API service, then REST client to perform REST request to my REST service and then to create Web service which will use my REST API. So for now I have done simple CRUD methods in controller class:
#RestController
#RequestMapping("/api/games")
public class ApiGamesController {
private static final String ENDPOINT_WITH_PLATFORM_AND_GAME_NAME = "/{platformName}/{gameName}";
private final GameService gameService;
private final ModelMapper modelMapper;
private final PlatformService platformService;
private final GameGenreService genreService;
private final DeveloperStudioService developerStudioService;
private final GameInfoService gameInfoService;
#Autowired
public ApiGamesController(GameService gameService, ModelMapper modelMapper,
PlatformService platformService, GameGenreService genreService,
DeveloperStudioService developerStudioService,
GameInfoService gameInfoService) {
this.gameService = gameService;
this.modelMapper = modelMapper;
this.platformService = platformService;
this.genreService = genreService;
this.developerStudioService = developerStudioService;
this.gameInfoService = gameInfoService;
}
#PostMapping
public ResponseEntity<BaseResponse> addGame(#RequestBody GameInfoDto gameInfoDto) {
return Utils.createResponse(createGameFromGameInfoDto(gameInfoDto), HttpStatus.CREATED);
}
#DeleteMapping(ENDPOINT_WITH_PLATFORM_AND_GAME_NAME)
public ResponseEntity<BaseResponse> deleteGame(#PathVariable String gameName,
#PathVariable String platformName) {
Game gameToDelete = gameService.getGameByPlatformAndName(platformName, gameName);
GameInfo gameInfo = gameToDelete.getGameInfo();
Set<Game> games = gameInfo.getGames();
games.remove(gameToDelete);
if (games.isEmpty()) {
gameInfoService.deleteEntity(gameInfo);
}
String responseMessage = String.format("Game -- %s has been deleted.", gameName);
return Utils.createResponse(responseMessage, HttpStatus.OK);
}
#PatchMapping("/{gameName}")
public ResponseEntity<BaseResponse> updateGame(#PathVariable String gameName,
#RequestBody GameInfoDto gameInfoDto) {
GameInfo gameInfo = findAndUpdateGameInfo(gameName, gameInfoDto);
Set<Game> gamesFromResponse = gameInfoDto.getPlatforms().stream()
.map(platform -> new Game(gameInfo, platformService
.findPlatformByName(platform.getPlatformName())))
.collect(Collectors.toSet());
Set<Game> gamesToSave = Utils.findNewAndExisted(gameInfo.getGames(), gamesFromResponse);
if (!gamesToSave.isEmpty()) {
gameInfo.setGames(gamesToSave);
}
gameInfoService.save(gameInfo);
return Utils.createResponse(new GameInfoDto(gameInfo), HttpStatus.OK);
}
#GetMapping
public ResponseEntity<BaseResponse> getAllGames() {
return Utils.createResponse(convertGameCollectionToDto(gameService.getAll()),
HttpStatus.OK);
}
#GetMapping(ENDPOINT_WITH_PLATFORM_AND_GAME_NAME)
public ResponseEntity<BaseResponse> getGame(#PathVariable String gameName,
#PathVariable String platformName) {
Game game = gameService.getGameByPlatformAndName(platformName, gameName);
return Utils.createResponse(convertGameToGameDto(game), HttpStatus.OK);
}
#ExceptionHandler
private ResponseEntity<GameNotFoundResponse> handleGameNotFoundException(
GameNotFoundException exception) {
GameNotFoundResponse response = new GameNotFoundResponse(exception.getMessage());
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
#ExceptionHandler
private ResponseEntity<PlatformNotFoundResponse> handlePlatformNotFoundException(
PlatformNotFoundException exception) {
PlatformNotFoundResponse response = new PlatformNotFoundResponse(exception.getMessage());
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
private GameDto convertGameToGameDto(Game game) {
GameDto dto = modelMapper.map(game, GameDto.class);
dto.setAverageRating(game.getAverageRating());
Set<PlatformDto> platforms = game.getOtherPlatforms()
.stream()
.map(platform -> new PlatformDto(platform.getPlatformName()))
.collect(Collectors.toSet());
dto.getGameInfo().setPlatforms(platforms);
return dto;
}
private Set<GameDto> convertGameCollectionToDto(Collection<Game> games) {
return games.stream()
.map(this::convertGameToGameDto)
.collect(Collectors.toSet());
}
private Set<Game> createGameFromGameInfoDto(GameInfoDto gameInfoDto) {
GameGenre genre = genreService.findGameGenreByGenreName(
gameInfoDto.getGameGenre().getGenreName());
DeveloperStudio developerStudio = developerStudioService
.findDeveloperStudioByStudioName(gameInfoDto.getGameDeveloperStudio()
.getStudioName());
Set<Platform> platforms = gameInfoDto.getPlatforms()
.stream()
.map(platformDto -> platformService
.findPlatformByName(platformDto.getPlatformName()))
.collect(Collectors.toSet());
GameInfo gameInfo = new GameInfo(gameInfoDto.getName(),
gameInfoDto.getGameDescription(), gameInfoDto.getGameReleaseDate(),
genre, developerStudio);
Set<Game> gamesToSave = platforms.stream()
.map(platform -> new Game(gameInfo, platform))
.collect(Collectors.toSet());
gameInfo.setGames(gamesToSave);
gameInfoService.save(gameInfo);
return gamesToSave;
}
private GameInfo findAndUpdateGameInfo(String gameName, GameInfoDto gameInfoDto) {
GameInfo gameInfo = gameInfoService.findGameInfoByName(gameName);
gameInfo.setGameName(gameInfoDto.getName());
gameInfo.setGameDescription(gameInfoDto.getGameDescription());
gameInfo.setGameReleaseDate(gameInfoDto.getGameReleaseDate());
GameGenre genre = genreService.findGameGenreByGenreName(
gameInfoDto.getGameGenre().getGenreName());
DeveloperStudio developerStudio = developerStudioService
.findDeveloperStudioByStudioName(gameInfoDto.getGameDeveloperStudio()
.getStudioName());
gameInfo.setGameGenre(genre);
gameInfo.setGameDeveloperStudio(developerStudio);
return gameInfo;
}
}
Here are my models classes:
Game
#Getter
#Setter
#ToString
#NoArgsConstructor
#Entity
#Table(name = "game")
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#NotNull
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "base_game_info_id", nullable = false)
#JsonManagedReference
private GameInfo gameInfo;
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "platform_id", nullable = false)
#JsonManagedReference
private Platform platform;
#OneToMany(mappedBy = "game", cascade = CascadeType.PERSIST)
#ToString.Exclude
#JsonManagedReference
private Set<GameRating> gameRatings = new HashSet<>();
public Game(GameInfo gameInfo, Platform platform) {
this.gameInfo = gameInfo;
this.platform = platform;
}
public Set<Platform> getOtherPlatforms() {
return gameInfo.getAllPlatforms().stream()
.filter(gamePlatform -> !gamePlatform.equals(this.platform))
.collect(Collectors.toSet());
}
public double getAverageRating() {
return gameRatings.stream()
.flatMapToInt(gameRating -> IntStream.of(gameRating.getRating()))
.average()
.orElse(0.0);
}
}
DeveloperStudio
#Getter
#Setter
#ToString
#Entity
#Table(name = "developer_studio")
public class DeveloperStudio {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "studio_id", nullable = false)
private Integer id;
#NotNull
#Column(name = "studio_name", nullable = false, length = Integer.MAX_VALUE)
private String studioName;
#OneToMany(mappedBy = "gameDeveloperStudio")
#ToString.Exclude
#JsonBackReference
private Set<GameInfo> gameInfos;
}
Game Genre
#NoArgsConstructor
#Getter
#Setter
#ToString
#Entity
#Table(name = "game_genre")
public class GameGenre {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "genre_id", nullable = false)
private Integer id;
#NotNull
#Column(name = "genre_name", nullable = false, length = Integer.MAX_VALUE)
private String genreName;
#OneToMany(mappedBy = "gameGenre")
#ToString.Exclude
#JsonBackReference
private Set<GameInfo> gameInfos = new HashSet<>();
}
and all other models which are made with same template, nothing specials.
So for now I have made those Dto classes, which are transform it. But is there some way to handle it? Because its ok to return this class as JSON but if i need to take it as response body it throws me error something like this:
Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (`java.util.Set<org.jcd2052.api.models.GameInfo>`) not compatible with managed type (org.jcd2052.api.models.GameInfo)
at [Source: (String)"[{"gameInfo":{"name":"test game sadsad","gameDescription":"some game 7 description","gameReleaseDate":2022,"gameGenre":{"genreName":"Studio 2"},"gameDeveloperStudio":{"studioName":"Studio 1"},"platforms":[{"platformName":"Playstation"},{"platformName":"Playstation 3"}]},"platform":{"id":5,"platformName":"Playstation 2"},"averageRating":0.0,"gameRatings":[]},{"gameInfo":{"name":"test game3213","gameDescription":"some game 7 description","gameReleaseDate":2000,"gameGenre":{"genreName":"Shooter"},""[truncated 5357 chars]; line: 1, column: 1]
So my main question how to transform json body from request into my jpa entity? or I need to transform every class to dto and then use find to every field from Dto?
Thanks.
Some answers how to handle in this situation.How to build system in right way?
Related
Im learning, and so far i created many to many bidirectional database - user can create many groups and group can have many users - and i cannot find a way for my GroupsController Post mapping to work, from my understanding, it requires to get firstly Users id, in order to set the right relationship in Join table for Group, because the relationship should be set only when user create/join group, not when user create sign up procedure. Postman throws 500 and intelliJ:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null] with root cause
java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null
I use lombok - #Data, #Getter, therefore getId() should be available for use from Group class. My GroupsController with POST mapping when user decides to create a new group:
#RestController
#RequestMapping("api/groups") // pre-path
public class GroupsController{
#Autowired
private GroupsService groupsService;
#Autowired
private UserService userService;
#Autowired
private final GroupsRepository groupsRepository;
#Autowired
private UserRepository userRepository;
public GroupsController(GroupsRepository groupsRepository) {
this.groupsRepository = groupsRepository;
}
#GetMapping("/all-groups")
public List<Groups> getGroups(){
return (List<Groups>) groupsRepository.findAll();
}
#PostMapping("/user/{usersId}/create-group")
public ResponseEntity<Groups> createGroup(#PathVariable(value = "usersId") Long usersId, #RequestBody Groups groupRequest){
Groups group = userRepository.findById(usersId).map(users -> {
long groupsId = groupRequest.getId();
// add and create new group
users.addGroup(groupRequest);
return groupsRepository.save(groupRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId));
return new ResponseEntity<>(group, HttpStatus.CREATED);
}
}
Group database class:
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Table(name = "group_collection")
public class Groups {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name ="group_name", unique = true, nullable = false, length = 20)
private String groupName;
#Column(name = "size", nullable = false)
private int size;
#Column(name = "strict", nullable = false)
private boolean strict;
#Column(name = "open", nullable = false)
private boolean open;
#Column(name ="description", length = 300)
private String description;
#Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
},
mappedBy = "groups")
#JsonIgnore
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
And Users class for database:
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username", unique = true, nullable = false, length = 100)
private String username;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
#Enumerated(EnumType.STRING)
#Column(name = "role", nullable = false)
private Role role;
#Transient
private String accessToken;
#Transient
private String refreshToken;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
})
#JoinTable(name = "groups_x_user",
joinColumns = { #JoinColumn(name = "users_id") },
inverseJoinColumns = {#JoinColumn(name = "groups_id")})
private Set<Groups> groups = new HashSet<>();
public void addGroup(Groups group) {
this.groups.add(group);
group.getUsers().add(this);
}
public void removeGroup(long id){
Groups group = this.groups.stream().filter(g ->
g.getId() == id).findFirst().orElse(null);
if(group != null){
this.groups.remove(group);
group.getUsers().remove(this);
}
}
For reference my GroupsService implementation:
#Service
public class GroupsServiceImpl implements GroupsService{
private final GroupsRepository groupsRepository;
public GroupsServiceImpl(GroupsRepository groupsRepository) {
this.groupsRepository = groupsRepository;
}
#Override
public Groups saveGroup(Groups group) {
group.setCreateTime(LocalDateTime.now());
return groupsRepository.save(group);
}
#Override
public Optional<Groups> findByGroupName(String groupName) {
return groupsRepository.findByGroupName(groupName);
}
}
You need to persist the object from request. And since you have Many-2-Many relation, you can insert related object from both sides. In your case: just add existing user to the newly created group
The method will look something like that:
#PostMapping("/user/{usersId}/groups")
public ResponseEntity<Groups> createGroup(#PathVariable(value = "usersId") Long usersId, #RequestBody Groups groupRequest) {
Groups createdGroup = userRepository.findById(usersId)
.map(user -> {
groupRequest.setId(null); // ID for new entry will be generated by entity framework, prevent override from outside
groupRequest.getUsers().add(user); // add relation
return groupsRepository.save(groupRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId));
return new ResponseEntity<>(createdGroup, HttpStatus.CREATED);
}
Goodmorning, i have a problem with a study project. If you consider the ER schema in the image, you can see one relation many-to-many with an associated table. The table Personaggi is composed:
ID_PERSONAGGIO (Auto Generated)
Name
My scope is that of insert with Postman, only one Personaggio without to insert an Albo. Therefore i try to call the API with Postman and pass this Json
{"nome": "Wolverine"}
but i recive this response:
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'application/json;charset=UTF-8' not supported"
On the console i have this
2020-10-21 09:06:04.647 WARN 33208 --- [nio-5051-exec-2] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class it.xxxxx.xxxxx.entities.Personaggio]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (java.util.Set) not compatible with managed type (it.xxxx.xxxx.entities.Albo)
Controller
#RestController
#RequestMapping("/api/personaggio")
public class PersonaggioController {
#Autowired
PersonaggioService personaggioService;
#RequestMapping(value = "/inserisci", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> insertNewCollezione(#Valid #RequestBody Personaggio personaggio, BindingResult bindingResult) {
HttpHeaders headers = new HttpHeaders();
ObjectMapper mapper = new ObjectMapper();
headers.setContentType(MediaType.APPLICATION_JSON);
ObjectNode responseNode = mapper.createObjectNode();
responseNode.put("code", HttpStatus.OK.toString());
responseNode.put("message", "Inserimento Personaggio " + personaggio.getNome() + " Eseguita Con Successo");
return new ResponseEntity<>(responseNode, headers, HttpStatus.CREATED);
}
}```
#Entity
#Table(name="personaggi")
#Data
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "idPersonaggio",
scope = Integer.class)
public class Personaggio implements Serializable {
public Personaggio(String nome) {
this.nome = nome;
}
public Personaggio() {
// TODO Auto-generated constructor stub
}
private static final long serialVersionUID = -4390691806313380055L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_personaggio")
private Integer idPersonaggio;
#Column(name = "nome")
private String nome;
#OneToMany(mappedBy="personaggio")
// #JsonManagedReference(value = "listPersonaggiAlbo")
private Set<AssAlboPersonaggi> listPersonaggi = new HashSet<>();
}
/******************** AssAlboPersonaggi *************/
#Entity
#Table(name="ass_albo_personaggi")
#Data
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = AssAlboPersonaggiKey.class)
public class AssAlboPersonaggi {
#EmbeddedId
AssAlboPersonaggiKey id = new AssAlboPersonaggiKey();
#ManyToOne
#EqualsAndHashCode.Exclude
#JoinColumn(name="fk_albo", insertable = false, updatable = false)
// #JsonBackReference(value = "listAlboPersonaggio")
Albo albo;
#ManyToOne
#EqualsAndHashCode.Exclude
#JoinColumn(name="fk_personaggio", insertable = false, updatable = false)
// #JsonBackReference(value = "listPersonaggioAlbo")
Personaggio personaggio;
#Column(name = "flg_protagonista")
private boolean flgProtagonista;
}
/******************** Albo *************/
#Entity
#Table(name="albi")
#Data
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "idAlbo",
scope = Integer.class)
public class Albo implements Serializable{
private static final long serialVersionUID = 6046242309533678207L;
public Albo(double prezzoDiAcquisto, int numeroAlbo, String annoDiPubblicazione, String meseDiPubblicazione, String titoloAlbo) {
this.prezzoDiAcquisto = prezzoDiAcquisto;
this.numeroAlbo = numeroAlbo;
this.annoDiPubblicazione = annoDiPubblicazione;
this.meseDiPubblicazione = meseDiPubblicazione;
this.titoloAlbo = titoloAlbo;
}
public Albo() { }
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id_albo")
private Integer idAlbo;
#ManyToOne
#EqualsAndHashCode.Exclude
#JoinColumn(name = "collezione", referencedColumnName = "id_collezione")
#JsonManagedReference
private Collezione collezione;
#OneToMany(mappedBy = "albo", cascade = CascadeType.ALL)
// #JsonManagedReference(value = "listAlboPersonaggio")
private Set<AssAlboPersonaggi> listPersonaggi = new HashSet<>();
#Column(name="prezzo_acquisto")
private Double prezzoDiAcquisto;
#Column(name="numero")
private Integer numeroAlbo;
#Column(name="anno_pubb")
private String annoDiPubblicazione;
#Column(name="mese_pubb")
private String meseDiPubblicazione;
#Column(name="titolo_albo")
private String titoloAlbo;
I have a simple RestController that returns a User Object after making calls to the DB by extending org.springframework.data.repository.PagingAndSortingRepository Interface.
The Challenge now is when I make this call for UserObject, addresses return Persistentbag Error
Error Exception occurred: com.sun.jdi.InvocationException occurred invoking method..
Snip of the Error
#Entity(name = "users")
public class UserEntity implements Serializable {
private static final long serialVersionUID = -2675537776836756234L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userDetails", cascade = CascadeType.ALL)
private List<AddressEntity> address = new ArrayList<>();
//Constructors and Getters and Setters and toString()
}
#Entity(name="addresses")
public class AddressEntity implements Serializable {
private static final long serialVersionUID = 4209374923046988553L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name="users_id")
private UserEntity userDetails;
//Constructors and Getters and Setters and toString()
}
And the Controller
#RequestMapping(value = "/pagedList", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE })
public List<UserRest> getUsers(#RequestParam(value = "page", defaultValue = "0") int page,
#RequestParam(value = "limit", defaultValue = "25") int limit) {
List<UserRest> returnvalue = new ArrayList<>();
List<UserDTO> userDTO = userService.getUsers(page, limit);
for (UserDTO source : userDTO) {
UserRest target = modelMapper.map(source, UserRest.class);
target.setDate(source.getCreatedAt());
returnvalue.add(target);
}
return returnvalue;
}
The UserService Class:
public List<UserDTO> getUsers(int page, int limit) {
List<UserDTO> returnvalue = new ArrayList<>();
if (page > 0)
page -= 1;
Pageable pageableRequest = PageRequest.of(page, limit);
Page<UserEntity> usersPage = userRepo.findAll(pageableRequest);//Returns Nothing for addresses at this point
List<UserEntity> users = usersPage.getContent();
for (UserEntity source : users) {
returnvalue.add(modelMapper.map(source, UserDTO.class));
}
return returnvalue;
}
Thanks
So I found out that changing the annotation to
#OneToMany(fetch = FetchType.EAGER, mappedBy = "userDetails", cascade = CascadeType.ALL)
Solved the issue.
Thank you all
I have two tables one is parent and other one is child. When I am trying to save initially, I am able to insert values in both the tables if values not present in Parent table. But at the time of update/insert the values in child table, it is inserting duplicate values.
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class RuleApi {
Long id;
private String market;
private int modelYear;
private String vehicleLine;
private String vehicleLineName;
private String locale;
private String binding;
private String description;
private String createUser;
private String updateUser;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class DescriptorSaveRequest {
#Valid
#NotNull
RuleApi rule;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "MNAVS03_DESCRIPTOR_CONTEXT")
#EntityListeners(AuditingEntityListener.class)
public class DescriptorContext implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(value = AccessLevel.NONE)
#Column(name = "NAVS03_DESCRIPTOR_CONTEXT_K")
private Long id;
#Column(name = "NAVS03_MARKET_N")
private String market;
#Column(name = "NAVS03_MODEL_YEAR_R")
private Integer modelYear;
#Column(name = "NAVS03_VEHICLE_LINE_C")
private String vehicleLine;
#Column(name = "NAVS03_VEHICLE_LINE_N")
private String vehicleLineName;
#Column(name = "NAVS03_LOCALE_N")
private String locale;
#Column(name = "NAVS03_CREATE_USER_C", nullable = false)
private String createUserId;
#CreationTimestamp
#Column(name = "NAVS03_CREATE_S")
private Timestamp createTimestamp;
#Column(name = "NAVS03_LAST_UPDT_USER_C", nullable = false)
private String updateUserId;
#UpdateTimestamp
#Column(name = "NAVS03_LAST_UPDT_S")
private Timestamp updateTimestamp;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "MNAVS04_DESCRIPTOR_RULE")
#EntityListeners(AuditingEntityListener.class)
public class DescriptorRule implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(value = AccessLevel.NONE)
#Column(name = "NAVS04_DESCRIPTOR_RULE_K")
private Long id;
#JoinColumn(name = "NAVS03_DESCRIPTOR_CONTEXT_K", nullable = false)
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private DescriptorContext descriptorContextId;
#Column(name = "NAVS04_BINDING_N",
unique = true)
private String binding;
#Column(name = "NAVS04_DESCRIPTOR_RULE_X")
private String description;
#Column(name = "NAVS04_CREATE_USER_C", nullable = false)
private String createUserId;
#CreationTimestamp
#Column(name = "NAVS04_CREATE_S")
private Timestamp createTimestamp;
#Column(name = "NAVS04_LAST_UPDT_USER_C", nullable = false)
private String updateUserId;
#UpdateTimestamp
#Column(name = "NAVS04_LAST_UPDT_S")
private Timestamp updateTimestamp;
}
#ApiOperation(value = "Create/Update Feature Descriptions", notes = "Create/Update a descriptions based on the given input")
#PostMapping("/descriptor/saveFeatures")
public ResponseEntity<BaseBodyResponse<String>> saveFeatureDescriptions(#Valid #RequestBody DescriptorSaveRequest descriptorSaveRequest) throws Exception {
this.descriptorContextService.saveFeatureDescriptions(
this.descriptorContextMapper.mapDescriptorContext(descriptorSaveRequest),
this.descriptorContextMapper.mapDescriptorRule(descriptorSaveRequest)
);
return ResponseEntity.ok(BaseBodyResponse.result("Saved Successfully"));
}
#Service
public class DescriptorContextService {
//SaveFeatureDescriptions
public void saveFeatureDescriptions(DescriptorContext descriptorContext, DescriptorRule descriptorRule) throws Exception {
DescriptorContext descriptorContext1 =
this.descriptorContextRepository.findByMarketAndModelYearAndVehicleLineAndVehicleLineNameAndLocale(
descriptorContext.getMarket(),
descriptorContext.getModelYear(),
descriptorContext.getVehicleLine(),
descriptorContext.getVehicleLineName(),
descriptorContext.getLocale());
if (descriptorContext1 == null) {
// add a new context
descriptorContext1 = descriptorContextRepository.save(DescriptorContext.builder()
.market(descriptorContext.getMarket())
.modelYear(descriptorContext.getModelYear())
.vehicleLine(descriptorContext.getVehicleLine())
.vehicleLineName(descriptorContext.getVehicleLineName())
.locale(descriptorContext.getLocale())
.createUserId(descriptorContext.getCreateUserId())
.updateUserId(descriptorContext.getUpdateUserId())
.build());
}
Long contextId = descriptorContext1.getId();
List<DescriptorRule> rule = this.descriptorRuleRepository.findByDescriptorContextId(contextId);
if (rule.size() == 0) {
// add a new rule
this.descriptorRuleRepository.save(DescriptorRule.builder()
.descriptorContextId(descriptorContext1)
.binding(descriptorRule.getBinding())
.description(descriptorRule.getDescription())
.createUserId(descriptorContext.getCreateUserId())
.updateUserId(descriptorContext.getUpdateUserId())
.build());
} else {
// update a existing rule
for (DescriptorRule descriptorRule1 : rule) {
if (descriptorRule1.getBinding().equals(descriptorRule.getBinding())) {
descriptorRule1.setDescription(descriptorRule.getDescription());
descriptorRule1.setupdateUserId(descriptorRule.getupdateUserId());
this.descriptorRuleRepository.save(descriptorRule1);
} else {
this.descriptorRuleRepository.save(DescriptorRule.builder()
.descriptorContextId(descriptorContext1)
.binding(descriptorRule.getBinding())
.description(descriptorRule.getDescription())
.createUserId(descriptorContext.getCreateUserId())
.updateUserId(descriptorContext.getUpdateUserId())
.build());
}
}
}
}
}
}
#Component
public class DescriptorContextMapper {
public DescriptorContext mapDescriptorContext(DescriptorSaveRequest descriptorSaveRequest) {
return DescriptorContext.builder()
.market(descriptorSaveRequest.getRule().getMarket())
.vehicleLine(descriptorSaveRequest.getRule().getVehicleLine())
.vehicleLineName(descriptorSaveRequest.getRule().getVehicleLineName())
.modelYear(descriptorSaveRequest.getRule().getModelYear())
.locale(descriptorSaveRequest.getRule().getLocale())
.createUserId(descriptorSaveRequest.getRule().getCreateUser())
.updateUserId(descriptorSaveRequest.getRule().getUpdateUser())
.build();
}
public DescriptorRule mapDescriptorRule(DescriptorSaveRequest descriptorSaveRequest) {
return DescriptorRule.builder()
.id(descriptorSaveRequest.getRule().getId())
.binding(descriptorSaveRequest.getRule().getBinding())
.description(descriptorSaveRequest.getRule().getDescription())
.createUserId(descriptorSaveRequest.getRule().getCreateUser())
.updateUserId(descriptorSaveRequest.getRule().getUpdateUser())
.build();
}
}
{
"rule": {
"binding": "5003",
"description": "Test new 5003-2023 Escape",
"locale": "fr_CA",
"market": "WANAC",
"modelYear": 2023,
"vehicleLine": "TMC",
"vehicleLineName": "Escape",
"createUser": "rdongre",
"updateUser": "rdongre"
}
}
If I am passing this request and values are not present in both the tables then it should insert the values in both the tables which is working as expected with above code. But at the time of update it is going inside the loop and inserting duplicate values. I am trying to update DESCRIPTION in child table if BINDING is present if not it should insert BINDING plus DESCRIPTION
I fixed this by separating Save and Update methods. Thanks to all.
I am not able to save the following entity. I would like to select Lab when I trying to save Server.
#Entity
#Getter
#Setter
public class Lab {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#NotNull
#Column(name = "LAB_NAME")
private String labName;
#NotNull
#Column(name = "LAB_PRIME")
private String labPrime;
#NotNull
#Column(name = "LAB_SERVICE_IP", nullable = false)
private String serviceIp;
#Column(name = "LAB_OWNER", nullable = false)
#Enumerated(EnumType.STRING)
private LabOwner labOwner;
#Column(name = "LAB_RELEASE")
#Enumerated(EnumType.STRING)
private LabRelease labRelease;
#JsonIgnore
#OneToMany(fetch = FetchType.EAGER)
private Set<Server> servers;
public Lab() {
}
public Lab(String labName, String labPrime, String serviceIp, LabOwner labOwner, LabRelease labRelease, Set<Server> servers) {
this.labName = labName;
this.labPrime = labPrime;
this.serviceIp = serviceIp;
this.labOwner = labOwner;
this.labRelease = labRelease;
this.servers = servers;
}
}
Repositories:
public interface LabRepository extends JpaRepository<Lab, Long> {
}
public interface ServerRepository extends JpaRepository<Server, Long> {
}
Server Entitiy;
#Entity
#Getter
#Setter
public class Server {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#NotNull
#Column(name = "LOGICAL_IP")
private String logicalIp;
#NotNull
#Column(name = "INSTANCE_TYPE")
private String instanceType;
#NotNull
#Column(name = "HOST_NAME", nullable = false)
private String hostName;
#NotNull
#Column(name = "HDWR_TYPE", nullable = false)
private String hardwareType;
#NotNull
#Column(name = "A2_TYPE", nullable = false)
private String a2Type;
#ManyToOne(fetch = FetchType.LAZY)
private Lab lab;
public Server() {
}
public Server(String logicalIp, String instanceType, String hostName, String hardwareType, String a2Type, Lab lab) {
this.logicalIp = logicalIp;
this.instanceType = instanceType;
this.hostName = hostName;
this.hardwareType = hardwareType;
this.a2Type = a2Type;
this.lab = lab;
}
}
Controller:
#RestController
#RequestMapping(value = "services/")
public class GenericController {
#Autowired
LabRepository labRepository;
#Autowired
LabRepository serverRepository;
#RequestMapping(value = "server", method = RequestMethod.POST)
public Server create(#RequestBody Server server) {
return serverRepository.saveAndFlush(server);
}
}
I cannot use serverRepository.saveAndFlush(server). It says that S is not within its bound, should extend Lab .
However, when I extend Lab entitiy, my tables were merged. I would like to 2 seperated tables.
In your controller you are using LabRepository instead of ServerRepository. It should be:
#RestController
#RequestMapping(value = "services/")
public class GenericController {
#Autowired
LabRepository labRepository;
#Autowired
ServerRepository serverRepository;
#RequestMapping(value = "server", method = RequestMethod.POST)
public Server create(#RequestBody Server server) {
return serverRepository.saveAndFlush(server);
}
}