Library Management System using Java Spring Boot and MySql

Features

  • Manage Users
    • Create User
    • Update User
    • Delete User
    • View All Users
  • Manage Students
    • Create Student
    • Update Student
    • Delete Student
    • View All Students
  • Manage Books
    • Create Book
    • Update Book
    • Delete Book
    • View All Books
  • Issue book
  • Return book
  • View Issued books
  • View Defaulters List

Technologies

  • Spring Boot
  • Spring Data JPA (Hibernate)
  • Spring Security
  • MySql Database

Watch on YouTube

Database Configurations

Using below commands create the tables in MySql

                    
content_copy copy
CREATE TABLE users ( user_id int auto_increment not null primary key, user_name VARCHAR(50) NOT NULL, email varchar(20) not null, contact varchar(20) not null, password VARCHAR(50) NOT NULL ); create table roles( role_id int auto_increment not null primary key, role_name varchar(20) NOT NULL ); create table user_roles( user_roles_id int auto_increment not null primary key, user_id int not null, role_id int not null, CONSTRAINT fk_users FOREIGN KEY (user_id) REFERENCES users(user_id), CONSTRAINT fk_roles FOREIGN KEY (role_id) REFERENCES roles(role_id) ); create table students( student_id int auto_increment not null primary key, student_name varchar(20) not null, student_department varchar(20) not null, student_contact_no varchar(20) not null ); create table books( book_id int auto_increment not null primary key, book_title varchar(20) not null, book_author varchar(20) not null, initial_qty int not null, issued_qty int not null ); create table issued_books( issued_book_id int auto_increment not null primary key, book_id int not null, student_id int not null, issued_date date not null, return_date date, duration int not null, CONSTRAINT fk_books FOREIGN KEY (book_id) REFERENCES books(book_id), CONSTRAINT fk_students FOREIGN KEY (student_id) REFERENCES students(student_id) );

Create a new Admin user and 2 default roles i.e 'Admin' and 'Librarian'

                    
content_copy copy
insert into users( user_name, email, contact, password) values( "admin", "admin@exp.com", "123456789", "adminPassword"); insert into roles(role_name) values("ADMIN"); insert into roles(role_name) values( "LIBRARIAN"); insert into user_roles(user_id, role_id) values( 1, 1);

Next, create a Java Maven project in IntelliJ and create a directory structure as shown in below figure. directory structure

Pom File

Include the following dependencies in pom file

                    
content_copy copy
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>LibraryMgmtSys</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> </project>

application.properties File

Define the datasource and hibernate configurations. Replace the datasource configurations mentioned below with your datasource configurations

                    
content_copy copy
#Datasource configuration spring.datasource.url=jdbc:mysql://localhost:3306/db spring.datasource.username=root spring.datasource.password=root #Hibernate Configuration spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

Entities

Create entity classes for all the tables that we created in MySql

