Last Modified 2022.2.14

Java 9 Modules

This article covers a Java 9 module example. You read the following articles before starting.

Custom Connection Pool

The below is not a Java module example.

src/
├── net
│    └── java_school
│            └── db
│                └── dbpool
│                     ├── DBConnectionPool.java
│                     ├── DBConnectionPoolManager.java
│                     └── ConnectionManager.java
├── net
│    └── java_school
│            └── db
│                └── dbpool
│                     └── oracle
│                           └── OracleConnectionManager.java
├── net
│    └── java_school
│            └── db
│                └── dbpool
│                     └── mysql
│                           └── MySqlConnectionManager.java
├── net
│    └── java_school
│            └── test
│                  └── GetEmp.java
├── mysql.properties
├── oracle.properties
jars/
├── ojdbc6.jar
└── mysql-connector-java-8.0.28.jar
DBConnectionPool.java
package net.java_school.db.dbpool; 

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Date;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DBConnectionPool {
  private static final Logger logger = Logger.getLogger(DBConnectionPool.class.getName());
	
  // Number of connections currently in use
  private int checkedOut;

  // Free Connection List
  private Vector<Connection> freeConnections = new Vector<Connection>();

  // Maximum number of connections
  private int maxConn;

  // Waiting time (maximum time to wait when there is no connection in the pool)
  private int maxWait;

  // Connection Pool Name
  private String poolName;

  // DB Password
  private String passwd;

  // DB URL
  private String URL;

  // DB UserID
  private String userID;

  // Constructor
  public DBConnectionPool(String poolName, 
      String URL, 
      String userID, 
      String passwd, 
      int maxConn, 
      int initConn, 
      int waitTime) {

    this.poolName = poolName;
    this.URL = URL;
    this.userID = userID;
    this.passwd = passwd;
    this.maxConn = maxConn;
    this.maxWait = waitTime;

    for (int i = 0; i < initConn; i++) {
      freeConnections.addElement(newConnection());
    }
  }

  // Returning Connection
  // @param con : Connection to return
  public synchronized void freeConnection(Connection con) {
    freeConnections.addElement(con);
    checkedOut--;
    //Notify thread waiting to get Connection
    notifyAll();
  }

  // Get Connection
  @SuppressWarnings("resource")
  public synchronized Connection getConnection() {
    Connection con = null;
    //If Connection is in Free List, get the first of List
    if (freeConnections.size() > 0) {
      con = (Connection) freeConnections.firstElement();
      freeConnections.removeElementAt(0);

      try {
        //If the connection is closed by the DBMS, request again
        if (con.isClosed()) {
          logger.log(Level.SEVERE, "Removed bad connection from " + poolName);
          con = getConnection();
        }
      } //If strange connection occurs, request again
      catch (SQLException e) {
        logger.log(Level.SEVERE, "Removed bad connection from " + poolName);
        con = getConnection();
      }
    } //If Connection is not in Free List, create new
    else if (maxConn == 0 || checkedOut < maxConn) {
      con = newConnection();
    }

    if (con != null) {
      checkedOut++;
    }

    return con;
  }

  // Get Connection
  // @param timeout : Maximum Wait Time to Obtain a Connection
  public synchronized Connection getConnection(long timeout) {
    long startTime = new Date().getTime();
    Connection con;
    while ((con = getConnection()) == null) {
      try {
        wait(timeout * maxWait);
      } catch (InterruptedException e) {}
      if ((new Date().getTime() - startTime) >= timeout) {
        //Wait timeout
        return null;
      }
    }

    return con;
  }

  // Get Connection
  private Connection newConnection() {
    Connection con = null;
    try {
      if (userID == null) {
        con = DriverManager.getConnection(URL);
      } else {
        con = DriverManager.getConnection(URL, userID, passwd);
      }
      logger.info("Created a new connection in pool " + poolName);
    } catch (SQLException e) {
      StringBuffer sb = new StringBuffer();
      sb.append("Can't create a new connection for ");
      sb.append(URL);
      sb.append(" user: ");
      sb.append(userID);
      sb.append(" passwd: ");
      sb.append(passwd);
      logger.log(Level.SEVERE, sb.toString());
      return null;
    }

    return con;
  }
}
DBConnectionPoolManager.java
package net.java_school.db.dbpool;

