classDiagram
direction BT
%% Interface
class PublicationOperations {
<<interface>>
+ publish() void
}
%% Abstract class
class Publication {
<<abstract>>
- String id
- String title
- Author author
- LocalDate publicationDate
- String format
+ getId() String
+ getTitle() String
+ getAuthor() Author
+ getPublicationDate() LocalDate
+ getFormat() String
}
Publication ..> PublicationOperations
%% Publication Entity classes
class Book {
- String ISBN
- int pages
- int genre
+ getISBN() String
+ getPages() int
+ getGenre() String
}
class CD {
- int duration
- int numberOfTracks
+ getDuration() int
+ getNumberOfTracks() int
}
class DVD {
- int duration
+ getDuration() int
}
Book ..> Publication
CD ..> Publication
DVD ..> Publication
LAB#SE03-2: Library/Book, Expand Model
Basic understanding of Java programming language is required, as well as some familiarity with Maven or Gradle for managing dependencies and building the project.
Use of Interfaces and Abstract classes to implement features.
Describe the required models that might define the solution to a Library implementation. Create multiple classes in Java using the most convenient entity relationship between them to implement the solution.
Test these classes using JUnit.
- Create a new Maven or Gradle project and setting up the project structure
- Modify the project’s
pom.xmlorbuild.gradlefile to import necessary dependencies, including JUnit for testing - Implement the required classes in Java
- Implement User interactive interfaces for each manager
- Write JUnit tests to verify that classes work as expected
- Allow the user to input data via the console, rather than using
hard-coded test datain JUnit tests
Introduction
In LibraryProject, the User and Staff classes could have different operations that they are authorized/able to perform.
The User class could have the following operations:
- Search for books
- Borrow a book
- Return a book
- Check the status of borrowed books
- Update personal information
The Staff class could have the following operations:
- Add a new book
- Remove a book
- Update the book details
- Register a new user
- Remove a user
- Update the user details
- Generate reports on library operations
To implement these operations, we could define three separate interfaces:
- UserOperations
- StaffOperations
- GeneralOperations
Remember, Interfaces have no state, thus they don’t have variables (any attribute defined in an Interface is treated as a constant), and they are responsible of telling the classes that implement it what methods to support.
If you need to keep track of the state of your instances (i.e. attributes), then go for the abstract class and extend it to the required subclasses.
Here are some good points regarding Interfaces and Abstract classes:
- Instance variables in interface: It’s a risky approach because they aren’t treated as variables but
final static(i.e, constants). If you need to define variables for your classes as well as mandatory methods implementation, then go for the Abstract class approach
Interfaces
The three interfaces GeneralOperations, UserOperations and StaffOperations provide a clear separation of concerns and responsibilities within the library management system.
GeneralOperationsinterfaceprovides methods that are commonly used by both users and staff, such as searching for books and creating an account.
UserOperationsinterfaceincludes methods that are specific to users, such as updating personal information, borrowing and returning books, and checking their borrowed books.
StaffOperationsinterfaceincludes methods that are specific to staff members, such as adding, removing, and updating book details, as well as registering and updating user details and generationg reports.
By separating these different functionailities into interfaces, it becomes easier to implement and maintain the system.
Interfaces allow the developers to implement their features knowing beforehand the expected available methods of future classes.
Abstract class
Finally, the LibraryMember abstract class is a parent class for users and staff members, which includes common fields such as id, address, and phoneNumber.
By defining these fields in an abstract class, it allows for different types of users and staff members to inherit them and avoid duplication of code.
UML
Previous drafts
The two UML diagrams describe the core features of a libraryProject, specifically the use cases for borrowing and issuing books, as well as the roles and responsibilities of users and librarians/staff members.
The first diagram,
Core features: borrow and issue, depicts the interaction between the user and the system, as well as the system and the librarian:
Create link to the previous UML
The second diagram,
Core features: user and librarian (staff), expands on the roles and responsibilities of users and librarians/staff members.
Create link to the previous UML
Current UML
Create new UML
Publications
Library Members
classDiagram
direction BT
%% Relationships
LibraryMember ..> GeneralOperations
LibraryMember ..> Person
Staff ..o StaffMember
StaffMember ..> StaffOperations
StaffMember ..> LibraryMember
User ..o LibraryMember
User ..> BorrowOperations
Librarian ..o StaffMember
Librarian ..> BorrowOperations
%% Interfaces
class GeneralOperations {
<<interface>>
+ createAccount(): void
+ searchPublications(query: String): void
}
class BorrowOperations {
<<interface>>
+ createBorrow(item: Publication) void
+ returnBorrow(item: Publication) void
+ updateBorrow(item: Publication) void
}
class StaffOperations {
<<interface>>
+ addPublication(item: Publication) void
+ removePublication(item: Publication) void
+ updatePublication(item: Publication) void
+ registerMember(member: LibraryMember) void
+ removeMember(member: LibraryMember) void
+ updateMember(member: LibraryMember) void
+ generateReports() void
}
%% Abstract class
class Person {
<<abstract>>
- firstName: String
- lastName: String
- nationality: String
- birthdate: LocalDate
}
class LibraryMember {
<<abstract>>
- id: String
- address: String
- phoneNumber: String
}
class StaffMember {
<<abstract>>
- double: salary
}
%% Entities
class User {
- String: mail
- String: password
}
class Staff {
}
class Librarian {
}
Migrating to SpringBoot
With the core model defined and its functionalities, let’s migrate the project to a SpringBoot project to enable Web features, so we can use REST controllers to access the application and its databases.
User interface
Let’s define the different domains that are accessible through a user interface.
- In a web interface, domains could be:
/userwith a private access, only available to Library Users/librarianwith a private access, only available to Librarians/webwith a public access, available to any person that access the webpage
Controllers
In the backend, we need controllers for user and librarian, with access to the Library db.
Requests can be of multiple forms, such as API Rest, HTML, websockets…
Required methods to resolve the requests coming from the frontend must be implemented into each actor (user and library). These methods invoke other classes like @Service and @Repository, that have the utils required to resolve the requests.
@Service and @Repository here are like the Manager classes
We treat them as the source of all utilities that execute the response logic
Databases
For this project, we’ll be using H2. H2 is a Java SQL database with the following main features:
- Very fast, open source, JDBC API
- Embedded and serer modes; in-memory databases
- Browser based Console application
- Small footprint: around 2.5 MB jar file size
Creating entities
We need to define entities that will persist inside DB: - Any Publication subclass object - Any LibraryMember subclass object: - This includes User, Librarian and Staff - Borrows objects
Each entity must define its primary key with the @Id annotation. When inheritance is present, then the parent class should define it as well as the @Inheritance type.
- If the parent class isn’t going to be instantiated, only define the
@MappedSuperClassannotation to allow subclasses to inherit its JPA attibutes - Each first class that defines a new DB table where to store this’ or their subclasses’ instances must define the following annotations:
@Entity, even though this class doesn’t create instances (mandatori for its subclasses)@Tablename where to store all instances@Inheritancestrategy, such asSINGLE_TABLEto store all subclass entities into this single table- If
SINGLE_TABLEis selected, then the parent class must define@DiscriminatorColumnwith a name and its value type. Child classes must then define the proper@DiscriminatorValuethat will be inserted in the previous column when inserted into the table - If
TABLE_PER_CLASSis selected, then each subclass will create its own@Tableinside the DB. If no@Tableannotation is defined in subclasses, then the subclass name is used as its table name
- If
When there’s a composition relationship between classes, then we have to define its multiplicity using the @ManyToMany, @OneToOne, @ManyToOne or @OneToMany annotations.
@ManyToOne relation
j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory: Association 'com.springbootlab0.approach_1.domain.Publication.author' targets an unknown entity named 'com.springbootlab0.approach_1.domain.Author'This error is due to bad annotations between inherited classes, not a @ManyToOne issue. Publication class couldn’t find the Author entity because Person superclass and its subclass Author were conflicting with the @Inheritance and @MappedSuperClass annotations.
Once fixed, an AUTHOR_TABLE is created in the H2 DB and Publication instance can correctly map the Author attribute to the AUTHOR_ID field in the previous table.
The final JPA entity and inheritance diagram is as follows:
classDiagram
direction BT
%% Abstract class
class Publication {
\@Entity(name = "Publication")
\@Table(name = "PUBLICATION_TABLE")
\@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
\@DiscriminatorColumn(name = "PUBLICATION_TYPE", discriminatorType = DiscriminatorType.STRING)
}
%% Publication Entity classes
class Book {
\@Entity(name = "Book")
\@DiscriminatorValue(value = "BOOK")
}
class CD {
\@Entity(name = "CD")
\@DiscriminatorValue(value = "CD")
}
class DVD {
\@Entity(name = "DVD")
\@DiscriminatorValue(value = "DVD")
}
Book ..> Publication
CD ..> Publication
DVD ..> Publication
Publication JPA annotations
classDiagram
direction BT
%% Relationships
LibraryMember ..> Person
Author ..> Person
Staff ..o StaffMember
StaffMember ..> LibraryMember
User ..o LibraryMember
Librarian ..o StaffMember
class Person {
@MappedSuperClass
}
class Author {
\@Entity(name = "Author")
\@Table(name = "AUTHOR_TABLE")
}
class LibraryMember {
\@Entity(name = "LibraryMember")
\@Table(name = "LIBRARYMEMBER_TABLE")
\@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
\@DiscriminatorColumn(name = "MEMBER_TYPE", discriminatorType = discriminatorType.STRING)
}
class StaffMember {
@MappedSuperClass
}
class User {
\@Entity(name = "User")
\@DiscriminatorValue(value = "USER")
}
class Staff {
\@Entity(name = "Staff")
\@DiscriminatorValue(value = "STAFF")
}
class Librarian {
\@Entity(name = "Librarian")
\@DiscriminatorValue(value = "LIBRARIAN")
}
LibraryMember JPA annotations
The resulting tables in the DB are as follow:

