I do have an entity OptionGroup with relationships to other entities. One of the relationships is making trouble: An OptionGroup has an owner (User). When I delete an OptionGroup, for some reason, the JPA provider hibernate is trying to set the owner_id of the OptionGroup to null which violates to the NotNull constraint defined for the owner field. I have no clue why hibernate is doing this, but I can see that it is doing this in the log:
2022-08-30 20:17:53.008 DEBUG 17488 --- [nio-8081-exec-1] org.hibernate.SQL : update option_group set description=?, option_group_name=?, owner_id=? where id=?
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [null]
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [Männliche Vornamen]
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2022-08-30 20:17:53.008 TRACE 17488 --- [nio-8081-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [20001]
2022-08-30 20:17:53.012 WARN 17488 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23502
2022-08-30 20:17:53.012 ERROR 17488 --- [nio-8081-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: NULL value in column »owner_id« of relation »option_group« violates Not-Null-Constraint
If I would have defined cascade delete on the owner field I could imagine that hibernate might delete the owner first, set the owner in the OptionGroup to null and then delete the OptionGroup - although it does not make much sense to first the the owner to null and then delete the OptionGroup...
Do you have any idea why hibernate is setting owner_id to null?
Btw. if I remove the NotNull constraint the behavior is as expected: the OptionGroup is deleted and the User (owner) remains.
This is the OptionGroupClass:
#Entity
#Table(name = "option_group"/*, uniqueConstraints = {
#UniqueConstraint(columnNames = { "owner_id", "option_group_name" }) }*/)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class OptionGroup {
/**
* Id of the Option Group. Generated by the database
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Name of the Option Group. Unique in the context of a user.
*/
#NotBlank(message = "Option Group name is mandatory")
#Column(name = "option_group_name")
private String optionGroupName;
/**
* Description for the Option Group
*/
private String description;
/**
* User that is the owner of the Option Group.
*/
#NotNull(message = "Owner cannot be null")
#ManyToOne(fetch = FetchType.LAZY, cascade={CascadeType.PERSIST})
#JoinColumn(name = "ownerId")
private User owner;
/**
* List of options that belong to the Option Group.
*/
#OneToMany(cascade = CascadeType.ALL, mappedBy = "optionGroup", orphanRemoval = true)
#NotEmpty(message = "Options cannot be empty")
private List<Option> options;
/**
* List of invitations that belong to the Option Group.
*/
#OneToMany(cascade = CascadeType.ALL, mappedBy = "optionGroup", orphanRemoval = true)
private List<Invitation> invitations;
#Override
public int hashCode() {
return Objects.hash(description, id, optionGroupName,
options == null ? null : options.stream().map(option -> option.getId()).toList(), owner,
invitations == null ? null : invitations.stream().map(invitation -> invitation.getId()).toList());
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OptionGroup other = (OptionGroup) obj;
return Objects.equals(description, other.description) && Objects.equals(id, other.id)
&& Objects.equals(optionGroupName, other.optionGroupName)
&& Objects.equals(options == null ? null : options.stream().map(option -> option.getId()).toList(),
other.options == null ? null : other.options.stream().map(option -> option.getId()).toList())
&& Objects.equals(owner, other.owner)
&& Objects.equals(
invitations == null ? null
: invitations.stream().map(invitation -> invitation.getId()).toList(),
other.invitations == null ? null
: other.invitations.stream().map(invitation -> invitation.getId()).toList());
}
#Override
public String toString() {
return "OptionGroup [id=" + id + ", optionGroupName=" + optionGroupName + ", description=" + description
+ ", owner=" + owner + ", options="
+ (options == null ? null : options.stream().map(option -> option.getId()).toList()) + ", invitations="
+ (invitations == null ? null : invitations.stream().map(invitation -> invitation.getId()).toList())
+ "]";
}
}
As you can see the cascade of owner is limited to persist. f a OptionGroup is created, the owner User is created as well. But if an OptionGroup is deleted the owner User should not be deleted.
This is the User class:
/**
* Entity that represents a user
*
* Primary key: id
*/
#Entity
#Table(name = "usert", uniqueConstraints = {
#UniqueConstraint(columnNames = { "email"}) })
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class User {
/**
* Id of the User. Generated by the database
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Email address of the invitee.
*/
#Email(message = "Email is not valid", regexp = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])")
private String email;
/**
* Option Groups of which the user is the owner.
*/
#OneToMany(cascade = CascadeType.ALL, mappedBy = "owner", orphanRemoval = true)
private List<OptionGroup> ownedOptionGroups;
/**
* Invitations of the user.
*/
#OneToMany(cascade = CascadeType.ALL, mappedBy = "invitee", orphanRemoval = true)
private List<Invitation> invitations;
}
And this is the class that triggers the delete
/**
* Service related to Option Groups.
*/
#Service
#Transactional
#AllArgsConstructor
public class OptionGroupService {
/**
* Repository used to access Option Groups.
*/
#Autowired
private OptionGroupRepository optionGroupRepository;
/**
* Deletes the Option Group with the given id.
*
* #param id Id of the Option Group to delete.
* #throws ObjectWithNameDoesNotExistException
* #throws ObjectWithIdDoesNotExistException
*/
public void deleteOptionGroupById(Long id) throws ObjectWithIdDoesNotExistException {
if (optionGroupRepository.existsById(id)) {
optionGroupRepository.deleteById(id);
} else {
throw new ObjectWithIdDoesNotExistException("Option Group", id);
}
}
}
And the repository
public interface OptionGroupRepository extends JpaRepository<OptionGroup, Long> {}
Appreciate your help on this. Thanks.
The root cause was an extensive use of cascades in both, parent and child entities which led to a chain of cascades: by saving an Option Group an Invitation was saved by which again an Option Group was saved. After cleaning this up it works.
I recommend reading: Hibernate - how to use cascade in relations correctly
Related
I have a Spring boot project with 3 entities, UserEntity which is a standalone and Person which is inherited by lawyer.
I set the automatic indexing strategy to sync, When I insert a new user into the database, the new user is picked immediately, but a new lawyer or person are indexed but the don't appear in the result until I restart the mass indexer.
UserEntity:
#Entity
#Accessors(chain = true)
#Getter
#Setter
#Indexed
#SyncAnnotation(convertor = UserConvertor.class, repoClass = UserDetailServiceImplementation.class)
public class UserEntity implements UserDetails {
#Id
#Basic
#Column(name = "id", columnDefinition = "uniqueidentifier")
#Type(type = "uuid-char")
private UUID id;
#Column(name = "user_name", length = 20)
#Basic
private String username;
#Basic
#Column(name = "email")
#Email
private String email;
#Basic
#Column(name = "full_name", length = 50, nullable = false, columnDefinition = "nvarchar(50)")
#NotNull
#FullTextField(termVector = TermVector.WITH_POSITIONS_OFFSETS)
private String fullName;
PersonEntity:
#Entity
#Accessors(chain = true)
#Getter
#Setter
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "person_type", discriminatorType = DiscriminatorType.INTEGER)
#DiscriminatorValue("1")
#Indexed
#SyncAnnotation(convertor = ClientConvertor.class, repoClass = PersonRepository.class)
public class PersonEntity implements Serializable {
public PersonEntity(){
this.personType=1;
}
#Id
#Basic
#Column(name = "id", columnDefinition = "uniqueidentifier")
#Type(type = "uuid-char")
private UUID id;
#Basic
#Column(name = "first_name", nullable = false, length = 50, columnDefinition = "nvarchar(50)")
private String firstName;
#Basic
#Column(name = "last_name", nullable = false, length = 50, columnDefinition = "nvarchar(50)")
private String lastName;
#Basic
#Column(name = "father_name", length = 50, columnDefinition = "nvarchar(50)")
private String fatherName;
#Basic
#FullTextField(termVector = TermVector.YES)
#Column(name = "full_name", columnDefinition = "as concat(first_name,' ',isnull(father_name,''),' ',last_name)", insertable = false, updatable = false)
private String fullName;
#Basic
#Column(name = "person_type", insertable = false, updatable = false)
#GenericField
private Integer personType;
And a LawyerEntity that inherits PersonEntity:
#Entity
#Accessors(chain = true)
#Getter
#Setter
#DiscriminatorValue("2")
#Indexed
#SyncAnnotation(convertor = ClientConvertor.class, repoClass = LawyerRepository.class)
public class LawyerEntity extends PersonEntity {
public LawyerEntity(){
this.setPersonType(2);
}
#Basic
#Column(name = "bar_id")
#GenericField
private Integer barId;
#Basic
#Column(name = "bar_card_number")
private Long barCardNumber;
#Basic
#Column(name = "bar_regisration_date")
private LocalDate barRegistrationDate;
#ManyToOne(targetEntity = BarEntity.class)
#JoinColumn(foreignKey = #ForeignKey(name = "fk_lawyer_bar"),
name = "bar_id", referencedColumnName = "id", insertable = false, updatable = false)
#JsonIgnore
private BarEntity bar;
}
When using Sync hibernate search automatic indexing strategy, the UserEntity index updates and includes the newly inserted entities in the index , the TRACE output:
2022-12-22 10:16:06.112 TRACE 68869 --- [nio-8080-exec-4] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's beforeCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#5193eb5f.
2022-12-22 10:16:06.119 TRACE 68869 --- [nio-8080-exec-4] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's afterCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#5193eb5f. Executing indexing plan.
2022-12-22 10:16:06.119 TRACE 68869 --- [nio-8080-exec-4] o.h.s.e.b.o.spi.SingletonTask : Scheduling task 'Lucene indexing orchestrator for index 'User' - 9'.
2022-12-22 10:16:06.120 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.SingletonTask : Running task 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.120 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.BatchingExecutor : Processing 1 works in executor 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.132 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.BatchingExecutor : Processed 1 works in executor 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.132 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.SingletonTask : Completed task 'Lucene indexing orchestrator for index 'User' - 9'
However, when entering a new person or a lawyer, the index doesn't reflect the changes, not even after awhile, I need to restart the massindexer for it work, it has a similar output to the previous log, but it doesn't reflect the changes on the index until I restart the mass indexer
2022-12-22 10:14:38.086 TRACE 68869 --- [nio-8080-exec-6] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's beforeCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#6b9d9f5e.
2022-12-22 10:14:38.089 TRACE 68869 --- [nio-8080-exec-6] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's afterCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl#6b9d9f5e. Executing indexing plan.
2022-12-22 10:14:38.091 TRACE 68869 --- [nio-8080-exec-6] o.h.s.e.b.o.spi.SingletonTask : Scheduling task 'Lucene indexing orchestrator for index 'Person' - 8'.
2022-12-22 10:14:38.091 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.SingletonTask : Running task 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.092 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.BatchingExecutor : Processing 1 works in executor 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.137 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.BatchingExecutor : Processed 1 works in executor 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.138 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.SingletonTask : Completed task 'Lucene indexing orchestrator for index 'Person' - 8'
How can I make it detect show the change in the index without restart mass index ?
I also tried calling hibernate search indexing plan but to no success
I am using Hibernate search 6.1.6.Final with lucene backend and spring boot 2.7.5
As per request:
The code used to search for UserEntity (user can belong to bar):
public List<UserEntity> findInAnyRole(String name, Integer barId, UUID[] role) {
var session = sessionProvider.getSearchSession();
var search = session.search(UserEntity.class);
var res = search.where(f -> f.bool(
b -> {
b.must(f.match().field("fullName").matching(name).fuzzy(2));
if (role != null && role.length > 0) {
b.must(f.bool(b2 -> {
for (var roleValue : role)
b2.should(f.match().field("roles.id").matching(roleValue));
}));
}
if (barId != null)
b.must(f.match().field("barId").matching(barId));
}
));
return res.fetchHits(10);
}
As for PersonEntity:
public List<PersonEntity> findSimilar(#NotNull String name, String[] ids) {
var session = sessionProvider.getSearchSession();
var search = session.search(PersonEntity.class);
var q=search.where(f -> f.bool().must(f.match().field("fullName").matching(name).fuzzy(2))
.must(f.match().field("personType").matching(1))).sort(searchSortFactory -> searchSortFactory.score().desc());
log.info(q.toQuery().queryString());
return q.fetchHits(10);
}
and LawyerEntity:
public List<LawyerEntity> findSimilar(#NotNull String name, Integer barId) {
var session = sessionProvider.getSearchSession();
var search = session.search(LawyerEntity.class);
var res = search.where(f -> f.match().field("fullName").matching(name).fuzzy(2));
if (barId != null)
res = search.where(f -> f.bool().must(f.match().field("fullName").matching(name).fuzzy(2))
.must(f.match().field("barId").matching(barId)));
var list = res.fetchHits(10);
return list;
}
I suspect your problem is here:
#Column(name = "full_name", columnDefinition = "as concat(first_name,' ',isnull(father_name,''),' ',last_name)", insertable = false, updatable = false)
private String fullName;
As you're defining the full name on the database side, it will only be populated correctly when it's loaded from the database. The first time you create your entity, or anytime you change the first name or last name on your Java object, the fullName property in your Java object will not have the correct value, until it's loaded back from the database.
I think that when you create your entity, fullName is null, so Hibernate Search is indexing your entities with a fullName index field set to null, which explains that your queries (with predicates on the fullName field) do not match anything.
When you use the mass indexer, on the other hand, entities are loaded from the database and fullName is populated correctly from the database.
As to solutions, either:
Always update fullName manually whenever you update firstName or lastName. That might be inconvenient.
OR, if you don't need to use fullName in SQL queries, do not persist fullName in the database, do not add a fullName property to your entity, and just declare a getFullName() getter annotated with #javax.persistence.Transient that does the concatenation in Java:
#Transient
#FullTextField(termVector = TermVector.YES)
#IndexingDependency(derivedFrom = {
#ObjectPath(#PropertyValue(propertyName = "firstName")),
#ObjectPath(#PropertyValue(propertyName = "fatherName")),
#ObjectPath(#PropertyValue(propertyName = "lastName"))
})
public String getFullName() {
return firstName
+ ( fatherName == null ? "" : " " + fatherName )
+ " " + lastName;
}
See this section of the documentation for #IndexingDependency.
I use: Spring Boot, Spring Data, and H2 (in-memory) DataBase.
DB TABLES:
CREATE TABLE user
(
id INT AUTO_INCREMENT,
name VARCHAR(250) NOT NULL,
age INT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE article
(
id INT AUTO_INCREMENT,
text TEXT NOT NULL,
color ENUM ('red', 'green', 'blue', 'yellow', 'pink') NOT NULL,
user_id INT,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ON UPDATE CASCADE
);
In ArticleRepository exploited such query:
SELECT u.name, a.user_id, count() FROM user AS u INNER JOIN article AS a ON u.id = a.user_id GROUP BY a.user_id HAVING COUNT() > 3;
#Repository
public interface ArticleRepository extends JpaRepository<Article, Integer> {
#Query(value = "SELECT u.name, a.user_id, count(*) FROM user AS u INNER JOIN article AS a ON u.id = a.user_id GROUP BY a.user_id HAVING COUNT(*) > 3", nativeQuery = true)
List<Article> findUserNamesByArticlesCountMoreThan();
}
After request I receive the error:
In Postman:
"error": "Internal Server Error", "message": "could not execute query;
SQL [SELECT u.name, a.user_id, count(text) FROM user AS u INNER JOIN
article AS a ON u.id = a.user_id GROUP BY a.user_id HAVING COUNT(*) >
3]; nested exception is org.hibernate.exception.SQLGrammarException:
could not execute query",
In IntelliJ Idea:
02:54:24.681 [http-nio-9090-exec-1] WARN SqlExceptionHelper - SQL
Error: 42122, SQLState: 42S22 02:54:24.681 [http-nio-9090-exec-1]
ERROR SqlExceptionHelper - Column "id" not found [42122-197]
02:54:24.703 [http-nio-9090-exec-1] ERROR [dispatcherServlet] -
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not execute query; SQL [SELECT u.name, a.user_id, count(text) FROM
user AS u INNER JOIN article AS a ON u.id = a.user_id GROUP BY
a.user_id HAVING COUNT(*) > 3]; nested exception is
org.hibernate.exception.SQLGrammarException: could not execute query]
with root cause org.h2.jdbc.JdbcSQLException: Column "id" not found
[42122-197] at
org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179) at
org.h2.message.DbException.get(DbException.java:155) at
org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3148) at
org.h2.jdbc.JdbcResultSet.get(JdbcResultSet.java:3247) at
org.h2.jdbc.JdbcResultSet.getInt(JdbcResultSet.java:346)..............
Help me to resolve this problem, and find a mistake.
Entities:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "article")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "text", nullable = false)
private String text;
#Column(name = "color", nullable = false, unique = true)
#Enumerated(EnumType.STRING)
private Color color;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL )
#JoinColumn(name = "user_id", referencedColumnName="id")
private User user;
}
.
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#ToString(exclude = "articles")
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "age", nullable = false)
private Integer age;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Article> articles;
}
Service:
#Service
public class ArticleService {
private ArticleRepository articleRepository;
#Autowired
public ArticleService(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
public List<Article> findAllUserNamesByArticlesMoreThan() {
return articleRepository.findUserNamesByArticlesCountMoreThan(); }}
ArticleDtoService:
#Service
public class ArticleDtoService {
private ArticleService articleService;
#Autowired
public ArticleDtoService(ArticleService articleService) {
this.articleService = articleService;
}
public ResponseEntity<List<ArticleDto>> getAllUserNamesByArticlesMoreThan() {
List<Article> articles = articleService.findAllUserNamesByArticlesMoreThan();
Link link = linkTo(methodOn(ArticleController.class).getAllUserNamesListByArticlesMoreThan()).withSelfRel();
return new ResponseEntity<>(createArticleDtoList(articles, link), HttpStatus.OK); }}
ArticleController:
#RestController
public class ArticleController {
private final ArticleDtoService articleDtoService;
#Autowired
public ArticleController(ArticleDtoService articleDtoService) {
this.articleDtoService = articleDtoService;
}
#GetMapping(value = "/api/articles/count")
public ResponseEntity<List<ArticleDto>> getAllUserNamesListByArticlesMoreThan() {
return articleDtoService.getAllUserNamesByArticlesMoreThan(); }}
ArticleDto:
public class ArticleDto extends ResourceSupport {
public Article article;
public ArticleDto(Article article, Link selfLink) {
this.article = article;
add(selfLink); }}
I have two Entities - TypeReport and GroupParameter. GroupParameter has a field TypeReport.
My Entities:
GroupParameter
#Entity
#Data
public class GroupParameter {
#Id
#GeneratedValue(generator = ID_GENERATOR)
private Long id;
private String title;
private boolean common;
#ManyToOne
#JoinColumn(name = "TYPE_REPORT_ID", nullable = false)
private TypeReport typeReport;
}
TypeReport
#Data
#Entity
public class TypeReport {
#Id
#GeneratedValue(generator = ID_GENERATOR)
private Long id;
#Column(nullable = false)
private String title;
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private Standard standard;
}
The client sends me GroupParameterDTO:
#Data
public class GroupParameterDTO {
private Long id;
private String title;
private boolean common;
private String typeReportId;
}
The client can change some fields from GroupParameter and he can send any sets of fields:
- title
- title, typeReportId
- typeReportId
For example:
{
title: "new Title"
}
Then I have:
GroupParameterDTO {
private Long id = null;
private String title = "new Title";
private boolean common = false; //It will be ignored
private String typeReportId = null;
}
When I try to change typeReport I can have two situations:
typeReportId = null - I shouldn't change typeReport in GroupParameter
typeReportId = something - I should change typeReport in GroupParameter without changing fileds of typeReport
What do I?
I find GroupParameter in DB by id:
GroupParameter groupParametersFromDB = findById(id);
I fill GroupParameter by data that I get from the client:
groupParameterMapper.fillGroupParameters(groupParameters, groupParametersFromDB);
#Override
public void fillGroupParameters(GroupParameter source, GroupParameter target) {
if ( source == null ) {
return;
}
if ( source.getTypeReport() != null ) {
if ( target.getTypeReport() == null ) {
target.setTypeReport( new TypeReport() );
}
typeReportToTypeReport( source.getTypeReport(), target.getTypeReport() );
}
if ( source.getTitle() != null ) {
target.setTitle( source.getTitle() );
}
}
protected void typeReportToTypeReport(TypeReport typeReport, TypeReport mappingTarget) {
if ( typeReport == null ) {
return;
}
if ( typeReport.getId() != null ) {
mappingTarget.setId( typeReport.getId() );
}
mappingTarget.setTitle( typeReport.getTitle() );
mappingTarget.setStandard( typeReport.getStandard() );
}
I try to save my GroupParameter:
groupParameterRepository.save(groupParametersFromDB);
But I get errors.
When I try to change id:
org.hibernate.HibernateException: identifier of an instance of ru.watchlist.domain.TypeReport was altered from 1 to 3
When I don't try to change id(when the title is changed only):
org.postgresql.util.PSQLException: ERROR: null value in column "standard" violates not-null constraint
I think that Repository tries to save typeReport instead change a link to another one in GroupParameter while saving GroupParameter.
I don't want to do any changes in typeReport when I save GroupParameter. I want only to change a link to typeReport in GroupParameter.
How can I do that?
If a client tells you that he/she wants the type report ID of a group parameter to be 3, that doesn't mean he/she wants to change the ID of the type report.
It means he/she wants the group parameter to have another type report, identified by the ID 3.
So you get that type report from the database, and you assign it to the group parameter:
groupParameter.setTypeReport(typeReportRepository.findById(3).orElseThrow(...))
I'm working with hibernate 4 and Maven :
package tn.onp.mvno.model;
#Entity
#Table(name="PERSON")
public class Person {
private int id;
private String name;
private String surname;
private String email;
private String adresse;
private String MobilePhone;
/**
* Get User Id
*
* #return int - User Id
*/
#Id
#GeneratedValue
#Column(name="ID")
public int getId() {
return id;
}
/**
* Set User Id
*
* #param int - User Id
*/
public void setId(int id) {
this.id = id;
}
}
package tn.onp.mvno.model;
#Entity
#Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)
#Table(name="USER")
public class User extends Person{
private String SIMCardNumber;
private String SIMCardValidityDate;
private Collection<Call> calls;
private Collection<Credit> credits;
#Column(name="SIMCARDNUMBER", unique = true, nullable = false)
public String getSIMCardNumber() {
return SIMCardNumber;
}
public void setSIMCardNumber(String sIMCardNumber) {
SIMCardNumber = sIMCardNumber;
}
#Column(name="SIMCARDVALIDITYDATE", nullable = false)
public String getSIMCardValidityDate() {
return SIMCardValidityDate;
}
public void setSIMCardValidityDate(String sIMCardValidityDate) {
SIMCardValidityDate = sIMCardValidityDate;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Collection<Call> getCalls() {
return calls;
}
public void setCalls(Collection<Call> calls) {
this.calls = calls;
}
}
Call.java
#Entity
#Table(name="CALL")
public class Call {
private String callId;
private String telNumber;
private String term;
private String duration;
private String direction;
private User user;
/** Get the callId
* #return the callId
*/
#Id
#GeneratedValue
#Column(name="CALLID")
public String getCallId() {
return callId;
}
/** Set the callId
* #param callId the callId to set
*/
public void setCallId(String callId) {
this.callId = callId;
}
/** Get the telNumber
* #return the telNumber
*/
#Column(name="TELNUMBER")
public String getTelNumber() {
return telNumber;
}
/** Set the telNumber
* #param telNumber the telNumber to set
*/
public void setTelNumber(String telNumber) {
this.telNumber = telNumber;
}
/** Get the term
* #return the term
*/
#Column(name="TERM")
public String getTerm() {
return term;
}
/** Set the term
* #param term the term to set
*/
public void setTerm(String term) {
this.term = term;
}
/** Get the duration
* #return the duration
*/
#Column(name="DURATION")
public String getDuration() {
return duration;
}
/** Set the duration
* #param duration the duration to set
*/
public void setDuration(String duration) {
this.duration = duration;
}
/** Get the direction
* #return the direction
*/
#Column(name="DIRECTION")
public String getDirection() {
return direction;
}
/** Set the direction
* #param direction the direction to set
*/
public void setDirection(String direction) {
this.direction = direction;
}
/** Get the user
* #return the user
*/
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id")
public User getUser() {
return user;
}
/** Set the user
* #param user the user to set
*/
public void setUser(User user) {
this.user = user;
}
}
When i deploy the application on tomcat server and run it i get this error :
INFO: HHH000227: Running hbm2ddl schema export
Hibernate: alter table CALL drop foreign key FK1F725EE04824FE
févr. 11, 2013 5:53:38 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table CALL drop foreign key FK1F725EE04824FE
févr. 11, 2013 5:53:38 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'CALL drop foreign key
FK1F725EE04824FE' at line 1
Hibernate: drop table if exists CALL
févr. 11, 2013 5:53:38 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: drop table if exists CALL
févr. 11, 2013 5:53:38 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'CALL' at line 1
Hibernate: drop table if exists CREDIT
Hibernate: drop table if exists PERSON
Hibernate: create table CALL (CALLID varchar(255) not null auto_increment, DIRECTION
varchar(255), DURATION varchar(255), TELNUMBER varchar(255), TERM varchar(255), id
integer, primary key (CALLID))
févr. 11, 2013 5:53:38 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: create table CALL (CALLID varchar(255) not null
auto_increment, DIRECTION varchar(255), DURATION varchar(255), TELNUMBER varchar(255),
TERM varchar(255), id integer, primary key (CALLID))
févr. 11, 2013 5:53:38 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'CALL (CALLID varchar(255) not
null auto_increment, DIRECTION varchar(255), DURAT' at line 1
CALL is a reserved word. Choose another table name.
I have a database (in postgres) and I'm working with spring 3 and hibernate 3, the problem is when try to delete or get a row from database, here is the class that I'm using, ohh another thing after the query, a don't have any mistake, the only thing es that class User is null
#Repository("userDao")
public class UserDao implements IUserDao {
#Autowired
private SessionFactory sessionFactory;
public void SaveUser(User user) {
sessionFactory.getCurrentSession().save(user);
}
#SuppressWarnings("unchecked")
public List<User> ListUsers() {
List<User> users = (List<User>) sessionFactory.getCurrentSession().createCriteria(User.class).list();
return users;
}
public User GetUserById(Integer id) {
User user = (User) sessionFactory.getCurrentSession().load(User.class, id);
return user;
}
}
by the way, methods, SaveUser and ListUser work really good.
this is the model class
#Entity
#Table(name = "mrbean")
public class User {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
#Column (name = "id")
private Integer id;
#NotNull (message = "not null")
#NotEmpty (message = "empty")
#Column (name = "user_name", length = 20, nullable = false, unique = true)
private String userName;
#NotNull
#NotEmpty (message = "empty password")
#Size (min = 7, max = 20, message = "La contraseña debe tener entre 7 y 20 caracteres")
#Column (name = "password", length = 20, nullable = false)
private String password;
//with getters and setters
this is what hibernate show in console when
User user = (User)sessionFactory.getCurrentSession().load(User.class, id) is executed
Hibernate: select user0_.id as id0_0_, user0_.password as password0_0_, user0_.user_name as user3_0_0_ from mrbean user0_ where user0_.id=?
I would use "get" instead of "load" because load() creates a proxy of User (see Hibernate ref doc )
so i would try : User user = (User) sessionFactory.getCurrentSession().get(User.class, id);