import java.sql.Connection;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DBConnectionPoolManager {
  private static final Logger logger = Logger.getLogger(DBConnectionPoolManager.class.getName());
	
  // To apply the singleton pattern to the DBConnectionPoolManager (keep only one instance), 
  // declare it as static
  static private DBConnectionPoolManager instance;
  private Vector<String> drivers = new Vector<String>();
  private Hashtable<String, DBConnectionPool> pools = new Hashtable<String, DBConnectionPool>();

  // Obtaining instance of DBConnectionPoolManager
  // @return DBConnectionManger
  static synchronized public DBConnectionPoolManager getInstance() {
    if (instance == null) {
      instance = new DBConnectionPoolManager();
    }

    return instance;
  }

  // Default Constructor
  private DBConnectionPoolManager() {}

  // Send current Connection to Free Connection List
  // @param name : Pool Name
  // @param con : Connection
  public void freeConnection(String name, Connection con) {
    DBConnectionPool pool = (DBConnectionPool) pools.get(name);
    if (pool != null) {
      pool.freeConnection(con);
    }

    logger.info("One Connection of " + name + " was freed");
  }

  // Obtain Open Connection.
  // Creates a new connection if there are no open connections and the maximum number 
  // of connections has not been reached.
  // Waits for the default wait time when there are no open connections currently 
  // and the maximum number of connections is in use.
  // @param name : Pool Name
  // @return Connection : The connection or null
  public Connection getConnection(String name) {
    DBConnectionPool pool = (DBConnectionPool) pools.get(name);
    if (pool != null) {
      return pool.getConnection(10);
    }
    return null;
  }

  // Create a Connection Pool
  // @param poolName : Name of Pool to create
  // @param url : DB URL
  // @param userID : DB UserID
  // @param passwd : DB Password
  private void createPools(String poolName, 
      String url, 
      String userID,
      String passwd, 
      int maxConn, 
      int initConn, 
      int maxWait) {

    DBConnectionPool pool = new DBConnectionPool(poolName, url, userID, passwd, maxConn, initConn, maxWait);
    pools.put(poolName, pool);
    logger.info("Initialized pool " + poolName);
  }

  // Initialization
  public void init(String poolName, 
      String driver, 
      String url,
      String userID, 
      String passwd, 
      int maxConn, 
      int initConn, 
      int maxWait) {

    loadDrivers(driver);
    createPools(poolName, url, userID, passwd, maxConn, initConn, maxWait);
  }

  // JDBC Driver Loading
  // @param driverClassName : The JDBC driver for the DB you want to use.
  private void loadDrivers(String driverClassName) {
    try {
      Class.forName(driverClassName);
      drivers.addElement(driverClassName);
      logger.info("Registered JDBC driver " + driverClassName);
    } catch (Exception e) {
      logger.log(Level.SEVERE, "Can't register JDBC driver: " + driverClassName);
    }
  }

  public Hashtable<String,DBConnectionPool> getPools() {
    return pools;
  }
	
  public int getDriverNumber() {
    return drivers.size();
  }
}
ConnectionManager.java
package net.java_school.db.dbpool;

import java.sql.Connection;

public abstract class ConnectionManager {

  protected DBConnectionPoolManager poolManager;
  protected String poolName;
	
  public ConnectionManager() {
    this.poolManager = DBConnectionPoolManager.getInstance();
  }
	
  public Connection getConnection() {
    return (poolManager.getConnection(poolName));
  }

  public void freeConnection(Connection con) {
    poolManager.freeConnection(poolName, con);
  }
	
  public abstract void initPoolManager(String poolName, String driver, String url, 
      String userID, String passwd, int maxConn, int initConn, int maxWait);
	
  public int getDriverNumber() {
    return poolManager.getDriverNumber();
  }
}
OracleConnectionManager.java
package net.java_school.db.dbpool.oracle;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.java_school.db.dbpool.ConnectionManager;

public class OracleConnectionManager extends ConnectionManager {
  private static final Logger logger = Logger.getLogger(OracleConnectionManager.class.getName());

  public OracleConnectionManager() {
    this.poolName = "oracle";
    String configFile = "oracle.properties";
		
    ClassLoader resource = this.getClass().getClassLoader();
    URL path = resource.getResource(configFile);
		
    try {
      Properties prop = new Properties();
      FileInputStream inputStream = new FileInputStream(new File(path.getFile()));
      prop.load(inputStream);
					
      String dbServer = prop.getProperty("dbServer");
      String port = prop.getProperty("port");
      String dbName = prop.getProperty("dbName");
      String userID = prop.getProperty("userID");
      String passwd = prop.getProperty("passwd");
      int maxConn = Integer.parseInt(prop.getProperty("maxConn"));
      int initConn = Integer.parseInt(prop.getProperty("initConn"));
      int maxWait = Integer.parseInt(prop.getProperty("maxWait"));
      String driver = "oracle.jdbc.driver.OracleDriver";
      String JDBCDriverType = "jdbc:oracle:thin";
      String url = JDBCDriverType + ":@" + dbServer + ":" + port + ":" + dbName;
		
      initPoolManager(this.poolName, driver, url, userID, passwd, maxConn, initConn, maxWait);
    } catch (IOException e) {
      logger.log(Level.SEVERE, "Error reading properties of " + configFile);
      throw new RuntimeException(e);
    }

  }

