I have a many-to-many relationship between accounts and groups. The data for the groups table comes pre-populated and there is no adding additional groups.
Using Hibernate, my desire is to:
Add an account to the account table,
Add a record to the account_group table, and
NOT add a record to the group table
It seems to me, no matter what I do, I always get a duplicate record in the group table. This is my problem. Here is my code:
Account.java
#Entity
#Table(name = "account", schema = "admin")
public class Account {
#Id
#GeneratedValue
private UUID id;
...
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "account_grouping", schema = "admin", joinColumns = #JoinColumn(name = "account_id"), inverseJoinColumns = #JoinColumn(name = "grouping_id"))
private Set<Grouping> groupings;
...
// getters and setters
}
Grouping.java
#Entity
#Table(name = "grouping", schema = "admin")
public class Grouping {
...
#Id
#GeneratedValue
private UUID id;
...
// getters and setters
}
AccountDao.java
#Repository
public class AccountDao extends AbstractJpaDao<Account, String> {
...
#Transactional
public boolean create(Account newAccount) {
try {
getEntityManager().merge(newAccount);
return true;
} catch(Exception e) {
System.out.println("Danger, Will Robinson!: " + e);
return false;
}
}
}
I have looked up many questions and websites regarding Hibernate #ManyToMany examples, but I must be missing something. Thank you for reading and for your help.
Unfortunately I do not have enough rep to make a comment, so I'll throw my guess as an answer. Make sure the Group entity has an ID when selecting a Group for your Account before saving your entity. Otherwise the Group entity is treated as a new Object and will be saved into the grouping table.
Related
I have 2 entities, Event and Tag, in a many-to-many relationship. Tags should have unique names, so I placed a constraint on it.
It works like expected for unique tag names, I save a batch of new events and entries are automatically inserted in the tag and join tables.
But the moment I try to save an event that has a tag with a duplicate name, an error is thrown due to it violating the constraint.
Is there a way around this that does not involve having to check and insert all the events/tags manually?
Code below:
Event entity:
#Entity
#Table(name = "event")
class Event {
#Id long id;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "event_tag",
joinColumns = #JoinColumn(name = "event_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
private Set<Tag> tags;
// other properties
}
Tag entity:
#Entity
#Table(name = "tag", uniqueConstraints = #UniqueConstraint(columnNames = "name"))
public class Tag {
#Id long id;
#Column(name = "name", unique = true)
private String name;
#ManyToMany(mappedBy = "tags", cascade = { CascadeType.ALL })
private Set<Event> events;
}
I'm using JpaRepository's method saveAll to persist the events. It throws:
java.sql.SQLException: Duplicate entry 'xxxxx' for key 'uq_tag_name'
at org.mariadb.jdbc.internal.protocol.AbstractQueryProtocol.readErrorPacket(AbstractQueryProtocol.java:1694) ~[mariadb-java-client-2.7.3.jar:na]
I have seen a few similar questions but have yet to find a working answer for this.
it happen because of { CascadeType.ALL }
when you set CascadeType.ALL hibernate changes the Tag table and if it need,insert data for Tag.you should remove CascadeType.ALL and if you get (cannot insert transient object) should use flush for handlinf that
I have a question regarding ManyToOne relationship.
Assume I have 2 beans:
#Entity
#Table(name = "accounts")
public class Account {
#Id
#Column(name = "account_id")
private int account_id;
}
#Entity
#Table(name = "broker_account")
public class BrokerAccount {
#Id
#Column(name = "broker_account_id")
private int broker_account_id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="account_id", referencedColumnName = "account_id")
private Account account;
}
I am querying the entity below (plain get all query)
entityManager.createQuery("from BrokerAccount", BrokerAccount.class)
I thought that if I query BrokerAccount entity the account_id column will be populated by default on the Account object, since it exists in the BrokerAccount table as well, however all the Account fields are empty.
Am I missing something, should I define this field/column on the BrokerAccount entity itself as well to get its value?
You have defined the Account association as #ManyToOne(fetch = FetchType.LAZY). This means that while performing the entityManager.createQuery("from BrokerAccount", BrokerAccount.class), there will be no join on the Account and its data will not be fetched at that time.
In order to make the persistence provider fetch the Account data you would need to interact with the reference while being in the same transactional method, f.e.: brokerAccount.getAccount().getAccountId();
If you want to simply have a repeated column for the fk you can do:
#Column(name = "account_id", insertable=false, updatable=false)
private int account_id;
I am looking for a way to model a relation between two or more objects of the same class in Hibernate. For example: I want to create a situation where I want to model relations between persons. In the database, I have two tables:
Person:
Id
Name
Relation:
Parent Id
Child Id
I tried to model this in Hibernate as a Person have two ManyToMany relations (Getter/Setter annotations are Lombok):
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id
#Column(name="name")
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "relations",
joinColumns = {#JoinColumn(name = "parent_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "child_id", nullable = false)}
)
private Set<Person> children = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "relations",
joinColumns = {#JoinColumn(name = "child_id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "parent_id", nullable = false)}
)
private Set<Person> parents = new HashSet<>();
}
This gave me the following problems:
With fetch type "LAZY" Hibernate complains about not having a Session when calling person.getChildren() or person.getParents()
With fetch type "EAGER" Hibernate returns null for the sets, which causes nullpointers when trying to add children or parents. The other thing I am worried about is that possible endless recursive eager fetching.
To get around this, I've tried to model the Relation as a class, so that I can use JPA queries in the PersonRepository to find Children and Parents without having to mess with the intricacies of ManyToMany :
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query(
"select p from Person p join Relation r on p.id = r.childId where r.childId = :childId"
)
List<Person> findParents(Long childId);
}
This caused Hibernate to complain that Relation does not have an #Id field. I do not want to add that because the relation table in the database should model the relation, and not have an id of its own.
When trying to search online for similar structures in Hibernate I usually find classic many-to-many examples in the documentation and questions like this one where there is a relation between two different classes instead of a relation between two objects of the same class.
I'm looking for a way to model a relation between two or more objects of the same class in Hibernate. I want to be able to fetch relations lazily, or fetch them afterwards for performance reasons.
It would be nice if the relation table could have an extra field "type" which indicates the type of relation between Persons (child, parent, nephew, friend) so that there is room for new relation types without too much changes to database and code.
Not sure I understand you correctly, but I had a similar case once.
What I did was to persist the child/parent without its relations and updated them with their relations afterwards (still in the same transaction).
private void insertEntity(final AbstractEntity entity) {
insertedEntities.add(entity.getId());
final List<AbstractEntity> relations = entity.onBeforeInsertion();
for (final AbstractEntity relation : relations) {
if (!insertedEntities.contains(relation.getId())) {
insertEntity(relation);
}
}
final RelationBundle relationBundle = new RelationBundle();
entity.onInsertion(relationBundle);
immediatelySaveNewEntityThroughProxy(entity);
for (final AbstractEntity relation : relations) {
entity.onRelationInsertion(relation, relationBundle);
}
dao.saveOrUpdate(entity);
}
private void immediatelySaveNewEntityThroughProxy(final DocumentEntity entity) {
proxiedAccess().immediatelySaveNewEntity(entity);
}
private MyConsumer proxiedAccess() {
return applicationContext.getBean(getClass());
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void immediatelySaveNewEntity(final DocumentEntity entity) {
try {
if (!dao.entityExistsFromId((int) entity.getId())) {
dao.save(entity);
}
} catch (final Exception e) {
LOGGER.error("Error saving entity: {}", entity.getId(), e);
}
}
I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.
I have these two class(table)
#Entity
#Table(name = "course")
public class Course {
#Id
#Column(name = "courseid")
private String courseId;
#Column(name = "coursename")
private String courseName;
#Column(name = "vahed")
private int vahed;
#Column(name = "coursedep")
private int dep;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "student_course", joinColumns = #JoinColumn(name = "course_id"), inverseJoinColumns = #JoinColumn(name = "student_id"))
private Set<Student> student = new HashSet<Student>();
//Some setter and getter
and this one:
#Entity
#Table(name = "student")
public class Student {
#Id
#Column(name="studid")
private String stId;
#Column(nullable = false, name="studname")
private String studName;
#Column(name="stmajor")
private String stMajor;
#Column(name="stlevel", length=3)
private String stLevel;
#Column(name="stdep")
private int stdep;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "student_course"
,joinColumns = #JoinColumn(name = "student_id")
,inverseJoinColumns = #JoinColumn(name = "course_id")
)
private Set<Course> course = new HashSet<Course>();
//Some setter and getter
After running this code an extra table was created in database(student_course), now I wanna know how can I add extra field in this table like (Grade, Date , and ... (I mean student_course table))
I see some solution but I don't like them, Also I have some problem with them:
First Sample
If you add extra fields on a linked table (STUDENT_COURSE), you have to choose an approach according to skaffman's answer or another as shown bellow.
There is an approach where the linked table (STUDENT_COURSE) behaves like a #Embeddable according to:
#Embeddable
public class JoinedStudentCourse {
// Lets suppose you have added this field
#Column(updatable=false)
private Date joinedDate;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="STUDENT_ID", insertable=false, updatable=false)
private Student student;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="COURSE_ID", insertable=false, updatable=false)
private Course course;
// getter's and setter's
public boolean equals(Object instance) {
if(instance == null)
return false;
if(!(instance instanceof JoinedStudentCourse))
return false;
JoinedStudentCourse other = (JoinedStudentCourse) instance;
if(!(student.getId().equals(other.getStudent().getId()))
return false;
if(!(course.getId().equals(other.getCourse().getId()))
return false;
// ATT: use immutable fields like joinedDate in equals() implementation
if(!(joinedDate.equals(other.getJoinedDate()))
return false;
return true;
}
public int hashcode() {
// hashcode implementation
}
}
So you will have in both Student and Course classes
public class Student {
#CollectionOfElements
#JoinTable(
table=#Table(name="STUDENT_COURSE"),
joinColumns=#JoinColumn(name="STUDENT_ID")
)
private Set<JoinedStudentCourse> joined = new HashSet<JoinedStudentCourse>();
}
public class Course {
#CollectionOfElements
#JoinTable(
table=#Table(name="STUDENT_COURSE"),
joinColumns=#JoinColumn(name="COURSE_ID")
)
private Set<JoinedStudentCourse> joined = new HashSet<JoinedStudentCourse>();
}
remember: #Embeddable class has its lifecycle bound to the owning entity class (Both Student and Course), so take care of it.
advice: Hibernate team suppports these two approachs (#OneToMany (skaffman's answer) or #CollectionsOfElements) due some limitations in #ManyToMany mapping - cascade operation.
regards,
The student_course table is there purely to record the association between the two entities. It is managed by hibernate, and can contain no other data.
The sort of data you want to record needs to be modelled as another entity. Perhaps you could a one-to-many association between Course and StudentResult (which contains the grade, etc), and then a many-to-one association between StdentResult and Student.
Drop the many-to-many, create a class called StudentCourseRelationship and set up one to manys on Student and Course to the StudentCourseRelationship.
You can put all sorts of things on it, like DateEnrolled, DateKickedOut etc. etc.
IMO the many-to-many mapping is a bit of a con.
The accepted answer unfortunately doesn't work for me, hibernate generates the join table in a weird way (all join columns are duplicated). However the variant with dedicated entity for the join table works fine. Here it is described in great detail: http://www.mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/