AUTHOR_TABLE: InheritsPersonfields and adds its ownIdandpenNameLIBRARY_MEMBER: Here it inherits:Personfields- Adds its own
Id,addressandphoneNumberfields - Adds the
StaffMember’ssalaryfield - And also adds the
User’smailandpasswordfields
PUBLICATION_TABLE: Here the inheritance is as follows:- Adds its own
Publicationfields, including theIdfield - Adds the
Book’s fields - Adds the
CD’s fields - Adds the
DVDfields, even though they overlap with theCDones - And also adds the
Author IDof that publication, coming from theAuthorentity. The@ManyToOneannotation can be verified in the Indexes ofPUBLICATION_TABLE, whereAUTHOR_IDappears as Not unique
- Adds its own
Defining the use cases
Creating a publication
It seems that we’ll have to work with particular publications instead of creating a one-size-fits-all solution, but the inheritance is well solved in the Thymeleaf form and the object is correctly composed when the request reaches the PublicationController.
The Spring boot controller sends a new empty Publication object to the thymeleaf engine with its already generated ID, but if the ID isn’t assigned again in the creation form, then the Controller recreates the object with a new ID!
Make sure to add a th:field=*{id} entry in the form to make sure that later @Setter methods will re-assign that value to the object
Creating a Libary Member
Inheritance is causing issues when trying to save the attributes of child classes:
- Only
Personattributes and theidcreated in the abstractLibraryMemberare considered when Thymeleaf sends the form values to the@Controller, still don’t know why - This doesn’t happen with the current
Publicationimplementation, where all fields, including@Many-to-oneandenumtypes when creating a newBookfrom the Publication Form View
@Setter annotations in all @Entity classes!!
I forgot to add the @Setter annotation in all @Entity classes except the parent one! 😥
Once added the @Setter annotation, tables are correctly populated!
As discussed in this StackOverflow thread,the only way to do this is by removing the inheritance from these classes and mark the previously parent class as @Embeddable, while defining new parent class attribute to each of the previously called child classes and annotating them as @Embedded.
This doesn’t make it easier to maintain as shown in this Baeldung example, and in the OOP realm we would have to access a new Person object for each derivated class.
Also, this Baeldung article explains that when using @MappedSuperclass, ancestors cannot contain associations with other entities. This might be the reason why it doesn’t work well with Librarian and User?
Updating publications or library members
Optional<Subclass> when dealing with Publication updates
This requires further investigation because, while using a very similar form, it looks like the Parent class ID is safely transfered when recreating the object on the @Controller side. This doesn’t happen when the recreated class doesn’t inherit the ID from another class.
Also, this ID problem also appears on subclasses creation forms. Does it have something to do with the GET/POST or RequestMapping annotations?
Creating Borrows
The Borrow class contains the information related to a Publication that has been borrowed by a LibraryMember. The instantiated object contains the following fields:
id: the unique ID of thatBorrowborrowUser: the user of thisBorrowborrowedPublication: the publication borrowedstartBorrowDate: the date where theBorrowwas created. It is automatically initialized onBorrowcreationdueBorrowDate: the date where theBorrowshould already be returned. It is automatically initialized onBorrowcreation, 15 days from thestartBorrowDatereturnedBorrowDate: the real date where theBorrowhas been returned. Initialized asnullonBorrowcreation.borrowStatus: The currentBorrowstatus, eitherIN_PROGRESS,LATEorCLOSED. TheborrowStatusshould be automatically updated depending on thedueBorrowDateand the current date (IN_PROGRESSorLATE), as well as thereturnedBorrowDate(CLOSED)
classDiagram
direction LR
Borrow --> Publication
Borrow --> LibraryMember
Borrow --> BorrowStatus
%% Abstract class
class Publication {
\@Entity(name = "Publication")
\@Table(name = "PUBLICATION_TABLE")
}
class LibraryMember {
\@Entity(name = "LibraryMember")
\@Table(name = "LIBRARYMEMBER_TABLE")
}
class Borrow {
@Id
- id: String
@Column
- borrowUser: LibraryMember
@Column
- borrowedPublication: Publication
@DateTimeFormat
- startBorrowDate: LocalDate
@DateTimeFormat
- dueBorrowDate: LocalDate
@DateTimeFormat
- returnedBorrowDate: LocalDate
@Enumerate
- borrowStatus: BorrowStatus
}
class BorrowStatus {GameStats
IN_PROGRESS
LATE
CLOSED
}
Create Borrow Entity and Repository
Borrow entities are named Borrow and stored in a @Table named BORROW_TABLE. The repository interface is a CrudRepository that will be extended with additional methods whenever required.
Create Borrow Service and Controller
The BorrowController has the following methods:
index: renders the Borrows main pagecreateBorrow(GET): aGetMappingthat preparesModelobject with the required attributes for Thymeleaf to render the Borrow creation form.- If the request doesn’t send a
LibraryMemberID, then the response includes a list ofLibraryMemberobjects so Thymeleaf renders a selection list - If the request sends a
LibraryMemberID, then the response sends again that ID and Thymeleaf doesn’t render a list ofLibraryMemberobjects
- If the request doesn’t send a
createBorrow(POST): aPostMappingthat takes an ID, either as aPathVariableor aRequestParam, and a list ofPublicationIDs, and creates a Borrow for each one- If the request doesn’t send a
PathVariable, then the received request is from aBorrowform with selectableLibraryMember, and thus the method will redirect to the same selectableLibraryMemberform - Else, the method will redirect to the Borrow form with the same
PathVariableID
- If the request doesn’t send a
updateBorrow(GET): aGetMappingthat retrieves theBorrowobject that matches the retrievedidbyPathVariableand inserts it into theModelobject for Thymeleaf to render.updateBorrow(POST): aPostMappingthat takes an ID as aPathVariableand anOptional<Borrow>object and proceeds to update it in the DataBase, if it exists.deleteBorrow(GET): aGetMappingthat takes an ID as aPathVariableand deletes its correspondingBorrowfrom the database if it exists.
The BorrowService has the following methods:
getBorrowById: return aBorrowif the provided ID exists in the databasecreateBorrow: insert aBorrowin the database if its ID doesn’t exist in the databasecreateMultipleBorrows: given aUserID and a list ofPublicationIDs, create and save into the database aBorrowfor eachPublicationassigned to thatUserand return the list of createdBorrowsdeleteBorrowById: delete aBorrowfrom the database if the provided ID exists. When deleting theBorrow, itsPublicationstatus is set toAVAILABLEupdateBorrow: update aBorrowin the database with the updated information from the client request.- Only
borrowStatusshould be available for update.
- Only
Updating a Borrow
When updating a Borrow, a similar form as when creating one shall appear, but only Borrow status should be updatable at the moment (there’s no reason to change neither the borrow user nor the borrowed publication, as well as the borrow dates, which should be automatically updated based on the Borrow status). - If Borrow.borrowStatus is set to IN_PROGRESS and previous value was different, then the dueBorrowDate is set to 15 days from the current time - If Borrow.borrowStatus is set to CLOSED and previous value was different, then the returnedBorrowDate is set to the current time
Using HTTP session to store User information
We’ll use an HTTP session to show all borrows of a specific user. The user information will be stored in the HTTP session instead of transferred on each request-response cycle as @PathVariable or @RequestParam
On login stage, we’ll store the following session attributes:
isLoggedIn: whether the user has already logged in or notmemberId: the ID of the Library Member logged inmemberClass: the class of the Library Member (User, LibraryMember, Staff…)memberName: the full name of the Library Member for rendering purposes