  @Override
  public void initPoolManager(String poolName, String driver, String url, 
      String userID, String passwd, int maxConn, int initConn, int maxWait) {
    this.poolManager.init(poolName, driver, url, userID, passwd, maxConn, initConn, maxWait);
  }
}
MySqlConnectionManager.java
package net.java_school.db.dbpool.mysql;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.java_school.db.dbpool.ConnectionManager;

public class MySqlConnectionManager extends ConnectionManager {
	
  private static final Logger logger = Logger.getLogger(MySqlConnectionManager.class.getName());

  public MySqlConnectionManager() {
    this.poolName = "mysql";
    String configFile = "mysql.properties";
		
    ClassLoader resource = this.getClass().getClassLoader();
    URL path = resource.getResource(configFile);

    try {
      Properties prop = new Properties();
      FileInputStream inputStream = new FileInputStream(new File(path.getFile()));
      prop.load(inputStream);
		
      String dbServer = prop.getProperty("dbServer");
      String port = prop.getProperty("port");
      String dbName = prop.getProperty("dbName");
      String userID = prop.getProperty("userID");
      String passwd = prop.getProperty("passwd");
      int maxConn = Integer.parseInt(prop.getProperty("maxConn"));
      int initConn = Integer.parseInt(prop.getProperty("initConn"));
      int maxWait = Integer.parseInt(prop.getProperty("maxWait"));
      String driver = "com.mysql.jdbc.Driver";
      String JDBCDriverType = "jdbc:mysql";
      String url = JDBCDriverType + "://" + dbServer + ":" + port + "/" + 
        dbName + "?useUnicode=true&characterEncoding=UTF-8&useSSL=false";
				
      initPoolManager(this.poolName, driver, url, userID, passwd, maxConn, initConn, maxWait);
    } catch (IOException e) {
      logger.log(Level.SEVERE, "Error reading properties of " + configFile);
      throw new RuntimeException(e);
    }

  }
	
  @Override
  public void initPoolManager(String poolName, String driver, String url, 
      String userID, String passwd, int maxConn, int initConn, int maxWait) {
    this.poolManager.init(poolName, driver, url, userID, passwd, maxConn, initConn, maxWait);
  }
}
oracle.properties
############################################ 
# Database Connection Properties for Oracle
############################################ 

# Database Server Name OR IP address 
dbServer = 127.0.0.1

# The port number your DB server listents to. 
port = 1521

# Database Name 
dbName = XE

# Database User 
userID = scott

# Database Password 
passwd = tiger

# Maximum Connection Number 
maxConn = 20

# Inital Connection Number 
initConn = 5

# Maximum Wait Time 
maxWait = 5
mysql.properties
############################################ 
# Database Connection Properties for MySQL
############################################ 

# Database Server Name OR IP address 
dbServer = localhost

# The port number your DB server listents to. 
port = 3306

# Database Name 
dbName = xe

# Database User 
userID = scott

# Database Password 
passwd = tiger

# Maximum Connection Number 
maxConn = 20

# Inital Connection Number 
initConn = 5

# Maximum Wait Time 
maxWait = 5

Create a MySQL user and database as follows:

mysql --user=root --password mysql

create user 'scott'@'%' identified by 'tiger';
grant all privileges on *.* to 'scott'@'%';

create database xe;
exit;

mysql --user=scott --password xe

