- Inspired by the JpaRepository of Spring framework which also provides many capabilities for the CRUD, Pagination and Sorting operations.
- Maximizing the performance by using the native JDBC API with built-in Thread-safe Singleton pattern for your Data Access Objects (DAO).
- Annotate your DTO with @Table, @Id, @Column to map your DTO to the table (Inspired by the ORM).
- Friendly-to-use syntax to retrieving your DAO by classpath or class type (Inspired by the Store of the NgRX library for Angular framework):
Store.select(UsersDAO.class)
to get UsersDAO instance.Store.select("com.bangmaple.dao.UsersDAO")
to also get the UsersDAO instance.
- CRUD Operations.
- Paging operations.
- Sorting operations.
- Support for Microsoft SQL Server (MSSQL).
- Add support for MySQL and PostgreSQL.
- Create a new project then add this library by downloading the jar file from the
Release
tab. - Or you can clone this repository without having download the jar file.
Remember to also add the JDBC driver
.- For Servlet environment (non-context.xml users), you can configure like this by creating a
new
ServletListener
class or the class you just created that implementing theServletContextListener
interface then override thecontextInitialized
method:
import bangmaple.jdbc.utils.ConnectionManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class ServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ConnectionManager.PROTOCOL = "jdbc:sqlserver";
ConnectionManager.HOST = "localhost";
ConnectionManager.PORT = 1433;
ConnectionManager.USERNAME = "sa";
ConnectionManager.PASSWORD = "IloveFPT";
}
}
- If you use
META-INF/context.xml
for Database datasource then remember to set thename
property of theResource
tag asJDBCRepository
or theConnectionManager
will not initialize your application, you should configure like this (You don't need to implement ServletListener, theConnectionManager
will automatically initialize the Connection Pool if you correctly set-up thecontext.xml
file):
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/">
<Resource name="JDBCRepository" auth="Container" type="javax.sql.DataSource"
username="sa" password="IloveFPT"
driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
url="jdbc:sqlserver://localhost:1433;databaseName=YOUR_DATABASE_NAME"
maxActive="8" maxIdle="4"/>
</Context>
- For non-Servlet environment, navigate to your Main class and configure like this:
import bangmaple.jdbc.dao.base.Store;
import bangmaple.jdbc.repository.JdbcRepository;
import bangmaple.jdbc.utils.ConnectionManager;
public class Main {
public static void main(String[] args) throws Exception {
JdbcRepository.DEBUG = true;
ConnectionManager.PROTOCOL = "jdbc:sqlserver";
ConnectionManager.HOST = "localhost";
ConnectionManager.PORT = 1433;
ConnectionManager.USERNAME = "sa";
ConnectionManager.PASSWORD = "IloveFPT";
}
}
- If you want to show the SQL Query while executing the methods, there is
a
JdbcRepository.DEBUG
variable needed to be set astrue
. The default value isfalse
. - You may notice there is a
ConnectionManager
class.- This class responsible for getting the
Connection
instance. - If you use Data Source way, there is a file
context.xml
in theMETA-INF
folder. - If you chose the way to create a new
context.xml
file, please set the name of the data source byJDBCRepository
or theConnectionManager
won't initialize your connection pool.
- This class responsible for getting the
Class | Variable | Description |
---|---|---|
JdbcRepository | DEBUG (boolean) | Responsible for logging SQL queries while running the application. |
ConnectionManager | PROTOCOL (String) | The protocol for connecting to the database vendor. For MSSQL: jdbc:sqlserver |
ConnectionManager | HOST (String) | The host of the SQL server. Example: localhost |
ConnectionManager | PORT (Integer) | The port number of the SQL server. Example: 1433 |
ConnectionManager | USERNAME (String) | The username for logging in to the SQL server. Example: sa |
ConnectionManager | PASSWORD (String) | The password for logging in to the SQL server. Example: IloveFPT |
- In order to use the JDBC Repository, you will need DTO and DAO. Let we configure these:
import bangmaple.jdbc.annotations.Column;
import bangmaple.jdbc.annotations.Id;
import bangmaple.jdbc.annotations.Table;
@Table(name = "users", catalog = "users_management", schema = "dbo")
public class UsersDTO {
@Id
@Column(value = "username")
private String username;
@Column(value = "password")
private String password;
@Column(value = "fullname")
private String fullname;
@Column(value = "role")
private String role;
//Constructor + Getter + Setter + ToString
}
Annotation | Can be applied on | Description |
---|---|---|
Table |
Class | Make a class become an object for communicating with the database corresponding with the current mapping table. name for specifying the current mapping table, catalog for specifying the current mapping database name. |
Id |
Field | Mark a field as primary key column. Each DTO class must have at least 1 Id annotation applied only for only one field. |
Column |
Field | Mark a field to be mapped with the corresponding table's column. Use value to specify the value. |
- Now for the DAO class, create a normal class with a private constructor to respect the Singleton pattern.
import bangmaple.dto.UsersDTO;
import bangmaple.jdbc.utils.ConnectionManager;
import bangmaple.jdbc.dao.base.Store;
public class UsersDAO extends Store<UsersDTO, String> {
private UsersDAO() {}
}
- You may notice our DAO class extends the
Store
class which will responsible for storing the DAO classes only one instance each class by the Thread-safe Singleton pattern.- Why you named the class is
Store
:- Inspired by the NgRx (@ngrx/store) library for the Angular framework.
- What is Singleton pattern?
- It is a pattern to allow class to have only one and one instance during the application's lifecycle.
- Preventing initializing many instances of a class to prevent memory leaks.
- Why Thread-safe Singleton pattern?
- I believe that in a Multithreading environment like
Servlet
, threads will modify/use the instance safely for CRUD operations by applying this pattern.
- I believe that in a Multithreading environment like
- Why you named the class is
- There is one thing that extends the
Store
class requires two generic typesT
andID
:T
stands for the matching DTO class. For example theUsersDTO
class.ID
stands for the matching Id of the DTO class (table) type. For example, my annotatedId
field isString
type.- The result would be
Store<UsersDTO, String>
instead ofStore<T, ID>
. For that, we have implemented our CRUD, Pagination, Sorting operations for our application.
To retrieve the UsersDAO
instance, we have to select the instance:
import bangmaple.jdbc.dao.base.Store;
public class Main {
public static void main(String[] args) {
UsersDAO dao = Store.select(UsersDAO.class);
}
}
This is similar to the usual way that we do:
UsersDAO dao = new UsersDAO();
Please don't initialize the UsersDAO
instance by using the new
keyword, this is against the Singleton pattern
.
Luckily, we have the implemented private constructor
to prevent that thing to be happening.
Now we have all the methods we need, I will list them all by the below table:
Method | Argument(s) | Returns | Description |
---|---|---|---|
count() | void | int | Returns the number of entities available. |
deleteAll() | void | void | Delete all the entities. |
deleteAllByIds(Iterable<? extends ID>) | List of ids | void | Deletes all instances of entities with the given IDs. |
deleteById(ID) | The id of the entity | void | Deletes the entity with the given id. |
existsById(ID) | The id of the entity | boolean | Returns whether an entity with the given id exists. |
findAll() | void | Iterable | Returns all instances of the type. |
findAll(Pageable) | Pageable object will be described below | Iterable | Returns entities meeting the paging restriction provided in the Pageable object. |
findAll(boolean) | boolean will be described below | Iterable | Returns all entities sorted by the given option. |
findAllByIds(Iterable<? extends ID>) | Iterable | void | Returns all instances of the type {@code T} with the given IDs. |
findById(ID) | The id of the entity | T | Retrieves an entity by its id. |
insert(T) | The entity to be inserted | void | Insert the entity to the table as a record. |
insertAll(Iterable) | The list of entities to be inserted | void | Insert the entities to the table as records. |
update(T, ID) | The entity to be updated, the id corresponding to the entity to be updated. | void | Update the specified entity with the corresponding id. |
updateAll(Iterable, Iterable<? extends ID> | The list entities to be updated, the list of ids corresponding to the the list of entities to be updated. | void | Update the specified entities with the corresponding ids. |
- We found that there are two operations
findAll(Pageable)
andfindAll(boolean)
.- For the
Pageable
there are many way to use this operation:
- For the
import bangmaple.jdbc.dao.base.Store;
import bangmaple.jdbc.paging.PageRequest;
import bangmaple.jdbc.paging.Pageable;
public class Main {
public static void main(String[] args) {
UsersDAO dao = Store.select(UsersDAO.class);
Pageable pageable = PageRequest.of(0, 5, Pageable.SORT_DESC);
dao.findAll(pageable);
}
}
- Respects to the
PageRequest
object, we used theof
method. For the example, we want to retrieve5
records by thefirst
page then we usePageRequest.of(0, 5);
.
import bangmaple.jdbc.dao.base.Store;
import bangmaple.jdbc.paging.PageRequest;
import bangmaple.jdbc.paging.Pageable;
public class Main {
public static void main(String[] args) {
UsersDAO dao = Store.select(UsersDAO.class);
Pageagle pageable = PageRequest.of(0, 5, Pageable.SORT_DESC);
dao.findAll(pageable);
}
}
- Now we passed the third parameter -
Pageable.SORT_DESC
this will be described as we want to retrieve5
records by thefirst
page in thedescending
order then we usedPageRequest.of(0, 5, Pageable.SORT_DESC)
.
import bangmaple.jdbc.dao.base.Store;
import bangmaple.jdbc.paging.PageRequest;
import bangmaple.jdbc.paging.Pageable;
public class Main {
public static void main(String[] args) {
UsersDAO dao = Store.select(UsersDAO.class);
Pageable pageable = PageRequest.of(0, 5, Pageable.SORT_DESC, "fullname", "role");
dao.findAll(pageable);
}
}
- By default
Pagination
operation, the records are ordered by the primary key column (property that is annotated with@Id
). - Now that we passed the final parameter -
String...
. For example, we want to retrieve5
records by thefirst
page in thedescending
order based onfullname
androle
then we usedPageRequest.of(0, 5, Pageable.SORT_DESC, "fullname", "role")
.
Let say we want to retrieve a list of users by their role name, but we didn't find any method related to that requirement. We can customize to add our method by the following code snippet:
import bangmaple.jdbc.utils.ConnectionManager;
import bangmaple.jdbc.dao.base.Store;
public class UsersDAO extends Store<UsersDTO, String> {
private UsersDAO() {}
public List<UsersDTO> findUsersByRole(String role) throws SQLException {
List<UsersDTO> list = null;
conn = ConnectionManager.getConnection();
try {
if (conn != null) {
conn.setCatalog("users_management");
String query = "SELECT username, password, fullname, role FROM users WHERE role = ?";
prStm = conn.prepareStatement(query);
prStm.setString(1, role);
rs = prStm.executeQuery();
list = new ArrayList<>();
while (rs.next()) {
list.add(this.parseResultSetToDTO(rs, UsersDTO.class));
}
}
} finally { closeConnection(); }
return list;
}
}
Now we implemented the findUsersByRole(String)
to retrieve the users by their role name.
We may notice there are many variables we don't know, they are described as below:
conn
isjavax.sql.Connection
prStm
isjavax.sql.PreparedStatement
rs
isjavax.sql.ResultSet
ConnectionManager.getConnection()
retrieves the connection instance between our application and the database.closeConnection()
closes the connection between our application and the database.conn.setCatalog("users_management")
this is how we specify the database name. For example:users_management
, please change to your database name.this.parseResultSetToDTO(ResultSet, UsersDTO.class)
this method helps us to map the ResultSet's records after theexecuteQuery()
operation to theUsersDTO.class
then it will return the UsersDTO instance containing the information we need, this helper instance method is inherited fromStore
class.
- For the complete example of the CRUD operations, please navigate to this page.
- For the example of Data Source configuration as
context.xml
, please navigate to this page.
The application server such as Apache Tomcat
won't load the library even we already embedded the library into the application:
05-Sep-2021 07:35:06.443 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class [com.example.demo.ServletListener]
java.lang.NoClassDefFoundError: bangmaple/jdbc/utils/ConnectionManager
at com.example.demo.ServletListener.contextInitialized(ServletListener.java:13)
...
Caused by: java.lang.ClassNotFoundException: bangmaple.jdbc.utils.ConnectionManager
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1365)
... 48 more
- This is how to fix the problem, this is the normal operation of the application. You may have even met this
if you didn't put the
JDBC Driver library
. Drag thebangmaple-jdbc-repository.jar
into yourlib
folder of yourApache Tomcat
directory, for me it is:/Users/bangmaple/apache-tomcat-9.0.40/lib/
. - You may also need to drag the
JDBC Driver library
likesqljdbc.jar
driver as well.
After that, confirm it is existed at this directory then restart your application server! Happy coding!