My situation:
I have an Organization that can have many workers. I am trying to add a new worker to an organization following this example by the major contributing author of the SDR project and I get various errors (including 204 but nothing happens).
Here are my entities and rest calls:
#Entity
public class Organization {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany
#JoinTable(name = "OrganizationWorker", joinColumns = {#JoinColumn(name = "OrganizationID")},
inverseJoinColumns = {#JoinColumn(name = "WorkerID")})
private Set<Worker> workers = new LinkedHashSet<>();
}
public class Worker {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#NotNull
private String givenName;
#NotNull
private String familyName;
#NotNull
private LocalDate dob;
#NotNull
private String nationalId;
private byte[] photo;
}
GET http://localhost:8080/hal
{
"_links": {
"workers": {
"href": "http://localhost:8080/hal/workers{?page,size,sort}",
"templated": true
}
}
}
POST http://localhost:8080/hal/workers
{
"givenName": "James",
"familyName": "Bond",
"dob": "1970-01-01",
"nationalId": "XXX-60-XXXX",
"photo": null,
}
Response:
Location: http://localhost:8080/hal/workers/8
Date: Mon, 02 May 2016 16:53:02 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application
Content-Type: application/hal+json;charset=UTF-8
{
"givenName": "James",
"familyName": "Bond",
"dob": "1970-01-01",
"nationalId": "XXX-60-XXXX",
"photo": null,
"_links": {
"self": {
"href": "http://localhost:8080/hal/workers/8"
},
"worker": {
"href": "http://localhost:8080/hal/workers/8"
}
}
}
Final step, as per link in description:
curl -X PUT -H "ContentType: text/uri-list" http://localhost:8080/hal/organizations/2 -d 'http://localhost:8080/hal/workers/8'
{
"cause": null,
"message": "Target bean is not of type of the persistent entity!"
}
Doing some debug it's pretty obvious what the specific complaint is. The stack trace leads here:
#Override
public PersistentPropertyAccessor getPropertyAccessor(Object bean) {
Assert.notNull(bean, "Target bean must not be null!");
Assert.isTrue(getType().isInstance(bean), "Target bean is not of type of the persistent entity!");
return new BeanWrapper<Object>(bean);
}
getType() -> Organization
isInstance(bean) -> bean instance of org.springframework.hateoas.Resource
Any input on this? I followed the instructions to the letter.
Here is the answer (it took going out for a walk to clear my head and hit this).
You have to post to the association resource
http://localhost:8080/hal/organizations/1/workers
That nugget occurred to me and then I went and re-read the post.
For dumb mistakes like mine, a 400 error would have been much more useful.
Related
I just added a relation with #CreatedBy to one of my entites and since then, I receive a NullPointerException accessing it via ID. But first things first:
The entity. I am leaving out some fields but left the "owner" in place, since the stack trace (see below) refers to that. The "creator" is the relation I added.
#Entity
#Data
#NoArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class Invitation implements BaseEntity<Long>, OwnedEntity {
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne
private Company owner;
#CreatedBy
#OneToOne
private Account creator;
...
}
The "creator" field is set by my implementation of AuditorAware which looks like this:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class AuditorProvider implements AuditorAware<Account> {
private static final Log LOG = LogFactory.getLog(AuditorProvider.class);
private final #NonNull AccountRepo accountRepo;
#Override
public Optional<Account> getCurrentAuditor() {
Optional<Account> opt = accountRepo.findMe();
if (opt.isPresent()) {
LOG.debug("Found auditor: " + opt.get());
} else {
LOG.debug("No auditor found.");
}
return opt;
}
}
The method accountRepo.findMe() finds the current instance of Account based on the security context.
With this in place, when I POST an Invitation entity like
curl -XPOST -H "Authorization: Bearer $TOKEN" -H "Content-type: application/hal+json" localhost:8081/invitations -d '{"email":"k#lo.de","role":"http://localhost:8081/roles/139"}'
the response body looks good:
{
"email" : "k#lo.de",
"_links" : {
"self" : {
"href" : "http://localhost:8081/invitations/144"
},
"invitation" : {
"href" : "http://localhost:8081/invitations/144"
},
"creator" : {
"href" : "http://localhost:8081/invitations/144/creator"
},
"role" : {
"href" : "http://localhost:8081/invitations/144/role"
},
"owner" : {
"href" : "http://localhost:8081/invitations/144/owner"
}
}
}
The database table for Invitations and the logs show that the "creator" has successfully been set.
Fetching all invitations works perfectly fine without any errors:
curl -v -H "Authorization: Bearer $TOKEN" -H "Content-type: application/hal+json" http://localhost:8081/invitations
Fetching that Invitation with ID 144 gives me an HTTP 500 error:
curl -v -H "Authorization: Bearer $TOKEN" -H "Content-type: application/hal+json" http://localhost:8081/invitations/144
Looking into the logs, I see this stack trace: https://pastebin.com/mVzHHddU
The reason I left the "owner" relation in the snippet above is this line:
at training.edit.identity.model.Company.hashCode(Company.java:22) ~[classes/:na]
Other than that, none of the lines are familiar to me and I cannot make any sense out of the error.
Any ideas would be highly appreciated!
Edit: The company entity
#Data
#NoArgsConstructor
#Entity
public class Company implements BaseEntity<Long>, OwnedEntity {
#Id
#GeneratedValue
private Long id;
#NotNull
private String name;
#NotNull
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Address> addresses = new HashSet<Address>();
public boolean addAddress(Address address) {
return this.addresses.add(address);
}
#JsonIgnore
#Override
public ScopedEntity getParent() {
return null;
}
#JsonIgnore
#Override
public Set<Company> getTenants() {
return Sets.newHashSet(this);
}
#Override
public void configureTenant(Company tenant) {
throw new RuntimeException("Cannot configure tenant on Company.");
}
}
Edit: Because of the lombok related comment below, I removed the #Data annotation from Company and created the getters and setters manually. Like this, fetching an Invitation by ID works.
Does that make sense to anyone?
I'm trying to model a business paying dividends and exposing a REST API to interact with such model.
I'm using spring-boot 2.2.0
Here is my Dividend entity:
#Entity
public class Dividend {
#EmbeddedId
DividendId dividendId;
#ManyToOne(fetch = FetchType.LAZY)
public Stock stock;
public Short period;
public Float amount;
#OneToOne
public Currency currency;
public static class DividendId implements Serializable
{
private DividendId() {}
public DividendId(String stockId, String payDay)
{
this.stockId = stockId;
this.payDay = payDay;
}
public String stockId;
public String payDay;
}
}
Doing a POST with the following body:
{
"amount": 0.6120,
"currency": {
"currencyId": "EUR"
},
"period": 2018,
"stock": {
"stockId": "BME:ENG"
},
"dividendId": {
"stockId": "BME:ENG",
"payDay": "2018-12-19"
}
}
dividendId is set to null and the other properties are not null
Why is dividendId set to null?
and How may I avoid repeating the stockId two times?
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 :)
Is it possible to only store the id of subobject as a String attribute when retrieve an Object from database with database.load("objectId") ?
see documentation here : https://orientdb.com/docs/3.0.x/java/Object-DB-Attach.html
More informations
What I see possible with the documentation but that's not enough for me :
The POJO
class Person {
#Id
private String id;
private Address address;
}
class Address {
#Id
private String id;
// Not loaded using lazy loading
private String city;
}
The corresponding built object :
{
"id": "#10:10",
"address": {
"id": "#15:2"
}
}
What I want
POJOs are identical except from address field which is a String now.
class Person {
#Id
private String id;
private String address;
}
// I didn't add again the code for Address POJO
The sought JSON :
{
"id": "#10:10",
"address": "#15:2"
}
i have this Rest-DSL:
// this api creates new user
rest("/user")
.post()
.type(User.class).to("jpa://com.project.User")
This is my entities:
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user")
private List<User> users;
}
my problem is in my swagger in the Body value parameter example. It contains like this:
{
"id": "string",
"role": {
"id": "string",
"users": [
{
"id": "string",
"roles": [
{}
]
}
]
}
}
quite complicated, although i need only id and id_role parameters to create (POST) new user. I hope the body example shows like this:
{
"id": "string",
"id_role": "string"
}
I realized that my entities are not created properly. These was i learned:
Configure CascadeType in associated JPA entities
#Entity
public class User {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<User> users;
}
to make class not recursive, set #JsonIgnore
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
#JsonIgnore
// this attribute will not appear inside Role class
private List<User> users;
}