CREATE TABLE DEPT (
    DEPTNO DECIMAL(2),
    DNAME VARCHAR(14),
    LOC VARCHAR(13),
    CONSTRAINT PK_DEPT PRIMARY KEY (DEPTNO) 
);
CREATE TABLE EMP (
    EMPNO DECIMAL(4),
    ENAME VARCHAR(10),
    JOB VARCHAR(9),
    MGR DECIMAL(4),
    HIREDATE DATE,
    SAL DECIMAL(7,2),
    COMM DECIMAL(7,2),
    DEPTNO DECIMAL(2),
    CONSTRAINT PK_EMP PRIMARY KEY (EMPNO),
    CONSTRAINT FK_DEPTNO FOREIGN KEY (DEPTNO) REFERENCES DEPT(DEPTNO)
);
CREATE TABLE SALGRADE ( 
    GRADE TINYINT,
    LOSAL SMALLINT,
    HISAL SMALLINT 
);
INSERT INTO DEPT VALUES (10,'ACCOUNTING','NEW YORK');
INSERT INTO DEPT VALUES (20,'RESEARCH','DALLAS');
INSERT INTO DEPT VALUES (30,'SALES','CHICAGO');
INSERT INTO DEPT VALUES (40,'OPERATIONS','BOSTON');
INSERT INTO EMP VALUES (7369,'SMITH','CLERK',7902,STR_TO_DATE('17-12-1980','%d-%m-%Y'),800,NULL,20);
INSERT INTO EMP VALUES (7499,'ALLEN','SALESMAN',7698,STR_TO_DATE('20-2-1981','%d-%m-%Y'),1600,300,30);
INSERT INTO EMP VALUES (7521,'WARD','SALESMAN',7698,STR_TO_DATE('22-2-1981','%d-%m-%Y'),1250,500,30);
INSERT INTO EMP VALUES (7566,'JONES','MANAGER',7839,STR_TO_DATE('2-4-1981','%d-%m-%Y'),2975,NULL,20);
INSERT INTO EMP VALUES (7654,'MARTIN','SALESMAN',7698,STR_TO_DATE('28-9-1981','%d-%m-%Y'),1250,1400,30);
INSERT INTO EMP VALUES (7698,'BLAKE','MANAGER',7839,STR_TO_DATE('1-5-1981','%d-%m-%Y'),2850,NULL,30);
INSERT INTO EMP VALUES (7782,'CLARK','MANAGER',7839,STR_TO_DATE('9-6-1981','%d-%m-%Y'),2450,NULL,10);
INSERT INTO EMP VALUES (7788,'SCOTT','ANALYST',7566,STR_TO_DATE('13-7-1987','%d-%m-%Y')-85,3000,NULL,20);
INSERT INTO EMP VALUES (7839,'KING','PRESIDENT',NULL,STR_TO_DATE('17-11-1981','%d-%m-%Y'),5000,NULL,10);
INSERT INTO EMP VALUES (7844,'TURNER','SALESMAN',7698,STR_TO_DATE('8-9-1981','%d-%m-%Y'),1500,0,30);
INSERT INTO EMP VALUES (7876,'ADAMS','CLERK',7788,STR_TO_DATE('13-7-1987', '%d-%m-%Y'),1100,NULL,20);
INSERT INTO EMP VALUES (7900,'JAMES','CLERK',7698,STR_TO_DATE('3-12-1981','%d-%m-%Y'),950,NULL,30);
INSERT INTO EMP VALUES (7902,'FORD','ANALYST',7566,STR_TO_DATE('3-12-1981','%d-%m-%Y'),3000,NULL,20);
INSERT INTO EMP VALUES (7934,'MILLER','CLERK',7782,STR_TO_DATE('23-1-1982','%d-%m-%Y'),1300,NULL,10);
INSERT INTO SALGRADE VALUES (1,700,1200);
INSERT INTO SALGRADE VALUES (2,1201,1400);
INSERT INTO SALGRADE VALUES (3,1401,2000);
INSERT INTO SALGRADE VALUES (4,2001,3000);
INSERT INTO SALGRADE VALUES (5,3001,9999);
COMMIT;
exit;
GetEmp.java
package net.java_school.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import net.java_school.db.dbpool.mysql.MySqlConnectionManager;
import net.java_school.db.dbpool.oracle.OracleConnectionManager;

public class GetEmp {

  public static void main(String[] args) {

    OracleConnectionManager oracleConnectionManager = new OracleConnectionManager();
		
    Connection con = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;

    String sql = "SELECT * FROM EMP";

    try {
      con = oracleConnectionManager.getConnection();
      stmt = con.prepareStatement(sql);
      rs = stmt.executeQuery();

      while (rs.next()) {
        String empno = rs.getString(1);
        String ename = rs.getString(2);
        String job = rs.getString(3);
        String mgr = rs.getString(4);
        String hiredate = rs.getString(5);
        String sal = rs.getString(6);
        String comm = rs.getString(7);
        String depno = rs.getString(8);

        System.out.println(empno + " : " + ename + " : " + job + " : " + 
          mgr + " : " + hiredate + " : " + sal + " : " + comm + " : " + depno);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        rs.close();
      } catch (SQLException e) {}
      try {
        stmt.close();
      } catch (SQLException e) {}
      oracleConnectionManager.freeConnection(con);
    }
		
    MySqlConnectionManager mysqlConnectionManager = new MySqlConnectionManager();
		
    try {
      con = mysqlConnectionManager.getConnection();
      stmt = con.prepareStatement(sql);
      rs = stmt.executeQuery();

      while (rs.next()) {
        String empno = rs.getString(1);
        String ename = rs.getString(2);
        String job = rs.getString(3);
        String mgr = rs.getString(4);
        String hiredate = rs.getString(5);
        String sal = rs.getString(6);
        String comm = rs.getString(7);
        String depno = rs.getString(8);

        System.out.println(empno + " : " + ename + " : " + job + " : " + 
          mgr + " : " + hiredate + " : " + sal + " : " + comm + " : " + depno);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        rs.close();
      } catch (SQLException e) {}
      try {
        stmt.close();
      } catch (SQLException e) {}
      mysqlConnectionManager.freeConnection(con);
    }
		
    System.out.println("Driver Number: " + mysqlConnectionManager.getDriverNumber());
		
  }
}

