How to map a many-to-many relantionship in OrientDB - java

I'm starting with OrientDB and I got the basics right. But I can't figured out how to map many-to-many relationships in a way that won't require me to make tons of integrity checking when removing a record.
I am using an object database (ODatabaseObjectTx) and I'm trying to map this simple relationship:
Person <-> Roles
A Person can have multiple Roles and a Role can be assigned to multiple Persons. N to M relationship.
My person class:
public class Person {
private String name;
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My Role class:
public class Role {
private String name;
public Role() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now I can't figure out if I have to put a Set<Role> in the User class or a Set<User> in the Role class to make the links. Either way if I remove one record from one I have to check the links of the other to avoid null references. To me it is more convenient to place a Set<Role> in the User class.
In a RMDBS you can just create a foreign key and add some cascading rules, does OrientDB provide similar functionality to save me the bother of doing this manually?
If in the end I have to do this manually, what strategy should I take? Should I fully check the other cluster when removing a record? Should I just leave the nulls and handle them when I see them? Should I create double references (having both Set<Role> in User AND Set<User> in Role, I guess this would bring faster removal at the cost of more space and complexity)?

take a look at documentation: https://github.com/orientechnologies/orientdb/wiki/Object-Database#cascade-deleting

Related

How to select only certain fields with Quarkus Panache?

Quarkus simplifies Hibernate ORM mappings with Panache.
Here is an example of my entity and PanacheRepository:
#Entity
public class Person {
#Id #GeneratedValue private Long id;
private String firstName;
private String lastName;
private LocalDate birth;
private Status status;
}
#ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// example
public Person findByName(String name){
return find("name", name).firstResult();
}
// ! and this is what I tried, but it's not possible to do it this way
// all the methods return Person or something of type Person like List<Person>
// so basically this won't even compile
public List<String> findAllLastNames() {
return this.find("select p.lastName from Person p").list();
}
}
All the guides explain how to write different queries, but is not clear how to select only certain attributes.
If I don't need the whole Person object, but rather the lastName of all persons in my DB?
Is it possible to select only certain attributes with Quarkus Panache?
This is currently not possible, you can subscribe to this issue regarding projection for Hibernate with Panache: https://github.com/quarkusio/quarkus/issues/6261
Don't hesistate to vote for it (+1 reaction) and provides feedback.
As #aksappy said, this feature was added and it's documentation is available here.

How exactly does the Hibernate #OneToMany annotation work?

I am pretty new to Hibernate and I am studying it on a tutorial. I have some problems understanding how exactly the OneToMany annotation works.
So I have these 2 entity classes: Student that represents a student and Guide that represents a person that guides the student. So each student is associated with a single guide but a single guide can follow more that one student. I want a guide to know the students associated to him.
So I have:
Student:
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="enrollment_id", nullable=false)
private String enrollmentId;
private String name;
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name="guide_id")
private Guide guide;
public Student() {}
public Student(String enrollmentId, String name, Guide guide) {
this.enrollmentId = enrollmentId;
this.name = name;
this.guide = guide;
}
public Guide getGuide() {
return guide;
}
public void setGuide(Guide guide) {
this.guide = guide;
}
}
So the #ManyToOne annotation on the guide field:
#ManyToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name="guide_id")
private Guide guide;
means that a single guide is associated a single student but a guide can follow many students. Is it right? What exactly does the specified cascade settings do? I think it means that when I persist a Student object that contains a Guide object as field also this Guide object is also automatically persisted. And the same thing happens when I remove a Student object, the related Guide record is deleted...but I am not absolutely sure about it...
Ok, doing it this way I will have a mono directional relationship between a record in the Student table and a record in the Guide table because in the Student table I will have a foreign key to join the Guide table so the student can know its guide but, doing it this way, the guide can not know the followed student...and this is not smart.
To do it the Guide class is implemented in this way:
#Entity
public class Guide {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="staff_id", nullable=false)
private String staffId;
private String name;
private Integer salary;
#OneToMany(mappedBy="guide", cascade={CascadeType.PERSIST})
private Set<Student> students = new HashSet<Student>();
public Guide() {}
public Guide(String staffId, String name, Integer salary) {
this.staffId = staffId;
this.name = name;
this.salary = salary;
}
public Set<Student> getStudents() {
return students;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
public void addStudent(Student student) {
students.add(student);
student.setGuide(this);
}
}
So, as you can see, this class contains:
#OneToMany(mappedBy="guide", cascade={CascadeType.PERSIST})
private Set<Student> students = new HashSet<Student>();
that it is used to declare the bidirectional relationship.
So it seems to me that this annotation automatically create a guide_id field into the Student table that represent the foreign key that implement the bidirectional relation.
In fact using this mapping the Student table is automatically created in this way in my database:
'id', 'bigint(20)', 'NO', 'PRI', NULL, 'auto_increment'
'enrollment_id', 'varchar(255)', 'NO', '', NULL, ''
'name', 'varchar(255)', 'YES', '', NULL, ''
'guide_id', 'bigint(20)', 'YES', 'MUL', NULL, ''
So in the Student entity class I have not defined the guide_id field but I have it in the Student table on the database. So I think that the creation of this field in the table depends on the previous #OneToMany annotation defined in the Guide entity class. Is that correct or am I missing something?
Yes, you can define a #OneToMany entity without a bidirectional association, and the added column is on the Many entity side in the database (even though the entity doesn't know it is linked to the One-side entity).
You can also use a join table for this, but it's not necessary.

CrudRepository: find by multiple related entities

