1

I'm confused with designing a client software with database integration to what should be a member variable of the class or just a query to the database. Let me be specific with a trivial example:

If I have, lets say, a Student class, which has a list of "friends" that are Student objects. Should my software design have an ArrayList<Student> as a member variable of the Student class or should the Database deal with the relationship itself and the Student class doesn't account for those "friends"? How should a proper UML class-diagram be in this case?

Christophe
  • 68,716
  • 7
  • 72
  • 138
ludicrous
  • 175
  • 9

2 Answers2

1

You need a one-to-many relationship between Student and friends in both the relational database and the object model.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • But why should the object model have the ArrayList if the application can request its friends with a query to the database? – ludicrous Feb 19 '22 at 23:18
  • What will it store them in? A collection. – duffymo Feb 19 '22 at 23:56
  • one-to-many? If one student can have many student friends, can't a student friend be friend of many students as well? – Christophe Feb 20 '22 at 01:01
  • yes, now you're thinking. many-to-many will mean a join table in your schema, but the objects will still have a collection as a private data member. – duffymo Feb 20 '22 at 02:38
1

This question is broader than you may think, as there are many ways to deal with it. Here some first ideas:

Let's start with a quick class diagram. The friendship between students is a many-to-many association.
Diagram with a student class and an association with itself

In a database, a many-to-many association is usually implemented using an association table. So you'd have two tables: STUDENTS and FRIENDSHIPS with pairs of ids of befriended students: Two tables related with a double foreign key constraint

To load a Student object from the database, you'd read the data in a STUDENTS row and use it to initialize your object. For the friendship, you'd have to read the relevant FRIENDSHIPS rows.

But how to use these tables in the application?

  • A first possibility would be to load each Student friend and insert it in the ArrayList<Student>. But each loaded student is like the first student and could have oneself friends that you'd have to load as well! You'd end up loading a lots of students, if not all, just for getting the single one you're interested in.
  • A second possibility would be use an ArrayList<StudentId> instead of an ArrayList<Student> and populate it. You'd then load the friends just in time, only when needed. But this would require some more important changes in your application.
  • A third possibility is not to expose an ArrayList. Not leaking the internals is always a good idea. Instead use a getter. So you'd load the friends only if student.getFriends() is called. This is a convenient approach, as you'd have a collection of friends at your disposal, but avoid being caught in a recursive loading of friends of friends.

In all the cases, you may be interested in using a repository object to get individual or collections of students, and encapsulate the database handling.

Advice: as said, there are many more options, the repository is one approach but there are also active records, table gateways and other approaches. To get a full overview, you may be interested in Martin Fowler's book Patterns of Enterprise Application Architecture.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    This is a great answer. Just to clarify: the Student class doesn't need to have a member ArrayList of Students, right? as I understand, the getFriends() method queries the database, populates the ArrayList and returns it to the caller function. – ludicrous Feb 20 '22 at 13:38
  • Seems inefficient if you need to call it repeatedly. – duffymo Feb 20 '22 at 13:48
  • @ludicrous Thanks! yes. At the design stage it is sufficient to spot the relationship and see if there are one or many related elements. Keeping the array list is an implementation decision. Not exposing it and use a getter has the advantage of letting you chose the best way to implement it: keeping it private if you want to have it in memory, or rebuilding it querying db everytime needed to be sure to have the latest version, or a mixture between both, getting it in memory when needed and keep it cached for performance reasons) – Christophe Feb 20 '22 at 13:53
  • @duffymo if you keep it in memory, you must also have a way to update it in case another student adds a friendship on another server. Relying solely on memory would otherwise create a scalability constraint. But my point here is to remain flexible: expose a getter. If performance is a concern, you can always keey a private array list and cache the result when doing the first query – Christophe Feb 20 '22 at 13:55
  • @duffymo I’m not here to expose my science but to help OP. So I did not mention JPA as it is not mentioned in the question to avoid introducing yet another concept in an already broad tooic. The scalability issue is the result of an in memory solution: each server process would have it’s own java instance that would not be aware of object changes made in other instances on other servers. I did not understand how you deal with such issues in your answer. Perhaps you could ellaborate on it instead of agressively criticizing mine? – Christophe Feb 20 '22 at 14:04
  • Let's not. I'm not going to learn anything from you. Have a good day. – duffymo Feb 20 '22 at 14:09
  • 1
    Don't forget to prevent that "A is friend of B" and "B is friend of A" are regarded as two different friendships by the system. – www.admiraalit.nl Feb 21 '22 at 12:17
  • @www.admiraalit.nl That’s a very valid remark. I hesitated about using a unidirectional navigability but decided to leave it open as the answer was already very long: if the friendship is viewed as bidirectional and every time a friendship in one direction is added the reverse friendship is added as well the set of friends represents both directions. But this model could also be for unidirectional friendship. Hence inspecified navigability. Likewise, in the implementation of getFriends() you could query for the id in both columns to make it symmetric. – Christophe Feb 21 '22 at 12:32
  • Well, here again is the problem of modeling a symmetrical relationship with UML. I think there is a reason, why you didn't name the opposite end of the friendship association. It would need to be the _same_ property "friend". Unfortunately this is not possible in UML. It seems the problem is not so uncommon after all. – Axel Scheithauer Feb 22 '22 at 16:05
  • In your model of the database I find it very strange, that `Student_ID` is an `INTEGER` and at the same time a `Students`. I understand what you mean, however it still looks wrong. Maybe you could use a derived property with a different name for the association end. – Axel Scheithauer Feb 22 '22 at 16:10
  • I think in a high level class diagram an implementation detail like `Arraylists` should not be used. Instead I would recommend parameters with Multiplicity. – Axel Scheithauer Feb 22 '22 at 16:14
  • @AxelScheithauer I agree. As I said in the introduction, the question is broad and I didn’t want to introduce too many concepts/ideas at once. This is why I used for convenience the array list (implicitly assuming a java specific profile). From a design point of view I saw it more important to promote hiding the property than abstracting the types. – Christophe Feb 22 '22 at 17:33
  • @AxelScheithauer I didn’t see your first comment. This is indeed the ambiguity of DB modelling where the ID represents the whole “entity” and that is difficult to tender at implementation level. Would a pseudo role STUDENT and STUDENT_WITH (i.e without the ID) seem less wrong? – Christophe Feb 22 '22 at 17:57
  • @AxelScheithauer Btw, in [this article](https://sparxsystems.com/resources/tutorials/uml/datamodel.html) the same approach is used, with a stereotype Table (with icon), for the physical model. The {ID} is replaced with a PK à-la-ERD and the FK is modeled as in my proposalk, using the ID (there: PK) as role (point 7). They're even more radical, as they do the mapping PK/FK with opposite roles ;-) – Christophe Feb 23 '22 at 22:58