이전 ORACLE 연결했던 부분
public static Connection getConnection() {
Connection conn=null;
String url="jdbc:oracle:thin:@127.0.0.1:1521:orcl11";
String id="scott";
String pw="tiger";
String driver="oracle.jdbc.driver.OracleDriver";
try {
Class.forName(driver);
conn=DriverManager.getConnection(url, id, pw);
System.out.println("DB연결 완료");
}catch(Exception e) {
e.printStackTrace();
System.out.println("DB연결 실패");
}
return conn;
}

사용자가 요청할 떄마다 드라이버를 로드하고 커넥션 객체를 생성하여 연결하고 종료하는 과정이 불편하다. & 자원에 대한 소모에 단점이 존재한다.
그래서 단점을 보완하기 위해 데이터 베이스 커넥션 풀을 사용한다.
HiKariCP를 사용하면?
HiKariCP는 데이터베이스 연결(Connection)을 관리해 주는 도구(라이브러리)이다.
HiKariCP는 커넥션 풀이 설정된 커넥션의 사이즈만큼의 연결을 허용하며 HTTP 요청에 대해 순차적으로 DB 커넥션을 처리해주는 기능이다.
오버헤드란?
출처| 블로그
💡 데이터베이스 커넥션 풀 (DBCP: Database Connection Pool)