Oracle JDBC Driver Download
MySQL JDBC Driver Download
Copy the JDBC drivers to the jars/ folder.

Test on Windows

Run the dir command and verify that the output looks like this:

C:\ Command Prompt
 Volume in drive H has no label.
 Volume Serial Number is 26D8-3E15

 Directory of H:\this-is-not-modules-example

03/05/2020  05:41 AM    <DIR>          .
03/05/2020  05:41 AM    <DIR>          ..
03/05/2020  05:35 AM    <DIR>          jars
03/05/2020  05:35 AM    <DIR>          src

Create a directory where Javac will generate byte codes.

C:\ Command Prompt
mkdir out

Compile in the following order:

C:\ Command Prompt
javac -d out src\net\java_school\db\dbpool\*.java
javac -d out src\net\java_school\db\dbpool\oracle\OracleConnectionManager.java
javac -d out src\net\java_school\db\dbpool\mysql\MySqlConnectionManager.java
javac -d out src\net\java_school\test\GetEmp.java

Copy the Java property files into the out directory.

C:\ Command Prompt
xcopy src\*.properties out

Run

C:\ Command Prompt
set classpath=jars/ojdbc6.jar;jars/mysql-connector-java-8.0.28.jar;out
java net.java_school.test.GetEmp

Test on Linux

Run the ls command and verify that the output looks like this:

jars  src

Compile

javac -d out -sourcepath src $(find src -name "*.java")

Copy the Java property files into the out directory.

cp $(find src -name '*.properties') out

Run

CP=jars/ojdbc6.jar
CP+=:jars/mysql-connector-java-8.0.28.jar
java -cp $CP:out net.java_school.test.GetEmp
Sep 17, 2019 11:46:41 AM net.java_school.db.dbpool.DBConnectionPoolManager loadDrivers
INFO: Registered JDBC driver oracle.jdbc.driver.OracleDriver
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool oracle
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool oracle
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool oracle
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool oracle
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool oracle
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPoolManager createPools
INFO: Initialized pool oracle
7369 : SMITH : CLERK : 7902 : 1980-12-17 00:00:00 : 800 : null : 20
7499 : ALLEN : SALESMAN : 7698 : 1981-02-20 00:00:00 : 1600 : 300 : 30
7521 : WARD : SALESMAN : 7698 : 1981-02-22 00:00:00 : 1250 : 500 : 30
7566 : JONES : MANAGER : 7839 : 1981-04-02 00:00:00 : 2975 : null : 20
7654 : MARTIN : SALESMAN : 7698 : 1981-09-28 00:00:00 : 1250 : 1400 : 30
7698 : BLAKE : MANAGER : 7839 : 1981-05-01 00:00:00 : 2850 : null : 30
7782 : CLARK : MANAGER : 7839 : 1981-06-09 00:00:00 : 2450 : null : 10
7788 : SCOTT : ANALYST : 7566 : 1987-07-13 00:00:00 : 3000 : null : 20
7839 : KING : PRESIDENT : null : 1981-11-17 00:00:00 : 5000 : null : 10
7844 : TURNER : SALESMAN : 7698 : 1981-09-08 00:00:00 : 1500 : 0 : 30
7876 : ADAMS : CLERK : 7788 : 1987-07-13 00:00:00 : 1100 : null : 20
7900 : JAMES : CLERK : 7698 : 1981-12-03 00:00:00 : 950 : null : 30
7902 : FORD : ANALYST : 7566 : 1981-12-03 00:00:00 : 3000 : null : 20
7934 : MILLER : CLERK : 7782 : 1982-01-23 00:00:00 : 1300 : null : 10
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPoolManager freeConnection
INFO: One Connection of oracle was freed
Sep 17, 2019 11:46:42 AM net.java_school.db.dbpool.DBConnectionPoolManager loadDrivers
INFO: Registered JDBC driver com.mysql.jdbc.Driver
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool mysql
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool mysql
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool mysql
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool mysql
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPool newConnection
INFO: Created a new connection in pool mysql
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPoolManager createPools
INFO: Initialized pool mysql
7369 : SMITH : CLERK : 7902 : 1980-12-17 : 800.00 : null : 20
7499 : ALLEN : SALESMAN : 7698 : 1981-02-20 : 1600.00 : 300.00 : 30
7521 : WARD : SALESMAN : 7698 : 1981-02-22 : 1250.00 : 500.00 : 30
7566 : JONES : MANAGER : 7839 : 1981-04-02 : 2975.00 : null : 20
7654 : MARTIN : SALESMAN : 7698 : 1981-09-28 : 1250.00 : 1400.00 : 30
7698 : BLAKE : MANAGER : 7839 : 1981-05-01 : 2850.00 : null : 30
7782 : CLARK : MANAGER : 7839 : 1981-06-09 : 2450.00 : null : 10
7788 : SCOTT : ANALYST : 7566 : 1987-06-28 : 3000.00 : null : 20
7839 : KING : PRESIDENT : null : 1981-11-17 : 5000.00 : null : 10
7844 : TURNER : SALESMAN : 7698 : 1981-09-08 : 1500.00 : 0.00 : 30
7876 : ADAMS : CLERK : 7788 : 1987-07-13 : 1100.00 : null : 20
7900 : JAMES : CLERK : 7698 : 1981-12-03 : 950.00 : null : 30
7902 : FORD : ANALYST : 7566 : 1981-12-03 : 3000.00 : null : 20
7934 : MILLER : CLERK : 7782 : 1982-01-23 : 1300.00 : null : 10
Sep 17, 2019 11:46:43 AM net.java_school.db.dbpool.DBConnectionPoolManager freeConnection
INFO: One Connection of mysql was freed
Driver Number: 2