Books

                    
content_copy copy
package org.example.entities; import jakarta.persistence.*; import java.util.List; @Entity @Table(name = "books") public class Books { @Id @Column(name = "book_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long bookId; @Column(name = "book_title") private String title; @Column(name = "book_author") private String authorName; @Column(name = "initial_qty") private Long initialQty; @Column(name = "issued_qty") private Long issuedQty; @OneToMany(mappedBy = "book") private List issuedBooks; public Long getBookId() { return bookId; } public void setBookId(Long bookId) { this.bookId = bookId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthorName() { return authorName; } public void setAuthorName(String authorName) { this.authorName = authorName; } public Long getInitialQty() { return initialQty; } public void setInitialQty(Long initialQty) { this.initialQty = initialQty; } public Long getIssuedQty() { return issuedQty; } public void setIssuedQty(Long issuedQty) { this.issuedQty = issuedQty; } public List getIssuedBooks() { return issuedBooks; } public void setIssuedBooks(List issuedBooks) { this.issuedBooks = issuedBooks; } }

IssuedBooks

                    
content_copy copy
package org.example.entities; import jakarta.persistence.*; import java.util.Date; @Entity @Table(name = "issued_books") public class IssuedBooks { @Id @Column(name = "issued_book_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long issuedBookId; @ManyToOne() @JoinColumn(name = "book_id") private Books book; @ManyToOne() @JoinColumn(name = "student_id") private Students student; @Column(name = "duration") private Long duration; @Column(name = "issued_date") private Date issuedDate; @Column(name = "return_date") private Date returnDate; public Long getIssuedBookId() { return issuedBookId; } public void setIssuedBookId(Long issuedBookId) { this.issuedBookId = issuedBookId; } public Books getBook() { return book; } public void setBook(Books book) { this.book = book; } public Students getStudent() { return student; } public void setStudent(Students student) { this.student = student; } public Long getDuration() { return duration; } public void setDuration(Long duration) { this.duration = duration; } public Date getIssuedDate() { return issuedDate; } public void setIssuedDate(Date issuedDate) { this.issuedDate = issuedDate; } public Date getReturnDate() { return returnDate; } public void setReturnDate(Date returnDate) { this.returnDate = returnDate; } }

Roles

                    
content_copy copy
package org.example.entities; import jakarta.persistence.*; import java.util.List; @Entity @Table(name = "roles") public class Roles { @Id @Column(name = "role_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long roleId; @Column(name = "role_name") private String roleName; @OneToMany(mappedBy = "roles") private List userRoles; public Long getRoleId() { return roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public List getUserRoles() { return userRoles; } public void setUserRoles(List userRoles) { this.userRoles = userRoles; } }

Students

                    
content_copy copy
package org.example.entities; import jakarta.persistence.*; import java.util.List; @Entity @Table(name = "students") public class Students { @Id @Column(name = "student_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long studentId; @Column(name = "student_name") private String name; @Column(name = "student_department") private String department; @Column(name = "student_contact_no") private String contact; @OneToMany(mappedBy = "student") private List issuedBooks; public Long getStudentId() { return studentId; } public void setStudentId(Long studentId) { this.studentId = studentId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public String getContact() { return contact; } public void setContact(String contact) { this.contact = contact; } public List getIssuedBooks() { return issuedBooks; } public void setIssuedBooks(List issuedBooks) { this.issuedBooks = issuedBooks; } }

UserRoles

                    
content_copy copy
package org.example.entities; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; @Entity @Table(name = "user_roles") public class UserRoles { @Id @Column(name = "user_roles_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userRolesId; @ManyToOne() @JsonIgnore @JoinColumn(name = "user_id") private Users users; @ManyToOne @JsonIgnore @JoinColumn(name = "role_id") private Roles roles; public Long getUserRolesId() { return userRolesId; } public void setUserRolesId(Long userRolesId) { this.userRolesId = userRolesId; } public Users getUsers() { return users; } public void setUsers(Users users) { this.users = users; } public Roles getRoles() { return roles; } public void setRoles(Roles roles) { this.roles = roles; } }

Users

                    
content_copy copy
package org.example.entities; import jakarta.persistence.*; import java.util.List; @Entity @Table(name = "users") public class Users { @Id @Column(name = "user_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @Column(name = "user_name") private String userName; @Column(name = "password") private String password; @Column(name = "email") private String email; @Column(name = "contact") private String contact; @OneToMany(mappedBy = "users", fetch = FetchType.EAGER) private List userRoles; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getContact() { return contact; } public void setContact(String contact) { this.contact = contact; } public List getUserRoles() { return userRoles; } public void setUserRoles(List userRoles) { this.userRoles = userRoles; } }

Dto's

Create data transfer objects for API's

PaginationDto

This dto carries the required pagination information for spring Jpa

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class PaginationDto implements Serializable { private Integer totalPages; private Integer currentPage; private Integer pageSize; public Integer getTotalPages() { return totalPages; } public void setTotalPages(Integer totalPages) { this.totalPages = totalPages; } public Integer getCurrentPage() { return currentPage; } public void setCurrentPage(Integer currentPage) { this.currentPage = currentPage; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } }

RequestDto

This Dto encapsulates the pagination dto and generic api related dto. It will be used as input Dto for all Api's

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class RequestDto implements Serializable { private T data; private PaginationDto pagination; public T getData() { return data; } public void setData(T data) { this.data = data; } public PaginationDto getPagination() { return pagination; } public void setPagination(PaginationDto pagination) { this.pagination = pagination; } }

ResponseDto

This Dto encapsulates the pagination dto and generic appi related dto. It will be used as output dot for all API's

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class ResponseDto implements Serializable { private T data; private String status; private String errorMessage; private PaginationDto pagination; public T getData() { return data; } public void setData(T data) { this.data = data; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } public PaginationDto getPagination() { return pagination; } public void setPagination(PaginationDto pagination) { this.pagination = pagination; } }

LoginReqDto

This dto carries the login request payload

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class LoginReqDto implements Serializable { private String userName; private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }

LoginResDto

This Dto carries the login response payload

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class LoginResDto implements Serializable { private String authToken; public String getAuthToken() { return authToken; } public void setAuthToken(String authToken) { this.authToken = authToken; } }

BookDto

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class BookDto implements Serializable { private Long bookId; private String title; private String authorName; private Long initialQty; private Long issuedQty; public Long getBookId() { return bookId; } public void setBookId(Long bookId) { this.bookId = bookId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthorName() { return authorName; } public void setAuthorName(String authorName) { this.authorName = authorName; } public Long getInitialQty() { return initialQty; } public void setInitialQty(Long initialQty) { this.initialQty = initialQty; } public Long getIssuedQty() { return issuedQty; } public void setIssuedQty(Long issuedQty) { this.issuedQty = issuedQty; } }

IssuedBookDto

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class IssuedBookDto implements Serializable { private String bookTitle; private Long studentId; private String studentName; private String issuedDate; private String returnDate; private Long duration; public String getBookTitle() { return bookTitle; } public void setBookTitle(String bookTitle) { this.bookTitle = bookTitle; } public Long getStudentId() { return studentId; } public void setStudentId(Long studentId) { this.studentId = studentId; } public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public String getIssuedDate() { return issuedDate; } public void setIssuedDate(String issuedDate) { this.issuedDate = issuedDate; } public String getReturnDate() { return returnDate; } public void setReturnDate(String returnDate) { this.returnDate = returnDate; } public Long getDuration() { return duration; } public void setDuration(Long duration) { this.duration = duration; } }

IssueReturnDto

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class IssueReturnDto implements Serializable { private Long bookId; private Long studentId; private Long duration; public Long getBookId() { return bookId; } public void setBookId(Long bookId) { this.bookId = bookId; } public Long getStudentId() { return studentId; } public void setStudentId(Long studentId) { this.studentId = studentId; } public Long getDuration() { return duration; } public void setDuration(Long duration) { this.duration = duration; } }

StudentDto

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class StudentDto implements Serializable { private Long studentId; private String name; private String department; private String contact; public Long getStudentId() { return studentId; } public void setStudentId(Long studentId) { this.studentId = studentId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public String getContact() { return contact; } public void setContact(String contact) { this.contact = contact; } }

UserDto

                    
content_copy copy
package org.example.dtos; import java.io.Serializable; public class UserDto implements Serializable { private Long userId; private String userName; private String email; private String contact; public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getContact() { return contact; } public void setContact(String contact) { this.contact = contact; } }

Mapper

We will use mapstruct to create mappings between the entities and Dto's. In the interface, specify the target and source data fields if data fields are not same. This interface will create the mapping class during compile time.

Mapper

                    
content_copy copy
package org.example.mappers; import org.example.dtos.IssuedBookDto; import org.example.dtos.UserDto; import org.example.dtos.BookDto; import org.example.dtos.StudentDto; import org.example.entities.Books; import org.example.entities.IssuedBooks; import org.example.entities.Students; import org.example.entities.Users; import org.mapstruct.IterableMapping; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.Named; import java.util.List; @org.mapstruct.Mapper(componentModel = "spring") public interface Mapper { @Named("userEntityToUserDto") UserDto userEntityToUserDto(Users user); @IterableMapping(qualifiedByName = "userEntityToUserDto") List<UserDto> userEntitiesToUserDtos(List<Users> users); @Named("userDtoToUserEntity") Users userDtoToUserEntity(UserDto userDto); @Named("studentEntityToStudentDto") StudentDto studentEntityToStudentDto(Students student); @IterableMapping(qualifiedByName = "studentEntityToStudentDto") List<StudentDto> studentEntitiesToStudentDtos(List<Students> students); Students studentDtoToStudentEntity(StudentDto studentDto); @Named("bookEntityToBookDto") BookDto bookEntityToBookDto(Books book); @IterableMapping(qualifiedByName = "bookEntityToBookDto") List<BookDto> bookEntitiesToBookDtos(List<Books> books); Books bookDtoToBookEntity(BookDto bookDto); @Named("issuedBookEntityToIssuedBooksDto") @Mappings(value = { @Mapping(target = "bookTitle", source = "book.title"), @Mapping(target = "studentId", source = "student.studentId"), @Mapping(target = "studentName", source = "student.name"), @Mapping(target = "issuedDate", source = "issuedDate"), @Mapping(target = "returnDate", source = "returnDate"), @Mapping(target = "duration", source = "duration"), }) IssuedBookDto issuedBookEntityToIssuedBooksDto(IssuedBooks issuedBook); @IterableMapping(qualifiedByName = "issuedBookEntityToIssuedBooksDto") List<IssuedBookDto> issuedBookEntitiesToIssuedBookDto(List<IssuedBooks> issuedBook); }

Controller Advice(Global Exception Handler)

We'll create a global exception handler class using @controllerAdvice annotation. All runtime exceptions will be directed to this class and it will return the error response.

                    
content_copy copy
package org.example.exception; import org.example.dtos.ResponseDto; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @ControllerAdvice public class ExceptionHandler { @org.springframework.web.bind.annotation.ExceptionHandler(RuntimeException.class) public ResponseEntity<ResponseDto<Object>> handleExceptions(RuntimeException e){ ResponseDto responseDto = new ResponseDto(); responseDto.setStatus("error"); responseDto.setErrorMessage(e.getMessage()); ResponseEntity responseEntity = new ResponseEntity(responseDto, HttpStatus.INTERNAL_SERVER_ERROR); return responseEntity; } }

Repositories

Create respository interfaces for all entities

BooksRepository

                    
content_copy copy
package org.example.repositories; import org.example.entities.Books; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface BooksRepository extends JpaRepository<Books, Long> { Books findByBookId(Long bookId); }

IssuedBooksRepository

                    
content_copy copy
package org.example.repositories; import org.example.entities.IssuedBooks; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface IssuedBooksRepository extends JpaRepository<IssuedBooks, Long> { IssuedBooks findByBook_BookIdAndStudent_StudentId(Long bookId, Long studentId); @Query(nativeQuery = true, value = "select * from issued_books where return_date is null and date_add(issued_date, interval duration day) < curdate()") Page<List<IssuedBooks>> findDefaulters(Pageable pageable); }

RolesRepository

                    
content_copy copy
package org.example.repositories; import org.example.entities.Roles; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface RolesRepository extends JpaRepository<Roles, Long> { Roles findByRoleName(String roleName); }

StudentsRepository

                    
content_copy copy
package org.example.repositories; import org.example.entities.Students; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface StudentsRepository extends JpaRepository<Students, Long> { Students findByStudentId(Long studentId); }

UserRolesRepository

                    
content_copy copy
package org.example.repositories; import org.example.entities.UserRoles; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRolesRepository extends JpaRepository<UserRoles, Long> { UserRoles findByUsers_userId(Long userId); }

UsersRepository

                    
content_copy copy
package org.example.repositories; import org.example.entities.Users; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UsersRepository extends JpaRepository<Users, Long> { Users findByUserName(String userName); Users findByUserId(Long userId); }

Spring Security

To add login functionality we need to utilize the spring security. First create the custom user class to incorporate the user details that we created in our database.

CustomUser

                    
content_copy copy
package org.example.security; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; public class CustomUser extends User { private String email; private String contact; public CustomUser(String userName, String password, Collection<? extends GrantedAuthority> grantedAuthorities) { super(userName, password, grantedAuthorities); } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getContact() { return contact; } public void setContact(String contact) { this.contact = contact; } }

UserDetailsServiceImpl

We'll implement the userDetailsService as we need to authenticate the user from database. We'll override the loadUserByUserName method to fetch the user from DB.

                    
content_copy copy
*ackage org.example.security; import org.example.entities.Users; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.example.repositories.UsersRepository; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.stream.Collectors; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UsersRepository usersRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Users user = usersRepository.findByUserName(username); if(user != null) { Collection<? extends GrantedAuthority> grantedAuthorities = user.getUserRoles().stream().map(userRoles -> { return new SimpleGrantedAuthority(userRoles.getRoles().getRoleName()); }).collect(Collectors.toList()); return new CustomUser(username, user.getPassword(), grantedAuthorities); }else throw new UsernameNotFoundException("UserName not found"); } }

JwtUtils

We will be using Json Web token to authenticate the user once the user has logged in. This class contains the utility functions for creating, validating and extracting the user form jwt.

                    
content_copy copy
package org.example.security; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Component; import java.security.Key; import java.util.Date; @Component public class JwtUtils { private String jwtSecret = "asfdjvbfljknvkrljnjrvekbdvnrjkglfjknvjhfssfvnfjnvnsjkfnvjlhfknvfnvfjvnfjvnjfvfjfvnfjvsflsljkfvnv"; private int jwtExpTime = 36000000; public Key generateKey() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); } public String generateJwt(String userName) { return Jwts.builder() .setSubject(userName) .setIssuedAt(new Date()) .setExpiration(new Date(new Date().getTime() + jwtExpTime)) .signWith(generateKey(), SignatureAlgorithm.HS256) .compact(); } public Boolean validateJwtToken(String token) { try { Jwts.parserBuilder() .setSigningKey(generateKey()).build().parse(token); return true; } catch (MalformedJwtException e) { throw new RuntimeException("Invalid Jwt Token"); } catch (ExpiredJwtException e) { throw new RuntimeException("JWT Token is expired"); } catch (UnsupportedJwtException e) { throw new RuntimeException("Jwt is unsupported"); } catch (IllegalArgumentException e) { throw new RuntimeException("Jwt string is empty"); } catch (Exception e) { throw new RuntimeException("Error in Jwt"); } } public String getUserNameFromJwt(String token){ return Jwts.parserBuilder().setSigningKey(generateKey()) .build().parseClaimsJws(token).getBody().getSubject(); } }

JwtFilter

This class extends the OncePerRequestFilter and will be used to autheticate the user by validating the jwt. If input jwt is valid then it will add the authentication token for the user in security context holder.

                    
content_copy copy
package org.example.security; import com.fasterxml.jackson.databind.ObjectMapper; import org.example.dtos.ResponseDto; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class JwtFilter extends OncePerRequestFilter { @Autowired JwtUtils jwtUtils; @Autowired UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { if (request.getHeader("Authorization") != null) { String token = request.getHeader("Authorization") .substring(7); if (jwtUtils.validateJwtToken(token)) { String userName = jwtUtils.getUserNameFromJwt(token); UserDetails userDetails = userDetailsService.loadUserByUsername(userName); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } filterChain.doFilter(request, response); }catch (Exception e){ response.getWriter().flush(); ResponseDto<Object> responseDto = new ResponseDto<>(); responseDto.setStatus("error"); responseDto.setErrorMessage(e.getMessage()); ObjectMapper objectMapper = new ObjectMapper(); response.getWriter().write(objectMapper.writeValueAsString(responseDto)); } } }

SecurityConfiguration

This class creates the beans for authentication Manager and authentication provider. Here we will be using the daoAuthenticationProvider for authentication using DB. daoAuthenticationProvider uses the UserDetailsService implementation to load the user from DB. We'll also create a bean of SecurityFilterChain and configure the authorization rules for our Api's. Login Api will be accessible without authentication and user related api's will only be accessible to users having admin role. Lastly add the jwtFilter before the usernamePasswordAuthenticationFilter. So, to authenticate the user using jwt once user has logged in.

                    
content_copy copy
package org.example.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfiguration { @Autowired UserDetailsService userDetailsService; @Autowired JwtFilter jwtFilter; @Bean AuthenticationManager getAuthenticationManagerBean(AuthenticationConfiguration authConf) throws Exception { return authConf.getAuthenticationManager(); } @Bean DaoAuthenticationProvider getDaoAuthProviderBean() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setPasswordEncoder(NoOpPasswordEncoder.getInstance()); daoAuthenticationProvider.setUserDetailsService(userDetailsService); return daoAuthenticationProvider; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{ httpSecurity.csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests( req -> req.requestMatchers("/login").permitAll() .requestMatchers("/getUsers", "/createUser", "/updateUser", "/deleteUser/{userId}") .hasAuthority("ADMIN") .anyRequest().authenticated()); httpSecurity.authenticationProvider(getDaoAuthProviderBean()); httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return httpSecurity.build(); } }

Services

Now create the service classes.

CommonUtils

This is a utility class that creates different tyepes of responses.

                    
content_copy copy
package org.example.services; import org.example.dtos.PaginationDto; import org.example.dtos.ResponseDto; import org.springframework.stereotype.Component; @Component public class CommonUtils { public <T> ResponseDto<T> successResponseWithoutData() { ResponseDto<T> responseDto = new ResponseDto<>(); responseDto.setStatus("success"); return responseDto; } public <T> ResponseDto<T> successResponseWithDataAndPagination(T data, PaginationDto paginationDto) { ResponseDto<T> responseDto = new ResponseDto<>(); responseDto.setStatus("success"); responseDto.setData(data); responseDto.setPagination(paginationDto); return responseDto; } public <T> ResponseDto<T> successResponseWithData(T data){ ResponseDto<T> responseDto = new ResponseDto<>(); responseDto.setStatus("success"); responseDto.setData(data); return responseDto; } }

BookService

                    
content_copy copy
package org.example.services; import org.example.dtos.BookDto; import org.example.dtos.PaginationDto; import org.example.dtos.ResponseDto; import org.example.entities.Books; import jakarta.transaction.Transactional; import org.example.mappers.Mapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.example.repositories.BooksRepository; import java.util.List; @Service public class BookService { @Autowired BooksRepository booksRepository; @Autowired Mapper mapper; @Autowired CommonUtils commonUtils; public ResponseDto<List<BookDto>> getAllBooks(PaginationDto paginationDto) { Pageable pageable = PageRequest.of(paginationDto.getCurrentPage(), paginationDto.getPageSize(), Sort.by("bookId")); Page<Books> books = booksRepository.findAll(pageable); List<BookDto> bookDtos = mapper.bookEntitiesToBookDtos(books.getContent()); paginationDto.setTotalPages(books.getTotalPages()); return commonUtils.successResponseWithDataAndPagination(bookDtos, paginationDto); } @Transactional public ResponseDto<Object> createBook(BookDto bookDto) { try { Books book = new Books(); book = mapper.bookDtoToBookEntity(bookDto); book.setIssuedQty(0L); booksRepository.save(book); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> updateBook(BookDto bookDto){ try{ Books book = booksRepository.findByBookId(bookDto.getBookId()); book.setTitle(bookDto.getTitle()); book.setAuthorName(bookDto.getAuthorName()); if(bookDto.getInitialQty() < book.getInitialQty()) { if (book.getInitialQty() - book.getIssuedQty() < bookDto.getInitialQty()) throw new RuntimeException("Initial qty can't be set less than the available qty"); } book.setInitialQty(bookDto.getInitialQty()); }catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> deleteBook(Long bookId){ try { Books book = booksRepository.findByBookId(bookId); booksRepository.delete(book); }catch(Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } }

StudentService

                    
content_copy copy
package org.example.services; import org.example.dtos.PaginationDto; import org.example.dtos.ResponseDto; import org.example.dtos.StudentDto; import org.example.entities.Students; import jakarta.transaction.Transactional; import org.example.mappers.Mapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.example.repositories.StudentsRepository; import java.util.List; @Service public class StudentService { @Autowired StudentsRepository studentsRepository; @Autowired Mapper mapper; @Autowired CommonUtils commonUtils; public ResponseDto<List<StudentDto>> getAllStudents(PaginationDto paginationDto) { Pageable pageable = PageRequest.of(paginationDto.getCurrentPage(), paginationDto.getPageSize(), Sort.by("studentId")); Page page = studentsRepository.findAll(pageable); List<StudentDto> studentDtos = mapper.studentEntitiesToStudentDtos(page.getContent()); paginationDto.setTotalPages(page.getTotalPages()); return commonUtils.successResponseWithDataAndPagination(studentDtos, paginationDto); } @Transactional public ResponseDto<Object> createStudent(StudentDto studentDto) { try { Students student = new Students(); student = mapper.studentDtoToStudentEntity(studentDto); studentsRepository.save(student); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> updateStudent(StudentDto studentDto) { try { Students student = studentsRepository.findByStudentId(studentDto.getStudentId()); student.setContact(studentDto.getContact()); student.setDepartment(studentDto.getDepartment()); student.setName(studentDto.getName()); studentsRepository.save(student); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> deleteStudent(Long studentId){ try { Students student = studentsRepository.findByStudentId(studentId); studentsRepository.delete(student); }catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } }

UserService

                    
content_copy copy
package org.example.services; import org.example.dtos.PaginationDto; import org.example.dtos.ResponseDto; import org.example.dtos.UserDto; import org.example.entities.Roles; import org.example.entities.UserRoles; import org.example.entities.Users; import jakarta.transaction.Transactional; import org.example.mappers.Mapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.example.repositories.RolesRepository; import org.example.repositories.UserRolesRepository; import org.example.repositories.UsersRepository; import java.util.List; @Service public class UserService { @Autowired UsersRepository usersRepository; @Autowired UserRolesRepository userRolesRepository; @Autowired RolesRepository rolesRepository; @Autowired Mapper mapper; @Autowired CommonUtils commonUtils; public ResponseDto<List<UserDto>> getUsers(PaginationDto paginationDto) { Pageable pageable = PageRequest.of(paginationDto.getCurrentPage(), paginationDto.getPageSize(), Sort.by("userId")); Page page = usersRepository.findAll(pageable); List<UserDto> userDtos = mapper.userEntitiesToUserDtos(page.getContent()); paginationDto.setTotalPages(page.getTotalPages()); return commonUtils.successResponseWithDataAndPagination(userDtos, paginationDto); } @Transactional public ResponseDto<Object> createUser(UserDto userDto) { try { Users user = new Users(); user = mapper.userDtoToUserEntity(userDto); user.setPassword("librarianPassword"); usersRepository.save(user); //create user roles UserRoles userRoles = new UserRoles(); Roles role = rolesRepository.findByRoleName("LIBRARIAN"); userRoles.setUsers(user); userRoles.setRoles(role); userRolesRepository.save(userRoles); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> updateUser(UserDto userDto) { try { Users user = usersRepository.findByUserId(userDto.getUserId()); user.setUserName(userDto.getUserName()); user.setContact(userDto.getContact()); user.setEmail(userDto.getEmail()); usersRepository.save(user); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> deleteUser(Long userId){ try { Users user = usersRepository.findByUserId(userId); UserRoles userRoles = userRolesRepository.findByUsers_userId(userId); userRolesRepository.delete(userRoles); usersRepository.delete(user); }catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } }

LmsSerivce

                    
content_copy copy
package org.example.services; import org.example.entities.Books; import org.example.entities.IssuedBooks; import org.example.entities.Students; import jakarta.transaction.Transactional; import org.example.mappers.Mapper; import org.example.dtos.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.example.repositories.BooksRepository; import org.example.repositories.IssuedBooksRepository; import org.example.repositories.StudentsRepository; import org.example.security.CustomUser; import org.example.security.JwtUtils; import java.util.Date; import java.util.List; @Service public class LmsService { @Autowired AuthenticationManager authenticationManager; @Autowired JwtUtils jwtUtils; @Autowired CommonUtils commonUtils; @Autowired StudentsRepository studentsRepository; @Autowired BooksRepository booksRepository; @Autowired IssuedBooksRepository issuedBooksRepository; @Autowired Mapper mapper; public ResponseDto<LoginResDto> login(LoginReqDto loginReqDto) { try { Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginReqDto.getUserName(), loginReqDto.getPassword())); CustomUser customUser = (CustomUser) authentication.getPrincipal(); String jwtToken = jwtUtils.generateJwt(customUser.getUsername()); LoginResDto loginResDto = new LoginResDto(); loginResDto.setAuthToken(jwtToken); return commonUtils.successResponseWithData(loginResDto); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } @Transactional public ResponseDto<Object> issueBook(IssueReturnDto issueReturnDto) { try { Books book = booksRepository.findByBookId(issueReturnDto.getBookId()); Students student = studentsRepository.findByStudentId(issueReturnDto.getStudentId()); if (book == null) throw new RuntimeException("Book not found"); if (student == null) throw new RuntimeException("student not found"); if (book.getInitialQty() - book.getIssuedQty() <= 0) throw new RuntimeException("book not available"); IssuedBooks alreadyIssuedBook = issuedBooksRepository.findByBook_BookIdAndStudent_StudentId(issueReturnDto.getBookId(), issueReturnDto.getStudentId()); if (alreadyIssuedBook != null) throw new RuntimeException("book is already issued to student"); book.setIssuedQty(book.getIssuedQty() + 1); booksRepository.save(book); IssuedBooks issuedBook = new IssuedBooks(); issuedBook.setBook(book); issuedBook.setStudent(student); issuedBook.setIssuedDate(new Date()); issuedBook.setDuration(issueReturnDto.getDuration()); issuedBooksRepository.save(issuedBook); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } @Transactional public ResponseDto<Object> returnBook(IssueReturnDto issueReturnDto) { try { Books book = booksRepository.findByBookId(issueReturnDto.getBookId()); IssuedBooks issuedBook = issuedBooksRepository.findByBook_BookIdAndStudent_StudentId(issueReturnDto.getBookId(), issueReturnDto.getStudentId()); if (book == null) throw new RuntimeException("book not found"); if (issuedBook == null) throw new RuntimeException("either book id or student id is incorrect"); book.setIssuedQty(book.getIssuedQty() - 1); booksRepository.save(book); issuedBook.setReturnDate(new Date()); issuedBooksRepository.save(issuedBook); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } return commonUtils.successResponseWithoutData(); } public ResponseDto<List<IssuedBookDto>> viewIssuedBooks(PaginationDto paginationDto) { Pageable pageable = PageRequest.of(paginationDto.getCurrentPage(), paginationDto.getPageSize()); Page page = issuedBooksRepository.findAll(pageable); List<IssuedBookDto> issuedBookDtos = mapper.issuedBookEntitiesToIssuedBookDto(page.getContent()); paginationDto.setTotalPages(page.getTotalPages()); return commonUtils.successResponseWithDataAndPagination(issuedBookDtos, paginationDto); } public ResponseDto<List<IssuedBookDto>> viewDefaultersList(PaginationDto paginationDto){ Pageable pageable = PageRequest.of(paginationDto.getCurrentPage(), paginationDto.getPageSize()); Page page = issuedBooksRepository.findDefaulters(pageable); List<IssuedBookDto> issuedBookDtos = mapper.issuedBookEntitiesToIssuedBookDto(page.getContent()); paginationDto.setTotalPages(page.getTotalPages()); return commonUtils.successResponseWithDataAndPagination(issuedBookDtos, paginationDto); } }

Rest Controller

LmsController

Create Rest Controller that will handle incoming requests on different endpoints.

                    
content_copy copy
package org.example.controllers; import org.example.dtos.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.example.services.BookService; import org.example.services.LmsService; import org.example.services.StudentService; import org.example.services.UserService; import java.util.List; @RestController public class LmsController { @Autowired LmsService lmsService; @Autowired BookService bookService; @Autowired StudentService studentService; @Autowired UserService userService; @PostMapping("/login") public ResponseDto<LoginResDto> login(@RequestBody RequestDto<LoginReqDto> requestDto) { return lmsService.login(requestDto.getData()); } @PostMapping("/getUsers") public ResponseDto<List<UserDto>> getUsers(@RequestBody RequestDto<UserDto> requestDto) { return userService.getUsers(requestDto.getPagination()); } @PostMapping("/createUser") public ResponseDto<Object> createUser(@RequestBody RequestDto<UserDto> requestDto) { return userService.createUser(requestDto.getData()); } @PostMapping("/updateUser") public ResponseDto<Object> updateUser(@RequestBody RequestDto<UserDto> requestDto) { return userService.updateUser(requestDto.getData()); } @PostMapping("/deleteUser/{userId}") public ResponseDto<Object> deleteUser(@PathVariable Long userId) { return userService.deleteUser(userId); } @PostMapping("/getBooks") public ResponseDto<List<BookDto>> getBooks(@RequestBody RequestDto<BookDto> requestDto) { return bookService.getAllBooks(requestDto.getPagination()); } @PostMapping("/createBook") public ResponseDto<Object> createBook(@RequestBody RequestDto<BookDto> requestDto) { return bookService.createBook(requestDto.getData()); } @PostMapping("/updateBook") public ResponseDto<Object> updateBook(@RequestBody RequestDto<BookDto> requestDto) { return bookService.updateBook(requestDto.getData()); } @PostMapping("/deleteBook/{bookId}") public ResponseDto<Object> deleteBook(@PathVariable Long bookId) { return bookService.deleteBook(bookId); } @PostMapping("/getStudents") public ResponseDto<List<StudentDto>> getStudents(@RequestBody RequestDto<StudentDto> requestDto) { return studentService.getAllStudents(requestDto.getPagination()); } @PostMapping("/createStudent") public ResponseDto<Object> createStudent(@RequestBody RequestDto<StudentDto> requestDto) { return studentService.createStudent(requestDto.getData()); } @PostMapping("/updateStudent") public ResponseDto<Object> updateStudent(@RequestBody RequestDto<StudentDto> requestDto) { return studentService.updateStudent(requestDto.getData()); } @PostMapping("/deleteStudent/{studentId}") public ResponseDto<Object> deleteStudent(@PathVariable Long studentId) { return studentService.deleteStudent(studentId); } @PostMapping("/issueBook") public ResponseDto<Object> issueBook(@RequestBody RequestDto<IssueReturnDto> requestDto) { return lmsService.issueBook(requestDto.getData()); } @PostMapping("/returnBook") public ResponseDto<Object> returnBook(@RequestBody RequestDto<IssueReturnDto> requestDto) { return lmsService.returnBook(requestDto.getData()); } @PostMapping("/viewIssuedBooks") public ResponseDto<List<IssuedBookDto>> viewIssuedBooks(@RequestBody RequestDto<Object> requestDto) { return lmsService.viewIssuedBooks(requestDto.getPagination()); } @PostMapping("/viewDefaulters") public ResponseDto<List<IssuedBookDto>> viewDefaulters(@RequestBody RequestDto<Object> requestDto){ return lmsService.viewDefaultersList(requestDto.getPagination()); } }

LmsApplication

Lastly, create the Spring boot application class.

                    
content_copy copy
package org.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LmsApplication { public static void main(String[] args){ SpringApplication.run(LmsApplication.class, args); } }

This concludes our Library Management System using Spring boot.