데이터베이스 커넥션 풀 (DBCP: Database Connection Pool)의 과정
WAS가 실행되면서 Pool 내에 Connection들을 생성해 둔다(미리).
HTTP의 요청이 올때 Pool 내에서 Connection 객체를 가져다가 사용한다.
사용이 완료된 Connection 객체는 Pool 내에 반환한다.
코드로 살펴보자.
datasource:
hikari:
jdbc-url: jdbc:oracle:thin:@localhost:1521/orcl11
username: scott
password: tiger
driver-class-name: oracle.jdbc.OracleDriver
connection-timeout: 20000
validation-timeout: 3000
minimum-idle: 5
maximum-pool-size: 12
idle-timeout: 300000
max-lifetime: 1200000
auto-commit: true
pool-name: oraPool
minimum-idle: 5
maximum-pool-size: 12 --> 13번쨰 사람이온다면, 기다려 --> 오버헤드가 발생한다.
idle-timeout: 300000 지나면 회수해나간다.
WAS와 데이터 베이스와의 연결(Connection)을 줄여서 비용을 준다.
'Pool'내에 미리 연결(Connection)되어 있기에 매번 생성하는 비용을 줄일 수 있다.
연결(Connection)에 대해서 조정을 할 수 있다. (단, 인프라의 DB Connection 수와 맞추어야 한다.)
트랜잭션 처리하려면
auto-commit : false로 변경해라!
[TestLogic]
public class TestLogic {
Logger logger = LoggerFactory.getLogger(TestLogic.class);
}
[TestDao]
public class TestDao {
Logger logger = LoggerFactory.getLogger(TestDao.class);
}
[TestController]
public class TestController {
Logger logger = LoggerFactory.getLogger(TestController.class);
}
[MyConfig.java]
// xml문서 대신 개발자가 선언할 객체를 미리 선언해둔다.
// 이러면 이른 객체 생성을 가져갈 수 있음 : 왜냐면 서비스를 위해 필요한 클래스 설계는 개발자의 몫인 거니깐
// 다시 말해 설계자에 따라 클래스 이름이 다 달라져야 하니깐 (스프링은 정할 수 없음.)
// @Configuration붙여서 필요한 객체를 미리 선언해둔다.
// 이때 함께 사용할 어노테이션이 Bean 어노테이션이다.
@Configuration
public class MyConfig {
// 스프링에서 해당 클래스를 관리함 - 스캔한다 - 클래스 정보 수집
@Bean
public TestController testController() {
return new TestController();
}
@Bean
public TestLogic testLogic() {
return new TestLogic();
}
@Bean
public TestDao testDao() {
return new TestDao();
}
}
ApplicationContext컨테이너: 이렇게 등록된 빈을 관리해준다.
xml문서 대신 개발자가 선언할 객체를 "미리" 선언해둔다.
이러면 이른 객체 생성을 가져갈 수 있음 : 왜냐면 서비스를 위해 필요한 클래스 설계는 개발자의 몫인 거니깐
다시 말해 설계자에 따라 클래스 이름이 다 달라져야 하니깐 (스프링은 정할 수 없음.)
@Configuration붙여서 필요한 객체를 미리 선언해둔다.
이때 함께 사용할 어노테이션이 Bean 어노테이션이다.
Configuration & Bean을(클래스) 같이 사용한다.
[MyContext]
package com.example.demo.di2;
import java.util.HashMap;
import java.util.Map;
public class MyContext {
Map<String, Object> map = new HashMap<>();
public MyContext() {
map.put("testController", new TestController());
map.put("testLogic", new TestLogic());
map.put("testDao", new TestDao());
}
}
// 빈을 찾을 떄, 이름으로 찾을 수 있음 -> 키값(testController)으로 객체를(new TestController()) 주입
// 받을수 있다.
// 지금 보면 타입 xx = new 타입() 했는가? --> no!
map.put("이름",new 타입) 어떻게 Bean으로 동작하는지 && ApplicationContext를 재현해보자.
[MyContext]
public class MyContext {
Map<String, Object> map = new HashMap<>();
public MyContext() {
map.put("testController", new TestController());
map.put("testLogic", new TestLogic());
map.put("testDao", new TestDao());
}
@SuppressWarnings("deprecation")
public MyContext(Class<?> clazz) // <?>로 한 이유는 앞에 MyConfig가 올수있음 ==> 거기엔 @Configuration - 스캔 - @Bean등록하는 클래스
{
try {
Object myConfig = clazz.newInstance();
for (Method m : clazz.getDeclaredMethods()) // 동적으로 클래스 메소드를 수집할 수 있다.
{
for (Annotation ann : m.getDeclaredAnnotations()) {// bean
if (ann.annotationType() == Bean.class) // 객체들을 생성하고 메소드 이름을 키값으로 하여 값을 생성한다.
{
map.put("m.getName", myConfig); // 메소드의 이름이 키값으로 사용된다. - testController + testDao + testLogic
map.put(m.getName(), m.invoke(myConfig, null));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
[MyContextMain]
public class MyContextMain {
public static void main(String[] args) {
// 생성자를 이용한다.
MyContext mc = new MyContext(MyConfig.class);
System.out.println(mc.map);
}
}
[MyConfig]
// xml문서 대신 개발자가 선언할 객체를 미리 선언해둔다.
// 이러면 이른 객체 생성을 가져갈 수 있음 : 왜냐면 서비스를 위해 필요한 클래스 설계는 개발자의 몫인 거니깐
// 다시 말해 설계자에 따라 클래스 이름이 다 달라져야 하니깐 (스프링은 정할 수 없음.)
// @Configuration붙여서 필요한 객체를 미리 선언해둔다.
// 이때 함께 사용할 어노테이션이 Bean 어노테이션이다.
@Configuration
public class MyConfig {
// 스프링에서 해당 클래스를 관리함 - 스캔한다 - 클래스 정보 수집
@Bean
public TestController testController() { // 생성자가 아니고 메소드 이름이다.
return new TestController();
}
@Bean
public TestLogic testLogic() {
return new TestLogic();
}
@Bean
public TestDao testDao() {
return new TestDao();
}
}
결과

순서 정리 -> Main(new MyContext(MyConfig.class) MyConfig 클래스 설계도를 주입(testController,testLogic,testDao) 메소드 사용 -> MyContext에 가서 put으로 메소드 이름, myConfig를 담는다.
TestController testController = new TestController();
이렇게 호출하지말자.
[MyContext]
public class MyContext { // ApplicationContext를 재현해보자.
Map<String, Object> map = new HashMap<>();
public MyContext() {
map.put("testController", new TestController());
map.put("testLogic", new TestLogic());
map.put("testDao", new TestDao());
}
@SuppressWarnings("deprecation")
public MyContext(Class<?> clazz) // <?>로 한 이유는 앞에 MyConfig가 올수있음 ==> 거기엔 @Configuration - 스캔 - @Bean등록하는 클래스
{
try {
Object myConfig = clazz.newInstance();
for (Method m : clazz.getDeclaredMethods()) // 동적으로 클래스 메소드를 수집할 수 있다.
{
for (Annotation ann : m.getDeclaredAnnotations()) {// bean
if (ann.annotationType() == Bean.class) // 객체들을 생성하고 메소드 이름을 키값으로 하여 값을 생성한다.
{
map.put("m.getName", myConfig); // 메소드의 이름이 키값으로 사용된다. - testController + testDao + testLogic
map.put(m.getName(), m.invoke(myConfig, null));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Object getBean(String id) // 아이디로 찾는거니깐 + 이름으로 객체찾기
{
return map.get(id);
}
}
[MyContextMain]
public class MyContextMain {
public static void main(String[] args) {
// 생성자를 이용한다.
MyContext mc = new MyContext(MyConfig.class);
System.out.println(mc.map);
TestController testController2 = new TestController();
TestController testController = (TestController)mc.getBean("testController");
}
}

Class 객체를 이용하면 new 연산자의 사용 없이 동적으로 객체 생성이 가능하다.
코드 작성 시에 클래스 이름을 결정할 수 없고, 런타임 시에 클래스 이름이 결정되는 경우에 유용하다.
아래 코드처럼 Class.forName() 메서드로 Class 객체를 얻음 다음 newInstance()메서드를 호출하면 Object 타입의 객체를 얻을 수 있다.
[기본문법]
try{
Class clazz = Class.forName("런타임 시 결정되는 클래스의 이름");
Object obj = clazz.newInstance();}
catch(ClassNotFoundException e){}
catch(InstantiationException e){}
catch(IllegalAccessException e){}
newInstance() 메서드는 기본 생성자를 호출해서 객체를 생성하기 때문에 반드시 클래스에 기본 생성자가 존재해야한다.
매개 변수가 있는 생성자를 호출하고 싶다면 리플랙션으로 Constructor 객체를 얻어 newInstance() 메서드를 호출한다.
newInstance() 메서드는 두 가지 예외가 발생할 수 있다.
InstantiationException예외는 해당 클래스가 추상 클래스이거나 인터페이스일 경우에 발생하고,
IllegalAccessException 예외는 클래스나 생성자가 접근 제한자로 인해 접근할 수 없을 경우에 발생한다.
그렇기에 예외 처리가 필요하다. newInstance() 메서드의 리턴 타입은 Object이므로 이것을 원래 클래스 타입으로 변환해야만 메서드를 사용할 수 있다.
그렇게 하기 위해 강제 타입 변환을 해야하는데 클래스타입을 모르는 상태이기에 변환을 할 수가 없다. 이 문제를 해결하기위해서는 인터페이스의 사용이 필요하다.
지금 MyContext 코드에선
public MyContext(Class<?> clazz) // <?>로 한 이유는 앞에 MyConfig가 올수있음 ==> 거기엔 @Configuration - 스캔 - @Bean등록하는 클래스
{
try {
Object myConfig = clazz.newInstance();
}
clazz = @Configuration에 대한 경로
clazz.newInstance를 하면 객체를 생성한다.
==============================================================
[MyConfiguation - 디폴트 생성자 존재]
@Configuration
public class MyConfig {
// 스프링에서 해당 클래스를 관리함 - 스캔한다 - 클래스 정보 수집
@Bean
public TestController testController() { // 생성자가 아니고 메소드 이름이다.
return new TestController();
}
@Bean
public TestLogic testLogic() {
return new TestLogic();
}
@Bean
public TestDao testDao() {
return new TestDao();
}
}
크게 보면 ApplicationContext가 빈을 어떻게 등록하는지 보여주는 예제이다.
MyContext mc = new MyContext(MyConfig.class);
MyConfig는
이렇게도 사용가능함
public static void main(String[] args) {
MyContext mc = new MyContext(MyConfig.class);
System.out.println(mc.map);
TestController testController2 = new TestController();// 하드코딩 - 라이프사이클 책임이 너
System.out.println(testController2);
TestController testController = (TestController) mc.getBean("testController");// - IoC, 관리 받음
System.out.println(testController);
TestController testController3 = (TestController) mc.getBean(TestController.class);// - IoC, 관리 받음
System.out.println(testController3);
}
===================================
{testController=com.example.demo.di2.TestController@4c203ea1, testLogic=com.example.demo.di2.TestLogic@27f674d, testDao=com.example.demo.di2.TestDao@1d251891}
com.example.demo.di2.TestController@48140564
com.example.demo.di2.TestController@4c203ea1
com.example.demo.di2.TestController@4c203ea1

testController와 testController3는 같은 주소번지를 가지고 있다 (싱글톤으로 관리되고 있다.)
멀티스레드를 운영하여 한정된 자원을 여러 사용자가 누릴 수 있다.
오체분시해보자.
MyConTextMain.java
public class MyContextMain {
public static void main(String[] args) {
MyContext mc = new MyContext(MyConfig.class);
System.out.println(mc.map);
TestController testController2 = new TestController();// 하드코딩 - 라이프사이클 책임이 너
System.out.println(testController2);
TestController testController = (TestController) mc.getBean("testController");// - IoC, 관리 받음
System.out.println(testController);
TestController testController3 = (TestController) mc.getBean(TestController.class);// - IoC, 관리 받음
System.out.println(testController3);
}
}
이렇게 작성하면,
MyContext mc = new MyContext(MyConfig.class);
[MyConfig.java]
@SuppressWarnings("deprecation")
public MyContext(Class<?> clazz) {// 파라미터 자리 - MyConfig - @Configuration - 스캔 - @Bean 등록
try {
Object myConfig = clazz.newInstance();
for (Method m : clazz.getDeclaredMethods()) {
for (Annotation ann : m.getDeclaredAnnotations()) {
if (ann.annotationType() == Bean.class) {// 객체를 생성
// 메소드 이름(testController, testLogic, testDao)을 키값으로 하여 값을 생성함
map.put(m.getName(), m.invoke(myConfig, null));
}
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
(Class<?> clazz) = (MyConfig.class) 를 파라미터로 받는다.
이 부분에서 Class clazz = Class.forName("런타임 시 결정되는 클래스의 이름"); 이 부분이 해결된다.
즉, 파라미터로 객체를 주입받는다.(IOC)
그렇게 되면 clazz는 Myconfig에 대한 주소값을 쥐고있다.
Object myConfig = clazz.newInstance()
newInstance()메서드를 호출하면 Object 타입의 객체를 얻을 수 있다.
getMethod
getDeclaredMethods
invoke
메소드 호출
invoke 메소드의 첫번째 파리미터는 메소드를 실행할 객체가 되며 두번째 파라미터는 해당 메소드의 파라미터 값을 전달 할 수 있다.
for (Method m : clazz.getDeclaredMethods())
메소드 타입 m : clazz(MyContext).getDeclaredMethods(모든 메소드 가져옴)
for (Annotation ann : m.getDeclaredAnnotations())
정리하면, m에서 클래스의 메소드 정보를 읽어오고 이중for문을 통해서 어노테이션을 읽어온다
m(메소드) + ann(어노테이션)
if (ann.annotationType() == Bean.class)map.put(m.getName(), m.invoke(myConfig, null));
getName() : Class 객체의 이름을 반환한다.
클래스의 패키지 이름을 포함한 전체경로
여기선 메소드이므로 메소드 이름을 반환한다.
invoke 메소드의 첫번째 파리미터는 메소드를 실행할 객체가 되며 두번째 파라미터는 해당 메소드의 파라미터 값을 전달 할 수 있다.
값이 전달되는 과정을 보자.
[MyContextMain]
TestController testController = (TestController) mc.getBean("testController");// - IoC, 관리 받음
System.out.println(testController);
TestController testController3 = (TestController) mc.getBean(TestController.class);// - IoC, 관리 받음
System.out.println(testController3);
[MyContext]
Object getBean(String id) {// 이름으로 객체 찾기
return map.get(id);
}
Object getBean(Class<?> clazz) {
for (Object obj : map.values()) {
if (clazz.isInstance(obj)) {
return obj;
}
}
return null;
}
Object getBean(String id) : "testController"가 주입된다.
Object getBean(Class<?> clazz) : "TestController.class"가 들어간다.
public class TestController {
Logger logger = LoggerFactory.getLogger(TestController.class);
}
그렇게 되면 파라미터에
Object getBean(Class<?> clazz)에
Object getBean(TestController.class)가 들어간다.
for (Object obj : map.values()) :
map.values() :
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "A");
map.put(2, "B");
map.put(3, "C");
Collection<String> values = map.values();
System.out.println(values);
// 결과
// [A, B, C]
디폴트 생성자엔 map엔 3가지가 저장되어있다.
public class MyContext {
Map<String, Object> map = new HashMap<>();
public MyContext() {
map.put("testController", new TestController());
map.put("testLogic", new TestLogic());
map.put("testDao", new TestDao());
}
즉,
TestController : new TestController
TestLogic : new TestLogic
TestDao를 : new TestDao 객체로 저장한다.
파라미터가 하나 있는 생성자

if (clazz.isInstance(obj)) {
return obj;
}
단, 파라미터가 하나인 생성자를 호출하기 위한 상황 때문에 (객체 주입이 필요한 상황) Main에서 인스턴스화를 통해서 객체를 주입하면, public MyContext(Class<?> clazz)가 실행된다.
파라미터 자리가 MyConfig이고 안에 있는 @Configuration을 스캔해서 Bean을 등록한다.
만약에 파라미터가 하나인 생성자가 호출되었을 때,
clazz.newInstance()를 통해서 동적으로 객체를 생성한다.
이중 for문을 통해서 Method m 은 메소드(순회)
and
모든 어노테이션을 돌면서 메소드 이름(testController, testLogic, testDao)을 키값으로 하여 값을 생성한다.
if (ann.annotationType() == Bean.class)

ann.annotationType() : 메소드의 어노테이션을 모두 읽어온다.

읽어온 값들을 map.put에 담는다.
m.getName() : 메소드 이름
m.invoke(myConfig, null): myConfig 인스턴스에서 메서드 m을 호출하여 객체를 생성
주어진 클래스(clazz)에서 @Bean 어노테이션이 달린 메서드들을 호출하여, 해당 메서드들이 생성한 객체들을 Map<String, Object>인 map에 저장하는 역할을 한다.
map.put(...) : 마지막으로 메서드 호출의 결과는 메서드 이름을 키로 사용하여 map에 저장된다. 이렇게 함으로써 맵은 사실상 빈과 그에 해당하는 이름들의 레지스트리가 된다.
List가 아닌 Map으로 사용한 이유는?
@Configuration
//@PropertySource("classpath:/application.properties")
@PropertySource("classpath:/application.yml")
//@MapperScan(basePackages = "com.example.demo.mapper")
public class DatabaseConfiguration {
private static final Logger logger = LogManager.getLogger(DatabaseConfiguration.class);
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Bean
public DataSource dataSource() {
DataSource dataSource = new HikariDataSource(hikariConfig());
logger.info("datasource : {}", dataSource);
return dataSource;
}
@Autowired
private ApplicationContext applicationContext;
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
//classpath는 src/main/resourcs이고 해당 쿼리가 있는 xml 위치는 본인의 취향대로 위치키시고 그에 맞도록 설정해주면 된다.
sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:/mapper/**/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
@Bean
public DataSource dataSource() {
DataSource dataSource = new HikariDataSource(hikariConfig());
logger.info("datasource : {}", dataSource);
return dataSource;
}
@Autowired
private ApplicationContext applicationContext;
==============================================================================
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariConfig hikariConfig() {
return new HikariConfig();
}
얘가 빈을 관리해준다.
@Autowired : 그물로 묶는다.(의존성 주입과 관련된 어노테이션이다)