I have the following JPA Mapping (getters and setters out for brevity purposes, the DDL also gets generated from the code which may/may not play a role):
Expense
#Entity
public class Expense {
#Id
#GeneratedValue
private Long id;
private String name;
private Long amount;
private Boolean monthly;
#OneToOne
#JoinColumn(name = "category")
#Fetch(FetchMode.JOIN)
private Category category;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<Label> labels = new ArrayList<>();
//constructor, getters and setters...
}
Category
#Entity
public class Category {
#Id
private String name;
//constructor, getters and setters...
}
Label
#Entity
public class Label {
#Id
private String name;
//constructor, getters and setters...
}
Usage with JpaRepository
So I am using a JpaRepository that looks like this:
public interface ExpensesRepository extends JpaRepository<Expense, Long> {
#Query("SELECT e FROM Expense e LEFT JOIN FETCH e.category")
List<Expense> findAllExpensesExploded();
}
When I use the default findAll() method of the JpaRepository, I get a n+1 select problem:
2017-01-03 19:35:22.665 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select expense0_.id as id1_1_, expense0_.amount as amount2_1_, expense0_.category_name as category5_1_, expense0_.monthly as monthly3_1_, expense0_.name as name4_1_ from expense expense0_
2017-01-03 19:35:22.673 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select category0_.name as name1_0_0_ from category category0_ where category0_.name=?
2017-01-03 19:35:22.674 TRACE 26040 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Rent]
2017-01-03 19:35:22.682 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select category0_.name as name1_0_0_ from category category0_ where category0_.name=?
2017-01-03 19:35:22.683 TRACE 26040 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Insurance]
However, when I use my own findAllExpensesExploded() method I get a single SQL query:
2017-01-03 19:35:22.691 DEBUG 26040 --- [nio-8080-exec-1] org.hibernate.SQL : select expense0_.id as id1_1_0_, category1_.name as name1_0_1_, expense0_.amount as amount2_1_0_, expense0_.category_name as category5_1_0_, expense0_.monthly as monthly3_1_0_, expense0_.name as name4_1_0_ from expense expense0_ left outer join category category1_ on expense0_.category_name=category1_.name
My expectation was for both findAll() and findAllExpensesExploded() to be executed with a single SQL query.
My query worked because I seemed to have constructed it correctly
But why does the findAll() not work with the given mapping annotations? Is it possible that Spring Data is ignoring the #Fetch annotation?
An additional question that seems reasonable to ask is whether the default findAll() should only be used for simple entities? (where simple is defined as no associations).
The Default fetch mode is lazy. It's always a good practice to use #NamedEntityGraph and #EntityGraph annotations when working with Spring Data JPA.You can go through this
whether the default findAll() should only be used for simple entities? (where simple is defined as no associations).
Fetch Mode - LAZY will only fire for primary table. If in the code you call any other method that has a parent table dependency then it will fire Fetch Mode - SELECT.
Related
I'm working on saving multiple JPA entities coming as a JSON from external system. The incoming data is a list of Authors and Books linked as One-To-Many (one author can have 0..n books). When I have a single author it works as expected, I see corresponding inserts executed by Hibernate:
2022-10-17 12:49:18.056 DEBUG 25640 --- [ main] org.hibernate.SQL : insert into book (author_id, title, id) values (?, ?, ?)
But once I need to save multiple authors the behavior changes. It seems after saving the first author it tries to refresh something and invokes select query which ends with EntityNotFoundException because there is nothing actually saved into the DB at that moment:
2022-10-17 12:48:33.980 DEBUG 436 --- [ main] org.hibernate.loader.Loader : Done entity load
2022-10-17 12:48:33.980 DEBUG 436 --- [ main] o.h.e.i.AbstractSaveEventListener : Generated identifier: 1, using strategy: org.hibernate.id.Assigned
2022-10-17 12:48:33.981 DEBUG 436 --- [ main] org.hibernate.SQL : select book0_.id as id1_1_0_, book0_.author_id as author_i3_1_0_, book0_.title as title2_1_0_, author1_.id as id1_0_1_, author1_.name as name2_0_1_ from book book0_ left outer join author author1_ on book0_.author_id=author1_.id where book0_.id=?
...
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.sandbox.demo.sql.puzzle.Book with id 1; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.sandbox.demo.sql.puzzle.Book with id 1
Here are the entities and the code I use to reproduce the issue:
public class Author {
#Id
private long id;
#Column
private String name;
#OneToMany(mappedBy = "author")
private Set<Book> books = new HashSet<>();
}
public class Book {
#Id
private long id;
#Column
private String title;
#ManyToOne
#JoinColumn(name = "author_id")
private Author author;
}
public class Main {
#Transactional
public void saveData() {
saveAuthor(0);
saveAuthor(1); // The code works if this line is commented out
}
private void saveAuthor(int id) {
Author author = new Author();
author.setId(id);
author.setName("NAME #" + id);
Book book = new Book();
book.setId(id);
book.setTitle("TITLE #" + id);
book.setAuthor(author);
author.getBooks().add(book);
authorDAO.save(author);
bookDAO.save(book);
}
}
How to properly save multiple entities with one-to-many relations?
Looks like I need to save the entity first. Then the save() method will return me the saved entity and I need to use it when specify one-to-many property:
private void saveAuthor(int id) {
Author author = new Author();
author.setId(id);
author.setName("NAME #" + id);
Book book = new Book();
book.setId(id);
book.setTitle("TITLE #" + id);
// The code below does the trick
Author savedAuthor = authorDAO.save(author);
Book savedBook = bookDAO.save(book);
savedAuthor.getBooks().add(savedBook);
savedBook.setAuthor(savedAuthor);
}
One of my entities is throwing a StaleStateException when trying to delete it. Code in question:
#RestController
#RequestMapping("/cycles")
public class DeleteCycleController {
#DeleteMapping("{cycleId}")
#ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteCycleById(#PathVariable("cycleId") UUID cycleId, Principal principal) {
// cycleRepository extends CrudRepository
cycleRepository.deleteById(new CycleId(cycleId));
// deleter.deleteCycle(new CycleId(cycleId));
}
}
Throws the following:
org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: HikariProxyPreparedStatement#716092925 wrapping delete from cycle where id='c4c1428e-c296-4199-85f6-8ef16e6999c9'::uuid; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: HikariProxyPreparedStatement#716092925 wrapping delete from cycle where id='c4c1428e-c296-4199-85f6-8ef16e6999c9'::uuid
Here are the hibernate logs just before the failure:
2021-03-11 20:34:11.532 DEBUG 55483 --- [nio-8080-exec-7] org.hibernate.SQL : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : select cycle0_.id as id1_0_0_, cycle0_.attributes as attribut2_0_0_, cycle0_.created_at as created_3_0_0_, cycle0_.cycle_type_id as cycle_ty4_0_0_, cycle0_.description as descript5_0_0_, cycle0_.end_time as end_time6_0_0_, cycle0_.start_time as start_ti7_0_0_, cycle0_.end_location as end_loca8_0_0_, cycle0_.last_modified_at as last_mod9_0_0_, cycle0_.name as name10_0_0_, cycle0_.organization_id as organiz11_0_0_, cycle0_.start_location as start_l12_0_0_ from cycle cycle0_ where cycle0_.id=?
Hibernate: select cycle0_.id as id1_0_0_, cycle0_.attributes as attribut2_0_0_, cycle0_.created_at as created_3_0_0_, cycle0_.cycle_type_id as cycle_ty4_0_0_, cycle0_.description as descript5_0_0_, cycle0_.end_time as end_time6_0_0_, cycle0_.start_time as start_ti7_0_0_, cycle0_.end_location as end_loca8_0_0_, cycle0_.last_modified_at as last_mod9_0_0_, cycle0_.name as name10_0_0_, cycle0_.organization_id as organiz11_0_0_, cycle0_.start_location as start_l12_0_0_ from cycle cycle0_ where cycle0_.id=?
2021-03-11 20:34:11.533 TRACE 55483 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : binding parameter [1] as [OTHER] - [c4c1428e-c296-4199-85f6-8ef16e6999c9]
2021-03-11 20:34:11.542 DEBUG 55483 --- [nio-8080-exec-7] org.hibernate.SQL : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : delete from cycle where id=?
Hibernate: delete from cycle where id=?
2021-03-11 20:34:11.542 TRACE 55483 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder : requestId=b53dd96089f748cf942fc46867d32fb5, requestMethod=DELETE, correlationId=b53dd96089f748cf942fc46867d32fb5, requestURI=/cycles/c4c1428e-c296-4199-85f6-8ef16e6999c9, userId=5ae11e82-f8df-4fad-ae10-1aa57423ba31, orgId=c7389774-9afa-452b-aa1c-74e7b18bc04d : binding parameter [1] as [OTHER] - [c4c1428e-c296-4199-85f6-8ef16e6999c9]
And the Cycle entity:
#javax.persistence.Entity
#Accessors(fluent = true)
#Getter
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
#TypeDefs({#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)})
public class Cycle {
#Id private UUID id;
private CycleName name;
private CycleDescription description;
private UUID cycleTypeId;
private UUID organizationId;
private Instant createdAt;
private Instant lastModifiedAt;
#Type(type = "jsonb")
private Map<String, Object> attributes;
private CycleDuration duration;
#Column(columnDefinition = "Geometry", nullable = true)
private Geometry startLocation;
private Geometry endLocation;
#OneToMany(mappedBy = "cycleId", cascade = CascadeType.ALL, orphanRemoval = false)
#Getter(AccessLevel.PRIVATE)
#Setter(AccessLevel.PUBLIC)
private List<CycleEntityMapping> entities = new ArrayList<>();
...
}
Any ideas as to what could be going wrong? Another (simpler) entity deletes fine.
Not sure if its the OneToMany causing issues, but I've tried changing the cascade type, orphan removal and fetch type with no luck. Otherwise, could it be caused by the value objects? Note that updating the entity works fine - it's only failing on delete.
In this case, it was caused by a bug in a before delete trigger. There was a bug in the trigger causing the row to not be deleted:
CREATE OR REPLACE FUNCTION trigger_delete_row_backup()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO deleted_cycle values (OLD.*);
RETURN NEW;
-- should actually be RETURN OLD
END;
$BODY$
language PLPGSQL;
Once the trigger was fixed, the issue no was resolved.
I have a very small doubt regarding #OneToMany mapping.
I have a model student and another model attendance.
A student can have multiple attendance. but student model should only be able to retrieve the attendance info.
But when I am trying to change some student info I am getting below error as it is trying to update attendance record.
here is my mapping
#Entity
#Table(name="student_detail")
#Getter #Setter
public class StudentDetailsModel {
#Id
#Column(name="reg_no",updatable = false, nullable = false)
private String regNo;
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinColumn(name = "reg_no")
private List<AttendanceModel> attendances;
}
and the exception I a getting.
update
student_detail
set
address=?,
alt_contact_number=?,
blood_group=?,
contact_number=?,
dob=?,
father_name=?,
first_name=?,
gender=?,
last_name=?,
middle_name=?,
mother_name=?,
photo_url=?,
school_id=?
where
reg_no=?
Hibernate:
update
attendance
set
reg_no=null
where
reg_no=?
2019-01-13 12:12:52.922 WARN 10708 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2019-01-13 12:12:52.923 ERROR 10708 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: null value in column "reg_no" violates not-null constraint
Detail: Failing row contains (null, 1, 2019-01-05, t, 2).
2019-01-13 12:12:52.926 INFO 10708 --- [nio-8081-exec-1] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [reg_no]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
my attenance model is as follows
#Entity
#Table(name="attendance")
#Getter #Setter
public class AttendanceModel {
//#EmbeddedId
//private AttendanceId attendanceId;
#Id
#Column(name="attendance_id")
private long id;
#Column(name="reg_no")
private String regNo;
#Column(name="subject_id")
private long subjectId;
#Column(name="class_date")
private Date classDate;
#Column(name="present")
private boolean present;
}
Could you show me Student Model.If i look your code post : You using Unidirectional relationship.
I think it must :
#OneToMany(fetch = FetchType.LAZY , cascade = CascedeType.ALL)
#JoinColumn(name="attendance_id")
private List<AttendanceModel> attendances = new ArrayList<>();
I am creating some simple Spring Boot project with Hibernate JPA.
I created some data model which consists 5 tables for now and created entities reflecting tables. I have set spring.jpa.generate-ddl=true and my entities was correctly reflected by schema created in PostgreSQL.
Next step was to start adding relations.
Part of my assumed datamodel is (paron my UML)
Very simple one to many relation.
My entities look that way (getters and setters omitted below, exist in code):
#Entity
public class AppUser {
#Id
#GeneratedValue
private long id;
private String name;
private String secondName;
private String email;
private java.util.Date joinDate;
#ManyToOne
#JoinColumn(name = "user_role_id")
private UserRole userRole;
}
#Entity
public class UserRole {
#Id
#GeneratedValue
private long id;
private String roleName;
}
I launch my application with spring.jpa.generate-ddl=true and column user_role_id gets created in AppUser table but application fails to start due errors:
2018-10-11 19:41:35.435 INFO 45564 --- [ main] org.hibernate.tool.hbm2ddl.SchemaUpdate : HHH000228: Running hbm2ddl schema update
2018-10-11 19:41:35.466 WARN 45564 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42703
2018-10-11 19:41:35.466 ERROR 45564 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: column t1.tgconstrname does not exist
There is full stacktrace (please advise if should paste it here instead of pastebin:
https://pastebin.com/x4qNJkK9
When I set spring.jpa.generate-ddl=false application starts succesfully.
Any ideas why is that happening?
Disclaimer: This is my first Java project; learning as I go.
Background: I've inherited a legacy database on which to build a new RESTful API. We're using Elide with Spring Boot to provide a JSON API compliant service.
Reference: Example source code
Problem: We have entities with a many-to-many relationship to each other and themselves by way of a join table. Consider the followig schema:
CREATE TABLE ALPHA (
ID VARCHAR(255),
NAME VARCHAR(255),
CONSTRAINT PK_ALPHA PRIMARY KEY (ID)
);
CREATE TABLE BRAVO (
ID VARCHAR(255),
NAME VARCHAR(255),
CONSTRAINT PK_BRAVO PRIMARY KEY (ID)
);
CREATE TABLE RELATIONSHIP (
ID INT AUTO_INCREMENT,
FROM_ID VARCHAR(255),
TO_ID VARCHAR(255)
);
Where the resource entities are modeled as follows:
public class Alpha implements Serializable {
private String id;
private String name;
private Set<Alpha> alphas = new HashSet<>();
private Set<Bravo> bravos = new HashSet<>();
#Id
#Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
public String getId() {
return id;
}
public String getName() {
return name;
}
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "RELATIONSHIP",
joinColumns = #JoinColumn(name = "FROM_ID"),
inverseJoinColumns = #JoinColumn(name = "TO_ID")
)
public Set<Alpha> getAlphas() {
return alphas;
}
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "RELATIONSHIP",
joinColumns = #JoinColumn(name = "FROM_ID"),
inverseJoinColumns = #JoinColumn(name = "TO_ID")
)
public Set<Bravo> getBravos() {
return bravos;
}
}
And the relationship table:
public class Relationship implements Serializable {
private Integer id;
private String fromId;
private String toId;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
#Column(name = "FROM_ID")
public String getFromId() {
return fromId;
}
#Column(name = "TO_ID")
public String getToId() {
return toId;
}
}
Now let's say we have an Alpha record A1 with relationships to A2, A3, B1, and B2. First we delete the relationship to A2.
From our API this would be a DELETE request to http://localhost:9000/api/alphas/a1/relationships/alphas with BODY
{
"data": [
{
"type": "alphas",
"id": "a2"
}
]
}
Behind the scenes Hibernates does what I'm expecting and generates the following SQL queries:
2018-07-13 09:48:23.687 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
select
alpha0_.id as id1_0_,
alpha0_.name as name2_0_
from
alpha alpha0_
where
alpha0_.id in (
?
)
2018-07-13 09:48:23.688 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a1]
2018-07-13 09:48:23.690 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
select
alphas0_.from_id as from_id2_2_0_,
alphas0_.to_id as to_id3_2_0_,
alpha1_.id as id1_0_1_,
alpha1_.name as name2_0_1_
from
relationship alphas0_
inner join
alpha alpha1_
on alphas0_.to_id=alpha1_.id
where
alphas0_.from_id=?
2018-07-13 09:48:23.690 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a1]
2018-07-13 09:48:23.699 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
select
alpha0_.id as id1_0_,
alpha0_.name as name2_0_
from
alpha alpha0_
where
alpha0_.id in (
?
)
2018-07-13 09:48:23.699 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a2]
2018-07-13 09:48:23.721 DEBUG 7964 --- [nio-9000-exec-5] org.hibernate.SQL :
Hibernate:
delete
from
relationship
where
from_id=?
and to_id=?
2018-07-13 09:48:23.722 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [a1]
2018-07-13 09:48:23.724 TRACE 7964 --- [nio-9000-exec-5] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [a2]
The key piece being delete from relationship where from_id=? and to_id=?
Now the problem arises when trying to delete the second Alpha relationship A3, in which Hibernate does almost the exact same sequence, except for the DELETE query which omits the and to_id=? from the query, i.e.
Hibernate:
delete
from
relationship
where
from_id=?
Which has the unintended consequence of deleting all other A1 relationships in the table, i.e. B1 and B2.
So that is the crux of my problem. It seems like Hibernate is only seeing one other related Alpha record and therefore deciding to simplify the query by omitting the and to_id statement.
I'm probably missing something terribly obvious!
I should also point out that I attempted to use a composite key on the relationship table but to no avail.
This is an unusual design, which I suspect is confusing Hibernate. Sharing a single join table between multiple Many-to-many relationships, isn't good database design, for one it can't have any foreign keys/referential integrity.
Secondly, Hibernate manages relationships, and therefore has control over the #JoinTable, I don't know how it would handle multiple entity relationships mapped with the same table. Evidently, not very well though!
The simplest solution (if you're able to), would be to have 2 mapping tables. One for the relationship between Alpha-Alpha and another between Alpha-Bravo.