Spring Data JPA/REST Update subcollection - java

I have a Spring Data Jpa/Rest web application.
I have created two entities, A and B, where entity A contains multiple B entities.
The DTO classes are defined as:
Getters/Setters are omitted.
#Entity
public class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany
#JoinColumn(name = "a_id")
private List<B> b = new LinkedList<>();
}
#Entity
public class B implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String bType;
}
The Respective repositories extend PagingAndSortingReposity.
At the moment, to create a record of type A, I create a collection of links to its subentities (namely Bs). It works fine, but when I attempt to update a record I find that using the same method updates only the first level of the json and the rest remains unchanged.
My question is: how to update entity A together with a new collection of entities B.

Class A has onetomany mapping
so in Class b there must be manytoone simeting like this
#JoinColumn(name = "A_ID", referencedColumnName = "ID")
#ManyToOne
private A aID;
If you add these lines
and add CascadeType.ALL in #ManyToOne
and merge parent entity changes are getting reflected to child table also.

After lots of searching, I received a help from a mentor and internet.
Since I am working with association resource it fails to do the task in such a way. The backend Java application seems to be configured correctly.
To update the content of the application one needs to set the content-type to text/uri-list as listed in the official documentation and simply send a string with link to each subobject.

Related

Bidirectional OneToMany JPA mapping with eager fetch on both sides worked [duplicate]

This question already has answers here:
Problem with LazyInitializationException
(2 answers)
Closed 4 months ago.
I have 3 tables in the DB and 3 JPA entities respectively in Java application.
#Data
#Entity
public class Fraud {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "fraud_type")
private String fraudType;
#Column(name = "fraud_value")
private String fraudValue;
#OneToMany(mappedBy = "fraud", fetch = FetchType.EAGER)
private List<FraudActionEntity> fraudActions;
}
#Data
#Entity
public class FraudActionEntity {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "fraud_id")
private Fraud fraud;
#ManyToOne
#JoinColumn(name = "action_id")
private Action action;
#Column(name = "enabled")
private Boolean enabled;
}
#Data
#Entity
public class Action {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "attribute_key")
private String attributeKey;
#Column(name = "attribute_value")
private String attributeValue;
}
#Repository
public interface FraudRepository extends JpaRepository<Fraud, Integer> {
public Fraud findByFraudTypeAndFraudValue(String fraudType, String fraudValue);
}
My use case
On a certain type of fraud, I want to traverse all the actions that triggers from that type of fraud and act on them.
Access code
Fraud fraud = fraudRepository.findByFraudTypeAndFraudValue("Type", "Value");
log.info(fraud.getFraudActions().get(0).getAction());
When I above code runs, everything works OK. I get the fraud and fraudActions associations as well, without getting any error.
I was under the impression that as both entities Fraud and FraudActionEntity are fetching each other eagerly, so it should give some error like cyclic fetch/infinite fetch loop, but it didn't!
Why did it work? And when exactly will give it error like cyclic fetch error OR infinite fetch loop error? And if it does give a cyclic fetch error, can we fix it using lazy fetch at #ManyToOne side as given below:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fraud_id")
private Fraud fraud;
Update: A simple and very effective work-around towards the LazyInitializationException is to annotate your method with #Transactional annotation. This will create and maintain the transaction while the method is being executed, thereby allowing your code to make the necessary calls to the DB's lazy init objects. Learn more about it here.
The return type of your JPA repository method should be a List of the Entity object, since the result could be more than one row (that is probably why you are getting the null of the fraud variable).
Regarding the Fetch strategy, you could use Eager on that particular association or maybe other strategies. One possible solution would be to make a second query in case you need the lazy-loaded FraudAction list of objects.
Also, as a side-note avoid using lombok data annotation, and always make sure that you have a NoArgsConstructor in your Entity/DTO classes (in your case #Data adds that by accident since it includes #RequiredArgsConstructor and you do not have any final variables.

How to implement lazy loading using Spring data JPA (JPARepository)?

I am using Spring Data JPA and Hibernate as a provider. I've created several Repository classes which extends to JPARepository<Entity,Serializable> class. I am failing at the moment when I am fetching one entity it brings attached / connected entities along with it ! which are either connected via #OneToOne #OneToMany etc. How can I avoid fetching those connected entities ?
I have tried with #OneToMany(fetch=FetchType.LAZY) etc but still no luck. Following are my java code:
Repository
public interface TicketRepository extends JpaRepository<Ticket, Integer>{
}
Ticket Entity
#Entity
#Table(name = "tbl_tickets")
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "customer", nullable = false, length = 256)
private String customer;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinColumn
private User creator;
// ... other properties
}
Service
#Service
public class TicketService {
public Ticket save(Ticket obj,String id) {
User user = userService.findById(Integer.valueOf(id));
obj.setCreator(user);
Ticket savedTicket = ticketRepository.save(obj);
}
}
savedTicket always fetches User entity as well which I do not want to. How could I achieve this ?
Thanks
Get Lazy loading working on nullable one-to-one mapping you need to let hibernate do Compile time instrumentation and add a #LazyToOne(value = LazyToOneOption.NO_PROXY) to the one-to-one relation.
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
#JoinColumn
#LazyToOne(value = LazyToOneOption.NO_PROXY)
private User creator;
Hope this will work.

