I am trying to build a bidirectional one to many relationship with the spring data jpa but the list annotated with #onetomany always return one element.
Here is the code for my entities(setters and getters omitted):
#Entity
#Table(name = "sdk_sdk")
public class SDKEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String version;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "sdk")
#OrderBy("order ASC")
private List<SDKFileEntity> fileEntities;
}
And the second entity:
#Entity
#Table(name = "sdk_file")
public class SDKFileEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String fileType;
private Integer sdkId;
public SDKFileEntity() {
}
#ManyToOne
#JoinColumn(name = "id", insertable = false, updatable = false)
private SDKEntity sdk;
I am trying to have a manytoone mapping where the sdkId corresponds to the id from the SDKEntity class.
Whenever I try to get the sdkfiles from the sdkEntity using spring's repository, the size of the list is always 1.
So for example:
SDKEntity entity=repository.findOne(foo);
List<SDKFileEntity> files=entity.getFileEntities();
here the size of files is 1, I have to delete the first element from the database to obtain the second element.
For me the reason here was that a parent entity implemented equals and hashcode
and unfortunately in a way that all existing entities were equal.
And non of the child entities implemented it herself.
So then the #OneToMany relation returned only the first element.
Took me quite some time.
This part of Code looks suspicious
#ManyToOne
#JoinColumn(name = "id", insertable = false, updatable = false)
private SDKEntity sdk;
name = "id" it should be actual column name as written in database column name like this
#JoinColumn(name = "VISIT_ID", referencedColumnName = "ID")
#ManyToOne
private Visit visitId;
Related
I'm trying to use Hibernate to map the following relationship:
Each order contains 2 images. When I delete an order I want the images gone as well.
I have two entities, OrderItems and Image and they look like this
public class OrderItems {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Long id;
#Transient
private String language;
#OneToMany(fetch = FetchType.EAGER ,orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "order")
private List<Image> images ;
}
public class Image implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Long id;
#Column(name = "IMAGE_NAME")
private String name;
#Column(name = "IMAGE_BYTES", unique = false, nullable = true, length = 1000000)
private byte[] image;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id" , nullable = false)
private OrderItems order;
}
Inserting new orders will also insert the coresponding images but when I try to delete an order I get an foreign key constraint error from the tables Image
Am I missing something about Hibernate ? Shouldn't the attribute cascade = CascadeType.ALL do the trick ?
Thanks for taking the time to provide any feedback. Cheers
I already tried OneToMany and ManyToOne unidirectional and bidirectional but I get the same foreign key violation error or my images are not saved at all when I save a new order.
I solved the issue by using Spring to delete an order and automagically it also deleted the images corresponding to that order.
So my first approach of deleting orders by executing sql queries directly on the DB was the issue.
Try like this
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#JoinColumn(name = "order_id", nullable = false)
I'm looking for a way to implement (0..n) many to many relation in JPA, much possibly using #ManyToMany annotation. All examples that I found were about (1..n) relations. What I need to accomplish:
- I've got two entities: Contact and Tag. Each Contact can have 0..n Tags. Each Tag can have 0..n Contacts. From SQL point of view it would look like
this: Contact (0..n) --- (1) Contact_has_Tag (1) --- (0..n) Tag.
Code below is not working for me because JPA is linking columns with INNER JOIN.
OFC I could do this using intermediate entity and #OneToMany and #ManyToOne annotations, but I want a simpler sollution.
#Data
#Entity
public class Contact {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private long id;
#Column(unique = true)
private String email;
// ...
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
#JoinTable(
name = "contact_has_tag",
joinColumns = #JoinColumn(name = "contact_id", referencedColumnName = "id", updatable = false, nullable = true),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "id", updatable = false, nullable = true))
private List<ContactTag> contactTags = new ArrayList<ContactTag>();
}
#Entity
#Data
public class ContactTag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String name;
#ManyToMany(mappedBy="contactTags", fetch = FetchType.LAZY)
#JsonIgnore
private List<Contact> contacts = new ArrayList<Contact>();
}
Any ideas how it should be done?
Scenario:
I have a products table with these fields: id, code, description, photo.
I have a product_stock view from other schema with fields: prod_code, stok_tot
Misson: What I need to do in Spring Boot is to join the products with their total stock.
Rules:
I can't use #Query on this task, I need to join them at the Product Entity Class, so that stock became some kind of Transient field of product.
I can't change the fact that product's ID is a Long and ProductStock's ID is a String, but I could use product's code field instead right? (how?)
So far... I tryed to use #OneToOne and #JoinColumn to do the job, but my REST gives me the stock field as NULL.
"Estoque.java"
#Entity
#Table(name = "VW_ESTOQUE", schema = "ASICAT")
public class Estoque {
#Id
#Column(name = "CD_BEM_SERVICO", unique = true, nullable = false)
private String codigo;
#Column(name = "ESTOQUE")
private Long estoque;
// getters and setters hidden by me
}
"Produto.java"
#Entity
#NamedEntityGraph(name = "Produto.detail", attributeNodes = #NamedAttributeNode("categorias"))
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
// here is where I get the null values
#Transient
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "VW_ESTOQUE", joinColumns = #JoinColumn(name = "CODIGO", referencedColumnName = "CODIGO"), inverseJoinColumns = #JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO"))
private Estoque estoque;
private String hash;
#ManyToMany(mappedBy = "produtos", fetch = FetchType.EAGER)
#BatchSize(size = 10)
private List<Categoria> categorias = new ArrayList<>();
// getters and setters hidden by me
}
In my product repository I call FindAll()
You have annotated Produto.estoque as #Transient, which means that it is not part of the persistent state of the entity. Such a field will be neither written nor read when instances of that entity are managed. That's not going to serve your purpose.
There are two things I can imagine you might have been trying to achieve:
That every time an Estoque is accessed via a Produto, it should be loaded from the DB to ensure its freshness. JPA does not provide for that, though you might want to annotate Estoque with #Cacheable(value = false), and specify the lazy fetch strategy on the Produto side of the relationship.
You want to avoid the persistence provider attempting to persist any changes to an Estoque, since it is backed by a view, not an updatable table. This we can address.
My first suggestion would be to map ASICAT.VW_ESTOQUE as a secondary table instead of an entirely separate entity. That might look something like this:
#Entity
#SecondaryTable(name = "VW_ESTOQUE", schema = "ASICAT"
pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "CD_BEM_SERVICO",
referencedColumnName = "CODIGO") })
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
#Column(name = "ESTOQUE", table = "VW_ESTOQUE", nullable = true,
insertable = false, updatable = false)
private Long estoque;
// ...
}
You might furthermore avoid providing a setter for the estoque property.
But the SecondaryTable approach might not work well if you cannot rely on the ESTOQUE view always to provide a row for every row of PRODUTO, as there will very likely be an inner join involved in retrievals. Moreover, you don't get lazy fetches this way. The main alternative is more or less what you present in your question: to set up a separate Estoque entity.
If you do set up a separate Estoque, however, then I would approach it a bit differently. Specifically,
I would make the relationship bidirectional, so that I could
make the Estoque entity the relationship owner.
Something like this, then:
#Entity
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
// must not be #Transient:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "produto", cascade = {
CascadeType.REFRESH
})
private Estoque estoque;
// ...
}
#Entity
#Table(name = "VW_ESTOQUE", schema = "ASICAT")
#Cacheable(value = false)
public class Estoque {
#Id
#Column(name = "CD_BEM_SERVICO", nullable = false,
insertable = false, updatable = false)
private String codigo;
#Column(name = "ESTOQUE", insertable = false, updatable = false)
private Long estoque;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO",
nullable = false, insertable = false, updatable = false, unique = true)
Produto produto;
// getters and setters hidden by me
}
In this case, I would avoid providing setter methods for any of the properties of Estoque, and avoid providing any constructor that allows initial property values to be set. Thus, to a first approximation, the only
way an instance's properties will take non-null values is if they are set by the persistence provider.
Additionally, since you mention Oracle, if you are using TopLink as your persistence provider then you might consider applying its #ReadOnly extension attribute to the Estoque entity, in place of or even in addition to some of these protections against trying to insert into or update the view.
When deleting a parent entity I also want to remove the associated child entities (from the database). I have tried to make use of cascade on remove as seen below but I must be doing something incorrectly.
When calling remove on the parent entity object, I recieve the error message: "The entity is still referenced elsewhere in the database". I can confirm that the only place where the entity is referenced elsewhere in the database is in the two tables below (if I manually delete the child row from the database, the remove call on the parent works fine). I have been reading about entity objects and trying different things for the last 9 hours. What am I doing wrong?
Here is my parent table:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
Here is my child table:
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne(cascade = CascadeType.REMOVE, optional = false)
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}
EDIT/UPDATE:
If I change CascadeType.REMOVE to CascadeType.ALL, the TurtleReview entities are successfully deleted from the database when deleting the parent TurtleLookup entity object. However, when calling the below function to create a new TurtleReview entity object, JPA tries to insert a new TurtleLookup entity in to the database, which throws the exception: "Entry already resides within the DB. Transaction rolled back". Below is the code executed when creating a new TurtleReview entity.
public void setDatasetReviewComplete(TurtleLookup turtle, Short year, boolean isComplete)
{
TurtleReview turtleReview = getTurtleReview(turtle, year);
if (turtleReview == null)
{
turtleReview = new TurtleReview();
turtleReview.setTurtleYear(year)
turtleReview.setTurtleType(new a.b.entity.TurtleLookup(turtle.getId(), turtle.getValue()));
}
turtleReview.setIsComplete(isComplete ? (short)1 : 0);
entityManager.persist(turtleReview);
}
try change cascade value to all or all-delete-orphan
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
There might be an issue with your domain model, a part that is left out in the question. Do you possibly have circular cascades? If you have a circle of cascades and some of them are CascadeType.REMOVE and some are CascadeType.PERSIST, then Hibernate (not sure about other JPA implementation) will just do.... nothing when you call the remove() method. Without an error or exception message.
Try with hibernate #Cascade annotation:
#Cascade(value = CascadeType.ALL)
#OneToOne(mappedBy = "turtleReview") // mappedBy name of TurtleRewiew object field in TurtleLookup entity class
private TurtleLookup turtleType;
If your relationship is oneToOne you can't have oneToMany to the other side and you can't have List<TurtleReview>. If your relationship is oneToMany then your entities will be for example:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType") // or add cascade = javax.persistence.CascadeType.ALL and remove #Cascade if you are not using hibernate
#Cascade(value = CascadeType.ALL)
List<TurtleReview> turtleReviews;
...
}
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}
I'm developing a logic in the server side of my project that will update an Entity in the database. But this entity has reference to a list of another entity.
So, for example I have an Entity Test like this one:
#Entity
public class Test {
#Id
#Column(name = "idTest", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "idTest", targetEntity = Test2.class, fetch = FetchType.LAZY)
private Collection<Test2> test2Collection;
}
which has references to Test2 Entity.
#Entity
public class Test2 {
#Id
#Column(name = "idTest2", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String field1;
private String field2;
#ManyToOne
#JoinColumn(name = "idTest", referencedColumnName = "idTest")
private Test idTest;
}
In my case, I'm updating the Collection test2Collection of Test entry and eventually I'm removing some of Test2 entries of it.
The simple merge method from EntityManager does not detect that some entries has been deleted when I call it passing the new updated Test entry.
I was thinking to track the removed entries from test2Collection by making a diff between the Test entry before updating the database with the new Test entry to be updated.
But I'm not sure this is the only (and best) way of doing this. Anyone else has another idea?
In your relationship, you have to add orphanRemoval, then if you remove one element of the collection and persist Test, the element deleted on the collection will be automatically delete of the Test2 table.
#OneToMany(orphanRemoval = true, mappedBy = "idTest", targetEntity = Test2.class, fetch = FetchType.LAZY)
private Collection<Test2> test2Collection;