Hibernate detached entity i believe - java

I am kinda beginner when it comes to hibernate, but i have been dealing with a problem for several hours, and i need your help.
I have an application that manages articles, and each article is written by an author.
What i want to do is to add an article to a database, with its author_id field being null, and only after that to update that field.
However, hibernate refuses to update my entity in the database.
public <Article> void updateElement(Article entity) {
EntityManager entityManager = createEntityManager();
try {
entityManager.getTransaction();
entityManager.merge(entity);
entityManager.getTransaction().commit();
}
catch(RuntimeException e ){
entityManager.getTransaction().rollback();
}
finally {
entityManager.close();
}
}
This is how i try to update the entry. I receive an Article as a parameter, for which i know for sure that it is in the database. And after that i try to do merge in order to update it. But i don't know if it the right approach. Please help me.
The article class:
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Setter
#Getter
#Entity
#Table( name = "Article")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "article_id")
private Integer id;
#NotBlank
#Column(name = "title")
private String title;
#NotBlank
#Column(name = "publication_date")
private Date publicationDate;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private Author author;
#ManyToMany(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#JoinTable(name = "article_tags",
joinColumns = #JoinColumn(name = "article_id"),
inverseJoinColumns = #JoinColumn(name ="tag_id"))
private Set<Tag> tags = new HashSet<>();
}
and the author class
#NoArgsConstructor
#Setter
#Getter
#Entity
#Table(name = "Authors")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "entity_id_seq")
#Column(name = "author_id",updatable = false)
private int id;
#NotBlank
#Column(name = "author_name")
private String name;
#OneToMany(mappedBy = "author",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private Set<Article> writtenArticles = new HashSet<>();
}```

please begin your transaction and not only get the transaction:
replace the
entityManager.getTransaction();
by
entityManager.getTransaction().begin();

Related

How to simulate lombok #data stackoverflow error within unit tests for repository class?

I have a relationship between entities that throws a stack overflow error if the #Data annotation from Lombok is used instead of the individual #Getter and #Setter annotations. This is fixed now, but I would like to write a unit test for it within my repository tests. However, I'm not sure how to achieve that and haven't been able to find samples for it.
Here are my entity classes:
#Entity
#Table(name = "users")
#Builder
//#Getter
//#Setter
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(
name = "users_hobbies",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "hobby_and_interest_id", referencedColumnName = "id"))
private Set<HobbyAndInterestEntity> hobbyAndInterestEntities;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "hometown_id", referencedColumnName = "id")
private HometownEntity hometownEntity;
#Entity
#Table(name = "hometown")
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
public class HometownEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "hometownEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = false)
private Set<UserEntity> userEntitySet;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class HobbyAndInterestEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "title")
private String title;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "hobbyAndInterestEntities")
private Set<UserEntity> userEntities;
And here is my test for a case without the exception, which I was aiming to modify to test for the exception scenario:
#Test
void testGetUser() {
UserEntity userEntity = saveUserEntity();
assertTrue(userRepository.findAll().size() > 0);
userEntity = userRepository.findById(userEntity.getId()).orElse(null);
assertNotNull(userEntity);
UserEntity finalUserEntity = userEntity;
assertAll(
() -> assertEquals("anyName", finalUserEntity.getName()),
() -> assertEquals("anyCountry", finalUserEntity.getHometownEntity().getCountry()),
() -> assertTrue(finalUserEntity.getHobbyAndInterestEntities().size() > 0));
finalUserEntity.getHobbyAndInterestEntities().forEach(h -> assertEquals("anyInterest", h.getTitle()));
}
#NotNull
private UserEntity saveUserEntity() {
HometownEntity hometownEntity = HometownEntity.builder().city("anyCity").country("anyCountry").build();
hometownEntity = hometownRepository.save(hometownEntity);
HobbyAndInterestEntity hobbyAndInterestEntity = HobbyAndInterestEntity.builder()
.title("anyInterest")
.build();
hobbyAndInterestEntity = hobbyAndInterestRepository.save(hobbyAndInterestEntity);
Set<HobbyAndInterestEntity> hobbyAndInterestEntities = new HashSet<>();
hobbyAndInterestEntities.add(hobbyAndInterestEntity);
UserEntity userEntity = UserEntity.builder()
.name("anyName")
.hometownEntity(hometownEntity)
.hobbyAndInterestEntities(hobbyAndInterestEntities)
.build();
return userRepository.save(userEntity);
}
So in summary, I know the application is throwing the stack overflow when I have the #Data annotation and so I would like to write a test that would fail for it and pass again when I modify the entity class to use #Getter and #Setter, but not sure what is needed here and would appreciate some guidance, please.
Thank you very much.
Could you check #Data annotation here. #Data is a shortcut for #ToString, #EqualsAndHashCode, #Getter on all fields, #Setter on all non-final fields, and #RequiredArgsConstructor! When you call toString or equals or hashCode method, the relationship entities will query in the database. You can try to review generated source, the relationship entities is used in those methods. I think it can throw a stack overflow error.

Why Hibernate provides me only interceptor and I catch JdbcSQLIntegrityConstraintViolationException: NULL not allowed?

I would be very grateful to you for help.
I use Spring Boot 2.5.2.
DB: H2 with Liquibase
I need to change ticket History when attachment is removed.
This is my entities:
Ticket:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_owner_id", nullable = false)
private User owner;
#OneToMany(mappedBy = "ticket", orphanRemoval = true)
private List<Attachment> attachments;
#OneToMany(mappedBy = "ticket", orphanRemoval = true)
private List<History> history;
// other fields and relationships
}
User:
#Entity
#Table(name = "users")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "owner", orphanRemoval = true)
private List<Ticket> ownerTickets;
// other fields and relationships
}
Attachment:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Attachment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String filename;
#Column(columnDefinition = "bytea")
private byte[] file;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "ticket_id", nullable = false)
private Ticket ticket;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
History:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class History {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#CreationTimestamp
#Column(name = "changed_date", nullable = false, updatable = false)
private LocalDateTime changedDate;
private String action;
private String description;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "ticket_id")
private Ticket ticket;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
Services:
JpaAttachmentService:
#Service
#AllArgsConstructor
public class JpaAttachmentService implements AttachmentService {
private final AttachmentRepository attachmentRepository;
private final HistoryService historyService;
#Transactional
#Override
public void delete(Long id, Long ticketId, Long userId) {
var attachment = attachmentRepository.getByIdAndTicketIdAndTicketOwnerId(id, ticketId, userId);
var action = "File is removed";
var description = "File is removed: " + attachment.getFilename();
var ticket = attachment.getTicket();
var user = attachment.getUser();
var history = new History(null, LocalDateTime.now(), action, description, ticket, user);
historyService.save(history);
attachmentRepository.deleteByIdAndTicketIdAndTicketOwnerId(id, ticketId, userId);
}
}
When I try to 'historyService.save(history)' I catch:
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "USER_ID"; SQL statement:
update history set action=?, description=?, ticket_id=?, user_id=? where id=? [23502-200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:459)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)
at org.h2.message.DbException.get(DbException.java:205)
at org.h2.message.DbException.get(DbException.java:181)
at org.h2.table.Column.validateConvertUpdateSequence(Column.java:374)
at org.h2.table.Table.validateConvertUpdateSequence(Table.java:845)
at org.h2.command.dml.Update.update(Update.java:176)
at org.h2.command.CommandContainer.update(CommandContainer.java:198)
at org.h2.command.Command.executeUpdate(Command.java:251)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:191)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:152)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
... 144 more
This is 'var user' debug:
picture
I can't understand why I get this Hibernate Interceptor but not entity. I confused when I see inside interceptor required UserID and when I see 'null' outside of it. Could you help me with this problem, please?
Thanks in advance for any help.
The exception message is quite informative regarding the error you are experiencing. Take a look at the following part:
NULL not allowed for column "USER_ID"; SQL statement: update history
set action=?, description=?, ticket_id=?, user_id=? where id=?
What you attempt to do is to save an instance of a History entity which without passing in a reference to a User object. Since your relation dictates that the user reference cannot be null:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", nullable = false)
To fix this, either make sure that a non-null reference to a User object is passed in when inserting/updating the History reference, or modify your database constraint design to allow for null user references at the History entity.
My problem was related to test, but in the further development I faced it again. So I want to share my solution.
Maybe it will help you.
Pay attention to CascadeType! Cascading operations must be specified above the link to the child relation. In my case, I have to remove this relation or leave Ticket with CascadeType.PERSIST
Fixed class Attachment:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Attachment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String filename;
#Column(columnDefinition = "bytea")
private byte[] file;
#ManyToOne(fetch = FetchType.LAZY) // or cascade = CascadeType.PERSIST
#JoinColumn(name = "ticket_id", nullable = false)
private Ticket ticket;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}

Many to many relationship using spring boot not saving data to join table

I have a many to many relationship between users and questions for favouriting items, and when I trigger my /questionId/favourite api for a user, it should create an entry under the join table question_favourite that would map out user_id and question_id (which are both called 'id' within their own table).
I see the question_favourite table has been created, but it's always empty. I tried also using a put rather than post thinking that perhaps that's why I don't see an insert statement printed on the console but it didn't help much and I think I'm a bit stuck now.
I see similar questions posted here but being a beginner on Spring and having spent literally a few months now trying to figure out why I can't save data to my join table, I thought I'd need some more specific help please.
Here is what I have:
#Entity
#Table(name = "user_profile")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<QuestionEntity> questionEntities;
#ManyToMany
#JoinTable(
name = "question_favourite",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "question_id", referencedColumnName = "id"))
private Set<QuestionEntity> questionFavouriteEntity;
public UserEntity(UUID id) {
this.id = id;
}
}
#Entity
#Table(name = "question")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class QuestionEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "title", nullable = false)
private String title;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private UserEntity userEntity;
#ManyToMany(mappedBy = "questionFavouriteEntity")
private Set<UserEntity> userEntities;
public QuestionEntity(UUID id) {
this.id = id;
}
public QuestionEntity(UUID id, String title) {
this.id = id;
this.title = title;
}
}
#PostMapping(value = "/{id}/favourite", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<QuestionFavouriteCreateDto> setFavouriteQuestion(#RequestHeader(required = false, value = "authorization") UUID userId, #RequestBody QuestionFavouriteCreateDto questionFavouriteCreateDto) {
if (userId == null) {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
} else {
return new ResponseEntity<>(questionCommandService.favouriteQuestion(userId, questionFavouriteCreateDto), HttpStatus.OK);
}
}
#Override
public QuestionFavouriteCreateDto favouriteQuestion(UUID userId, QuestionFavouriteCreateDto qf) {
if (questionRepository.findById(qf.getQuestionId()).isPresent()) {
QuestionEntity questionEntity = questionRepository.findById(qf.getQuestionId()).get();
UserEntity userEntity = userRepository.findById(userId).get();
questionEntity.getUserEntities().add(userEntity);
userEntity.getQuestionEntities().add(questionEntity);
userRepository.save(userEntity);
questionRepository.save(questionEntity);
return new QuestionFavouriteCreateDto(questionEntity.getId(), true);
} else {
return null;
}
}
Here is the github link for the project:
https://github.com/francislainy/so/tree/master/backend/src/main/java/com/francislainy/so/backend
Thank you very much.
In your class QuestionCommandServiceImpl you should add a set of the question entity created as a Question Favourite like bellow :
#Override
public QuestionFavouriteCreateDto favouriteQuestion(UUID userId, QuestionFavouriteCreateDto qf) {
if (questionRepository.findById(qf.getQuestionId()).isPresent()) {
QuestionEntity questionEntity = questionRepository.findById(qf.getQuestionId()).get();
UserEntity userEntity = userRepository.findById(userId).get();
questionEntity.getUserEntities().add(userEntity);
userEntity.getQuestionEntities().add(questionEntity);
userEntity.getQuestionFavouriteEntity().add(questionEntity); // get the Set of favorite question and add the new question
userRepository.save(userEntity);
}
return null;
}
But preferably in the your case you can remplace in UserEntity the #OneToMany by joinColumn it's more perform:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<QuestionEntity> questionEntities;
and try to unused the bidirectional or use with them the #JacksonBackReference
I hope that's help you ;)

Hibernate One to many mapping override

I am facing a hibernate problem in updainting the join table in one to many mapping with hibernate. Below are my two entity class and join table entity class.
ArticleCategoryMap.java
#Entity
#Table(name = "ARTICLECATEGORYMAP")
public class ArticleCategoryMap {
private static final long serialVersionUID = -5653708523600543988L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column ( name = "id")
Long id;
#ManyToOne(targetEntity = Article.class, fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
#JoinColumn(name = "ARTICLE_ID", nullable = true, insertable = true, updatable = true)
private Article article;
#ManyToOne(targetEntity = Category.class, fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
#JoinColumn(name = "CATEGORY_ID", nullable = true, insertable = true, updatable = true)
private Category category;
//setter and getter
}
Article.java
#Entity
#Table(name = "ARTICLE")
public class Article {
private long id;
private String title;
private String description;
private String keywords;
private String content;
#Id
#GeneratedValue
#Column(name = "ARTICLE_ID")
public long getId() {
return id;
}
//setter and getter
}
Category.java
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = #JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = #JoinColumn(name = "ARTICLE_ID")
)
#CollectionId(
columns = #Column(name="id"),
type=#Type(type="long"),
generator = "sequence"
)
private Collection<Article> articles;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
public long getId() {
return id;
}
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = #JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = #JoinColumn(name = "ARTICLE_ID")
)
#CollectionId(
columns = #Column(name="id"),
type=#Type(type="long"),
generator = "sequence"
)
// setter an getter
}
Now suppose first time I have 2 elements in article table which is mapping to one entry of the category table. so the join table will look something like
Now due to some reason, I want to update the entry where the article entry will map to a new category ID. So the final DB should look like
So My problem Is how can I update this join table.
If you want one to many relationship (1 category have many articles and 1 article to 1 category) you dont need a join table.
The entity classes should look like that:
Category Entity:
Contains a Set of articles:
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
#OneToMany(mappedBy="category")
private Set<Article> articles;
......
}
Article Entity:
#Entity
#Table(name = "ARTICLE")
public class Article {
#ManyToOne
#JoinColumn(name="id", nullable=false)
private Category category;
private long id;
private String title;
private String description;
private String keywords;
private String content;
.......
}
For more details take a look at hibernate-one-to-many. Hope this helps.
Also move annotation from methods to fields. This:
private long id;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
public long getId() {
return id;
}
Should be:
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
private long id;
public long getId() {
return id;
}
Many to many relationship:
At your database you have 3 tables:
CATEGORY
ARTICLE
ARTICLECATEGORYMAP (join table)
For many to many relationship entities would be:
Category Entity:
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
private long id;
private String name;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = { #JoinColumn(name = "CATEGORY_ID") },
inverseJoinColumns = { #JoinColumn(name = "ARTICLE_ID") }
)
Set<Article > articles = new HashSet<>();
.....
}
Article Entity:
#Entity
#Table(name = "ARTICLE")
public class Article {
#Id
#GeneratedValue
#Column(name = "ARTICLE_ID")
private long id;
private String title;
private String description;
private String keywords;
private String content;
#ManyToMany(mappedBy = "articles")
private Set<Category> categories = new HashSet<>();
.......
}
For more info take a look at many-to-many ralationship

Joining with cross-reference table in hibernate and spring

I use hibernate and spring-data. There are two tables with many-to-many relationship.
#Entity
#Table(name = "FirstEntity")
public class FirstEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "first_entity_id")
private Long id;
#Column(name = "first_entiry_name")
private String name;
/* getters and setters are below*/
}
#Entity
#Table(name = "SecondEntity")
public class SecondEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "second_entity_id")
private Long id;
#Column(name = "second_entiry_name")
private String name;
#Column(name = "second_entiry_desc")
private String description;
/* getters and setters are below*/
}
And entity for cross-reference table.
#Entity
#Table(name = "FirstSecondEntity")
public class FirstSecondEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "first_second_entity_id")
private Long id;
#Column(name = "first_entity_id")
private Long firstEntityId;
#Column(name = "second_entity_id")
private Long secondEntityId;
/* getters and setters are below*/
}
I need SELECT like this
SELECT FirstEntity.name, SecondEntity.name, SecondEntity.description FROM SecondEntity INNER JOIN FirstSecondEntity ON SecondEntity.id = FirstSecondEntity.secondEntityId INNER JOIN User ON FirstEntity.id = FirstSecondEntity.firstEntityId
i.e. I need all records from cross-reference table where instead of ids there is actual info from entities.
Inserting this query into #Query annotation in my CrudRepository-extended class doesn't work because of
ERROR [main][org.hibernate.hql.internal.ast.ErrorCounter] Path expected for join!
So I need your help.
Your join table is all screwed up. In this case, you actually don't even need the join table as a hibernate mapping:
In Second Entity add the following list:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FirstSecondEntity",
joinColumns = {
#JoinColumn(name = "first_entity_id",
nullable = false,
updatable = false) },
inverseJoinColumns = {
#JoinColumn(name = "second_entity_id",
nullable = false,
updatable = false) },
)
private List<FirstEntity> firstEntities;
In FirstEntity add the following list:
#ManyToMany(fetch = FetchType.LAZY,
mappedBy = "firstEntities")
private List<SecondEntity> secondEntities;

Categories

Resources