I have a Spring REST application. The application has few entities like Question, PossibleAnswer.
These entities are as follows:
Question.java
public class Question {
#Id
#Column(name = "id")
#GeneratedValue
private UUID id;
//some other fields here
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable( name = "possibleanswer_question",
joinColumns = #JoinColumn(name = "question_id"),
inverseJoinColumns = #JoinColumn(name = "possible_answer_id"))
#EqualsAndHashCode.Exclude
private Set<PossibleAnswer> possibleAnswers = new HashSet<>();
}
PossibleAnswer.java
public class PossibleAnswer {
#Id
#Column(name = "id")
#GeneratedValue
private UUID id;
#JsonIgnore
#ManyToMany(mappedBy = "possibleAnswers")
#EqualsAndHashCode.Exclude
private Set<Question> questions = new HashSet<>();
}
on REST controller side I used PUT request to assign each PossibleAnswer to Question like the following.
#PutMapping(
path = "/{questionId}/possibleAnswers/{possibleAnswerId}"
)
public QuestionDTOResponse assignPossibleAnswerToQuestion(
#PathVariable UUID questionId,
#PathVariable UUID possibleAnswerId
){
Question questionById = questionService.findQuestionById(questionId);
PossibleAnswer possibleAnswerById = possibleAnswerService.findPossibleAnswerById(possibleAnswerId);
questionById.addPossibleAnswerToQuestion(possibleAnswerById);// custom method to add PossibleAnswer to Question
Question savedQuestion = questionService.saveQuestion(questionById);
return mapToQuestionResponse(savedQuestion);
}
This endpoint works only if I create Question and PossibleAnswer separately, later we can assign PossibleAnswer to Question.
But when I try to save Question using the following api endpoint (POST endpoint shown below) to save Question with PossibleAnswer together, there just a new Question gets created with no PossibleAnswer.
#PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public QuestionDTOResponse createQuestion( #RequestBody QuestionDTORequest questionDTORequest ){
Question savedQuestion = questionService.saveQuestion(mapToQuestion(questionDTORequest));
return mapToQuestionResponse(savedQuestion);
}
The request to save Question looks like this.
{
"text" : "Question 9",
"comment": "some comment about the question",
"possibleAnswers": [
{
"text": "possible answer 19"
}
]
}
The save method implementation is as follows:
public Question saveQuestion(Question question) {
return questionRepository.save(question);
}
I have three questions here:
Am I doing the save operation with entities correctly, or is there any better way of doing this.
This is the backend side of the application, how to handle the request frontend when the request JSON has PossibleAnswer with Question togther.
Why is the save operation not working correctly.
Could anyone please help me with these queries?
Related
I have to entities exposed by spring boot application powered by Spring data REST.
#Entity
#Table(name = "joke")
#Data
public class Joke {
#Id
#Column(name = "joke_id")
private Long id;
#Column(name = "content")
private String content;
#JsonProperty("category")
#JoinColumn(name = "category_fk")
#ManyToOne(fetch = FetchType.EAGER)
private Category category;
}
and category
#Entity
#Table(name = "category")
#Data
public class Category {
#Id
#Column(name = "category_id")
private int id;
#Column(name = "name")
private String name;
}
It is working fine and exposing the HAL+Json format. I'm using Traverson client which is working fine:
Traverson client = new Traverson(URI.create("http://localhost:8080/api/"),
MediaTypes.HAL_JSON);
HashMap<String, Object> parameters = Maps.newHashMap();
parameters.put("size", "2");
PagedModel<JokesDTO> jokes = client
.follow("jokes")
.withTemplateParameters(parameters)
.toObject(new PagedModelType<JokesDTO>() {
});
return jokes;
where JokesDTO is:
#Builder(toBuilder = true)
#Value
#JsonDeserialize(builder = JokesDTO.JokesDTOBuilder.class)
#JsonInclude(Include.NON_NULL)
public class JokesDTO {
private String content;
#JsonPOJOBuilder(withPrefix = "")
#JsonIgnoreProperties(ignoreUnknown = true)
public static class JokesDTOBuilder {
}
}
I'm new in HAL and HateOS and I would like to achieve 2 things (and question is - is it possible, and how):
Base on Traverson client call, how to retrieve category (or link to category) in one call? How to extend what I wrote. And I'm not talking about adding additional #JsonProperty annotation to my class definition.
Is it possible to expose the inner query from Spring data REST, so I would be able to get all data with one call, is it possible with #RepositoryRestResource?
I am tryng to do a workaround for this issue, that I have entered:
https://github.com/jhipster/generator-jhipster/issues/9639
I did some work (I added findAllWithEagerRelationships in the repository), and the GET method works fine: I get all the master and children.
What is not working, and I need your help, is the POST method:
when I post a parent with some children (pets), the children are not posted with the parent, so children are lost.
so summarizing this is the get result, correct:
[
{
"id": 1002,
"name": "Piera",
"pets": [
{
"id": 1051,
"name": "fido",
"species": "barboncino",
"owner": {
"id": 1002,
"name": "Piera"
}
}
]
}
]
but the post does not work correctly:
{
"name": "newName",
"pets": [
{
"id": 1051
}
]
}
newName is created, but pet 1051 is not attached to it
I am working on a app generated with Jhipster:
entity Owner {
name String required
}
entity Pet {
name String required,
species String required
}
relationship OneToMany {
Owner{pet} to Pet{owner}
}
for the get, I added those two methods that I copied form a manytomany relationship, and they worked:
#Query(value = "select distinct owner from Owner owner left join fetch owner.pets")
List<Owner> findAllWithEagerRelationships();
#Query("select owner from Owner owner left join fetch owner.pets where owner.id =:id")
Optional<Owner> findOneWithEagerRelationships(#Param("id") Long id);
Public class Owner implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "owner")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Pet> pets = new HashSet<>();
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public class Pet implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#NotNull
#Column(name = "species", nullable = false)
private String species;
#ManyToOne
#JsonIgnoreProperties("pets")
private Owner owner;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
I expect that when I do a post on a parent with some children, the children (pets) are also posted.
In your Owner service interface and implementation layer (it should be under com.yourpackage.service and com.yourpackage.service.impl) create a new method like
public Optional<Owner> findOneWithChildren(long id) {
Optional<Owner> owner = ownerRepository.findById(id);
owner.ifPresent(o -> {
o.getPets().addAll(petRepository.getByParentId(id);
}
return owner.map(ownerMapper::toDto);
}
And in your Pet repository, create a new method, like
List<Pet> getByParentId(long id);
in the bug report, I could find a workaround without touching the JPA. I still don't know how we can put a new owner associating to it some pets with JPA as I did receive a resolving answer, anyway my workaround works. thanks :)
It's my first post, so I hope I do it the right way. I have searched two days for an equivalent Problem, but did not find anything.
Here is what I did:
We have an Entity, that contains (beside others) the folowing fields:
#Entity
#Access(AccessType.FIELD)
#Table(name = "component")
public class Component {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
#OneToMany
#JoinTable(name = "component_dokumentation",
joinColumns = #JoinColumn( name = "component_id" ),
inverseJoinColumns = #JoinColumn(name = "dokumentation_id"))
private Set<FileType> dokumentation;
private Long keySisMf = 0L;
.
.
// Getter and Setter and stuff
}
After one year of usage we have found out, that our Entity became too big and that we have to use DTO Objects to transfer data to the Client, modify them and return them to the Server. For this purpose we modelled an embeddable Entity ComponentAttributes.
So right now it Looks like:
#Entity
#Access(AccessType.FIELD)
#Table(name = "component")
public class Component {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
.
.
#Embedded
private ComponentAttributes componentAttributes;
.
.
}
#Embeddable
#Access(AccessType.FIELD)
public class ComponentAttributes {
private static final long serialVersionUID = 1L;
#OneToMany
#JoinTable(name = "component_dokumentation",
joinColumns = #JoinColumn( name = "component_id" ),
inverseJoinColumns = #JoinColumn(name = "dokumentation_id"))
private Set<FileType> dokumentation;
private Long keySisMf = 0L;
.
.
// Getter and Setter and stuff
}
We did not change anything in the Database. We have encountered Problems in setting values for the set documentation. The field keySisMf is not a Problem. The Problems are just related to the documentation (I must add that FileType is just a Basic Entity consisting of an id and several Strings, so nothing Special). Getting the values and transfering them to the Client is fast and correct. Telling the Server to Change keySisMf is not a Problem. Telling the Server to add or remove a FileType instance simply does not work. No Errors but no changes.
We have logged the JPA generated SQL and there is no SQL generated for component.getComponentAttributes().setDokumentation(fileSet).
We use a Glassfish 4.1.1 Server with an ORACLE Database. Did I miss something when moving dokumentation from Component to ComponentAttributes????
Thanks for your help and patience.
Chris
My code has a Place and Address classes. Address 1:N Places.
There is one index, Address Index.
When the Address is saved, that is ok, works fine. But when the Place is saved, the Index of Address isn't updated.
The problem, maybe is a bug:
When #ContainedIn is on field inside #EmbeddedId class, that doesn't work. The intern mechanism isn't notified, hence index isn't changed.
I've tried some workarounds:
Updated index manually fullTextSession.index(obj). -> DON'T WORK
Mapped Address out of PlaceID class, with insertable and updatable as false. -> DON'T WORK
I've tried to study intern mechanism of HSearch, to try to resolve this problem inside a PreInsertEventListener/PreUpdateEventListener of Hibernate. -> Unsuccessfully.
So, I need to make this work in some way, then my main question is... How can I make it work?
For instance: Is there some intern mechanism/class of Hibernate Search? that I could use inside of a PreInsertEventListener/PreUpdateEventListener (manually way) or somethink like that...
Code of Place class:
#Entity
public class Place {
#IndexedEmbedded
#EmbeddedId
private PlaceID id;
#ContainedIn
#ManyToOne
#JoinColumns({
#JoinColumn(name = "address_id", insertable = false, updatable = false, referencedColumnName = "id"),
#JoinColumn(name = "address_secondId", insertable = false, updatable = false, referencedColumnName = "secondId")
})
private Address address;
#Field(store = Store.YES)
private String name;
}
Code of Address class:
#Indexed
#Entity
public class Address {
#FieldBridge(impl = AddressIdFieldBridge.class)
#IndexedEmbedded
#EmbeddedId
private AddressID id;
#Field(store = Store.YES)
private String street;
#Field(store = Store.YES)
private String city;
#IndexedEmbedded
#OneToMany(mappedBy = "id.address")
private Set<Place> places;
}
Versions of dependencies:
<hibernate.version>5.2.11.Final</hibernate.version>
<hibernate-search.version>5.8.0.Final</hibernate-search.version>
<lucene.version>5.5.4</lucene.version>
Code to update entity and index:
#Test
public void givenPlaces_updateAddressIndex(Address address) {
List<Place> places = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Place place = new Place(new PlaceID((long) new Random().nextInt(500), address));
place.setName("Place " + new Random().nextInt(500));
places.add(place);
}
placeRepository.save(places);
}
Result:
Updated index manually fullTextSession.index(obj). -> DON'T WORK
If that doesn't work, and you're passing the "address" object, it's not related to the #IndexedEmbedded/#ContainedIn.
Code to update entity and index:
This code doesn't update the places field in Address. Hibernate Search may be reindexing Address, but since the address currently in the Hibernate session has an empty places field, well...
Try adding the new places to Address.places in your code, it should work.
I have this piece of code which adds an entry to film_actor (in theory), but it doesn't work. This code doesn't crash, but doesn't save the added entry to the database.
Actor nuevo = ActorFacadeEJB.find(actor_id);
Film pelicula = FilmFacadeEJB.find(film_id);
pelicula.getActors().add(nuevo);
Also I have this code:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "film_actor", joinColumns = { #JoinColumn(name = "film_id") }, inverseJoinColumns = {
#JoinColumn(name = "actor_id") })
private Set<Actor> actors;
public Set<Actor> getActors() {
return actors;
}
The actor also has a film set:
#ManyToMany(mappedBy="actors")
private Set<Film> film= new HashSet<Film>();
How can I fix all this to make it work? I googled it, and many people have similar code to mine, but just mine doesn't work.
Make sure your Annotations are proper as below :
#Entity
#Table(name="film")
class Film{
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="film_actor",
joinColumns={#JoinColumn(name="film_id")},
inverseJoinColumns={#JoinColumn(name="actor_id")})
private Set<Actor> actors=new HashSet<Actor>;
}
#Entity
#Table(name="actor")
class Actor{
#ManyToMany(mappedBy="actors")
private Set<Film> film= new HashSet<Film>();
}
You need to assign both sides of the relationship:
pelicula.getActors().add(nuevo);
nuevo.getFilms().add(pelĂcula);