@Primary
spring 은 기본적으로 동일한 타입의 bean 을 생성하지 않습니다 예를 들어서 다음과 같은 소스가 있다고 생각을 해봅시다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface Printer {
void print();
}
==============================
@Component
public class InkjetPrinter implements Printer {
@Override
public void print() {
System.out.println("InkjetPrinter");
}
}
==============================
@Component
public class LaserPrinter implements Printer {
@Override
public void print() {
System.out.println("LaserPrinter");
}
}
예를 들어서 상단에 Printer 인터페이스가 존재하고 그 아래 각각 InkjetPrinter LaserPrinter 가 각각을 구현할 때 둘 다 @Bean으로 만들려고 한다면 다음과 같은 문제에 직면하게 됩니다
1
2
3
4
5
6
7
Description:
Field printer in com.example.demo.SpringCoreApplication required a single bean, but 2 were found:
- inkjetPrinter: defined in file [C:\Users\kimdo\Documents\workspace-spring-tool-suite-4-4.17.0.RELEASE\Spring-Core\target\classes\com\example\demo\InkjetPrinter.class]
- laserPrinter: defined in file [C:\Users\kimdo\Documents\workspace-spring-tool-suite-4-4.17.0.RELEASE\Spring-Core\target\classes\com\example\demo\LaserPrinter.class]
지금 printer 타입의 bean 이 2가지가 있어서 bean 을 만들 수 없다는 뜻입니다 그렇기 이때 필요한 것이 @Primary입니다
1
2
3
4
5
6
7
8
9
10
@Component
@Primary
public class LaserPrinter implements Printer {
@Override
public void print() {
System.out.println("LaserPrinter");
}
}
그래서 LaserPrinter @Primary 달게 되면 bean 을 선정하고 주입할 때 우선순위를 차지하게 됩니다 그렇기 때문에 항상 LaserPrinter 가 bean으로 만들어져서 주입을 받게 됩니다 그래서 그래서 우선순위로 bean 을 주입받고 싶을 때에는 @Primary를 사용합니다
@Qualifier
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public interface Printer {
void print();
}
==============================
@Component
public class InkjetPrinter implements Printer {
@Override
public void print() {
System.out.println("InkjetPrinter");
}
}
==============================
@Component
public class LaserPrinter implements Printer {
@Override
public void print() {
System.out.println("LaserPrinter");
}
}
==============================
@Component("zebraPrint")
public class ZebraPrint implements Printer {
@Override
public void print() {
System.out.println("ZebraPrint");
}
}
이번엔 Printer를 상속받는 ZebraPrint를 선언하고 이때 bean 이름을 지정할 수 있는데 이때는 zebraPrint 이름을 짓게 됩니다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootApplication
public class SpringCoreApplication implements ApplicationRunner {
@Autowired
private Printer printer;
@Autowired
@Qualifier("zebraPrint")
private Printer printer2;
public static void main(String[] args) {
SpringApplication.run(SpringCoreApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
printer.print();
printer2.print();
}
}
그리고 bean 을 @Autowired 할 때 @Qualifier(“zebraPrint”)에서 bean 이름을 찾을 수 있게 지정을 할 수 있으면 그러면 이제 printer2는 같은 타입의 print 가 있다 할지라도 bean 이름이 zebraPrint인 bean만 찾아서 주입을 하게 됩니다
그럼 실전에서는 어떻게 쓰이느냐 보통 다중 DB를 생성할 때 사용하게 됩니다
다중DB 선언해서 사용하기
실전에서는 주로 다중DB 에서 많이 사용됩니다 예를 들어서 오라클 하고 mysql 을 동시에 연결할것이다 이때 주로 사용할 테이블은 오라클이 될것이다 보조 연결 DB 는 mysql 이 될것입니다
spring - boot 2.7.1 jdk11 oracle19c mysql8
이렇게 연결할 예정입니다
MAVEN 은 아래와 같다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.6.0.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
적절한 의존성을 삽입해준다음
application.properties
1
2
3
4
5
6
7
8
9
10
# Oracle db
oracle.datasource.url=jdbc:oracle:thin:@192.168.46.129:1521:ORCL
oracle.datasource.username=kimdongy1000
oracle.datasource.password=***********
# Mysql db
mysql.datasource.url=jdbc:mysql://192.168.46.129:3306/kimdongy1000?characterEncoding=UTF-8&serverTimezone=UTC
mysql.datasource.username=kimdongy1000
mysql.datasource.password=************
먼저 application.properties에 접속 정보를 입력합니다 히카리 CP는 여러 가지 설정을 커스텀 할 수 있지만 반드시 필요한 url , user name, password만 적어서 진행을 하겠습니다
OracleConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Configuration
public class OracleConfig {
@Value("${oracle.datasource.url}")
private String url;
@Value("${oracle.datasource.username}")
private String username;
@Value("${oracle.datasource.password}")
private String password;
@Bean
@Primary
public HikariConfig oracleHikariConfig(){
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
return hikariConfig;
}
@Bean
@Primary
public DataSource oracleDataSource(HikariConfig oracleHikariConfig){
DataSource oracleDataSource = new HikariDataSource(oracleHikariConfig);
return oracleDataSource;
}
@Bean
@Primary
public SqlSessionFactory oracleSqlSessionFactory(DataSource oracleDataSource) throws Exception{
SqlSessionFactoryBean oracleSqlSessionFactoryBean = new SqlSessionFactoryBean();
oracleSqlSessionFactoryBean.setDataSource(oracleDataSource);
oracleSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/static/mapper/multiple/oracle/*.xml"));
return oracleSqlSessionFactoryBean.getObject();
}
@Bean
@Primary
public SqlSessionTemplate oracleSqlSessionTemplate(SqlSessionFactory oracleSessionFactory) throws Exception{
SqlSessionTemplate oracleSqlSessionTemplate = new SqlSessionTemplate(oracleSessionFactory);
return oracleSqlSessionTemplate;
}
}
내용은 별것이 없습니다 다만 Bean 들은 전부 @Primary 붙었습니다 주 테이블이 오라클 DB로 생각을 하기 때문에 런타임 시에는 전부 오라클 DB를 우선으로 bean 생성하고 주입합니다
MysqlConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Configuration
public class MysqlConfig {
@Value("${mysql.datasource.url}")
private String url;
@Value("${mysql.datasource.username}")
private String username;
@Value("${mysql.datasource.password}")
private String password;
@Bean("mysqlHikariConfig")
public HikariConfig mysqlHikariConfig(){
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
return hikariConfig;
}
@Bean("mysqlDataSource")
public DataSource mysqlDataSource(@Qualifier(value = "mysqlHikariConfig") HikariConfig mysqlHikariConfig){
DataSource mysqlDataSource = new HikariDataSource(mysqlHikariConfig);
return mysqlDataSource;
}
@Bean("mysqlSessionFactory")
public SqlSessionFactory mysqlSessionFactory(@Qualifier(value = "mysqlDataSource") DataSource mysqlDataSource) throws Exception{
SqlSessionFactoryBean mysqlSessionFactoryBean = new SqlSessionFactoryBean();
mysqlSessionFactoryBean.setDataSource(mysqlDataSource);
mysqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/static/mapper/multiple/mysql/*.xml"));
return mysqlSessionFactoryBean.getObject();
}
@Bean("mysqlSessionTemplate")
public SqlSessionTemplate mySqlsessionTemplate(@Qualifier(value = "mysqlSessionFactory") SqlSessionFactory mySqlSessionFactory) throws Exception{
SqlSessionTemplate mysqlSessionTemplate = new SqlSessionTemplate(mySqlSessionFactory);
return mysqlSessionTemplate;
}
}
같은 내용이긴 한데 Bean 전부 이름이 들어가 있는 것을 볼 수 있고 주입할 대상들도 전부 @Qualifier로 해서 이때 value를 지정을 하면 해당 bean 을 찾게 됩니다 즉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Bean("mysqlHikariConfig")
public HikariConfig mysqlHikariConfig(){
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
return hikariConfig;
}
@Bean("mysqlDataSource")
public DataSource mysqlDataSource(@Qualifier(value = "mysqlHikariConfig") HikariConfig mysqlHikariConfig){
DataSource mysqlDataSource = new HikariDataSource(mysqlHikariConfig);
return mysqlDataSource;
}
이 두 줄만 살펴보면 제일 먼저 HikariConfig bean 을 만들 때 이름으로 mysqlHikariConfig 되면 런타임 시 이 bean 이 이름으로 만들게 됩니다 즉 타입 상관없이 이름이 중복되지 않으면 같은 타입을 생성할 수 있습니다
그리고 주입할 대상에는 반드시 @Qualifier(value = “mysqlHikariConfig”) 이렇게 value에 해당 이름을 찾아서 주입을 하게 됩니다 그러면 다른 이름의 bean 이 주입이 되는 것이 아니라 반드시 같은 이름으로 생성된 bean 을 찾아서 주입을 하게 됩니다
그렇기 때문에 OracleConfig 와 MysqlConfig는 타입은 같지만 결국 bean 이름이 다르기 때문에 애플리케이션 런타임 시 충돌 나지 않습니다
OracleRepository
1
2
3
4
5
6
7
8
9
10
11
12
public class OracleRepository {
@Autowired
private SqlSessionTemplate oracleSessionTemplate;
public int oracleResult() {
return oracleSessionTemplate.selectOne(this.getClass().getName() + ".test_oracle");
}
}
그리고 쿼리를 불러올 때는 SqlSessionTemplate 을 불러와서 표출하게 되는데 이때 @Autowired 아무것도 적지 않으면 기본적으로 @Primary 타입으로 된 bean 을 찾아서 주입을 하게 됩니다
MysqlRepository
1
2
3
4
5
6
7
8
9
10
11
12
@Repository
public class MysqlRepository {
@Autowired(required = false)
@Qualifier(value = "mysqlSessionTemplate")
public SqlSessionTemplate mySqlSessionTemplate;
public int mysql_result(){
return mySqlSessionTemplate.selectOne(this.getClass().getName() + ".test_mysql");
}
}
반대로 mysql 쿼리를 불러올 때는 주입 bean의 이름을 정확히 찾아서 주입을 해야 하므로 @Autowired(required = false) 이 설정은 이 bean 이 있을 수도 있고 없을 수도 있으니 bean 이 없어도 의존 에러를 발생시키지 않습니다 @Qualifier(value = “mysqlSessionTemplate”) 이름으로 bean 을 찾아서 주입하기 위한 장치입니다
oracle.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cybb.main.repository.OracleRepository">
<select id="test_oracle" resultType="Integer">
SELECT 1 + 2 + 3 + 4 + 5 FROM DUAL
</select>
</mapper>
테이블 유무보다는 이렇게 dual 테이블로 연결을 해서 연산 결과를 return 받을 것이고
mysql.xml
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cybb.main.repository.MysqlRepository">
<select id="test_mysql" resultType="Integer">
SELECT 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
</select>
</mapper>
8.0 이상 버전부터는 mysql 도 daul 테이블을 도입할 수 있는데 oracle 하고 차이점을 보기 위해서 일부로 쓰지 않았습니다
쿼리 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@SpringBootApplication
public class SpringBootWebSecurityApplication implements ApplicationRunner {
@Autowired
private OracleRepository oracleRepository;
@Autowired
private MysqlRepository mysqlRepository;
public static void main(String[] args) {
SpringApplication.run(SpringBootWebSecurityApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
int oracle_result = oracleRepository.oracleResult();
int mysql_result = mysqlRepository.mysql_result();;
System.out.println("Connect_oracle :" + oracle_result);
System.out.println("Connect_mysql :" + mysql_result);
}
}
쿼리는 이렇게 각각 repository를 호출하는 것으로 적었고 결과는
1
2
3
Connect_oracle :15
Connect_mysql :55
이렇게 나와서 다중 DB 연결할 때 @Primary 역할과 @Qualifier 역할에 대해서 알아보았습니다