I am creating an application with REST API so one of the endpoints is to create and another to update. My issue comes when I try to update the entity it updates the fields I added in the creation but not in the update.
I am trying with #DynamicUpdate and #SelectBeforeUpdate and it still follows the same behaviour.
Controller
#RestController
#RequestMapping("/v01")
#Slf4j
public class ProjectController {
#Autowired
private ProjectServiceIface projectService;
#PostMapping(path = "/project", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Project> createProject(#Valid #RequestBody Project project, BindingResult result){
if(result.hasErrors()){
throw new BusinessServiceException(result.getFieldError().getDefaultMessage(), result.getFieldError().getField() + " " + result.getFieldError().getCode());
}
Project projectSaved = projectService.createProject(project);
HttpHeaders headers = new HttpHeaders();
headers.add("Location", projectSaved.getId().toString());
return new ResponseEntity<>(project, headers, HttpStatus.CREATED);
}
#PatchMapping(path = "/project", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Project> updateProject(#Valid #RequestBody Project project, BindingResult result){
if(result.hasErrors()){
throw new BusinessServiceException(result.getFieldError().getDefaultMessage(), result.getFieldError().getField() + " " + result.getFieldError().getCode());
}
Project projectUpdated = projectService.updateProject(project);
if(projectUpdated == null) {
return new ResponseEntity<>(null, new HttpHeaders(), HttpStatus.NOT_FOUND);
}
HttpHeaders headers = new HttpHeaders();
headers.add("Location", projectUpdated.getId().toString());
return new ResponseEntity<>(projectUpdated, headers, HttpStatus.CREATED);
}
}
Service
#Service
public class ProjectServiceImpl implements ProjectServiceIface {
#Autowired
private ProjectRepository projectRepository;
#Autowired
private ProjectRepository projectRepository;
#Override
public Project createProject(Project project) {
Project projectFound = projectRepository.findByName(project.getName());
if(projectFound != null){
throw new BusinessServiceException(Constants.FUNCTIONAL_ERROR, "The Project already exists");
}
project.setCreateTime(new Date());
Project projectSaved = projectRepository.save(project);
return projectSaved;
}
#Override
public Project findProjectById(String id) {
Project projectFound = null;
if(!StringUtils.isNumeric(id)){
throw new BusinessServiceException(Constants.FUNCTIONAL_ERROR, "The ID is not in a correct format");
}
Optional<Project> projectOptional = projectRepository.findById(Integer.valueOf(id));
if(projectOptional.isPresent()){
projectFound = projectOptional.get();
}
return projectFound;
}
#Override
public Project updateProject(Project project) {
Project projectUpdated = null;
Optional<Project> projectFound = projectRepository.findById(project.getId());
if(projectFound.isPresent()){
project.setUpdateTime(new Date());
//Project projectMapped = EntityMapper.projectMapper(project);
projectUpdated = projectRepository.save(project);
}
return projectUpdated;
}
#Override
public Project findProjectByName(String projectName) {
Project project = projectRepository.findByName(projectName);
return project;
}
}
Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
#Query(value = "SELECT * FROM project p WHERE p.name = :projectName", nativeQuery = true)
Project findByName(String projectName);
}
Project (Entity)
#Entity
#Data
#DynamicUpdate
#SelectBeforeUpdate
#Table(name = "project")
public class Project implements Serializable {
private static final long serialVersionUID = -6163778458602900643L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
private String name;
#NotEmpty
private String tag;
private String icon;
#Column(name = "create_user", updatable = false)
private String createUser;
#Column(name = "update_user")
private String updateUser;
#Column(name = "create_time", updatable = false)
private Date createTime;
#Column(name = "update_time")
private Date updateTime;
}
This is the request I use for creating:
{
"name" : "Test12",
"tag" : "TST",
"icon" : "/var/usr"
}
The response I obtain:
{
"id": 5,
"name": "Test12",
"tag": "TST",
"icon": "/var/usr",
"createUser": null,
"updateUser": null,
"createTime": "2020-01-24T22:33:48.499+0000",
"updateTime": null
}
And this is the request I perform to update:
{
"id": 5,
"name": "Test5",
"tag": "AAA"
}
The response I obtain:
{
"id": 5,
"name": "Test67",
"tag": "AAA",
"icon": null,
"createUser": null,
"updateUser": null,
"createTime": null,
"updateTime": "2020-01-24T22:44:50.914+0000"
}
As you can see both icon and createTime have been set to null. I only want to be updated specifically the fields I send in the request/entity.
You can use Spring Data Rests Patch method, or explicitly call merge on EntityManager
it would be helpful if you share the code that you are trying to insert/update
for creation time remove updatable = false in entity class
Issue is you are not getting the values for Project from database ..you are checking in DB if the value exists but saving the values sent in json in Project Json icon is not there
fix is you need to get the values based on ID and set the values again which are going to be changed..this way old values willl be retrieved and new values will be updated
Related
First, I apologize if my english is unclear ; I am french.
I also am a very junior developer, and this is my first real personal project with no tutorial or whatsoever.
I am having some trouble with my Rest api.
I use java 11 and Spring/JPA
I have two DO classes that each represent a table in the database : Artist and Country.
An artist can have several nationalities, and a country can have several artist born in it.
So that means : many to many.
I joined them with an Association table ; ArtistNationality, that is also a class.
I know I could do without an additional class but, since in my app some relations also have some extra-fields (like the year of an award) I decided that all many to many relationships would be materialized the same way, by "join"classes (sorry I really have an hard time to explain in english)
When I create an Artist, I want my response json to contain the created artist with all its nationalities. But it always comes null.
The creation works fine. But here is the response :
What is odd is the results of my API call.
Here is the result of POST method :
{
"artistFirstName": "OH",
"artistLastName": "Test",
"artistBiography": "Je suis un test.",
"artistBirthDate": "1380-11-10",
"artistDeathDate": "1500-11-12",
"artistNationalities": [],
"artist_ID": 3
}
As you can see, nationalities come null, always.
What is expected, is the same as when I do a find or findall :
{
"artistFirstName": "OH",
"artistLastName": "Test",
"artistBiography": "Je suis un test.",
"artistBirthDate": "1380-11-10",
"artistDeathDate": "1500-11-12",
"artistNationalities": [
{
"nationality": {
"countryId": 1,
"countryName": "Andorre",
"countryShortCode": "AD",
"countryFlagFileName": "ad_16.png"
},
"nationalityId": 5
},
{
"nationality": {
"countryId": 12,
"countryName": "Autriche",
"countryShortCode": "AT",
"countryFlagFileName": "at_16.png"
},
"nationalityId": 6
}
],
"artist_ID": 3
}
What I don't understand is that my save method returns the result of a "find" method so why isn't it the same ?? Find and findall work perfectly, and the insertion also works fine.
Here are the DataObject classes, I shortened them to leave only the fields related to question but of course they all come with their constructors and getters/setter stuff :
Artist class :
#Entity
#Table(name = "artist")
public class Artist implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_artist")
private final Integer ARTIST_ID;
#OneToMany(targetEntity = ArtistNationality.class, mappedBy = "artistIdAsForeignKey", cascade = CascadeType.ALL)
#JsonManagedReference
private List<ArtistNationality> artistNationalities;
Artist DTO
public class ArtistDto {
private final Integer ARTIST_ID;
private String artistFirstName;
private String artistLastName;
private String artistBiography;
private String artistBirthDate;
private String artistDeathDate;
private List<NationalityDto> artistNationalities;
Country class :
#Entity
#Table(name = "country")
public class Country implements Serializable {
#Id
#Column(name = "id_country")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private final Integer COUNTRY_ID;
#OneToMany(targetEntity = ArtistNationality.class, mappedBy = "countryIdAsForeignKey", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonBackReference
private List<ArtistNationality> artistsComingFromCountry;
** COUNTRY DTO **
public class CountryDto {
private int countryId;
private String countryName;
private String countryShortCode;
private String countryFlagFileName;
ArtistNationality class :
#Entity
#Table(name="artist_x_nationality")
public class ArtistNationality implements Serializable {
#Id
#GeneratedValue
#Column(name="id_nationality")
private final Integer NATIONALITY_ID;
#ManyToOne (fetch = FetchType.LAZY)
#JsonBackReference
#JoinColumn(name= "fk_nationality_to_artist")
private Artist artistIdAsForeignKey;
#ManyToOne (fetch = FetchType.LAZY)
#JsonBackReference
#JoinColumn(name="fk_nationality_to_country")
private Country countryIdAsForeignKey;
Nationality Dto:
public class NationalityDto {
private final Integer NATIONALITY_ID;
private CountryDto nationality;
ArtistService :
#Service
public class ArtistServiceImpl implements IArtistService {
#Autowired
private IArtistDao artistDao;
#Autowired
private IArtistDoDtoMapper mapper;
#Autowired
private IArtistValidator validator;
#Autowired
private IArtistNationalityDao nationalityDao;
#Override
#Transactional(readOnly = true)
public List<ArtistDto> findAll() {
List<ArtistDto> resultList = new ArrayList<ArtistDto>();
List<Artist> artistsFromDatabase = artistDao.findAll();
if (artistsFromDatabase != null && !artistsFromDatabase.isEmpty()) {
resultList = mapper.mapDoListToDto(artistsFromDatabase);
}
return resultList;
}
#Override
#Transactional(readOnly = true)
public ArtistDto find(final int id) {
Optional<Artist> optArtistFromDatabase = artistDao.findById(id);
if (!optArtistFromDatabase.isPresent()) {
throw new ResourceNotFoundException();
}
Artist artistFromDatabase = optArtistFromDatabase.get();
ArtistDto result = mapper.mapDoToDto(artistFromDatabase);
return result;
}
#Override
#Transactional(rollbackFor = Exception.class)
public ArtistDto save(final ArtistDto objDto) {
if(validator.isValidForDatabase(objDto)){
//Save without nationalities (mapper does not map it);
Artist artistToSave = mapper.mapDtoToDo(objDto);
int artistId = artistDao.save(artistToSave).ARTIST_ID();
//Add the id of the artist to all his nationalities, then save the nationalities
for (NationalityDto nationality : objDto.getArtistNationalities()){
ArtistNationality doNationality = new ArtistNationality(nationality.getNationalityId());
doNationality.setArtistIdAsForeignKey(new Artist(artistId));
doNationality.setCountryIdAsForeignKey(new Country(nationality.getNationality().getCountryId()));
nationalityDao.save(doNationality);
}
ArtistDto returnArtist = this.find(artistId);
return returnArtist;
}
throw new InsertionException("Invalid object. Could not insert into database.");
}
Mappers :
#Override
#Transactional(rollbackFor = Exception.class)
public ArtistDto mapDoToDto(final Artist pDataObject) {
ArtistDto artistDto = new ArtistDto(pDataObject.ARTIST_ID());
if (artistValidator.isNotNullAndNotEmpty(pDataObject.getArtistFirstName())) {
artistDto.setArtistFirstName(pDataObject.getArtistFirstName());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistLastName())) {
artistDto.setArtistLastName(pDataObject.getArtistLastName());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistBiography())) {
artistDto.setArtistBiography(pDataObject.getArtistBiography());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistBirthDate())) {
artistDto.setArtistBirthDate(pDataObject.getArtistBirthDate());
}
if (artistValidator.isOptionPresent(pDataObject.getArtistDeathDate())) {
artistDto.setArtistDeathDate(pDataObject.getArtistDeathDate());
}
List<NationalityDto> artistNationalities = new ArrayList<NationalityDto>();
if (artistValidator.isOptionPresent(pDataObject.getArtistNationalities())) {
System.out.println("yes, we're in !");
for (ArtistNationality nationality : pDataObject.getArtistNationalities()) {
NationalityDto nDto = new NationalityDto(nationality.getNationalityId());
CountryDto cDto = countryMapper.mapDoToDto(nationality.getCountryIdAsForeignKey());
nDto.setNationality(cDto);
artistNationalities.add(nDto);
}
}
artistDto.setArtistNationalities(artistNationalities);
return artistDto;
}
/**
* Note : we add nationality separately since we do not have Artist's ID yet.
*/
#Override
#Transactional(rollbackFor = Exception.class)
public Artist mapDtoToDo(final ArtistDto pDataTransfertObject) {
System.out.println(pDataTransfertObject.toString());
Artist artist = new Artist(pDataTransfertObject.getARTIST_ID());
if (artistValidator.isNotNullAndNotEmpty(pDataTransfertObject.getArtistFirstName())) {
artist.setArtistFirstName(pDataTransfertObject.getArtistFirstName());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistLastName())) {
artist.setArtistLastName(pDataTransfertObject.getArtistLastName());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistBiography())) {
artist.setArtistBiography(pDataTransfertObject.getArtistBiography());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistBirthDate())) {
artist.setArtistBirthDate(pDataTransfertObject.getArtistBirthDate());
}
if (artistValidator.isOptionPresent(pDataTransfertObject.getArtistDeathDate())) {
artist.setArtistDeathDate(pDataTransfertObject.getArtistDeathDate());
}
return artist;
}
This is the Json I send to my controller :
{
"ARTIST_ID" : null,
"artistFirstName":"OH",
"artistLastName":"Test",
"artistBiography":"Je suis un test.",
"artistBirthDate":"1380-11-10",
"artistDeathDate": "1500-11-12",
"artistNationalities": [{
"nationality": {
"countryId" : 1
}
},
{"nationality":{
"countryId": 12
}
}
]
}
I also had to find a trick to save nationalities after artist because when I sent nationalities to database, jpa did not automatically add the saved artist to the nationality and the nationality was saved only with the country data, as you can see in the save method and the dto to do mapper.
I am sorry if it is not really clear, I do my best in english, thank you for understanding.
If your find and findAll is working then, you should be able to override return from save and use find jpa method using the artist id returned by save in your service layer that way you will have a complete json to return.
I'm creating eCommerce for merchants using spring boot with JPA.
I have an issue while creating the order service.
I want to only pass the ID of the nested objects in the request body instead of sending the full nest objects because the size will be extremely big.
Here is my code.
Merchant can do many orders
Order
#Entity
#Table(name = "Orders")
#XmlRootElement
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Order extends BasicModelWithIDInt {
#Basic(optional = false)
#Column(name = "Quantity")
private Integer quantity;
#Basic(optional = false)
#Size(min = 1, max = 150)
#Column(name = "Notes")
private String notes;
#JoinColumn(name = "ProductID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JsonIgnoreProperties
private Product productID;
#JoinColumn(name = "MerchantID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Merchent merchent;
#JoinColumn(name = "OrderSatusID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private OrderStatus orderStatus;
// Getters and Setters
}
Order Holder
public class OrderHolder {
#NotNull
private Order order;
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
OrderRepo
public interface OrderRepo extends JpaRepository<Order, Integer> {
}
Order Controller
#RestController
#RequestMapping(value = "order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderRestController extends BasicController<OrderHolder>{
#Autowired
private OrderRepo orderRepo;
#PostMapping("create")
public ResponseEntity<?> create(#RequestBody #Valid OrderHolder orderHolder, Principal principal) throws GeneralException {
log.debug( "create order {} requested", orderHolder.toString());
Order order = new Order();
order = orderHolder.getOrder();
System.out.println("###############"+order);
try {
order = orderRepo.save(order);
log.info( "Order {} has been created", order );
} catch (Exception e) {
log.error( "Error creating Order: ", e );
e.printStackTrace();
throw new GeneralException( Errors.ORDER_CREATION_FAILURE, e.toString() );
}
return ResponseEntity.ok( order );
}
}
I need request body to look like the below instead of including the full Merchant and Product objects inside the request.
You can make use of JsonView to return only id of product and merchant
public class OrderView {}
...
public class Product{
#Id
#JsonView(OrderView.class)
private Integer id
private String otherFieldWithoutJsonView
...
}
and then in your controller
#PostMapping("create")
#JsonView(OrderView.class) // this will return the product object with one field (id)
public ResponseEntity<?> create(#RequestBody #Valid OrderHolder orderHolder, Principal principal) throws GeneralException {
...
}
hope this can help you
Just have a separate contract class.
public class OrderContract {
private int merchantID;
private String notes;
....
//getter, setters
}
public class OrderHolder {
#NotNull
private OrderContract orderContract;
public OrderContract getOrderContract() {
return orderContract;
}
public void setOrder(OrderContract orderContract) {
this.orderContract = orderContract;
}
}
And before making a call to the Repository , translate from OrderContract to Order.
I would like to share something regarding this.
I have searched a lot on internet and tried lot of things, but the solution given here suited well for this scenario.
https://www.baeldung.com/jackson-deserialization
You need to create a Custom-deserializer for your model by extending StdDeserializer from com.fasterxml.jackson.databind.deser.std.StdDeserializer, where you just want to pass id's and not the whole object in the request.
I have given below example for User Model with Address object.
User(long userId, String name, Address addressId)
Address(long addressId, String wholeAddress)
Writing Deserializer for User class
public class UserDeserializer extends StdDeserializer<User> {
public User() {
this(null);
}
public User Deserializer(Class<?> vc) {
super(vc);
}
#Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = p.getCodec().readTree(p);
long id = 0;
long addressId = (Long) ((IntNode) node.get("addressId")).numberValue().longValue();
return new User(id, name, new Address(addressId, null)
}
Now you have to use
#JsonDeserialize(using = UserDeserializer.class)
public Class User {
...
}
POST request
Before custom deserialization
{
"name" : "Ravi",
"addressId" : { "id" : 1}
}
After custom Deserialization
{
"name" : "Ravi",
"addressId" : 1
}
Also while GET /user/:id call you will get the whole obj like
{
"name" : "Ravi",
"addressId" : { "id" : 1, "wholeAddress" : "Some address"}
}
entity
Entity
#Table(name ="posts")
public class Post {
#Id
#SequenceGenerator(name = "jpaSequence.Post",
sequenceName = "SEQUENCE_POST",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaSequence.Post")
private Long id;
private String subject;
#OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private User user;
public Post() {
}
#Entity
#Table(name = "comments")
public class Comment {
#Id
#SequenceGenerator(name = "jpaSequence.Comment",
sequenceName = "SEQUENCE_COMMENT",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaSequence.Comment")
private Long id;
private String reply;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private User user;
public Comment() {
}
#Entity
#Table(name = "users")
public class User {
#Id
#SequenceGenerator(name = "jpaSequence.User",
sequenceName = "SEQUENCE_USER",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "jpaSequence.User")
private Long id;
private String name;
private String email;
public User() {
}
mapper
#Mapper(componentModel = "spring" )
public interface PostMapper {
Post postDtoToPostEntity(PostDto dto);
PostDto postEntityToPostDto(Post entity);
Iterable<Post> postListDtoToPostListEntity(Iterable<PostDto> list);
Iterable<PostDto> postListEntityToPostListDto(Iterable<Post> list);
}
#Mapper(componentModel = "spring")
public interface CommentMapper {
Comment commentDtoToCommentEntity (CommentDto dto);
CommentDto commentEntityToCommentDto(Comment entity);
Iterable<Comment> commentListDtoToCommentListEntity(Iterable<CommentDto> list);
Iterable<CommentDto> commentListEntityToCommentListDto(Iterable<Comment> list);
}
-service
#Service
public class PostReadServiceImpl implements PostReadService {
private PostRepository repository;
private PostTransformer transformer;
private CommentTransformer transformerComment;
#Autowired
public PostReadServiceImpl(PostRepository repository, PostTransformer transformer,
CommentTransformer transformerComment) {
this.repository = repository;
this.transformer = transformer;
this.transformerComment = transformerComment;
}
#Transactional
#Override
public PostDto getEntryById(Long id) {
Post entity = find(id);
return this.transformer.transformEntityToDto(entity);
}
private Post find(Long id){
return this.repository.findById(id).orElseThrow(() -> new RuntimeException("Post not found!"));
}
#Transactional
#Override
public Iterable<CommentDto> getListEntriesCommentById(Long id) {
Iterable<Comment> listComment = findListComment(id);
Iterable<CommentDto> commentDtoList = this.transformerComment.transformListEntityToDtoList(listComment);
return commentDtoList;
}
private Iterable<Comment> findListComment(Long id){
Post post = this.repository.findById(id).orElseThrow(() -> new RuntimeException("Post not found!"));
List<Comment> comments = post.getComments();
return comments;
}
}
When using MapStruct, I get infinite recursion.
Even if I didn 't request the related comments entity from the received object , I still see infinite nesting in debug mode, and this is despite the fact that I selected
fetch = FetchType.LAZY
Who has any ideas how to fix this and why it works like this ?
Solution
rest
#RestController
#RequestMapping("api/read")
public class PostReadRest {
private static final String PATH_TO_READ_POST_BY_ID = "post/{id}";
private static final String PATH_TO_READ_POST_LIST = "post/list";
private PostReadService service;
#Autowired
public PostReadRest(PostReadService service) {
this.service = service;
}
#GetMapping(PATH_TO_READ_POST_BY_ID)
public PostDto getPostById(#PathVariable String id){
Long idLong = Long.valueOf(id);
return service.getEntryById(idLong);
}
}
service
#Service
public class PostReadServiceImpl implements PostReadService {
private PostRepository repository;
private PostTransformer transformer;
private CommentTransformer transformerComment;
#Autowired
public PostReadServiceImpl(PostRepository repository, PostTransformer transformer,
CommentTransformer transformerComment) {
this.repository = repository;
this.transformer = transformer;
this.transformerComment = transformerComment;
}
#Transactional
#Override
public PostDto getEntryById(Long id) {
Post entity = find(id);
PostDto postDto = this.transformer.transformEntityToDto(entity);
return postDto;
}
private Post find(Long id){
return this.repository.findById(id).orElseThrow(() -> new RuntimeException("Post not found!"));
}
}
mapper
#Mapper(componentModel = "spring")
public interface PostMapper {
Post postDtoToPostEntity(PostDto dto);
#Mapping(target = "post.comments", ignore = true)
CommentDto toCommentDto(Comment entity);
PostDto postEntityToPostDto(Post entity);
Iterable<Post> postListDtoToPostListEntity(Iterable<PostDto> list);
Iterable<PostDto> postListEntityToPostListDto(Iterable<Post> list);
}
#Mapping(target = "post.comments", ignore = true)
post - this is the name of a field 'Comment' Entity.
comments - this is the name of a field 'Post' Entity, but ,
it is field here is second level field. And This is must ignore.
transformers
#Component
public class PostTransformer {
private PostMapper mapper;
#Autowired
public PostTransformer(PostMapper mapper) {
this.mapper = mapper;
}
public PostDto transformEntityToDto(Post entity){
PostDto postDto = this.mapper.postEntityToPostDto(entity);
return postDto;
}
public Post transformDtoToEntity(PostDto dto){
return this.mapper.postDtoToPostEntity(dto);
}
public Iterable<PostDto> transformListEntityToDtoList(Iterable<Post> listEntity){
Iterable<PostDto> postDtoList = this.mapper.postListEntityToPostListDto(listEntity);
return postDtoList;
}
public Iterable<Post> transformListDtoToEntityList(Iterable<PostDto> listDto){
return this.mapper.postListDtoToPostListEntity(listDto);
}
}
result
{
"id": 1,
"subject": "JPA Entity Graph In Action",
"comments": [
{
"id": 1,
"reply": "Nice !!",
"post": {
"id": 1,
"subject": "JPA Entity Graph In Action",
"comments": [],
"user": {
"id": 1,
"name": "user1",
"email": "user1#test.com"
}
},
"user": {
"id": 2,
"name": "user2",
"email": "user2#test.com"
}
},
{
"id": 2,
"reply": "Cool !!",
"post": {
"id": 1,
"subject": "JPA Entity Graph In Action",
"comments": [],
"user": {
"id": 1,
"name": "user1",
"email": "user1#test.com"
}
},
"user": {
"id": 3,
"name": "user3",
"email": "user3#test.com"
}
}
],
"user": {
"id": 1,
"name": "user1",
"email": "user1#test.com"
}
}
It's important.
If you didn't point over a method (it have an annotation #Override) the annotation #Transactional , you encounter with an Exception, if you use - FetchType.LAZY
#Transactional
#Override
public PostDto getEntryById(Long id) {
Post entity = findEntry(id);
List<Comment> comments = entity.getComments();
PostDto postDto = this.transformer.transformEntityToDto(entity);
return postDto;
}
While the transaction is been performing, while the Mapper is been performing, there will be perform additional query, that get nested entries from tables related.
I try to make an API call with the post method via postman to my spring boot application.
Here is the input:
{
"username": "name",
"password": "1234",
"age": 12,
"salary": 5000,
"role": 1
}
Here is the code in the controller:
#RequestMapping(value = "/signup", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> saveUser(#RequestBody UserDto user) {
try {
System.out.println(user.getUsername()); // => name
System.out.println(user.getPassword()); // => 1234
System.out.println(user.getSalary()); // => 5000
System.out.println(user.getRoleDto()); // => null
System.out.println(user.getAge()); // => 24
userService.save(user);
return ResponseEntity.ok().body("insert done");
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
All properties print their value except the user.getRoleDto(), it always null. But, when I try to use this (below code) in the controller, it print the whole object exactly like the input object.
#RequestMapping(value="/signup", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> saveUser(HttpEntity<String> httpEntity){
try {
String json = httpEntity.getBody();
System.out.println(json);
return ResponseEntity.ok().body("insert done");
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
Here is my User.java
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String username;
#Column
#JsonIgnore
private String password;
#Column
private long salary;
#Column
private int age;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "role_id")
private Role role;
// getters and setters
Here is my Role.java
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "role_name", nullable = false)
private String roleName;
// getters and setters
Here is my UserDto.java
private String username;
private String password;
private int age;
private long salary;
private RoleDto roleDto;
Here is my RoleDto.java
private Long id;
private String roleName;
What went wrong here? Any help would be very helpful. Thanks!
UPDATE
Thanks to Burm87, I took the option 1 from his suggestions in the answer. But, somehow the spring still see role_id as null, even the value is printed now. Here is in the userServiceImpl:
#Override
public User save(UserDto user) throws Exception {
User newUser = new User();
BeanUtils.copyProperties(user, newUser, "password");
// above line is refer to [here][1].
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
try {
userDao.save(newUser);
} catch (Exception e) {
throw new Exception(e);
// this throw "Column role_id cannot be null"
}
return newUser;
}
But, if I use below code, the setRole method is not applicable since I define user.getRole() in the DTO as int but define newUser.setRole() in the User entity as Role. But if I change the setRole in User entity as int, then, how I can tell the spring about the ManyToOne relation between User and Role? Note: I think I just want to make the input parameter for role to be just an integer as provided above.
#Override
public User save(UserDto user) throws Exception {
User newUser = new User();
newUser.setUsername(user.getUsername());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
newUser.setAge(user.getAge());
newUser.setSalary(user.getSalary());
newUser.setRole(user.getRole()); // here is the problem
try {
userDao.save(newUser);
} catch (Exception e) {
throw new Exception(e);
}
return newUser;
}
You have two options here:
1) you keep the request you have and you change this field roleDto in your DTO to be an Integer field named role (renaming getter and setter accordingly);
2) you keep your DTO as it is and you change your request sending:
{
"username": "name",
"password": "1234",
"age": 12,
"salary": 5000,
"roleDto": {
"id": 1,
"roleName": "your role name"
}
}
role is null, because In UserDTO you have RoleDto. So you need to rename "role" to "roleDto" as it is a complex object ,it will have its own json. Please replace it with the following Json and see it works. Please follow this tutorial for more understanding.
{
"username": "name",
"password": "1234",
"age": 12,
"salary": 5000,
"roleDto": {
"id":1
}
}
It's because in your json you have "role": 1 and in dto - user.getRoleDto(). There is a mismatch between names, so it cannot be properly mapped. Try changing field name from roleDto in UserDto into role.
I use Angular 5 + Spring Boot. The problem is that I can not send information to my rest controller by post method.
I do not get any error either from the client side or from the server side.
Below the code you will see that I make get method which works correctly.
Let me apologize for my Еnglish.
Spring Entity { Dish }
#Entity
#Table(name = "DISHES")
#Data
public class Dish implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", unique = true)
#NotNull(message = "Ястието трябва да има име.")
#Size(min = 3, max = 30, message = "Името на ястието трябва да е между 3 и 30 символа.")
private String name;
#Column(name = "DESCRIPTION")
#NotNull(message = "Описанието на ястието не може да е празно.")
#Size(min = 3, max = 300, message = "Описанието на ястието трябва да е между 3 и 30 символа.")
private String description;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL ,mappedBy = "dish")
#JsonBackReference
private List<DishCounter> dishCounters;
}
Angular Entity {Dish}
export class Dish {
constructor(public id?: number, public name?: string, public description?: string) {
}
}
Spring Rest Controller {Dish}
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/dish")
public class DishRestController {
private static final Logger logger = LoggerFactory.getLogger(DishRestController.class);
private final DishService dishService;
#Autowired
public DishRestController(final DishService dishService) {
this.dishService = dishService;
}
#GetMapping("/all")
public ResponseEntity<List<Dish>> getAllDishes() {
logger.info("Rest controller find all dishes");
List<Dish> dishes = dishService.getAllDishes();
return ResponseEntity.status(HttpStatus.OK).body(dishes);
}
#PostMapping("/save")
public ResponseEntity<Void> saveDish(#RequestBody Dish dish) {
dishService.saveDish(dish);
return new ResponseEntity<>(HttpStatus.OK);
}
}
And Angular post Method
save(dish: Dish): Observable<Dish> {
let result: Observable<Dish>;
result = this.http.post(this.saveDishUrl, dish)
.map((resp => {
console.log(resp);
return resp;
}))
.catch(e => {
console.log(e);
return Observable.throw(e);
});
console.log(result);
return result;
}
Where are you calling subscribe on the post function? I don't see it here. As http post returns an observable, you must subscribe to it to make the call.
http.post(....).subscribe(response => <DO SOMETHING WITH IT>);
This might not be all of the errors on your code but this is something I noticed.
Your Java #PostMapping doesn't specify what it should be expected to receive and what it should produce in return.
#PostMapping(value = "save", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
For Angular 5, you're using Angular 4 service Syntax, I thought they changed that on 5.