I have a parent entity that holds a list of child entities. I'm using Spring Data Rest so there is no custom controller.
These are my entities:
#Entity
class Template(
childComponents: MutableList<AbstractLabelComponent> = mutableListOf(),
){
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null
#JsonManagedReference
#OneToMany(mappedBy = "template", cascade = [CascadeType.ALL], orphanRemoval = true)
private var components: MutableList<AbstractLabelComponent> = childComponents
set(value){
field.clear()
field.addAll(value)
field.forEach { it.template = this }
}
fun addComponent(component: AbstractLabelComponent){
component.template = this
this.components.add(component)
}
}
And the child class:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes(
JsonSubTypes.Type(value = TextComponent::class, name = TextComponent.TYPE),
JsonSubTypes.Type(value = TextAreaComponent::class, name = TextAreaComponent.TYPE),
JsonSubTypes.Type(value = ImageComponent::class, name = ImageComponent.TYPE)
)
abstract class AbstractLabelComponent(
#field:ManyToOne
#field:JsonBackReference
#field:JoinColumn(name="template_id")
open var template: Template?
){
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
open var id: Long? = null
}
This is the json I'm posting to the API:
{
"components": [
{
"id": 2300,
},
{
"id": 2302,
}
],
"id": 1789
}
I've left some simple fields out to make it more readable. I have no repository for components and the template repository has no code other than extending the basic crud repo.
At first, the child components were created without any problem but the template_id field was not set. This is why I created the setter for the components field to set the template. This works fine when I add components to a template that has no components, however I noticed that when I try to add more of them, they end up having no template ID again.
I've added a breakpoint to the setter and turns out, it is not called. I figured probably because if the list is not empty, JPA is not setting the list but instead adding elements to it. Am I right? If so, is there a way to tell it to use the addComponent method instead so I can set the template? Or any other way to ensure that a reference to the template is set?
Turns out, the difference between the two scenarios was not the number of components present in the database but whether there were any changes to the entity. Not sure but seems like JPA is not persisting the entity if there are no changes and probably it does not bother setting these kind of relationships either. There may be a better solution but the workaround is to set the template id in the JSON from the frontend if the entity is not new.
Related
have a many-1 relationship pupil-formGroup: pupils are assigned to a formGroup and a formGroup can contain many pupils. I have attempted to implement an InverseRelationShadowVariable having watched your video/tutorial https://www.youtube.com/watch?v=ENKHGBMDaCM (which does not quite correspond with the latest optaplanner documentation I realise)
FormGroup extracts
#Entity
#PlanningEntity
public class FormGroup {
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
Pupil extracts
#Entity
#PlanningEntity
public class Pupil
#PlanningVariable(valueRangeProviderRefs = "formGroupRange")
#ManyToOne
private FormGroup formGroup;
Config extracts
<solutionClass>org.acme.optaplanner.domain.Plan</solutionClass>
<entityClass>org.acme.optaplanner.domain.Pupil</entityClass>
<entityClass>org.acme.optaplanner.domain.FormGroup</entityClass>
I believe I've followed the steps in the videoexactly (don't we all say that) but at solve time I get hundreds of errors... Repetitions of the following
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
Any hint gratefully received...
The InverseRelationShadowVariable creates a bi-directional relationship between the genuine planning entity (Pupil) and the planning value (FormGroup). This may become problematic if you re-use your planning domain classes for other purposes, such as ORM persistence or serialization.
In this case, Jackson is unable to serialize Pupil, because it references a FormGroup, which has a List containing a reference back to that Pupil. See? An endless loop.
Solve this issue by adding the #JsonIgnore annotation on top of your inverse relation property and breaking that loop for Jackson:
#Entity
#PlanningEntity
public class FormGroup {
#JsonIgnore // THIS IS THE FIX
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
...
I have a parent child entity configured in a Spring Date REST repository. The parent looks like this
#Entity
#Table(name = "DPST_DTL")
public class Deposit {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "deposit", orphanRemoval=true)
private List<Instrument> instrumentList = new ArrayList<Instrument>();
}
The child looks like this:
#Entity
#Table(name = "INSTR_DTL")
public class Instrument {
#ManyToOne
#JoinColumn(name = "DPST_ID")
#JsonBackReference
private Deposit deposit;
}
I have defined a RepositoryRestresource for Deposit as follows:
#RepositoryRestResource(collectionResourceRel = "deposit", path = "deposit")
public interface DepositRepository extends PagingAndSortingRepository<Deposit, Long>{
}
and a same one for Instrument as follows:
#RepositoryRestResource(collectionResourceRel = "instrument", path = "instrument")
public interface InstrumentRepository extends PagingAndSortingRepository<Instrument, Long>{
}
If I attempt to POST the parent with some child records, I get a message like below:
"message": "Failed to convert from type [java.net.URI] to type [com.XXX.irh.insprc.instrument.Instrument] for value 'countryCode'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI countryCode. Is it local or remote? Only local URIs are resolvable."
},
"countryCode" happens to be the first field in the child JSON
If I query a the parent with some children, teh resulting JSON does not expant the children and just display a link like this:
"instrumentList": {"href": "http://localhost:9090/deposit/8/instrumentList"}
However if I mark the child repository with exported=false, I am able to get past this issue. But the child entity cannot be exposed via a REST API.
Question is:
Is there anyway I can expose basic CRUD functionalities for both parent and child entities, without writing custom controllers etc.?
I understand that as per DDD best practices my parent is an aggregate that should be exposed via a REST Repository, but I do have some use cases unfortunately where I need independent CRUD functionality for both.
You can use projections:
#Projection(name = "withInstruments", types = Person.class)
public interface WithInstruments {
#Value("#{target.depositName}")
String getDepositName();
List<Instrument> getInstrumentList();
}
Then you can GET your entities together:
GET /deposits?projection=withInstruments
{
"depositName": "deposit1",
"instrumentList": [
{
"intrumentName": "instrument1",
},
{
"intrumentName": "instrument1",
}
]
}
Additional info
I've got an NHibernate 4 project with several collection relationships. I'm unit-testing the object model, exercising all the collections. Most work fine, but in one case, the child collection is cascade-saved properly, but on loading the parent entity and examining the collection property, the child collection is empty.
Here are the abbreviated classes. GatewayUser is the parent object, and it has a collection of Student. The collection has a private backing property, and AddStudent/RemoveStudent methods.
Further complications: I'm using the NHibernate.AspNet.Identity library for OAuth2 user management, and GatewayUser inherits from IdentityUser. That in turn inherits from the the library's internal base entity class, which is different from my project's own base class.
public class GatewayUser : IdentityUser
{
public GatewayUser()
{
}
public virtual string FirstName { get; set; }
// ...More value properties and OAuth stuff omitted
// students associated with this user
private IList<Student> _students = new List<Student>();
public virtual IList<Student> Students
{
get { return new ReadOnlyCollection<Student>(_students); }
}
public virtual GatewayUser AddStudent(Student s)
{
if (_students.Contains(s))
return this;
s.GatewayUser = this;
_students.Add(s);
return this;
}
public virtual GatewayUser RemoveStudent(Student s)
{
if (_students.Contains(s))
{
_students.Remove(s);
}
return this;
}
Student is more ordinary; it inherits from my own BaseEntity class, has many value properties, and its own child collection of ProgramApplication items. Interestingly, this collection saves and loads fine; it's got the same structure (private backer, etc.) as the failing collection in GatewayUser.
The mapping is complicated, because the library internally maps its classes with NHiberante.Mapping.ByCode.Conformist classes (which I have no prior experience with).
I'm mapping my own classes with NHibernate automapping, because I have so many classes and properties to map. To get it all working, I copied the library's mapping helper class, and modified it a bit to add my base entity classes to it's list called baseEntityToIgnore. I also had to create a conformist mapping for GatewayUser, since it has a different base entity type, and my automapping wouldn't pick it up.
The unit test looks like this:
[Test]
public void GatewayUserCascadesStudents()
{
var u = new GatewayUser() { FirstName = "Mama", LastName = "Bear", UserName = "somebody#example.com" };
var s1 = new Student() { FirstName = "First", LastName = "Student" };
var s2 = new Student() { FirstName = "Second", LastName = "Student" };
u.AddStudent(s1).AddStudent(s2);
using (var s = NewSession())
using (var tx = s.BeginTransaction())
{
s.Save(u);
tx.Commit();
}
GatewayUser fetched = null;
int count = 0;
using (var s = NewSession())
{
fetched = s.Get<GatewayUser>(u.Id);
count = fetched.Students.Count;
}
Assert.AreEqual(2, count);
}
The generated SQL inserts into both AspNetUsers and GatewayUser (reflecting the inheritance relationship), and inserts two records into Student. All good. On fetching, the SELECT joins the two user tables, and I get a GatewayUser object, but accessing the Students collection does not trigger a SELECT on the Student table. But if I change the mapping to Lazy(CollectionLazy.NoLazy), the SQL to select eagerly load Students appears in the log, but the collection is not populated. If I switch the database from SQLite to Sql Server, I see the student records in the table. The generated SQL (when NoLazy is applied) will fetch them. So on the database end, things look fine.
I have to think my Frankenstein mapping situation is to blame. I'm mixing the library's conformist mapping with Fluent mapping, and there are two different base entity classes. However, the generated schema looks correct, and the save cascades correctly, so I don't know if that's the issue.
Found my own answer. My mapping of the parent class's list was like this:
public class GatewayUserMap : JoinedSubclassMapping
{
public GatewayUserMap()
{
Key(g => g.Column("Id"));
Property(c => c.FirstName, m => m.Length(50));
// ... more properties
List(gu => gu.Students, map =>
{
map.Key(c => c.Column("GatewayUser_Id"));
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
map.Index(li => li.Column("ListIndex"));
map.Access(Accessor.Field | Accessor.NoSetter);
}
);
}
}
I have a private backing field for the collection. Removing Accessor.NoSetter from the collection mapping fixed it. In fact, it still worked without Accessor.Field -- I guess the mapper does a good job of looking around for one, and using it if found. Changing the name of the private backer from "_students" to "funnyName" prevented the mapper from finding it.
I've tried with the Swagger JaxRs current master 1.0, and the devel_2.0 branch for Swagger 2.0.
#ApiModel(value = "Animal",
subTypes = {Dog.class, Lion.class},
discriminator = "type")
public class Animal {
#ApiModelProperty(value = "the discriminator field.")
private String type;
And here is one of the sub classes,
#ApiModel(value = "Lion", parent = Animal.class)
public class Lion {
#ApiModelProperty(value = "the discriminator field.")
private String type;
I haven't found any many examples of what to expect, but here is the output in my current Swagger 2.0 projects swagger.json file.
"definitions":{
"Animal":{
"properties":{
"type":{
"type":"string",
"description":"the discriminator field."
}
},
"discriminator":"type"
},
No sign of the Dog or Lion object under definitions. Nothing in the request object. I'm not sure what this would look like if it worked, but let me know if you know how it should work.
All the code is here if you want to see the full context.
https://github.com/javatestcase/RestEasy/tree/RestEasyVersion2
Your examples helped me alot, so I thought I should help you in return because I got it working now!
You need to tell the serialisation/deserialisation how to bind the implementation:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, // Were binding by providing a name
include = JsonTypeInfo.As.PROPERTY, // The name is provided in a property
property = "type", // Property name is type
visible = true // Retain the value of type after deserialisation
)
#JsonSubTypes({//Below, we define the names and the binding classes.
#JsonSubTypes.Type(value = Lion.class, name = "Lion"),
#JsonSubTypes.Type(value = Dog.class, name = "Dog")
})
#ApiModel(value = "Animal", subTypes = {Dog.class, Lion.class}, discriminator = "type")
public class Animal {
I am using Gorm standalone with spring-boot.
I have a domain class, annotated with #Entity, which has several children collections.
#Entity
#EqualsAndHashCode(includes="id")
class Order {
String hash
static hasMany = [lines:OrderLine]
static mapping = {
hash column: 'hash', index: 'hash_index'
inputs lazy: false
}
}
Here is one of the child classes:
#Entity
#EqualsAndHashCode(includes="id")
class OrderLine {
int lineNo
double amount
static belongsTo = [transaction: Transaction]
static constraints = {
lineNo validator: {val -> val>=0}
amount validator: {val -> val>=0.0}
}
}
In a Spock test, after creating an Order, I cannot use the addToLines method. It complains that there is no signature of method addToLines.
If I manually set the lines collection to an ArrayList, I can then manually add a line, which is later persisted. If I don't manually initialize the lines collection, attempts to simply add fail.
So when in GORM do these collections get created and the addTo methods added to the class?
Dependencies in my gradle.build include:
"org.springframework.boot:spring-boot-starter",
"org.grails:gorm-hibernate4-spring-boot:1.0.0.RELEASE",