I'm having some trouble designing a query in a CrudRepository.
I have two entities, CourseOffering and Department (only relevant code shown):
CourseOffering.java:
public class CourseOffering implements Serializable
{
private Department department;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "DepartmentId", nullable = true)
#JsonProperty
public Department getDepartment()
{
return this.department;
}
public void setDepartment(Department department)
{
this.department = department;
}
}
Department.java:
public class Department implements Serializable
{
private Set<CourseOffering> courses;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
public Set<CourseOffering> getCourses() {
return this.courses;
}
public void setCourses(Set<CourseOffering> courses) {
this.courses = courses;
}
}
and the CrudRepository in question:
CourseOfferingRepository.java:
import java.util.List;
import edu.ucdavis.dss.dw.entities.CourseOffering;
import org.springframework.data.repository.CrudRepository;
public interface CourseOfferingRepository extends CrudRepository<CourseOffering, Long>
{
CourseOffering getOneByTermIdAndNumberAndDepartmentId(long termId, String number,
long departmentId);
List<CourseOffering> findByDepartmentCode(String deptCode);
//List<CourseOffering> findAllByDepartmentCode(String deptCodes);
List<CourseOffering> findByTermCode(String termCode);
}
The three functions in CourseOfferingRepository which are not commented out work as expected. I am trying to get the fourth to work.
What I'd like to do is be able to return all CourseOfferings where the department code is one of many department codes. Note that the CourseOffering table itself only holds a department_id integer which references the ID in the Department table, where the actual deptCode is stored.
How would I go about getting that commented out CrudRepository function to work properly? Or put another way, how does one make the plural version of "List findByDepartmentCode(String deptCode);"?
Thanks in advance for any advice you can offer.
You need to change the commented out code to:
List<CourseOffering> findByDeptCodeIn(Collection<String> deptCodes)
Check out this part of the documentation to see what other keywords are allowed
As geoand pointed out in the comments, the answer is:
List<CourseOffering> findByDepartmentCodeIn(List<String> deptCodes);
Thanks geoand!

Hibernate, Spring and foreign keys

I'm working on a hibernate, spring project to help me understand the basics of those two.
I'm running into a problem where i want to be able to add foreign keys to my tables.
I've been browsing the internet for information regarding this subject and I haven't been able to find something that suits my needs.
I have two classes:
Schools
Classes
Now i want to map the primary key from Schools to Classes.
This is the code I have now:
#ManyToOne
#JoinColumn(name = "SCHOOL_ID", table = "SCHOOL")
private School school;
and for my getter and setter:
public long getSchool() {
return school.getId();
}
public void setSchool(long schoolId) {
this.school.setId(schoolId);
}
Is this the way to go? Or am I totally looking at it the wrong way.
Thanks!
you are on the right track, although its better to deal with the actual objects and not the ids e.g.
#ManyToOne
#JoinColumn(name = "SCHOOL_ID", table = "SCHOOL")
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school=school;
}
Change it to this :
public long getSchool() {
return this.school;
}
public void setSchool(School school) {
this.school = school;
}

Hibernate/JPA - Foreign Key Index in Object itself

I am currently working on 100+ Java Objects created by someone with no JPA/Hibernate experience into JPA Entities. Their Objects reference other objects based on a Foreign Key in the Class itself. All of the primary Keys are generated outside of the database.
For Example (Just to Illustrate)
Car
#Entity
#Table(name="CAR")
public class Car {
private Integer id;
private String name;
#Id
#Column(name="CAR_ID")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name="CAR_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Engine
#Entity
#Table(name="ENGINE")
public class Engine {
private Integer id;
private String name;
private Integer carId;
#Id
#Column(name="ENGINE_ID")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name="ENGINE_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="CAR_ID")
public Integer getCarId() {
return carId;
}
public void setCarId(Integer carId) {
this.carId = carId;
}
}
Here is what the schema looks like:
CREATE TABLE CAR(CAR_ID INTEGER NOT NULL PRIMARY KEY,CAR_NAME VARCHAR(255))
CREATE TABLE ENGINE(ENGINE_ID INTEGER NOT NULL PRIMARY KEY,CAR_ID INTEGER,ENGINE_NAME VARCHAR(255))
ALTER TABLE ENGINE ADD FOREIGN KEY (CAR_ID) REFERENCES CAR(CAR_ID) ON DELETE CASCADE ON UPDATE CASCADE
Testing Code
Car c = new Car();
c.setId(100);
c.setName("Dodge Intrepid");
em.persist(c);
Engine e = new Engine();
e.setId(999);
e.setName("V6");
e.setCarId(c.getId());
em.persist(e);
Object entity = em.find(c.getClass(),c.getId());
em.remove(entity);
So, the Car ID holds a reference to the Engine in the Database. Losing Lazy Loading is not a big deal because we are using an TRANSACTIONAL Entity Manager. I have tested this out and it seems to work fine.
Are there any obvious problems with this that I am missing? I know it does not exactly fit the JPA/Hibernate spec, but I think it works.
The main thing you're losing is easy navigation through the object graph without having to explicitly fetch associated objects by ID all the time via your data access layer. I understand you're reluctance to go through and convert all of the object IDs into object references, but in the long-run, you're probably better off making that investment. By continuing with your current design, you'll end up writing a lot of extra data access code to fetch related objects every time you need to navigate an association. Plus, it will make it more difficult to model object relationships when the objects are not persisted in the database (e.g. when constructing a new object graph or in unit tests). You're also giving up cascading (transitive) saves and updates.
In short, I think your system will be more difficult to maintain in the long run and navigating object graphs will be quite awkward unless you address this design problem.
Not having any mapped relationships is going to make it almost impossible to have any joins in your HQL Queries or Criteria. A simple Select will work but you could not do
from Engine as eng where eng.car.name = 'DODGE'
If you at least get the major relationships coded it will make thing a lot easier in the long run.

Categories

Resources