Last Modified 2020.4.11

JavaBank 예제 모듈화

이 글은 자바 은행 예제를 모듈화하는 과정을 다룬다.
자바 은행 소스에 내려받는다.

git clone https://github.com/kimjonghoon/JavaBank

오라클 JDBC 드라이버SLF4J의 Maven central 링크를 클릭해서 내려받은 최신 버전의 slf4j-api 와 slf4j-simple 을 lib/ 폴더에 복사한다.

src/
├── net
│   └── java_school
│       └── bank
│            ├── Account.java
│            ├── Bank.java
│            ├── BankDao.java
│            ├── BankUi.java
│            ├── MyBank.java
│            ├── MyBankDao.java
│            └── Transaction.java
├── simplelogger.properties
lib/
├── ojdbc6.jar
├── slf4j-api-1.7.30.jar
└── slf4j-simple-1.7.30.jar

SCOTT 계정에서 은행 계좌 테이블과 거래명세 테이블, 그리고 입금과 출금 시 거래명세 테이블에 입출금 정보를 인서트하는 트리거를 생성한다.

create table bankaccount (
	accountno varchar2(50),
	owner varchar2(20) not null,
	balance number,
	kind varchar2(10),
	constraint PK_BANKACCOUNT primary key(accountno),
	constraint CK_BANKACCOUNT_NORMAL 
		CHECK (balance >= CASE WHEN kind='NORMAL' THEN 0 END),
	constraint CK_BANKACCOUNT_KIND CHECK (kind in ('NORMAL', 'MINUS'))
);  

create table transaction (
    transactiondate timestamp,
    kind varchar2(10),
    amount number,
    balance number,
    accountno varchar2(50),
    constraint FK_TRANSACTION FOREIGN KEY(accountno)
    	REFERENCES bankaccount(accountno)
);

create or replace trigger bank_trigger
after insert or update of balance on bankaccount
for each row
begin
	if :new.balance > :old.balance then
		insert into transaction 
		values 
		(
			systimestamp,
			'DEPOSIT',
			:new.balance - :old.balance,
			:new.balance,
			:old.accountno
		);
	end if;
	if :new.balance < :old.balance then
		insert into transaction 
		values 
		(
			systimestamp,
			'WITHDRAW',
			:old.balance - :new.balance,
			:new.balance,
			:old.accountno
		);
	end if;
end;
/

모듈화하기 전 프로그램이 동작하는지 확인한다.

윈도에서 테스트

컴파일

C:\ Command Prompt
mkdir out
set classpath=lib\slf4j-api-1.7.30.jar
javac -d out src\net\java_school\bank\*.java

프로퍼티 파일 복사

C:\ Command Prompt
copy src\simplelogger.properties out\

실행

C:\ Command Prompt
set classpath=lib\slf4j-api-1.7.30.jar;lib\slf4j-simple-1.7.30.jar;lib\ojdbc6.jar;out
java net.java_school.bank.BankUi

리눅스에서 테스트

컴파일

CP=lib/slf4j-api-1.7.30.jar
javac -cp $CP -d out -sourcepath src $(find src -name "*.java")

프로퍼티 파일 복사

cp src/simplelogger.properties out/

실행

CP+=:lib/slf4j-simple-1.7.30.jar
CP+=:lib/ojdbc6.jar
java -cp $CP:out net.java_school.bank.BankUi

모듈화를 진행하기 전에 다음을 실행하여 자바 은행이 의존하는 외부 라이브러리를 확인한다.

