- 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
Releasetab. - 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
ServletListenerclass or the class you just created that implementing theServletContextListenerinterface then override thecontextInitializedmethod:
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.xmlfor Database datasource then remember to set thenameproperty of theResourcetag asJDBCRepositoryor theConnectionManagerwill not initialize your application, you should configure like this (You don't need to implement ServletListener, theConnectionManagerwill automatically initialize the Connection Pool if you correctly set-up thecontext.xmlfile):
<?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.DEBUGvariable needed to be set astrue. The default value isfalse. - You may notice there is a
ConnectionManagerclass.- This class responsible for getting the
Connectioninstance. - If you use Data Source way, there is a file
context.xmlin theMETA-INFfolder. - If you chose the way to create a new
context.xmlfile, please set the name of the data source byJDBCRepositoryor theConnectionManagerwon'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
Storeclass 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
Storeclass requires two generic typesTandID:Tstands for the matching DTO class. For example theUsersDTOclass.IDstands for the matching Id of the DTO class (table) type. For example, my annotatedIdfield isStringtype.- 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
Pageablethere 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
PageRequestobject, we used theofmethod. For the example, we want to retrieve5records by thefirstpage 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_DESCthis will be described as we want to retrieve5records by thefirstpage in thedescendingorder 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
Paginationoperation, 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 retrieve5records by thefirstpage in thedescendingorder based onfullnameandrolethen 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:
connisjavax.sql.ConnectionprStmisjavax.sql.PreparedStatementrsisjavax.sql.ResultSetConnectionManager.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.classthen it will return the UsersDTO instance containing the information we need, this helper instance method is inherited fromStoreclass.
- 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.jarinto yourlibfolder of yourApache Tomcatdirectory, for me it is:/Users/bangmaple/apache-tomcat-9.0.40/lib/. - You may also need to drag the
JDBC Driver librarylikesqljdbc.jardriver as well.
After that, confirm it is existed at this directory then restart your application server! Happy coding!