JPA OneToMany insert not setting the id's correctly

I have two entities, let's say
Person.java:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#OneToMany(mappedBy = "personData", cascade = CascadeType.PERSIST)
private List<SkillsData> skillsData;
// ...
}
SkillsData.java
#Entity
#Table(name = "SkillsData")
public class SkillsData implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#JoinColumn(name = "PERSONID")
#ManyToOne(cascade = REMOVE)
private Person personData;
// ...
}
When I create a person, add a list of type SkillsData to it's skillsData field and persist it everything works with no exceptions thrown, but when I browse the database directly in the SkillsData table the field PERSONID is not populated and because of that the skills added can't be referenced to the right person.
I'm trying to fix this problem for quite some time and I'll be thankful for any help.
The problem might be in the fact that you're not setting SkillsData.personData before persisting leaving it null.
You must set it cause adding SkillsData to the Person.skillsData list is not enough since you declared this side of relationship as inverse(mappedBy attribute).
Therefore it is the SkillsData.personData non-inverse side who is responsible for establishing this relationship.

Hibernate and #JoinFormula: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column

I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!

Using JPA mapped by not primary key field?

I am having troubles getting this working and I wonder if what I am doing simply does not make sense?
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
....
}
#MappedSuperclass
public abstract class Sample {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne (cascade=CascadeType.ALL)
protected Application application;
....
}
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="application", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
Application application;
}
The problem I am having is that I am able to add parts, the database looks correct, then when I attempt to fetch the the parts list I get an empty list.
I can get it to work if I compromise on the database structure by changing these classes:
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="testSample", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
TestSample testSample;
}
The tables are being auto generated by hibernate, so they are coming out like this:
application
id : number
....
test_sample
id : number
application_id : number
...
part
id : number
application_id : number
If I change it to the less desirable way that works, the last table is different:
part
id : number
test_sample_id : number
Because the id's in all cases are being auto generated, there are no shared primary keys. Essentially what I am trying to do is use mappedby where mappedby is referring to a field that is not the primary key of the table/class called "TestSample". This is what I am not sure if makes sense in JPA.
The OneToMany is bi-directional with the "Part" class. I think this is getting very difficult to explain (:
Your one-to-many association between TestSample and Part is not bidirectional, the mappedBy is not correct (the application table is not owning the relation, it is not even aware of test_sample), your mapping doesn't make sense. There is something to change.
I think that you should show what the expected tables are, not the generated one (since the mappings are incoherent, the generated result can't be satisfying). You are talking about compromise so I believe that you have an idea of what the expected result should be. Please show it.

Categories