jdeps -summary -cp lib/*.jar out
..
out -> lib/slf4j-api-1.7.30.jar
..

자바 은행이 slf4j-api-1.7.30.jar를 의존함을 확인했다.
이와 같은 라이브러리는 모듈 패스에 추가한다.

모듈화

자바 은행에 사용할, 모듈화된 사용자 정의 커넥션 풀을 내려받는다.

git clone https://github.com/kimjonghoon/java-module-test

java-module-test의 src/ 폴더에 자바 은행을 다음과 같이 배치한다.
--main.app 모듈과 net.java_school.db.dbpool.mysql 모듈은 제거한다--

src/
├── net.java_school.javabank
│   ├── net
│   │   └── java_school
│   │       └── bank
│   │            ├── Account.java
│   │            ├── Bank.java
│   │            ├── BankDao.java
│   │            ├── BankUi.java
│   │            ├── MyBank.java
│   │            ├── MyBankDao.java
│   │            └── Transaction.java
│   ├── module-info.java
│   └── simplelogger.properties
├── net.java_school.db.dbpool.api
│   ├── net
│   │   └── java_school
│   │       └── db
│   │           └── dbpool
│   │               └── api
│   │                   └── ConnectionManageable.java
│   └── module-info.java
├── net.java_school.db.dbpool
│   ├── 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
lib/
├── slf4j-simple-1.7.30.jar
jars/
├── ojdbc6.jar
└── slf4j-api-1.7.30.jar

jar 파일을 lib/ 와 jars/ 로 분리해서 배치했는지 설명이 필요하다.
jars/는 모듈 패스에 추가할 것이다.
모듈 패스에 있는, 모듈이 아닌 jar는 자동 모듈이 된다.
lib/는 클래스 패스로 사용할 것이다.
클래스 패스에 있는, 모듈이 아닌 jar는 이름 없는 모듈이 된다.
자동 모듈만이 이름 없는 모듈을 읽을 수 있다.
slf4j-api 가 slf4j-simple 을 읽으리라는 건 이름에서 쉽게 유추할 수 있다.
자동 모듈 slf4j-api 가 이름 없는 모듈 slf4j-simple 을 읽도록 배치한 것이다.

자바 은행 모듈의 디스크립터를 생성한다.

module net.java_school.javabank {
  requires net.java_school.db.dbpool.api;
  
  uses net.java_school.db.dbpool.api.ConnectionManageable;
}

자동 모듈의 이름이 어떻게 결정되는지 알기 위해 일단 컴파일한다.

윈도에서 컴파일

C:\ Command Prompt
javac -p jars -d out --module-source-path src ^
-m net.java_school.javabank,net.java_school.db.dbpool.api,
net.java_school.db.dbpool,net.java_school.db.dbpool.oracle

리눅스에서 컴파일

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

컴파일 에러가 발생한다.

src/net.java_school.javabank/net/java_school/bank/MyBankDao.java:12: error: package org.slf4j is not visible
import org.slf4j.Logger;
          ^
  (package org.slf4j is declared in module org.slf4j, but module net.java_school.javabank does not read it)
src/net.java_school.javabank/net/java_school/bank/MyBankDao.java:13: error: package org.slf4j is not visible
import org.slf4j.LoggerFactory;
          ^
  (package org.slf4j is declared in module org.slf4j, but module net.java_school.javabank does not read it)
2 errors

에러 메시지 중 (package org.slf4j is declared in module org.slf4j, but module net.java_school.javabank does not read it) 에서 모듈 이름이 org.slf4j 임을 알 수 있다.
자바 은행 모듈이 org.slf4j 모듈을 사용한다는 선언을 모듈 디스크립터에 추가한다.

module net.java_school.javabank {
  requires net.java_school.db.dbpool.api;
  requires org.slf4j;
  
  uses net.java_school.db.dbpool.api.ConnectionManageable;
}

윈도에서 테스트

컴파일

C:\ Command Prompt
javac -p jars -d out --module-source-path src ^
-m net.java_school.javabank,net.java_school.db.dbpool.api,
net.java_school.db.dbpool,net.java_school.db.dbpool.oracle

프로퍼티 파일 복사

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.javabank\simplelogger.properties ^
out\net.java_school.javabank\

실행

C:\ Command Prompt
set classpath=lib\slf4j-simple-1.7.30.jar
java -p jars;out ^ 
-m net.java_school.javabank/net.java_school.bank.BankUi

리눅스에서 테스트

컴파일

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

프로퍼티 파일 복사

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

실행

CP=lib/slf4j-simple-1.7.30.jar
java -cp $CP -p jars:out \
-m net.java_school.javabank/net.java_school.bank.BankUi
관련 글 참조