I have the following structure in my code:
A user has a favourites list of TvShows, a TvShow has a list of seasons, a season has a list of episodes.
User
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", updatable = false, nullable = false)
private int id;
#OneToMany(mappedBy = "tvshows", fetch = FetchType.EAGER)
private List<TvShow> favourites;
/// everything else removed for clarity
}
TvShow
#Entity
#Table(name = "tvshows")
public class TvShow {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tvShow_id", updatable = false, nullable = false)
private int id;
#OneToMany(mappedBy = "tvshows", fetch = FetchType.EAGER)
private List<Season> seasons;
/// everything else removed for clarity
}
Season
#Entity
#Table(name = "seasons")
public class Season{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "season_id", updatable = false, nullable = false)
private int id;
#OneToMany(mappedBy = "seasons", fetch = FetchType.EAGER)
private List<Episode> episodes;
/// everything else removed for clarity
}
The app won't start with the following error message:
mappedBy reference an unknown target entity property: com.gcimpoies.project.model.Season.seasons in com.gcimpoies.project.model.TvShow.seasons
Also,
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.gcimpoies.project.model.Season.seasons in com.gcimpoies.project.model.TvShow.seasons
I'm pretty sure I'm just missing the correct attribute names(arguments of mappedBy) but I have no idea what I'm doing wrong.
Thanks in advance!
The name that you have to put inside mappedBy is the id of the class that you are referencing.
Instead of:
#OneToMany(mappedBy = "tvshows", fetch = FetchType.EAGER)
Try:
#OneToMany(mappedBy = "id", fetch = FetchType.EAGER)
You will have to do this for the rest of the mappings as well.
Related
I have two java entity classes :
#Table(name = "user")
public class UserEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinColumn(name = "opportunity_id")
private OpportunityEntity opportunity;
}
and
#Table(name = "opportunity")
public class OpportunityEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany
#JoinColumn(name = "opportunity_id")
private List<UserEntity> users;
#OneToOne
#JoinColumn(name = "mainuser_id")
private UserEntity mainUser;
}
When i search for a list of Users [find users], i've got a "stackoverflow" when mapping User.opportunity.
the bug was clear that the opportunity.mainUser refer to User which itself refer to the same opportunity.
Is there another way to design my models ?
For example create a boolean isMain in User Model ?
Try to specify relationship to UserEntity by adding mappedBy to annotatation
#Table(name = "opportunity")
public class OpportunityEntity
{
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany
#JoinColumn(name = "opportunity_id")
private List<UserEntity> users;
#OneToOne(mappedBy="opportunity")
#JoinColumn(name = "mainuser_id")
private UserEntity mainUser;
}
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;
}
Consider the following database structure
I need to implement unidirectional one to one mapping like that (structure is simplified):
#Entity
#Table(name = "entity")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne
#JoinColumn(name = "customer_info", nullable = false)
private CustomerInfo customerInfo;
#OneToOne
#JoinColumn(name = "customer_credentials", nullable = false)
private CustomerCredentials customerCredentials;
// getter, setters etc
}
#Entity
#Table(name = "customer_info")
public class CustomerInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc
}
#Entity
#Table(name = "customer_credentials")
public class CustomerCredentials {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc
}
But somehow hibernate unable to differentiate that those joins are from different tables and throws such error:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.example.Customer column: customer_id (should be mapped with insert="false" update="false")
Important notice: I do not want to use #OneToOne(mappedBy = "customer") because I need cascade save functionality
You can use #JoinTable instead of #JoinColumn to solve your problem:
#Entity #Table(name = "entity") public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL, targetEntity = CustomerInfo.class)
#JoinTable(name = "customer_info", inverseJoinColumns = {#JoinColumn(name = "customer_id", nullable = false)})
private CustomerInfo customerInfo;
#OneToOne(cascade = CascadeType.ALL, targetEntity = CustomerCredentials.class)
#JoinTable(name = "customer_credentials", inverseJoinColumns = {#JoinColumn(name = "customer_id", nullable = false)})
private CustomerCredentials customerCredentials;
// getter, setters etc }
#Entity #Table(name = "customer_info") public class CustomerInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc }
#Entity #Table(name = "customer_credentials") public class CustomerCredentials {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// getter, setters etc }
You could change the cascade strategy to any strategy you need. I just left CascadeType.ALL there as an example.
I have tried to create some JPA Entities for a DB designed with the following tables: PRINCIPALS and CREDENTIALS which have the following relations with other tables:
#Entity
#Table(name = "CREDENTIALS")
public class Credentials {
#Id
#Column(name = "CREDENTIAL_ID")
private Integer credentialID;
#Id
#Column(name = "CREDENTIAL_TYPE_ID")
private String credentialTypeID;
#OneToOne
#JoinColumn(name = "CREDENTIAL_TYPE_ID", insertable = false, updatable = false)
private CredentialTypes credentialTypes;
}
CREDENTIALS has a oneToOne relation with CREDENTIAL_TYPES
#Entity
#Table(name = "CREDENTIAL_TYPES")
public class CredentialTypes {
#Id
#Column(name = "CREDENTIAL_TYPE_ID")
private String credentialTypeID;
#Column(name = "DESCRIPTION")
private String description;
}
#Entity
#Table(name = "PRINCIPALS")
public class Principals implements Serializable {
#Id
#Column(name = "PRINCIPAL_TYPE_ID", nullable = false)
private String principalTypeID;
#Column(name = "PRINCIPAL_ID", nullable = false)
private String principalID;
#OneToOne
#JoinColumn(name = "PRINCIPAL_TYPE_ID", insertable = false, updatable = false)
private PrincipalTypes principalTypes;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "PRINCIPAL_CREDENTIAL",
joinColumns = #JoinColumn(name = "CREDENTIAL_ID"),
inverseJoinColumns = #JoinColumn(name = "PRINCIPAL_ID"))
private List<Credentials> credentials;
PRINCIPALS has a oneToOne relation with PRINCIPAL_TYPES
#Entity
#Table(name = "PRINCIPAL_TYPES")
public class PrincipalTypes implements Serializable {
#Id
#Column(name = "PRINCIPAL_TYPE_ID", nullable = false)
private String principalTypeID;
#Column(name = "DESCRIPTION")
private String description;
And finally PRINCIPALS has a oneToMany relation with CREDENTIALS and uses a join table PRINCIPLE_CREDENTIAL
#Entity
#Table(name = "PRINCIPAL_CREDENTIAL")
public class PrincipalCredential implements Serializable {
#Id
#Column(name = "PRINCIPAL_TYPE_ID", nullable = false)
private String principalTypeID;
#Id
#Column(name = "PRINCIPAL_ID", nullable = false)
private String principalID;
#Id
#Column(name = "CREDENTIAL_ID")
private Integer credentialID;
#Id
#Column(name = "CREDENTIAL_TYPE_ID")
private String credentialTypeID;
At startup (using SpringBoot) I receive an error for the oneToMany relation between Principals and Credentials and just don't have any idea how to fix it... Tried various other methods (The DB design cannot be changed).
Caused by: org.hibernate.AnnotationException: A Foreign key refering entities.Principals from entities.Credentials has the wrong number of column. should be 2
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:502)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1467)
at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1233)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:794)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:729)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:70)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1697)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1426)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:85
I find the exception wierd because there is no refering of Principlas from Credentials....
PRINCIPLE_TYPE_ID and CREDENTIAL_TYPE_ID are missing in the joinColumns/inverseJoinColumns. I think you must use the #JoinColumns Annotation
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;