I am facing an issue with Spring Data repository + Hibernate (in spring 2.1.4)
Note: Everything was working fine in spring 1.5.x
The problem is with #ManyToOne(fetch = FetchType.Lazy) I get correct records in 1.5 but error in spring 2.0 but with #ManyToOne(fetch = FetchType.Eager) I get duplicate records in List<Stories>.
I am using Spring data repositories epicRepository.findById(Long id) (previously Spring had epicRepository.find() but they removed it in spring 2.x)
I don't want to use #Fetch (FetchMode.SELECT) solution as it has to execute multiple select queries and will become very non-performant.
Problem:
if I use fetch = FetchType.Lazy i am getting an error could not
initialize proxy - no session (only started after upgrading to
spring 2.x and returned correct number of rows) [This error seems to be ok as I am trying to fetch the list.count later in the code, and there are no duplicates when I checked through debugger before the erroring line]
so based on some solutions here in SO I used FetchType.Eager
(I understand the performance implications of this, but anyway I
needed to do this for another work so I did this because this list is smaller and need to preserve some business logic during updates). But Now I am getting duplicate records.
Even If what I do is wrong, the count in list (mean the duplicates) should not be wrong ?
I have the following JPA entities / tables
Epic -> [id (pk), name, status_id (fk))
Story->[id (pk), name, status_id (fk), epic_id (fk))
Task -> [id (pk), name, resolution_type_id (fk), story_id (fk))
forgive me if there any typos (as i recreated code manually using different use case)
Epic.java
#Data
public class Epic {
#Id
private Long id;
private String name;
#OneToOne(fetch = FetchType.Eager, optional = false)
#JoinColumn(name = id, referenceColumnName = 'id', nullable = false, insertable = true, updatable = true)
private Status status;
#OneToMany(fetch = FetchType.Eager, cascade = ALL, mappedBy = epic)
private List<Story> stories;
}
Story.java
#Data
public class Story {
#Id
private Long id;
private String name;
#OneToOne(fetch = FetchType.Eager, optional = false)
#JoinColumn(name = id, referenceColumnName = 'id', nullable = false, insertable = true, updatable = true)
private Status status;
#OneToMany(fetch = FetchType.Eager, cascade = ALL, mappedBy = epic)
private List<Task> tasks;
#ManyToOne(fetch = FetchType.Lazy)
// This is the problem area
// Error if FetchType.Eager
// But duplicates in FetchType.Lazy
#JoinColumn(name = "id", nullable = false)
private Epic epic;
}
Task.java
#Data
public class Task {
#Id
private Long id;
private String name;
#ManyToOne(fetch = FetchType.Lazy)
#JoinColumn(name = "id")
private Story story;
#OneToOne (fetch = FetchType.Eager, optional = true)
#JoinColumn (name = "id", )
private Resolution resolution;
}
This question has been replied to many times before. Your options could be:
all EAGER collections you should migrate to Sets
manually do duplicate filtering using stream.distinct.collect on getters of collections.
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)
These are the entities defined in my project:
Post
#Entity
public class Post implements Serializable {
public enum Type {TEXT, IMG}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "section_id")
protected Section section;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "author_id")
protected User author;
#Column(length = 255, nullable = false)
protected String title;
#Column(columnDefinition = "TEXT", nullable = false)
protected String content;
#Enumerated(EnumType.STRING)
#Column(nullable = false)
protected Type type;
#CreationTimestamp
#Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
#Column(insertable = false, updatable = false)
protected Integer votes;
/*accessor methods*/
}
Comment
#Entity
public class Comment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "post_id")
protected Post post;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "author_id")
protected User author;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "parent_comment_id")
protected Comment parentComment;
#Column(columnDefinition = "TEXT", nullable = false)
protected String content;
#CreationTimestamp
#Column(nullable = false, updatable = false, insertable = false)
protected Instant creationDate;
#Column(insertable = false, updatable = false)
protected Integer votes;
#Column(insertable = false, updatable = false)
protected String path;
/*accessor methods*/
}
As you can see, aggregations are only tracked from the child's point of view: there are no #ManyToOne relationships due to a design choice.
However, I'd like to add a commentCount field to the Post entity. This field should be loaded eagerly (since it is always read in my use cases and a COUNT statement isn't that costly... i guess)
These are the options I'm currently aware of:
Use the #Formula or the #PostLoad annotations: there would be N+1select statements
Retrieve the posts, then call another repository method that uses the IN operator with all the retrieved posts as argument:
select post, count(comment) from Post post, Comment comment where comment.post in (:posts) group by comment.post
(Given that my data access layer might load even 50 posts at once, would a 50 parameters long in be optimal?)
Create a non-mapped entity attribute, then take care of filling it manually in all repository methods involving Post with a join: i'd like to avoid this.
Store commentCount in the database and mantain it with triggers: could be an option, but it's not the focus of this question (I'm not allowed to touch the schema)
What can be a valid solution? I'm using Hibernate but i prefer implementation-agnostic solutions (Implementation-specific options won't be ignored, though)
Using #Formula would not cause any N+1 select problem (different to #PostLoad), since the specified expression is evaluated as part of the initial select query.
(Infact, you would need to enable Hibernates byte code enhancement to be able to lazy-load the calculated #Formula property: Controlling lazy/eager loading of #Formula columns dynamically )
So even though it is a Hibernate-specific feature which needs native SQL, #Formula is probably a good and simple way to realize this.
In case the performance drawback of the correlated subquery gets significant and there are cases when the calculated property is not needed, you might additionally create a DTO projection only containing needed properties.
I have two entities
#Entity
#Table(name = "Documents")
public class Document extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "asset_id")
private Asset asset;
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "debt__id")
private Debt debt;
}
and
#Entity
#Table(name = "debts")
public class Debt extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "debt", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Document> documents;
}
This mapping:
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY, optional = false)
#JoinColumn(nullable = true, name = "debt_id")
private Debt debt;
was recently added.
On running the app, the app is crashing with an error saying: Error executing DDL "alter table documents add debt__id bigint not null" via JDBC Statement
I checked the sql query hibernate sent to the database and it was: alter table documents add debt_id bigint not null
This query fails because there are records already on the documents table so a non-nullable column without default value could not be added.
So why is the nullable=true in the #JoinColumn annotation ignored by hibernate.
I couldn't find an answer anywhere. This is a spring-boot app, if it helps.
Just remove the optional = false from the #ManyToOne. That is the culprit. Also, you don't need to mention nullable=true in the #JoinColumn, that is by default true
#ManyToOne( targetEntity = Debt.class,
fetch = FetchType.LAZY)
#JoinColumn(name = "debt__id")
private Debt debt;
I have an entity classes in JPA as below in which maId acts as a primary key and foreign key for many other tables.
#Table(name = "Table1")
public class Test implements Serializable {
#Id
#Column(name = "maId", updatable = false, nullable = false)
private String id;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private MFieldData fieldData;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private MPS mps;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private MJob mJob;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="maId")
private List<MExtension> mlExtensions;
private Date createdDate;
private Date lastUpdatedDate;
}
Now,this is my another entity.
#Table(name = "table 2")
public class Test implements Serializable {
#Id
#Column(name = "maId", updatable = false, nullable = false)
private String maId;
private Integer cmd;
private String routeId;
}
By the time I receive a request this is API. I need to Insert the data across multiple tables.
How to implement a custom UUID (maId) generator and use it in #Id?
I need to use the same maId which is the unique id for this request across multiple entities while inserting into the DB. I am using a dirty way to do that. But is there any JPA way to solve this problem?
Any help would be highly appreciated!
The request has to pass the UUID (Called correlation-id) to track. https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html can be used. If you generate in code it may go unknown to caller. The correlation-id is used to track the request data across systems it may travel due to event publish as well.
I have 2 EJB 3 Entity Beans:
#Entity
public class Organisation
{
#Id
#Column(length = 64)
private String guid;
private String name;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#JoinColumn(name = "home_unit_fk", nullable = true)
private Unit homeUnit;
}
#Entity
public class Unit
{
#Id
#Column(length = 64)
private String guid;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisation_fk", nullable = false)
private Organisation organisation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_unit_fk", nullable = true)
private Unit parentUnit;
#OneToMany(mappedBy = "parentUnit", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#OrderBy("shortName")
#OptimisticLock(excluded = true)
private Set<Unit> childUnits;
}
If I do a delete on the Organisation using standard Dao :
public int deleteByGuid(final String guid)
{
final Query query = entityManager.createQuery("delete from " + getPersistentClass().getName() + " where guid = :guid");
query.setParameter("guid", guid);
return query.executeUpdate();
}
But I get the following exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (config.unit, CONSTRAINT FK27D184F5D4393D FOREIGN KEY (organisation_fk) REFERENCES organisation (guid))
I don't get it. What am I doing wrong? Shouldn't JPA/Hibernate perform deletes on both the Unit and the Organisation within the same transaction?
A bulk delete query does not load objects into memory and it bypasses any cascade specified on associations.
I would code your delete method as:
public int deleteByGuid(final String guid){
Organization org = entityManager.find(Organization.class, guid);
entityManager.remove(org);
}
If you use a Query to do bulk updates, the operation is delegated to the database directly. If you wish to delete child objects, you have to set a DELETE CASCADE trigger at the "database" level.
By loading the object and removing it, Hibernate will trigger the cascade at the "object" level.
More info available at: http://twasink.net/blog/2005/04/differences-in-behaviour-between-hibernate-delete-queries-and-the-old-way/