Modularization

Let's divide the example into parts. Each one will be a module.

1st module : net.java_school.db.dbpool (Module Name)
DBConnectionPool.java
DBConnectionPoolManager.java
ConnectionManager.java
2nd module : net.java_school.db.dbpool.oracle
OracleConnectionManager.java
oracle.properties
3rd module : net.java_school.db.dbpool.mysql
MySqlConnectionManager.java
mysql.properties
4th module : main.app
GetEmp.java
5th module
ojdbc6.jar
6th module
mysql-connector-java-8.0.28.jar

Change the directory structure as shown below.
Just under src/, there should be folders with module names.

src/
├── net.java_school.db.dbpool (Module Name)
│   ├── net
│   │   └── java_school
│   │       └── db
│   │           └── dbpool
│   │               ├── DBConnectionPool.java
│   │               ├── DBConnectionPoolManager.java
│   │               └── ConnectionManager.java
│   └── module-info.java
├── net.java_school.db.dbpool.oracle
│   ├── net
│   │   └── java_school
│   │       └── db
│   │           └── dbpool
│   │               └── oracle
│   │                   └── OracleConnectionManager.java
│   ├── module-info.java
│   └── oracle.properties
├── net.java_school.db.dbpool.mysql
│   ├── net
│   │   └── java_school
│   │       └── db
│   │           └── dbpool
│   │               └── mysql
│   │                   └── MySqlConnectionManager.java
│   ├── module-info.java
│   └── mysql.properties
├── main.app
│   ├── net
│   │   └── java_school
│   │       └── test
│   │            └── GetEmp.java
│   └── module-info.java
jars/
├── ojdbc6.jar
└── mysql-connector-java-8.0.28.jar

1st Module : net.java_school.db.dbpool

module-info.java
module net.java_school.db.dbpool {
  requires java.logging;
  requires transitive java.sql;
	
  exports net.java_school.db.dbpool;
}

requires java.logging;
The requires keyword specifies on which this module depends the external module.
java.logging is a module name, not a package name.

requires transitive java.sql;
The module that depends on net.java_school.db.dbpool will automatically depend on java.sql.

exports net.java_school.db.dbpool;
The exports keyword specifies the package that this module exports.
net.java_school.db.dbpool is the package name and the the module name.

Although module A requires module B, module A can only use the public elements of packages exported by module B.

2nd Module : net.java_school.db.dbpool.oracle

module-info.java
module net.java_school.db.dbpool.oracle {
  requires net.java_school.db.dbpool;
	
  exports net.java_school.db.dbpool.oracle;
}

3rd Module : net.java_school.db.dbpool.mysql

module-info.java
module net.java_school.db.dbpool.mysql {
  requires net.java_school.db.dbpool;
	
  exports net.java_school.db.dbpool.mysql;
}

4th Module : main.app

module-info.java
module main.app {
  requires java.sql;
  requires net.java_school.db.dbpool.oracle;
  requires net.java_school.db.dbpool.mysql;
}

java modules 1

See below to modify the code that reads the Java property file.

