Related
Whenever I update UserEntity hibernate triggers an update on the related UserRoles table. I found similar issues here but could not find a working solution.
#Entity
#Table(name = "users")
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class UserEntity implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private UUID uuid;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
#JsonIgnore private String password;
#JsonIgnore
#OneToMany(mappedBy = "user", fetch = LAZY, cascade = REMOVE)
#Builder.Default
private List<UserRoleEntity> roles = new ArrayList<>();
#Entity
#Table(name = "user_roles")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#EntityListeners(AuditingEntityListener.class)
#TypeDef(name = "enum_postgres_sql", typeClass = PostgresEnumType.class)
public class UserRoleEntity {
#Id
#GeneratedValue(strategy = AUTO)
private UUID uuid;
#Column(name = "role")
#Enumerated(STRING)
#Type(type = "enum_postgres_sql")
private Role role;
#ManyToOne
#JoinColumn(name = "user_uuid", updatable = false)
private UserEntity user;
#Type(
type = "com.vladmihalcea.hibernate.type.array.ListArrayType",
parameters = {#Parameter(name = ListArrayType.SQL_ARRAY_TYPE, value = "permission")})
#Column(name = "permissions", columnDefinition = "permission[]")
#Builder.Default
private List<Permission> permissions = new ArrayList<>();
#CreatedBy #OneToOne private UserEntity createdBy;
#LastModifiedBy #OneToOne private UserEntity lastModifiedBy;
}
Create user:
UserEntity user =
UserEntity.builder()
.firstName(dto.getFirstName())
.lastName(dto.getLastName())
.email(dto.getEmail())
.createdAt(LocalDateTime.now())
.build();
repository.save(user);
Update user:
repository
.findById(uuid)
.ifPresentOrElse(
e -> {
e.setFirstName(dto.getFirstName());
e.setLastName(dto.getLastName());
if (!StringUtils.equals(dto.getEmail(), e.getEmail())) {
e.setEmail(dto.getEmail());
}
e.setUpdatedAt(LocalDateTime.now());
repository.save(e);
},
() -> {
LOGGER.error("UserEntity with uuid: {} not found", uuid);
throw new UserEntityNotFoundException(uuid);
});
Repo
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<UserEntity, UUID> {}
Does anyone have an idea how to prevent this?
The reason I want to prevent this is that I'm using triggers in Postgresql to write logs of user actions and unnecessary updates mess things up... :(
Here are some logs:
2021-07-07 01:17:04,748 INFO o.s.d.r.c.DeferredRepositoryInitializationListener:53 - Spring Data repositories initialized!
2021-07-07 01:17:04,768 INFO c.s.j.l.u.w.UserLogControllerGetCreatedUserIntegrationTest:61 - Started UserLogControllerGetCreatedUserIntegrationTest in 36.711 seconds (JVM running for 39.424)
2021-07-07 01:17:04,936 DEBUG c.s.j.t.m.DatabaseMixin:31 - TestContainers database properties: test#jdbc:postgresql://localhost:56611/test?loggerLevel=OFF
2021-07-07 01:17:05,112 DEBUG o.h.SQL:128 -
insert
into
users
(changed_by_uuid, company_uuid, created_at, email, first_name, last_name, password, status, type, updated_at, uuid)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2021-07-07 01:17:05,115 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [1] as [OTHER] - [null]
2021-07-07 01:17:05,115 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [2] as [OTHER] - [null]
2021-07-07 01:17:05,116 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [3] as [TIMESTAMP] - [2021-07-07T01:17:05.104298]
2021-07-07 01:17:05,117 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [VARCHAR] - [new#test.com]
2021-07-07 01:17:05,117 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [VARCHAR] - [John]
2021-07-07 01:17:05,118 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [6] as [VARCHAR] - [Doe]
2021-07-07 01:17:05,118 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [7] as [VARCHAR] - [$2a$10$woVXWhuoHEqoIZMlul6/4.cc.33AyTCUc8nDWDiqImuj0vqlY.PK.]
2021-07-07 01:17:05,118 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [10] as [TIMESTAMP] - [2021-07-07T01:17:05.104346]
2021-07-07 01:17:05,119 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [11] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,140 DEBUG o.h.SQL:128 -
insert
into
user_roles
(changed_by_uuid, permissions, role, user_uuid, uuid)
values
(?, ?, ?, ?, ?)
2021-07-07 01:17:05,140 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [1] as [OTHER] - [null]
2021-07-07 01:17:05,141 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [2] as [ARRAY] - [[Ljava.lang.Object;#24be7cee]
2021-07-07 01:17:05,152 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,153 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [OTHER] - [2fa664b9-f9cd-4092-a38b-c46d47c0ab95]
2021-07-07 01:17:05,156 DEBUG o.h.SQL:128 -
update
user_roles
set
changed_by_uuid=?,
permissions=?,
role=?,
user_uuid=?
where
uuid=?
2021-07-07 01:17:05,156 TRACE o.h.t.d.s.BasicBinder:52 - binding parameter [1] as [OTHER] - [null]
2021-07-07 01:17:05,157 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [2] as [ARRAY] - [[CREATE]]
2021-07-07 01:17:05,157 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,157 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [OTHER] - [2fa664b9-f9cd-4092-a38b-c46d47c0ab95]
2021-07-07 01:17:05,165 DEBUG o.h.SQL:128 -
insert
into
user_roles
(changed_by_uuid, permissions, role, user_uuid, uuid)
values
(?, ?, ?, ?, ?)
2021-07-07 01:17:05,165 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [1] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,165 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [2] as [ARRAY] - [[Ljava.lang.Object;#4bc4b33b]
2021-07-07 01:17:05,166 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [4] as [OTHER] - [4df92c40-fde1-4a09-bfe9-53f2deb2f41d]
2021-07-07 01:17:05,166 TRACE o.h.t.d.s.BasicBinder:64 - binding parameter [5] as [OTHER] - [afe1ed15-005f-4c2e-8d55-bd3db570dc2e]
2021-07-07 01:17:05,168 DEBUG o.h.SQL:128 -
update
user_roles
set
changed_by_uuid=?,
permissions=?,
role=?,
user_uuid=?
where
uuid=?
Actually, it is a bug in the Hibernate: HHH-5855
I used List instead of Set in many-to-many
The permissions were mapped badly
This is the way it should look
permissions column is mutable and Hibernate flushes again because it can't determine dirtiness
The solution was:
#Type(
type = "com.vladmihalcea.hibernate.type.array.EnumArrayType",
parameters = {#Parameter(name = EnumArrayType.SQL_ARRAY_TYPE, value = "permission")})
#Column(name = "permissions", columnDefinition = "permission[]")
private Permission[] permissions;
I have these entities (unnecessary columns removed for brevity)
Order
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToOne(mappedBy = "order", cascade = CascadeType.ALL, optional = false)
private Shipment shipment;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Shipment getShipment() {
return shipment;
}
public void setShipment(Shipment shipment) {
this.shipment = shipment;
this.shipment.setOrder(this);
}
}
Shipment
#Entity
#Table(name = "shipments")
public class Shipment {
#Id
#Column(name = "order_id")
private int id;
#OneToOne
#MapsId
private Order order;
#OneToOne(mappedBy = "shipment", cascade = CascadeType.ALL, optional = false)
private ShippingAddress shippingAddress;
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public ShippingAddress getShippingAddress() {
return shippingAddress;
}
public void setShippingAddress(ShippingAddress shippingAddress) {
this.shippingAddress = shippingAddress;
this.shippingAddress.setShipment(this);
}
}
ShippingAddress
#Entity
#Table(name = "shipping_address")
public class ShippingAddress {
#Id
#Column(name = "shipment_id")
private int id;
#OneToOne
#MapsId("shipment_id")
private Shipment shipment;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Shipment getShipment() {
return shipment;
}
public void setShipment(Shipment shipment) {
this.shipment = shipment;
}
}
The problem arises when I attempt to save a new Order. As you can see from the logs, Hibernate correctly sets the values to the Order and Shipment entities but assigns 0 to the shipment_id column in shipping_address, which of course causes a FK conflict.
I believe I'm using #MapsId correctly, because like I said, it works for the Order and Shipment relationship, but it doesn't seem to work for the Shipment and ShippingAddress.
These are the logs
2021-01-14 14:25:56,563 DEBUG [http-nio-8080-exec-3] org.hibernate.SQL: insert into orders (branch_office, coupon_code, customer_id, external_id, increment_id, interests, origin_channel, store_id, subtotal, total, total_discount, total_qty_ordered) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2021-01-14 14:25:56,564 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [1] as [VARCHAR] - [null]
2021-01-14 14:25:56,564 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [2] as [VARCHAR] - [null]
2021-01-14 14:25:56,564 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [3] as [INTEGER] - [20]
2021-01-14 14:25:56,564 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [4] as [VARCHAR] - [5501505642]
2021-01-14 14:25:56,564 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [5] as [VARCHAR] - [5501505642]
2021-01-14 14:25:56,564 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [6] as [NUMERIC] - [0.0000]
2021-01-14 14:25:56,565 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [7] as [VARCHAR] - [store]
2021-01-14 14:25:56,565 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [8] as [INTEGER] - [55]
2021-01-14 14:25:56,565 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [9] as [NUMERIC] - [2270.0000]
2021-01-14 14:25:56,565 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [10] as [NUMERIC] - [1580]
2021-01-14 14:25:56,565 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [11] as [NUMERIC] - [690]
2021-01-14 14:25:56,565 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [12] as [INTEGER] - [3]
2021-01-14 14:25:56,621 DEBUG [http-nio-8080-exec-3] org.hibernate.SQL: insert into shipments (carrier_id, pickup_id, shipment_id, shipping_cost, order_id) values (?, ?, ?, ?, ?)
2021-01-14 14:25:56,621 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [1] as [INTEGER] - [19]
2021-01-14 14:25:56,622 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [2] as [INTEGER] - [19]
2021-01-14 14:25:56,622 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [3] as [INTEGER] - [null]
2021-01-14 14:25:56,622 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [4] as [NUMERIC] - [0.0]
2021-01-14 14:25:56,622 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [5] as [INTEGER] - [20]
2021-01-14 14:25:56,623 DEBUG [http-nio-8080-exec-3] org.hibernate.SQL: insert into shipping_address (city, country_code, province_id, street_name, street_number, zip_code, shipment_id) values (?, ?, ?, ?, ?, ?, ?)
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [1] as [VARCHAR] - [Vicente López]
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [2] as [VARCHAR] - [AR]
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [3] as [INTEGER] - [21]
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [4] as [VARCHAR] - [ramon melgar,Nro 8170]
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [5] as [VARCHAR] - []
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [6] as [VARCHAR] - [1638]
2021-01-14 14:25:56,624 TRACE [http-nio-8080-exec-3] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [7] as [INTEGER] - [0]
As you can see on the last insert, Hibernate attempts to insert a 0 instead of the shipment_id.
Is there something I'm missing?
According to the documentation the value of the #MapsId annotation is referred to the name of the attribute within the composite key to which the relationship attribute corresponds. You do not use composite key.
So, this:
#OneToOne
#MapsId("shipment_id")
private Shipment shipment;
should be corrected to this:
#OneToOne
#MapsId
#JoinColumn(name = "shipment_id")
private Shipment shipment;
I can not find proof in the documentation, but hibernate ignore #Column(name = "shipment_id") on #Id when you use #MapsId, so you should specify PK column name of the ShippingAddress by adding #JoinColumn.
I have the following JPA entity class. And another Entity OptProject that extends Project. I'm using InheritanceType.JOINED
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false)
private Long id;
#Basic
#Column(name = "NAME", nullable = false, length = 150)
private String name;
#Basic
#Column(name = "DESCRIPTION", nullable = false, length = 500)
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
#Entity
#Table( name = "opt_project")
public class OptProject extends Project {
#OneToMany(mappedBy = "project", cascade = {CascadeType.ALL})
private List<Case> cases = new ArrayList<>();
public OptProject() {}
public OptProject(String name, String description) {
super(name, description);
}
}
The JPA repository for OptProject entity is the following
#Repository
public interface OptProjectRepository extends CrudRepository<OptProject, Long> {
}
When I create a OptProject and try to save it, I get the error message below. It makes zero sense to me since I'm using #GeneratedValue(strategy = GenerationType.IDENTITY) for SQL server to generate the ID primary key.
OptProject optProject = new OptProject("name", "description");
OptProject savedProject = optProjectRepo.save(optProject);
com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert explicit value for identity column in table 'optimization_project' when IDENTITY_INSERT is set to OFF.
After turning hibernate logging to debug with the following snippets
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
These are the hibernate SQL logs:
DEBUG org.hibernate.SQL - insert into dbo.project (name, description) values (?, ?)
TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [name]
TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - [description]
DEBUG org.hibernate.SQL - insert into opt_project (id) values (?)
TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [11]
WARN o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 544, SQLState: S0001
ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Cannot insert explicit value for identity column in table 'opt_project' when IDENTITY_INSERT is set to OFF.
The first SQL statement to insert the data into the table for the base class Project is correct. But the SQL statement to insert the data to the second table for the child class OptProject is the issue. It is manually adding the ID with value 11 and not letting the db generate it perhaps because there is only field here the ID. I'm not sure what is going on here.
It is manually adding the ID with value 11 and not letting the db generate it perhaps because there is only field here the ID. I'm not sure what is going on here.
You are using joined inheritance type. The dependent entity must have the same PK as the parent, otherwise how exactly are they going to be joined? The only way that is going to happen then is if the ID value for the dependent entity is manually set on insert. So therefore you need to change the column definition in the opt_project table so it is not an Identity column.
in my Spring Boot application I'm trying to save an Entity with a few nested Entities that have a one-to-many relationship. I'm using JPA and Hibernate to save them to a MySQL database.
On the primary keys, I am using #GeneratedValue(strategy = GenerationType.AUTO) on a Long to automatically create values for new entities. This worked for some time, however recently I'm getting an error when trying to save the entity:
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '282' for key 'PRIMARY'
This is what my entities look like:
Question.java
#Entity
#Table(name = "question")
public class Question {
private Long id;
private List<Answer> answers;
// other fields
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Answer> getAnswers() { return answers; }
public void setAnswers(List<Answer> answers) { this.answers = answers; }
}
Answer.java
#Entity
#Table(name = "answer")
public class Answer {
private Long id;
private Question question;
// other fields
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#ManyToOne
#JoinColumn(name = "question_id")
public Question getQuestion() { return question; }
public void setQuestion(Question question) { this.question = question; }
}
Repositories are simply:
public interface QuestionRepository extends JpaRepository<Question, Long>{ ... }
Now let's say I am adding a new Question via a web form, including 4 Answers. When calling the repository simply like:
questionRepository.save(question);
After some investigation, it appears that Hibernate is using the auto increment value from the question table for the answers entities, which doesn't work because it already exist.
Trace output log:
Hibernate: insert into question (id) values (?)
2019-08-31 18:47:49.652 TRACE 11516 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [6] as [BIGINT] - [281]
Hibernate: insert into answer (question_id, id) values (?, ?)
2019-08-31 18:47:49.688 TRACE 11516 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [281]
2019-08-31 18:47:49.688 TRACE 11516 --- [nio-8080-exec-6] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [282]
2019-08-31 18:47:49.746 WARN 11516 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000
2019-08-31 18:47:49.746 ERROR 11516 --- [nio-8080-exec-6] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '282' for key 'PRIMARY'
2019-08-31 18:47:49.748 ERROR 11516 --- [nio-8080-exec-6] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
2019-08-31 18:47:49.847 ERROR 11516 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '282' for key 'PRIMARY'
I've verified that the auto_increment value on the answer table is 581.
What could be the cause of this? Is there property/configuration I'm missing?
Since MySQL doesn't support sequences natively so this issue might occur when #GeneratedValue(strategy = GenerationType.AUTO) is used.
Use the below workaround for id generation.
#Id
#GeneratedValue(
strategy= GenerationType.AUTO,
generator="native"
)
#GenericGenerator(
name = "native",
strategy = "native"
)
Please refer https://hibernate.atlassian.net/browse/HHH-11014 for further details.
You can also refer ::: https://vladmihalcea.com/why-should-not-use-the-auto-jpa-generationtype-with-mysql-and-hibernate/
I have 2 tables: genres(genre_id, genre_name) and movies (movie_id, movie_name, movie_score, genre_id). genre_id_fk from movies referenced to genre_id in genres.
#Entity
#Table(name = "genres", schema = "test")
public class Genre {
#Id
#Column(name = "genre_id")
private int id;
#Column(name = "genre_name")
private String name;
#OneToMany(mappedBy = "genre", fetch = FetchType.LAZY)
private List<Movie> movies = new ArrayList<>();
}
and 2nd entity for movies
#Entity
#Table(name = "movies", schema = "test")
public class Movie {
#Id
#GeneratedValue
#Column(name = "movie_id")
private int id;
#Column(name = "movie_name")
private String name;
#Column(name = "movie_score")
private double score;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "genre_id")
private Genre genre;
}
when i am trying to print it in console with this code:
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu");
EntityManager em = emf.createEntityManager();
Genre genre = em.find(Genre.class, 1);
System.out.println(genre);
}
receiving Exception in thread "main" java.lang.StackOverflowError
only removing from toString() in movie class field "genres" can fix it. But is it possible to avoid it?
And same problem with spring boot application
#RestController
public class GenreController {
#Autowired
private GenreService genreService;
#RequestMapping("/test/{id}")
public List<Genre> getGenreInfo(#PathVariable int id){
return genreService.getGenreFilms(id);
}
}
here service
#Service
public class GenreService {
public List<Genre> getGenreFilms(int id){
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu");
EntityManager em = emf.createEntityManager();
List<Genre> genres = new ArrayList<>();
Genre genre = em.find(Genre.class, id);
genres.add(genre);
return genres;
}
}
and receiving this problem like:
[{"id":1,"name":"Thriller","movies":[{"id":1,"name":"Any, are you ok?","score":5.45,"genre":{"id":1,"name":"Thriller","movies":[{"id":1,"name":"Any, are you ok?","score":5.45,"genre":{"id":1,"name":"Thriller","movies":[{"id":1,"name":"Any, are you ok?","score":5.45,"genre":....
and to infinity with sof exception. Console i can fix just with ignoring field in toString() method. But how to fix this problem in webapplication?
here hibernate debug log when console print
22:15:37.154 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad -
Resolving associations for [com.company.Genre#1]
22:15:37.164 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done
materializing entity [com.company.Genre#1]
22:15:37.164 [main] DEBUG
org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl -
HHH000387: ResultSet's statement was not registered
22:15:37.165 [main] DEBUG
org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done
entity load : com.company.Genre#1
22:15:37.165 [main] DEBUG
org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl -
Initiating JDBC connection release from afterTransaction
22:15:37.167 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer
- Loading collection: [com.company.Genre.movies#1]
22:15:37.167 [main] DEBUG org.hibernate.SQL - select movies0_.genre_id as
genre_id4_1_0_, movies0_.movie_id as movie_id1_1_0_, movies0_.movie_id as
movie_id1_1_1_, movies0_.genre_id as genre_id4_1_1_, movies0_.movie_name as
movie_na2_1_1_, movies0_.movie_score as movie_sc3_1_1_ from test.movies
movies0_ where movies0_.genre_id=?
Hibernate: select movies0_.genre_id as genre_id4_1_0_, movies0_.movie_id as
movie_id1_1_0_, movies0_.movie_id as movie_id1_1_1_, movies0_.genre_id as
genre_id4_1_1_, movies0_.movie_name as movie_na2_1_1_, movies0_.movie_score
as movie_sc3_1_1_ from test.movies movies0_ where movies0_.genre_id=?
22:15:37.168 [main] DEBUG
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl -
Preparing collection intializer : [com.company.Genre.movies#1]
22:15:37.170 [main] DEBUG
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl -
Starting ResultSet row #0
22:15:37.171 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerIm
pl - Found row of collection: [com.company.Genre.movies#1]
22:15:37.171 [main] DEBUG
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl -
Starting ResultSet row #1
22:15:37.172 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerIm
pl - Found row of collection: [com.company.Genre.movies#1]
22:15:37.172 [main] DEBUG
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl -
Starting ResultSet row #2
22:15:37.172 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerIm
pl - Found row of collection: [com.company.Genre.movies#1]
22:15:37.172 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad -
Resolving associations for [com.company.Movie#1]
22:15:37.172 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done
materializing entity [com.company.Movie#1]
22:15:37.173 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad -
Resolving associations for [com.company.Movie#2]
22:15:37.173 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done
materializing entity [com.company.Movie#2]
22:15:37.173 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad -
Resolving associations for [com.company.Movie#3]
22:15:37.173 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done
materializing entity [com.company.Movie#3]
22:15:37.173 [main] DEBUG
org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections
were found in result set for role: com.company.Genre.movies
22:15:37.173 [main] DEBUG
org.hibernate.engine.loading.internal.CollectionLoadContext - Collection
fully initialized: [com.company.Genre.movies#1]
22:15:37.173 [main] DEBUG
org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections
initialized for role: com.company.Genre.movies
22:15:37.173 [main] DEBUG
org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl -
HHH000387: ResultSet's statement was not registered
22:15:37.173 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer
- Done loading collection
Exception in thread "main" java.lang.StackOverflowError
tell me where i am doing wrong and how to fix or google this problem? just not print this object by localhost:8080/genre/id? make something particular print or what?
It seems like you have an infinite recursion when trying toString your Genre entities. Your code first loads your Genre entity by id, and then calls Genre.toString(). Because you have #OneToMany relationship with Movies, it lazy loads the list and then calls Movie.toString() for every movie related to the genre. Then for every movie you have a #ManyToOne relationship back with Genre. And here lies the problem. It will call again Genre.toString() for each movie in the list.
Possible solutions
If you only want to simply print it in console, do not include movies list in Genre.toString()
If you are using Jackson, add #JsonBackReference to your #ManyToOne relationship in Movie, this way Jackson will ignore it when mapping to Json
Annotation documentation here
If using DTOs, just do not include Movie property in your DTO.
Hope this helps.