OracleConnectionManager.java
public OracleConnectionManager() {
  this.poolName = "oracle";
  String configFile = "oracle.properties";

  Class<?> clazz = OracleConnectionManager.class;
  Module m = clazz.getModule();

  try {
    InputStream inputStream = m.getResourceAsStream(configFile);
    Properties prop = new Properties();
    prop.load(inputStream);
		
    //..Omit..
			
MySqlConnectionManager.java
public MySqlConnectionManager() {
  this.poolName = "mysql";
  String configFile = "mysql.properties";

  Class<?> clazz = MySqlConnectionManager.class;
  Module m = clazz.getModule();

  try {
    InputStream inputStream = m.getResourceAsStream(configFile);
    Properties prop = new Properties();
    prop.load(inputStream);
		
    //..Omit..
			

Test on Windows

Compile

C:\ Command Prompt
javac -d out --module-source-path src ^
-m main.app,net.java_school.db.dbpool, ^
net.java_school.db.dbpool.oracle,net.java_school.db.dbpool.mysql

--module-source-path: Specify where to find input source files for multiple modules.
-m: Compile only the specified module.

Copy the Java property file into each module directory.

C:\ Command Prompt
copy src\net.java_school.db.dbpool.oracle\oracle.properties ^
out\net.java_school.db.dbpool.oracle\
copy src\net.java_school.db.dbpool.mysql\mysql.properties ^
out\net.java_school.db.dbpool.mysql\

Run

C:\ Command Prompt
java -p jars:out -m main.app/net.java_school.test.GetEmp

-p: module path
-m: module/base class --For modular jars, enter module name only--

Non-modular jars in the module path become automatic modules. Java VM interprets automatic modules as requires transitive all other modules on the module path. It means that automatic modules can read all packages exported by all named modules in the module path.

Automatic Modules export all their packages.

Test on Linux

Compile

javac -d out --module-source-path src $(find src -name "*.java")

Copy the Java property files into each module directory.

cp src/net.java_school.db.dbpool.oracle/oracle.properties \
out/net.java_school.db.dbpool.oracle/
cp src/net.java_school.db.dbpool.mysql/mysql.properties \
out/net.java_school.db.dbpool.mysql/

Run

java -p jars:out -m main.app/net.java_school.test.GetEmp

Using ServiceLoader

Using ServiceLoader, you can make services and service implementations into separate modules.

module main.app {
  requires net.java_school.db.dbpool;
  
  uses net.java_school.db.dbpool.ConnectionManager;
}

uses net.java_school.db.dbpool.ConnectionManager;
A uses keyword specifies a service used by this module.
The net.java_school.db.dbpool.ConnectionManager is an abstract class.
Abstract or implementation classes can also be services.

module net.java_school.db.dbpool.oracle {
  requires net.java_school.db.dbpool;
  
  provides net.java_school.db.dbpool.ConnectionManager 
      with net.java_school.db.dbpool.oracle.OracleConnectionManager;
}

provides net.java_school.db.dbpool.ConnectionManagerService
with net.java_school.db.dbpool.oracle.OracleConnectionManagerImplementation;

module net.java_school.db.dbpool.mysql {
  requires net.java_school.db.dbpool;
  
  provides net.java_school.db.dbpool.ConnectionManager 
      with net.java_school.db.dbpool.mysql.MySqlConnectionManager;
}

provides net.java_school.db.dbpool.ConnectionManagerService
with net.java_school.db.dbpool.mysql.MySqlConnectionManagerImplementation;

Note that the module providing the implementationProvider does not export any packages of its own. Therefore, the GetEmp class of the main.app moduleConsumer cannot use any type in Providers.

java modules 2

package net.java_school.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ServiceLoader;

import net.java_school.db.dbpool.ConnectionManager;

public class GetEmp {

  public static void main(String[] args) {

    Iterable<ConnectionManager> managers = ServiceLoader.load(ConnectionManager.class);
		
    for (ConnectionManager manager : managers) {
      Connection con = null;
      PreparedStatement stmt = null;
      ResultSet rs = null;
	
      String sql = "SELECT * FROM EMP";
	
      try {
        con = manager.getConnection();
        stmt = con.prepareStatement(sql);
        rs = stmt.executeQuery();
	
        while (rs.next()) {
          String empno = rs.getString(1);
          String ename = rs.getString(2);
          String job = rs.getString(3);
          String mgr = rs.getString(4);
          String hiredate = rs.getString(5);
          String sal = rs.getString(6);
          String comm = rs.getString(7);
          String depno = rs.getString(8);
          
          System.out.println(empno + " : " + ename + " : " + job
              + " : " + mgr + " : " + hiredate + " : " + sal
              + " : " + comm + " : " + depno);
        }
      } catch (Exception e) {
        e.printStackTrace();
      } finally {
        try {
          rs.close();
        } catch (SQLException e) {}
        try {
          stmt.close();
        } catch (SQLException e) {}
          manager.freeConnection(con);
      }

      System.out.println("Driver Number: " + manager.getDriverNumber());
		
    }

  }
}

The service and implementation are now entirely separated into their respective modules.

If only net.java_school.db.dbpool.oracle or net.java_school.db.dbpool.mysql is in the module path, you can run main.app. Even if neither net.java_school.db.dbpool.oracle nor net.java_school.db.dbpool.mysql is in the module path, compilation succeeds.

Filtering Services

Create the following file in net.java_school.db.dbpool module.

package net.java_school.db.dbpool;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Oracle {
  public boolean value() default true;
}

Add an Oracle annotation to the OracleConnectionManager class in net.java_school.db.dbpool.oracle module.

import net.java_school.db.dbpool.Oracle;

@Oracle
public class OracleConnectionManager extends ConnectionManager {
  //..
}

Modify GetEmp class.

import net.java_school.db.dbpool.Oracle;

public class GetEmp {

  public static void main(String[] args) {

    ServiceLoader<ConnectionManager> managers = ServiceLoader.load(ConnectionManager.class);
		
    ConnectionManager manager = managers.stream()
      .filter(provider -> isOracle(provider.type()))
      .map(ServiceLoader.Provider::get).findAny().get();
				
    //..Omit..
      
  }
  
  private static boolean isOracle(Class<?> clazz) {
    return clazz.isAnnotationPresent(Oracle.class)
	&& clazz.getAnnotation(Oracle.class).value() == true;
  }
}

Adding Interface

Create the net.java_school.db.dbpool.api module.
Create the following interface in net.java_school.db.dbpool.api module.

ConnectionManageable.java
package net.java_school.db.dbpool.api;

import java.sql.Connection;

public interface ConnectionManageable {
	
  public Connection getConnection();

  public void freeConnection(Connection con);

  public int getDriverNumber();

}

Modify ConnectionManager abstract class in net.java_school.db.dbpool module.

package net.java_school.db.dbpool;

import java.sql.Connection;

import net.java_school.db.dbpool.api.ConnectionManageable;

public abstract class ConnectionManager implements ConnectionManageable {

  protected DBConnectionPoolManager poolManager;
  protected String poolName;

  public ConnectionManager() {
    this.poolManager = DBConnectionPoolManager.getInstance();
  }

  @Override	
  public Connection getConnection() {
    return (poolManager.getConnection(poolName));
  }

  @Override
  public void freeConnection(Connection con) {
    poolManager.freeConnection(poolName, con);
  }

  public abstract void initPoolManager(String poolName, String driver, String url, 
      String userID, String passwd, int maxConn, int initConn, int maxWait);

  @Override	
  public int getDriverNumber() {
    return poolManager.getDriverNumber();
  }
}

Create the module descriptor for net.java_school.db.dbpool.api module.

module net.java_school.db.dbpool.api {
  requires transitive java.sql;
  
  exports net.java_school.db.dbpool.api;
}

Modify module descriptors.

module net.java_school.db.dbpool {
  requires transitive net.java_school.db.dbpool.api;
  
  exports net.java_school.db.dbpool;
}
module net.java_school.db.dbpool.mysql {
  requires net.java_school.db.dbpool;

  provides net.java_school.db.dbpool.api.ConnectionManageable
    with net.java_school.db.dbpool.mysql.MySqlConnectionManager;
}
module net.java_school.db.dbpool.oracle {
  requires net.java_school.db.dbpool;

  provides net.java_school.db.dbpool.api.ConnectionManageable
    with net.java_school.db.dbpool.oracle.OracleConnectionManager;
}
module main.app {
  requires net.java_school.db.dbpool.api;

  uses net.java_school.db.dbpool.api.ConnectionManageable;
}

java modules 3

After moving net.java_school.db.dbpool.Oracle.java to net.java_school.db.dbpool.api module, modify it as bellows:

package net.java_school.db.dbpool.api;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Oracle {
  public boolean value() default true;
}

Modify OracleConnectionManager in net.java_school.db.dbpool.oracle module.

package net.java_school.db.dbpool.oracle;

import net.java_school.db.dbpool.ConnectionManager;
import net.java_school.db.dbpool.api.Oracle;

@Oracle
public class OracleConnectionManager extends ConnectionManager {

Modify GetEmp class.

import net.java_school.db.dbpool.api.ConnectionManageable;
import net.java_school.db.dbpool.api.Oracle;

public class GetEmp {

  public static void main(String[] args) {

    ServiceLoader<ConnectionManageable> managers = ServiceLoader.load(ConnectionManageable.class);

    ConnectionManageable manager = managers.stream()
        .filter(provider -> isOracle(provider.type()))
        .map(ServiceLoader.Provider::get).findAny().get();
			

Source: https://github.com/kimjonghoon/java-module-test

Related Articles References